diff --git a/drivers/net/dsa/mv88e6123_61_65.c b/drivers/net/dsa/mv88e6123_61_65.c index e9c736e1cef3..2d7e1ffe9fdc 100644 --- a/drivers/net/dsa/mv88e6123_61_65.c +++ b/drivers/net/dsa/mv88e6123_61_65.c @@ -222,28 +222,6 @@ static int mv88e6123_61_65_setup_port(struct dsa_switch *ds, int p) val |= 0x000c; REG_WRITE(addr, 0x04, val); - /* Port Control 1: disable trunking. Also, if this is the - * CPU port, enable learn messages to be sent to this port. - */ - REG_WRITE(addr, 0x05, dsa_is_cpu_port(ds, p) ? 0x8000 : 0x0000); - - /* Port based VLAN map: give each port its own address - * database, allow the CPU port to talk to each of the 'real' - * ports, and allow each of the 'real' ports to only talk to - * the upstream port. - */ - val = (p & 0xf) << 12; - if (dsa_is_cpu_port(ds, p)) - val |= ds->phys_port_mask; - else - val |= 1 << dsa_upstream_port(ds); - REG_WRITE(addr, 0x06, val); - - /* Default VLAN ID and priority: don't set a default VLAN - * ID, and set the default packet priority to zero. - */ - REG_WRITE(addr, 0x07, 0x0000); - /* Port Control 2: don't force a good FCS, set the maximum * frame size to 10240 bytes, don't let the switch add or * strip 802.1q tags, don't discard tagged or untagged frames @@ -288,18 +266,17 @@ static int mv88e6123_61_65_setup_port(struct dsa_switch *ds, int p) */ REG_WRITE(addr, 0x19, 0x7654); - return 0; + return mv88e6xxx_setup_port_common(ds, p); } static int mv88e6123_61_65_setup(struct dsa_switch *ds) { - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); int i; int ret; - mutex_init(&ps->smi_mutex); - mutex_init(&ps->stats_mutex); - mutex_init(&ps->phy_mutex); + ret = mv88e6xxx_setup_common(ds); + if (ret < 0) + return ret; ret = mv88e6123_61_65_switch_reset(ds); if (ret < 0) diff --git a/drivers/net/dsa/mv88e6171.c b/drivers/net/dsa/mv88e6171.c index 9808c860a797..18cfead83dc9 100644 --- a/drivers/net/dsa/mv88e6171.c +++ b/drivers/net/dsa/mv88e6171.c @@ -17,6 +17,10 @@ #include #include "mv88e6xxx.h" +/* Switch product IDs */ +#define ID_6171 0x1710 +#define ID_6172 0x1720 + static char *mv88e6171_probe(struct device *host_dev, int sw_addr) { struct mii_bus *bus = dsa_host_dev_to_mii_bus(host_dev); @@ -27,9 +31,9 @@ static char *mv88e6171_probe(struct device *host_dev, int sw_addr) ret = __mv88e6xxx_reg_read(bus, sw_addr, REG_PORT(0), 0x03); if (ret >= 0) { - if ((ret & 0xfff0) == 0x1710) + if ((ret & 0xfff0) == ID_6171) return "Marvell 88E6171"; - if ((ret & 0xfff0) == 0x1720) + if ((ret & 0xfff0) == ID_6172) return "Marvell 88E6172"; } @@ -221,28 +225,6 @@ static int mv88e6171_setup_port(struct dsa_switch *ds, int p) val |= 0x000c; REG_WRITE(addr, 0x04, val); - /* Port Control 1: disable trunking. Also, if this is the - * CPU port, enable learn messages to be sent to this port. - */ - REG_WRITE(addr, 0x05, dsa_is_cpu_port(ds, p) ? 0x8000 : 0x0000); - - /* Port based VLAN map: give each port its own address - * database, allow the CPU port to talk to each of the 'real' - * ports, and allow each of the 'real' ports to only talk to - * the upstream port. - */ - val = (p & 0xf) << 12; - if (dsa_is_cpu_port(ds, p)) - val |= ds->phys_port_mask; - else - val |= 1 << dsa_upstream_port(ds); - REG_WRITE(addr, 0x06, val); - - /* Default VLAN ID and priority: don't set a default VLAN - * ID, and set the default packet priority to zero. - */ - REG_WRITE(addr, 0x07, 0x0000); - /* Port Control 2: don't force a good FCS, set the maximum * frame size to 10240 bytes, don't let the switch add or * strip 802.1q tags, don't discard tagged or untagged frames @@ -287,17 +269,17 @@ static int mv88e6171_setup_port(struct dsa_switch *ds, int p) */ REG_WRITE(addr, 0x19, 0x7654); - return 0; + return mv88e6xxx_setup_port_common(ds, p); } static int mv88e6171_setup(struct dsa_switch *ds) { - struct mv88e6xxx_priv_state *ps = (void *)(ds + 1); int i; int ret; - mutex_init(&ps->smi_mutex); - mutex_init(&ps->stats_mutex); + ret = mv88e6xxx_setup_common(ds); + if (ret < 0) + return ret; ret = mv88e6171_switch_reset(ds); if (ret < 0) @@ -318,8 +300,6 @@ static int mv88e6171_setup(struct dsa_switch *ds) return ret; } - mutex_init(&ps->phy_mutex); - return 0; } @@ -410,6 +390,28 @@ static int mv88e6171_get_sset_count(struct dsa_switch *ds) return ARRAY_SIZE(mv88e6171_hw_stats); } +static int mv88e6171_get_eee(struct dsa_switch *ds, int port, + struct ethtool_eee *e) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + if (ps->id == ID_6172) + return mv88e6xxx_get_eee(ds, port, e); + + return -EOPNOTSUPP; +} + +static int mv88e6171_set_eee(struct dsa_switch *ds, int port, + struct phy_device *phydev, struct ethtool_eee *e) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + if (ps->id == ID_6172) + return mv88e6xxx_set_eee(ds, port, phydev, e); + + return -EOPNOTSUPP; +} + struct dsa_switch_driver mv88e6171_switch_driver = { .tag_protocol = DSA_TAG_PROTO_EDSA, .priv_size = sizeof(struct mv88e6xxx_priv_state), @@ -422,11 +424,19 @@ struct dsa_switch_driver mv88e6171_switch_driver = { .get_strings = mv88e6171_get_strings, .get_ethtool_stats = mv88e6171_get_ethtool_stats, .get_sset_count = mv88e6171_get_sset_count, + .set_eee = mv88e6171_set_eee, + .get_eee = mv88e6171_get_eee, #ifdef CONFIG_NET_DSA_HWMON .get_temp = mv88e6xxx_get_temp, #endif .get_regs_len = mv88e6xxx_get_regs_len, .get_regs = mv88e6xxx_get_regs, + .port_join_bridge = mv88e6xxx_join_bridge, + .port_leave_bridge = mv88e6xxx_leave_bridge, + .port_stp_update = mv88e6xxx_port_stp_update, + .fdb_add = mv88e6xxx_port_fdb_add, + .fdb_del = mv88e6xxx_port_fdb_del, + .fdb_getnext = mv88e6xxx_port_fdb_getnext, }; MODULE_ALIAS("platform:mv88e6171"); diff --git a/drivers/net/dsa/mv88e6352.c b/drivers/net/dsa/mv88e6352.c index 7bc5998384c6..41fe3a6a72d1 100644 --- a/drivers/net/dsa/mv88e6352.c +++ b/drivers/net/dsa/mv88e6352.c @@ -215,28 +215,6 @@ static int mv88e6352_setup_port(struct dsa_switch *ds, int p) val |= 0x000c; REG_WRITE(addr, 0x04, val); - /* Port Control 1: disable trunking. Also, if this is the - * CPU port, enable learn messages to be sent to this port. - */ - REG_WRITE(addr, 0x05, dsa_is_cpu_port(ds, p) ? 0x8000 : 0x0000); - - /* Port based VLAN map: give each port its own address - * database, allow the CPU port to talk to each of the 'real' - * ports, and allow each of the 'real' ports to only talk to - * the upstream port. - */ - val = (p & 0xf) << 12; - if (dsa_is_cpu_port(ds, p)) - val |= ds->phys_port_mask; - else - val |= 1 << dsa_upstream_port(ds); - REG_WRITE(addr, 0x06, val); - - /* Default VLAN ID and priority: don't set a default VLAN - * ID, and set the default packet priority to zero. - */ - REG_WRITE(addr, 0x07, 0x0000); - /* Port Control 2: don't force a good FCS, set the maximum * frame size to 10240 bytes, don't let the switch add or * strip 802.1q tags, don't discard tagged or untagged frames @@ -281,7 +259,7 @@ static int mv88e6352_setup_port(struct dsa_switch *ds, int p) */ REG_WRITE(addr, 0x19, 0x7654); - return 0; + return mv88e6xxx_setup_port_common(ds, p); } #ifdef CONFIG_NET_DSA_HWMON @@ -385,12 +363,11 @@ static int mv88e6352_setup(struct dsa_switch *ds) int ret; int i; - mutex_init(&ps->smi_mutex); - mutex_init(&ps->stats_mutex); - mutex_init(&ps->phy_mutex); - mutex_init(&ps->eeprom_mutex); + ret = mv88e6xxx_setup_common(ds); + if (ret < 0) + return ret; - ps->id = REG_READ(REG_PORT(0), 0x03) & 0xfff0; + mutex_init(&ps->eeprom_mutex); ret = mv88e6352_switch_reset(ds); if (ret < 0) @@ -729,6 +706,12 @@ struct dsa_switch_driver mv88e6352_switch_driver = { .set_eeprom = mv88e6352_set_eeprom, .get_regs_len = mv88e6xxx_get_regs_len, .get_regs = mv88e6xxx_get_regs, + .port_join_bridge = mv88e6xxx_join_bridge, + .port_leave_bridge = mv88e6xxx_leave_bridge, + .port_stp_update = mv88e6xxx_port_stp_update, + .fdb_add = mv88e6xxx_port_fdb_add, + .fdb_del = mv88e6xxx_port_fdb_del, + .fdb_getnext = mv88e6xxx_port_fdb_getnext, }; MODULE_ALIAS("platform:mv88e6352"); diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c index c18ffc98aacc..13572cc24c6d 100644 --- a/drivers/net/dsa/mv88e6xxx.c +++ b/drivers/net/dsa/mv88e6xxx.c @@ -9,6 +9,8 @@ */ #include +#include +#include #include #include #include @@ -72,19 +74,16 @@ int __mv88e6xxx_reg_read(struct mii_bus *bus, int sw_addr, int addr, int reg) return ret & 0xffff; } -int mv88e6xxx_reg_read(struct dsa_switch *ds, int addr, int reg) +/* Must be called with SMI mutex held */ +static int _mv88e6xxx_reg_read(struct dsa_switch *ds, int addr, int reg) { - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); struct mii_bus *bus = dsa_host_dev_to_mii_bus(ds->master_dev); int ret; if (bus == NULL) return -EINVAL; - mutex_lock(&ps->smi_mutex); ret = __mv88e6xxx_reg_read(bus, ds->pd->sw_addr, addr, reg); - mutex_unlock(&ps->smi_mutex); - if (ret < 0) return ret; @@ -94,6 +93,18 @@ int mv88e6xxx_reg_read(struct dsa_switch *ds, int addr, int reg) return ret; } +int mv88e6xxx_reg_read(struct dsa_switch *ds, int addr, int reg) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + + mutex_lock(&ps->smi_mutex); + ret = _mv88e6xxx_reg_read(ds, addr, reg); + mutex_unlock(&ps->smi_mutex); + + return ret; +} + int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr, int reg, u16 val) { @@ -125,11 +136,11 @@ int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr, return 0; } -int mv88e6xxx_reg_write(struct dsa_switch *ds, int addr, int reg, u16 val) +/* Must be called with SMI mutex held */ +static int _mv88e6xxx_reg_write(struct dsa_switch *ds, int addr, int reg, + u16 val) { - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); struct mii_bus *bus = dsa_host_dev_to_mii_bus(ds->master_dev); - int ret; if (bus == NULL) return -EINVAL; @@ -137,8 +148,16 @@ int mv88e6xxx_reg_write(struct dsa_switch *ds, int addr, int reg, u16 val) dev_dbg(ds->master_dev, "-> addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n", addr, reg, val); + return __mv88e6xxx_reg_write(bus, ds->pd->sw_addr, addr, reg, val); +} + +int mv88e6xxx_reg_write(struct dsa_switch *ds, int addr, int reg, u16 val) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + mutex_lock(&ps->smi_mutex); - ret = __mv88e6xxx_reg_write(bus, ds->pd->sw_addr, addr, reg, val); + ret = _mv88e6xxx_reg_write(ds, addr, reg, val); mutex_unlock(&ps->smi_mutex); return ret; @@ -627,6 +646,31 @@ int mv88e6xxx_eeprom_busy_wait(struct dsa_switch *ds) return mv88e6xxx_wait(ds, REG_GLOBAL2, 0x14, 0x8000); } +/* Must be called with SMI lock held */ +static int _mv88e6xxx_wait(struct dsa_switch *ds, int reg, int offset, u16 mask) +{ + unsigned long timeout = jiffies + HZ / 10; + + while (time_before(jiffies, timeout)) { + int ret; + + ret = _mv88e6xxx_reg_read(ds, reg, offset); + if (ret < 0) + return ret; + if (!(ret & mask)) + return 0; + + usleep_range(1000, 2000); + } + return -ETIMEDOUT; +} + +/* Must be called with SMI lock held */ +static int _mv88e6xxx_atu_wait(struct dsa_switch *ds) +{ + return _mv88e6xxx_wait(ds, REG_GLOBAL, 0x0b, ATU_BUSY); +} + int mv88e6xxx_phy_read_indirect(struct dsa_switch *ds, int addr, int regnum) { int ret; @@ -700,6 +744,423 @@ int mv88e6xxx_set_eee(struct dsa_switch *ds, int port, return 0; } +static int _mv88e6xxx_atu_cmd(struct dsa_switch *ds, int fid, u16 cmd) +{ + int ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x01, fid); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x0b, cmd); + if (ret < 0) + return ret; + + return _mv88e6xxx_atu_wait(ds); +} + +static int _mv88e6xxx_flush_fid(struct dsa_switch *ds, int fid) +{ + int ret; + + ret = _mv88e6xxx_atu_wait(ds); + if (ret < 0) + return ret; + + return _mv88e6xxx_atu_cmd(ds, fid, ATU_CMD_FLUSH_NONSTATIC_FID); +} + +static int mv88e6xxx_set_port_state(struct dsa_switch *ds, int port, u8 state) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int reg, ret; + u8 oldstate; + + mutex_lock(&ps->smi_mutex); + + reg = _mv88e6xxx_reg_read(ds, REG_PORT(port), 0x04); + if (reg < 0) + goto abort; + + oldstate = reg & PSTATE_MASK; + if (oldstate != state) { + /* Flush forwarding database if we're moving a port + * from Learning or Forwarding state to Disabled or + * Blocking or Listening state. + */ + if (oldstate >= PSTATE_LEARNING && state <= PSTATE_BLOCKING) { + ret = _mv88e6xxx_flush_fid(ds, ps->fid[port]); + if (ret) + goto abort; + } + reg = (reg & ~PSTATE_MASK) | state; + ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x04, reg); + } + +abort: + mutex_unlock(&ps->smi_mutex); + return ret; +} + +/* Must be called with smi lock held */ +static int _mv88e6xxx_update_port_config(struct dsa_switch *ds, int port) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + u8 fid = ps->fid[port]; + u16 reg = fid << 12; + + if (dsa_is_cpu_port(ds, port)) + reg |= ds->phys_port_mask; + else + reg |= (ps->bridge_mask[fid] | + (1 << dsa_upstream_port(ds))) & ~(1 << port); + + return _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x06, reg); +} + +/* Must be called with smi lock held */ +static int _mv88e6xxx_update_bridge_config(struct dsa_switch *ds, int fid) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int port; + u32 mask; + int ret; + + mask = ds->phys_port_mask; + while (mask) { + port = __ffs(mask); + mask &= ~(1 << port); + if (ps->fid[port] != fid) + continue; + + ret = _mv88e6xxx_update_port_config(ds, port); + if (ret) + return ret; + } + + return _mv88e6xxx_flush_fid(ds, fid); +} + +/* Bridge handling functions */ + +int mv88e6xxx_join_bridge(struct dsa_switch *ds, int port, u32 br_port_mask) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret = 0; + u32 nmask; + int fid; + + /* If the bridge group is not empty, join that group. + * Otherwise create a new group. + */ + fid = ps->fid[port]; + nmask = br_port_mask & ~(1 << port); + if (nmask) + fid = ps->fid[__ffs(nmask)]; + + nmask = ps->bridge_mask[fid] | (1 << port); + if (nmask != br_port_mask) { + netdev_err(ds->ports[port], + "join: Bridge port mask mismatch fid=%d mask=0x%x expected 0x%x\n", + fid, br_port_mask, nmask); + return -EINVAL; + } + + mutex_lock(&ps->smi_mutex); + + ps->bridge_mask[fid] = br_port_mask; + + if (fid != ps->fid[port]) { + ps->fid_mask |= 1 << ps->fid[port]; + ps->fid[port] = fid; + ret = _mv88e6xxx_update_bridge_config(ds, fid); + } + + mutex_unlock(&ps->smi_mutex); + + return ret; +} + +int mv88e6xxx_leave_bridge(struct dsa_switch *ds, int port, u32 br_port_mask) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + u8 fid, newfid; + int ret; + + fid = ps->fid[port]; + + if (ps->bridge_mask[fid] != br_port_mask) { + netdev_err(ds->ports[port], + "leave: Bridge port mask mismatch fid=%d mask=0x%x expected 0x%x\n", + fid, br_port_mask, ps->bridge_mask[fid]); + return -EINVAL; + } + + /* If the port was the last port of a bridge, we are done. + * Otherwise assign a new fid to the port, and fix up + * the bridge configuration. + */ + if (br_port_mask == (1 << port)) + return 0; + + mutex_lock(&ps->smi_mutex); + + newfid = __ffs(ps->fid_mask); + ps->fid[port] = newfid; + ps->fid_mask &= (1 << newfid); + ps->bridge_mask[fid] &= ~(1 << port); + ps->bridge_mask[newfid] = 1 << port; + + ret = _mv88e6xxx_update_bridge_config(ds, fid); + if (!ret) + ret = _mv88e6xxx_update_bridge_config(ds, newfid); + + mutex_unlock(&ps->smi_mutex); + + return ret; +} + +int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int stp_state; + + switch (state) { + case BR_STATE_DISABLED: + stp_state = PSTATE_DISABLED; + break; + case BR_STATE_BLOCKING: + case BR_STATE_LISTENING: + stp_state = PSTATE_BLOCKING; + break; + case BR_STATE_LEARNING: + stp_state = PSTATE_LEARNING; + break; + case BR_STATE_FORWARDING: + default: + stp_state = PSTATE_FORWARDING; + break; + } + + netdev_dbg(ds->ports[port], "port state %d [%d]\n", state, stp_state); + + /* mv88e6xxx_port_stp_update may be called with softirqs disabled, + * so we can not update the port state directly but need to schedule it. + */ + ps->port_state[port] = stp_state; + set_bit(port, &ps->port_state_update_mask); + schedule_work(&ps->bridge_work); + + return 0; +} + +static int __mv88e6xxx_write_addr(struct dsa_switch *ds, + const unsigned char *addr) +{ + int i, ret; + + for (i = 0; i < 3; i++) { + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x0d + i, + (addr[i * 2] << 8) | addr[i * 2 + 1]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int __mv88e6xxx_read_addr(struct dsa_switch *ds, unsigned char *addr) +{ + int i, ret; + + for (i = 0; i < 3; i++) { + ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL, 0x0d + i); + if (ret < 0) + return ret; + addr[i * 2] = ret >> 8; + addr[i * 2 + 1] = ret & 0xff; + } + + return 0; +} + +static int __mv88e6xxx_port_fdb_cmd(struct dsa_switch *ds, int port, + const unsigned char *addr, int state) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + u8 fid = ps->fid[port]; + int ret; + + ret = _mv88e6xxx_atu_wait(ds); + if (ret < 0) + return ret; + + ret = __mv88e6xxx_write_addr(ds, addr); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x0c, + (0x10 << port) | state); + if (ret) + return ret; + + ret = _mv88e6xxx_atu_cmd(ds, fid, ATU_CMD_LOAD_FID); + + return ret; +} + +int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + int state = is_multicast_ether_addr(addr) ? + FDB_STATE_MC_STATIC : FDB_STATE_STATIC; + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + + mutex_lock(&ps->smi_mutex); + ret = __mv88e6xxx_port_fdb_cmd(ds, port, addr, state); + mutex_unlock(&ps->smi_mutex); + + return ret; +} + +int mv88e6xxx_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + + mutex_lock(&ps->smi_mutex); + ret = __mv88e6xxx_port_fdb_cmd(ds, port, addr, FDB_STATE_UNUSED); + mutex_unlock(&ps->smi_mutex); + + return ret; +} + +static int __mv88e6xxx_port_getnext(struct dsa_switch *ds, int port, + unsigned char *addr, bool *is_static) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + u8 fid = ps->fid[port]; + int ret, state; + + ret = _mv88e6xxx_atu_wait(ds); + if (ret < 0) + return ret; + + ret = __mv88e6xxx_write_addr(ds, addr); + if (ret < 0) + return ret; + + do { + ret = _mv88e6xxx_atu_cmd(ds, fid, ATU_CMD_GETNEXT_FID); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL, 0x0c); + if (ret < 0) + return ret; + state = ret & FDB_STATE_MASK; + if (state == FDB_STATE_UNUSED) + return -ENOENT; + } while (!(((ret >> 4) & 0xff) & (1 << port))); + + ret = __mv88e6xxx_read_addr(ds, addr); + if (ret < 0) + return ret; + + *is_static = state == (is_multicast_ether_addr(addr) ? + FDB_STATE_MC_STATIC : FDB_STATE_STATIC); + + return 0; +} + +/* get next entry for port */ +int mv88e6xxx_port_fdb_getnext(struct dsa_switch *ds, int port, + unsigned char *addr, bool *is_static) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + + mutex_lock(&ps->smi_mutex); + ret = __mv88e6xxx_port_getnext(ds, port, addr, is_static); + mutex_unlock(&ps->smi_mutex); + + return ret; +} + +static void mv88e6xxx_bridge_work(struct work_struct *work) +{ + struct mv88e6xxx_priv_state *ps; + struct dsa_switch *ds; + int port; + + ps = container_of(work, struct mv88e6xxx_priv_state, bridge_work); + ds = ((struct dsa_switch *)ps) - 1; + + while (ps->port_state_update_mask) { + port = __ffs(ps->port_state_update_mask); + clear_bit(port, &ps->port_state_update_mask); + mv88e6xxx_set_port_state(ds, port, ps->port_state[port]); + } +} + +int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret, fid; + + mutex_lock(&ps->smi_mutex); + + /* Port Control 1: disable trunking, disable sending + * learning messages to this port. + */ + ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x05, 0x0000); + if (ret) + goto abort; + + /* Port based VLAN map: give each port its own address + * database, allow the CPU port to talk to each of the 'real' + * ports, and allow each of the 'real' ports to only talk to + * the upstream port. + */ + fid = __ffs(ps->fid_mask); + ps->fid[port] = fid; + ps->fid_mask &= ~(1 << fid); + + if (!dsa_is_cpu_port(ds, port)) + ps->bridge_mask[fid] = 1 << port; + + ret = _mv88e6xxx_update_port_config(ds, port); + if (ret) + goto abort; + + /* Default VLAN ID and priority: don't set a default VLAN + * ID, and set the default packet priority to zero. + */ + ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x07, 0x0000); +abort: + mutex_unlock(&ps->smi_mutex); + return ret; +} + +int mv88e6xxx_setup_common(struct dsa_switch *ds) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + mutex_init(&ps->smi_mutex); + mutex_init(&ps->stats_mutex); + mutex_init(&ps->phy_mutex); + + ps->id = REG_READ(REG_PORT(0), 0x03) & 0xfff0; + + ps->fid_mask = (1 << DSA_MAX_PORTS) - 1; + + INIT_WORK(&ps->bridge_work, mv88e6xxx_bridge_work); + + return 0; +} + static int __init mv88e6xxx_init(void) { #if IS_ENABLED(CONFIG_NET_DSA_MV88E6131) diff --git a/drivers/net/dsa/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx.h index 5fd42ced9011..aaf239aba726 100644 --- a/drivers/net/dsa/mv88e6xxx.h +++ b/drivers/net/dsa/mv88e6xxx.h @@ -15,6 +15,30 @@ #define REG_GLOBAL 0x1b #define REG_GLOBAL2 0x1c +/* ATU commands */ + +#define ATU_BUSY 0x8000 + +#define ATU_CMD_LOAD_FID (ATU_BUSY | 0x3000) +#define ATU_CMD_GETNEXT_FID (ATU_BUSY | 0x4000) +#define ATU_CMD_FLUSH_NONSTATIC_FID (ATU_BUSY | 0x6000) + +/* port states */ + +#define PSTATE_MASK 0x03 +#define PSTATE_DISABLED 0x00 +#define PSTATE_BLOCKING 0x01 +#define PSTATE_LEARNING 0x02 +#define PSTATE_FORWARDING 0x03 + +/* FDB states */ + +#define FDB_STATE_MASK 0x0f + +#define FDB_STATE_UNUSED 0x00 +#define FDB_STATE_MC_STATIC 0x07 /* static multicast */ +#define FDB_STATE_STATIC 0x0e /* static unicast */ + struct mv88e6xxx_priv_state { /* When using multi-chip addressing, this mutex protects * access to the indirect access registers. (In single-chip @@ -49,6 +73,17 @@ struct mv88e6xxx_priv_state { struct mutex eeprom_mutex; int id; /* switch product id */ + + /* hw bridging */ + + u32 fid_mask; + u8 fid[DSA_MAX_PORTS]; + u16 bridge_mask[DSA_MAX_PORTS]; + + unsigned long port_state_update_mask; + u8 port_state[DSA_MAX_PORTS]; + + struct work_struct bridge_work; }; struct mv88e6xxx_hw_stat { @@ -57,6 +92,8 @@ struct mv88e6xxx_hw_stat { int reg; }; +int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port); +int mv88e6xxx_setup_common(struct dsa_switch *ds); int __mv88e6xxx_reg_read(struct mii_bus *bus, int sw_addr, int addr, int reg); int mv88e6xxx_reg_read(struct dsa_switch *ds, int addr, int reg); int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr, @@ -91,6 +128,15 @@ int mv88e6xxx_phy_write_indirect(struct dsa_switch *ds, int addr, int regnum, int mv88e6xxx_get_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e); int mv88e6xxx_set_eee(struct dsa_switch *ds, int port, struct phy_device *phydev, struct ethtool_eee *e); +int mv88e6xxx_join_bridge(struct dsa_switch *ds, int port, u32 br_port_mask); +int mv88e6xxx_leave_bridge(struct dsa_switch *ds, int port, u32 br_port_mask); +int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state); +int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); +int mv88e6xxx_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); +int mv88e6xxx_port_fdb_getnext(struct dsa_switch *ds, int port, + unsigned char *addr, bool *is_static); extern struct dsa_switch_driver mv88e6131_switch_driver; extern struct dsa_switch_driver mv88e6123_61_65_switch_driver; diff --git a/include/net/dsa.h b/include/net/dsa.h index 47917e5e1e12..fbca63ba8f73 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -296,6 +296,12 @@ struct dsa_switch_driver { u32 br_port_mask); int (*port_stp_update)(struct dsa_switch *ds, int port, u8 state); + int (*fdb_add)(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); + int (*fdb_del)(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); + int (*fdb_getnext)(struct dsa_switch *ds, int port, + unsigned char *addr, bool *is_static); }; void register_switch_driver(struct dsa_switch_driver *type); diff --git a/net/dsa/slave.c b/net/dsa/slave.c index 39555f3f263b..3597724ec3d8 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -201,6 +201,105 @@ static int dsa_slave_set_mac_address(struct net_device *dev, void *a) return 0; } +static int dsa_slave_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + struct net_device *dev, + const unsigned char *addr, u16 vid, u16 nlm_flags) +{ + struct dsa_slave_priv *p = netdev_priv(dev); + struct dsa_switch *ds = p->parent; + int ret = -EOPNOTSUPP; + + if (ds->drv->fdb_add) + ret = ds->drv->fdb_add(ds, p->port, addr, vid); + + return ret; +} + +static int dsa_slave_fdb_del(struct ndmsg *ndm, struct nlattr *tb[], + struct net_device *dev, + const unsigned char *addr, u16 vid) +{ + struct dsa_slave_priv *p = netdev_priv(dev); + struct dsa_switch *ds = p->parent; + int ret = -EOPNOTSUPP; + + if (ds->drv->fdb_del) + ret = ds->drv->fdb_del(ds, p->port, addr, vid); + + return ret; +} + +static int dsa_slave_fill_info(struct net_device *dev, struct sk_buff *skb, + const unsigned char *addr, u16 vid, + bool is_static, + u32 portid, u32 seq, int type, + unsigned int flags) +{ + struct nlmsghdr *nlh; + struct ndmsg *ndm; + + nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags); + if (!nlh) + return -EMSGSIZE; + + ndm = nlmsg_data(nlh); + ndm->ndm_family = AF_BRIDGE; + ndm->ndm_pad1 = 0; + ndm->ndm_pad2 = 0; + ndm->ndm_flags = NTF_EXT_LEARNED; + ndm->ndm_type = 0; + ndm->ndm_ifindex = dev->ifindex; + ndm->ndm_state = is_static ? NUD_NOARP : NUD_REACHABLE; + + if (nla_put(skb, NDA_LLADDR, ETH_ALEN, addr)) + goto nla_put_failure; + + if (vid && nla_put_u16(skb, NDA_VLAN, vid)) + goto nla_put_failure; + + nlmsg_end(skb, nlh); + return 0; + +nla_put_failure: + nlmsg_cancel(skb, nlh); + return -EMSGSIZE; +} + +/* Dump information about entries, in response to GETNEIGH */ +static int dsa_slave_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb, + struct net_device *dev, + struct net_device *filter_dev, int idx) +{ + struct dsa_slave_priv *p = netdev_priv(dev); + struct dsa_switch *ds = p->parent; + unsigned char addr[ETH_ALEN] = { 0 }; + int ret; + + if (!ds->drv->fdb_getnext) + return -EOPNOTSUPP; + + for (; ; idx++) { + bool is_static; + + ret = ds->drv->fdb_getnext(ds, p->port, addr, &is_static); + if (ret < 0) + break; + + if (idx < cb->args[0]) + continue; + + ret = dsa_slave_fill_info(dev, skb, addr, 0, + is_static, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, + RTM_NEWNEIGH, NLM_F_MULTI); + if (ret < 0) + break; + } + + return idx; +} + static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { struct dsa_slave_priv *p = netdev_priv(dev); @@ -572,6 +671,9 @@ static const struct net_device_ops dsa_slave_netdev_ops = { .ndo_change_rx_flags = dsa_slave_change_rx_flags, .ndo_set_rx_mode = dsa_slave_set_rx_mode, .ndo_set_mac_address = dsa_slave_set_mac_address, + .ndo_fdb_add = dsa_slave_fdb_add, + .ndo_fdb_del = dsa_slave_fdb_del, + .ndo_fdb_dump = dsa_slave_fdb_dump, .ndo_do_ioctl = dsa_slave_ioctl, };