diff options
Diffstat (limited to 'drivers/net/dsa/mv88e6xxx.c')
-rw-r--r-- | drivers/net/dsa/mv88e6xxx.c | 1087 |
1 files changed, 396 insertions, 691 deletions
diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c index 1f7dd927cc5e..b06dba05594a 100644 --- a/drivers/net/dsa/mv88e6xxx.c +++ b/drivers/net/dsa/mv88e6xxx.c @@ -11,7 +11,6 @@ * (at your option) any later version. */ -#include <linux/debugfs.h> #include <linux/delay.h> #include <linux/etherdevice.h> #include <linux/ethtool.h> @@ -21,36 +20,18 @@ #include <linux/module.h> #include <linux/netdevice.h> #include <linux/phy.h> -#include <linux/seq_file.h> #include <net/dsa.h> +#include <net/switchdev.h> #include "mv88e6xxx.h" -/* MDIO bus access can be nested in the case of PHYs connected to the - * internal MDIO bus of the switch, which is accessed via MDIO bus of - * the Ethernet interface. Avoid lockdep false positives by using - * mutex_lock_nested(). - */ -static int mv88e6xxx_mdiobus_read(struct mii_bus *bus, int addr, u32 regnum) -{ - int ret; - - mutex_lock_nested(&bus->mdio_lock, SINGLE_DEPTH_NESTING); - ret = bus->read(bus, addr, regnum); - mutex_unlock(&bus->mdio_lock); - - return ret; -} - -static int mv88e6xxx_mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, - u16 val) +static void assert_smi_lock(struct dsa_switch *ds) { - int ret; - - mutex_lock_nested(&bus->mdio_lock, SINGLE_DEPTH_NESTING); - ret = bus->write(bus, addr, regnum, val); - mutex_unlock(&bus->mdio_lock); + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - return ret; + if (unlikely(!mutex_is_locked(&ps->smi_mutex))) { + dev_err(ds->master_dev, "SMI lock not held!\n"); + dump_stack(); + } } /* If the switch's ADDR[4:0] strap pins are strapped to zero, it will @@ -67,7 +48,7 @@ static int mv88e6xxx_reg_wait_ready(struct mii_bus *bus, int sw_addr) int i; for (i = 0; i < 16; i++) { - ret = mv88e6xxx_mdiobus_read(bus, sw_addr, SMI_CMD); + ret = mdiobus_read_nested(bus, sw_addr, SMI_CMD); if (ret < 0) return ret; @@ -78,12 +59,13 @@ static int mv88e6xxx_reg_wait_ready(struct mii_bus *bus, int sw_addr) return -ETIMEDOUT; } -int __mv88e6xxx_reg_read(struct mii_bus *bus, int sw_addr, int addr, int reg) +static int __mv88e6xxx_reg_read(struct mii_bus *bus, int sw_addr, int addr, + int reg) { int ret; if (sw_addr == 0) - return mv88e6xxx_mdiobus_read(bus, addr, reg); + return mdiobus_read_nested(bus, addr, reg); /* Wait for the bus to become free. */ ret = mv88e6xxx_reg_wait_ready(bus, sw_addr); @@ -91,8 +73,8 @@ int __mv88e6xxx_reg_read(struct mii_bus *bus, int sw_addr, int addr, int reg) return ret; /* Transmit the read command. */ - ret = mv88e6xxx_mdiobus_write(bus, sw_addr, SMI_CMD, - SMI_CMD_OP_22_READ | (addr << 5) | reg); + ret = mdiobus_write_nested(bus, sw_addr, SMI_CMD, + SMI_CMD_OP_22_READ | (addr << 5) | reg); if (ret < 0) return ret; @@ -102,19 +84,20 @@ int __mv88e6xxx_reg_read(struct mii_bus *bus, int sw_addr, int addr, int reg) return ret; /* Read the data. */ - ret = mv88e6xxx_mdiobus_read(bus, sw_addr, SMI_DATA); + ret = mdiobus_read_nested(bus, sw_addr, SMI_DATA); if (ret < 0) return ret; return ret & 0xffff; } -/* Must be called with SMI mutex held */ static int _mv88e6xxx_reg_read(struct dsa_switch *ds, int addr, int reg) { struct mii_bus *bus = dsa_host_dev_to_mii_bus(ds->master_dev); int ret; + assert_smi_lock(ds); + if (bus == NULL) return -EINVAL; @@ -140,13 +123,13 @@ int mv88e6xxx_reg_read(struct dsa_switch *ds, int addr, int reg) return ret; } -int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr, - int reg, u16 val) +static int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr, + int reg, u16 val) { int ret; if (sw_addr == 0) - return mv88e6xxx_mdiobus_write(bus, addr, reg, val); + return mdiobus_write_nested(bus, addr, reg, val); /* Wait for the bus to become free. */ ret = mv88e6xxx_reg_wait_ready(bus, sw_addr); @@ -154,13 +137,13 @@ int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr, return ret; /* Transmit the data to write. */ - ret = mv88e6xxx_mdiobus_write(bus, sw_addr, SMI_DATA, val); + ret = mdiobus_write_nested(bus, sw_addr, SMI_DATA, val); if (ret < 0) return ret; /* Transmit the write command. */ - ret = mv88e6xxx_mdiobus_write(bus, sw_addr, SMI_CMD, - SMI_CMD_OP_22_WRITE | (addr << 5) | reg); + ret = mdiobus_write_nested(bus, sw_addr, SMI_CMD, + SMI_CMD_OP_22_WRITE | (addr << 5) | reg); if (ret < 0) return ret; @@ -172,12 +155,13 @@ int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr, return 0; } -/* Must be called with SMI mutex held */ static int _mv88e6xxx_reg_write(struct dsa_switch *ds, int addr, int reg, u16 val) { struct mii_bus *bus = dsa_host_dev_to_mii_bus(ds->master_dev); + assert_smi_lock(ds); + if (bus == NULL) return -EINVAL; @@ -233,7 +217,6 @@ int mv88e6xxx_set_addr_indirect(struct dsa_switch *ds, u8 *addr) return 0; } -/* Must be called with SMI mutex held */ static int _mv88e6xxx_phy_read(struct dsa_switch *ds, int addr, int regnum) { if (addr >= 0) @@ -241,7 +224,6 @@ static int _mv88e6xxx_phy_read(struct dsa_switch *ds, int addr, int regnum) return 0xffff; } -/* Must be called with SMI mutex held */ static int _mv88e6xxx_phy_write(struct dsa_switch *ds, int addr, int regnum, u16 val) { @@ -388,73 +370,6 @@ int mv88e6xxx_phy_write_ppu(struct dsa_switch *ds, int addr, } #endif -void mv88e6xxx_poll_link(struct dsa_switch *ds) -{ - int i; - - for (i = 0; i < DSA_MAX_PORTS; i++) { - struct net_device *dev; - int uninitialized_var(port_status); - int pcs_ctrl; - int link; - int speed; - int duplex; - int fc; - - dev = ds->ports[i]; - if (dev == NULL) - continue; - - pcs_ctrl = mv88e6xxx_reg_read(ds, REG_PORT(i), PORT_PCS_CTRL); - if (pcs_ctrl < 0 || pcs_ctrl & PORT_PCS_CTRL_FORCE_LINK) - continue; - - link = 0; - if (dev->flags & IFF_UP) { - port_status = mv88e6xxx_reg_read(ds, REG_PORT(i), - PORT_STATUS); - if (port_status < 0) - continue; - - link = !!(port_status & PORT_STATUS_LINK); - } - - if (!link) { - if (netif_carrier_ok(dev)) { - netdev_info(dev, "link down\n"); - netif_carrier_off(dev); - } - continue; - } - - switch (port_status & PORT_STATUS_SPEED_MASK) { - case PORT_STATUS_SPEED_10: - speed = 10; - break; - case PORT_STATUS_SPEED_100: - speed = 100; - break; - case PORT_STATUS_SPEED_1000: - speed = 1000; - break; - default: - speed = -1; - break; - } - duplex = (port_status & PORT_STATUS_DUPLEX) ? 1 : 0; - fc = (port_status & PORT_STATUS_PAUSE_EN) ? 1 : 0; - - if (!netif_carrier_ok(dev)) { - netdev_info(dev, - "link up, %d Mb/s, %s duplex, flow control %sabled\n", - speed, - duplex ? "full" : "half", - fc ? "en" : "dis"); - netif_carrier_on(dev); - } - } -} - static bool mv88e6xxx_6065_family(struct dsa_switch *ds) { struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); @@ -574,7 +489,8 @@ void mv88e6xxx_adjust_link(struct dsa_switch *ds, int port, struct phy_device *phydev) { struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - u32 ret, reg; + u32 reg; + int ret; if (!phy_is_pseudo_fixed_link(phydev)) return; @@ -633,7 +549,6 @@ out: mutex_unlock(&ps->smi_mutex); } -/* Must be called with SMI mutex held */ static int _mv88e6xxx_stats_wait(struct dsa_switch *ds) { int ret; @@ -648,7 +563,6 @@ static int _mv88e6xxx_stats_wait(struct dsa_switch *ds) return -ETIMEDOUT; } -/* Must be called with SMI mutex held */ static int _mv88e6xxx_stats_snapshot(struct dsa_switch *ds, int port) { int ret; @@ -671,7 +585,6 @@ static int _mv88e6xxx_stats_snapshot(struct dsa_switch *ds, int port) return 0; } -/* Must be called with SMI mutex held */ static void _mv88e6xxx_stats_read(struct dsa_switch *ds, int stat, u32 *val) { u32 _val; @@ -884,7 +797,6 @@ void mv88e6xxx_get_regs(struct dsa_switch *ds, int port, } } -/* Must be called with SMI lock held */ static int _mv88e6xxx_wait(struct dsa_switch *ds, int reg, int offset, u16 mask) { @@ -934,21 +846,12 @@ int mv88e6xxx_eeprom_busy_wait(struct dsa_switch *ds) GLOBAL2_EEPROM_OP_BUSY); } -/* Must be called with SMI lock held */ static int _mv88e6xxx_atu_wait(struct dsa_switch *ds) { return _mv88e6xxx_wait(ds, REG_GLOBAL, GLOBAL_ATU_OP, GLOBAL_ATU_OP_BUSY); } -/* Must be called with SMI lock held */ -static int _mv88e6xxx_scratch_wait(struct dsa_switch *ds) -{ - return _mv88e6xxx_wait(ds, REG_GLOBAL2, GLOBAL2_SCRATCH_MISC, - GLOBAL2_SCRATCH_BUSY); -} - -/* Must be called with SMI mutex held */ static int _mv88e6xxx_phy_read_indirect(struct dsa_switch *ds, int addr, int regnum) { @@ -967,7 +870,6 @@ static int _mv88e6xxx_phy_read_indirect(struct dsa_switch *ds, int addr, return _mv88e6xxx_reg_read(ds, REG_GLOBAL2, GLOBAL2_SMI_DATA); } -/* Must be called with SMI mutex held */ static int _mv88e6xxx_phy_write_indirect(struct dsa_switch *ds, int addr, int regnum, u16 val) { @@ -1036,14 +938,10 @@ out: return ret; } -static int _mv88e6xxx_atu_cmd(struct dsa_switch *ds, int fid, u16 cmd) +static int _mv88e6xxx_atu_cmd(struct dsa_switch *ds, u16 cmd) { int ret; - ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_ATU_FID, fid); - if (ret < 0) - return ret; - ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_ATU_OP, cmd); if (ret < 0) return ret; @@ -1051,15 +949,93 @@ static int _mv88e6xxx_atu_cmd(struct dsa_switch *ds, int fid, u16 cmd) return _mv88e6xxx_atu_wait(ds); } -static int _mv88e6xxx_flush_fid(struct dsa_switch *ds, int fid) +static int _mv88e6xxx_atu_data_write(struct dsa_switch *ds, + struct mv88e6xxx_atu_entry *entry) { - int ret; + u16 data = entry->state & GLOBAL_ATU_DATA_STATE_MASK; - ret = _mv88e6xxx_atu_wait(ds); - if (ret < 0) - return ret; + if (entry->state != GLOBAL_ATU_DATA_STATE_UNUSED) { + unsigned int mask, shift; + + if (entry->trunk) { + data |= GLOBAL_ATU_DATA_TRUNK; + mask = GLOBAL_ATU_DATA_TRUNK_ID_MASK; + shift = GLOBAL_ATU_DATA_TRUNK_ID_SHIFT; + } else { + mask = GLOBAL_ATU_DATA_PORT_VECTOR_MASK; + shift = GLOBAL_ATU_DATA_PORT_VECTOR_SHIFT; + } + + data |= (entry->portv_trunkid << shift) & mask; + } + + return _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_ATU_DATA, data); +} + +static int _mv88e6xxx_atu_flush_move(struct dsa_switch *ds, + struct mv88e6xxx_atu_entry *entry, + bool static_too) +{ + int op; + int err; + + err = _mv88e6xxx_atu_wait(ds); + if (err) + return err; + + err = _mv88e6xxx_atu_data_write(ds, entry); + if (err) + return err; + + if (entry->fid) { + err = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_ATU_FID, + entry->fid); + if (err) + return err; + + op = static_too ? GLOBAL_ATU_OP_FLUSH_MOVE_ALL_DB : + GLOBAL_ATU_OP_FLUSH_MOVE_NON_STATIC_DB; + } else { + op = static_too ? GLOBAL_ATU_OP_FLUSH_MOVE_ALL : + GLOBAL_ATU_OP_FLUSH_MOVE_NON_STATIC; + } + + return _mv88e6xxx_atu_cmd(ds, op); +} + +static int _mv88e6xxx_atu_flush(struct dsa_switch *ds, u16 fid, bool static_too) +{ + struct mv88e6xxx_atu_entry entry = { + .fid = fid, + .state = 0, /* EntryState bits must be 0 */ + }; + + return _mv88e6xxx_atu_flush_move(ds, &entry, static_too); +} + +static int _mv88e6xxx_atu_move(struct dsa_switch *ds, u16 fid, int from_port, + int to_port, bool static_too) +{ + struct mv88e6xxx_atu_entry entry = { + .trunk = false, + .fid = fid, + }; + + /* EntryState bits must be 0xF */ + entry.state = GLOBAL_ATU_DATA_STATE_MASK; + + /* ToPort and FromPort are respectively in PortVec bits 7:4 and 3:0 */ + entry.portv_trunkid = (to_port & 0x0f) << 4; + entry.portv_trunkid |= from_port & 0x0f; - return _mv88e6xxx_atu_cmd(ds, fid, GLOBAL_ATU_OP_FLUSH_NON_STATIC_DB); + return _mv88e6xxx_atu_flush_move(ds, &entry, static_too); +} + +static int _mv88e6xxx_atu_remove(struct dsa_switch *ds, u16 fid, int port, + bool static_too) +{ + /* Destination port 0xF means remove the entries */ + return _mv88e6xxx_atu_move(ds, fid, port, 0x0f, static_too); } static int mv88e6xxx_set_port_state(struct dsa_switch *ds, int port, u8 state) @@ -1084,7 +1060,7 @@ static int mv88e6xxx_set_port_state(struct dsa_switch *ds, int port, u8 state) */ if (oldstate >= PORT_CONTROL_STATE_LEARNING && state <= PORT_CONTROL_STATE_BLOCKING) { - ret = _mv88e6xxx_flush_fid(ds, ps->fid[port]); + ret = _mv88e6xxx_atu_remove(ds, 0, port, false); if (ret) goto abort; } @@ -1098,130 +1074,21 @@ abort: 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), PORT_BASE_VLAN, 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) +static int _mv88e6xxx_port_vlan_map_set(struct dsa_switch *ds, int port, + u16 output_ports) { 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]) { - clear_bit(ps->fid[port], ps->fid_bitmap); - 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 = find_next_zero_bit(ps->fid_bitmap, VLAN_N_VID, 1); - if (unlikely(newfid > ps->num_ports)) { - netdev_err(ds->ports[port], "all first %d FIDs are used\n", - ps->num_ports); - ret = -ENOSPC; - goto unlock; - } - - ps->fid[port] = newfid; - set_bit(newfid, ps->fid_bitmap); - ps->bridge_mask[fid] &= ~(1 << port); - ps->bridge_mask[newfid] = 1 << port; + const u16 mask = (1 << ps->num_ports) - 1; + int reg; - ret = _mv88e6xxx_update_bridge_config(ds, fid); - if (!ret) - ret = _mv88e6xxx_update_bridge_config(ds, newfid); + reg = _mv88e6xxx_reg_read(ds, REG_PORT(port), PORT_BASE_VLAN); + if (reg < 0) + return reg; -unlock: - mutex_unlock(&ps->smi_mutex); + reg &= ~mask; + reg |= output_ports & mask; - return ret; + return _mv88e6xxx_reg_write(ds, REG_PORT(port), PORT_BASE_VLAN, reg); } int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state) @@ -1258,6 +1125,19 @@ int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state) return 0; } +static int _mv88e6xxx_port_pvid_get(struct dsa_switch *ds, int port, u16 *pvid) +{ + int ret; + + ret = _mv88e6xxx_reg_read(ds, REG_PORT(port), PORT_DEFAULT_VLAN); + if (ret < 0) + return ret; + + *pvid = ret & PORT_DEFAULT_VLAN_MASK; + + return 0; +} + int mv88e6xxx_port_pvid_get(struct dsa_switch *ds, int port, u16 *pvid) { int ret; @@ -1271,9 +1151,9 @@ int mv88e6xxx_port_pvid_get(struct dsa_switch *ds, int port, u16 *pvid) return 0; } -int mv88e6xxx_port_pvid_set(struct dsa_switch *ds, int port, u16 pvid) +static int _mv88e6xxx_port_pvid_set(struct dsa_switch *ds, int port, u16 pvid) { - return mv88e6xxx_reg_write(ds, REG_PORT(port), PORT_DEFAULT_VLAN, + return _mv88e6xxx_reg_write(ds, REG_PORT(port), PORT_DEFAULT_VLAN, pvid & PORT_DEFAULT_VLAN_MASK); } @@ -1359,7 +1239,13 @@ static int _mv88e6xxx_vtu_stu_data_write(struct dsa_switch *ds, return 0; } -static int _mv88e6xxx_vtu_getnext(struct dsa_switch *ds, u16 vid, +static int _mv88e6xxx_vtu_vid_write(struct dsa_switch *ds, u16 vid) +{ + return _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_VID, + vid & GLOBAL_VTU_VID_MASK); +} + +static int _mv88e6xxx_vtu_getnext(struct dsa_switch *ds, struct mv88e6xxx_vtu_stu_entry *entry) { struct mv88e6xxx_vtu_stu_entry next = { 0 }; @@ -1369,11 +1255,6 @@ static int _mv88e6xxx_vtu_getnext(struct dsa_switch *ds, u16 vid, if (ret < 0) return ret; - ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_VID, - vid & GLOBAL_VTU_VID_MASK); - if (ret < 0) - return ret; - ret = _mv88e6xxx_vtu_cmd(ds, GLOBAL_VTU_OP_VTU_GET_NEXT); if (ret < 0) return ret; @@ -1533,14 +1414,15 @@ static int _mv88e6xxx_vlan_init(struct dsa_switch *ds, u16 vid, struct mv88e6xxx_vtu_stu_entry vlan = { .valid = true, .vid = vid, + .fid = vid, /* We use one FID per VLAN */ }; int i; - /* exclude all ports except the CPU */ + /* exclude all ports except the CPU and DSA ports */ for (i = 0; i < ps->num_ports; ++i) - vlan.data[i] = dsa_is_cpu_port(ds, i) ? - GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED : - GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER; + vlan.data[i] = dsa_is_cpu_port(ds, i) || dsa_is_dsa_port(ds, i) + ? GLOBAL_VTU_DATA_MEMBER_TAG_UNMODIFIED + : GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER; if (mv88e6xxx_6097_family(ds) || mv88e6xxx_6165_family(ds) || mv88e6xxx_6351_family(ds) || mv88e6xxx_6352_family(ds)) { @@ -1566,123 +1448,152 @@ static int _mv88e6xxx_vlan_init(struct dsa_switch *ds, u16 vid, return err; } - /* Non-bridged ports and bridge groups use FIDs from 1 to - * num_ports; VLANs use FIDs from num_ports+1 to 4095. - */ - vlan.fid = find_next_zero_bit(ps->fid_bitmap, VLAN_N_VID, - ps->num_ports + 1); - if (unlikely(vlan.fid == VLAN_N_VID)) { - pr_err("no more FID available for VLAN %d\n", vid); - return -ENOSPC; - } - - err = _mv88e6xxx_flush_fid(ds, vlan.fid); + /* Clear all MAC addresses from the new database */ + err = _mv88e6xxx_atu_flush(ds, vlan.fid, true); if (err) return err; - - set_bit(vlan.fid, ps->fid_bitmap); } *entry = vlan; return 0; } -int mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, u16 vid, - bool untagged) +int mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct switchdev_trans *trans) +{ + /* We reserve a few VLANs to isolate unbridged ports */ + if (vlan->vid_end >= 4000) + return -EOPNOTSUPP; + + /* We don't need any dynamic resource from the kernel (yet), + * so skip the prepare phase. + */ + return 0; +} + +static int _mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, u16 vid, + bool untagged) { - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); struct mv88e6xxx_vtu_stu_entry vlan; int err; - mutex_lock(&ps->smi_mutex); - err = _mv88e6xxx_vtu_getnext(ds, vid - 1, &vlan); + err = _mv88e6xxx_vtu_vid_write(ds, vid - 1); if (err) - goto unlock; + return err; + + err = _mv88e6xxx_vtu_getnext(ds, &vlan); + if (err) + return err; if (vlan.vid != vid || !vlan.valid) { err = _mv88e6xxx_vlan_init(ds, vid, &vlan); if (err) - goto unlock; + return err; } vlan.data[port] = untagged ? GLOBAL_VTU_DATA_MEMBER_TAG_UNTAGGED : GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED; - err = _mv88e6xxx_vtu_loadpurge(ds, &vlan); + return _mv88e6xxx_vtu_loadpurge(ds, &vlan); +} + +int mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct switchdev_trans *trans) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + u16 vid; + int err = 0; + + mutex_lock(&ps->smi_mutex); + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + err = _mv88e6xxx_port_vlan_add(ds, port, vid, untagged); + if (err) + goto unlock; + } + + /* no PVID with ranges, otherwise it's a bug */ + if (pvid) + err = _mv88e6xxx_port_pvid_set(ds, port, vid); unlock: mutex_unlock(&ps->smi_mutex); return err; } -int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, u16 vid) +static int _mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, u16 vid) { struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); struct mv88e6xxx_vtu_stu_entry vlan; - bool keep = false; int i, err; - mutex_lock(&ps->smi_mutex); + err = _mv88e6xxx_vtu_vid_write(ds, vid - 1); + if (err) + return err; - err = _mv88e6xxx_vtu_getnext(ds, vid - 1, &vlan); + err = _mv88e6xxx_vtu_getnext(ds, &vlan); if (err) - goto unlock; + return err; if (vlan.vid != vid || !vlan.valid || - vlan.data[port] == GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER) { - err = -ENOENT; - goto unlock; - } + vlan.data[port] == GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER) + return -ENOENT; vlan.data[port] = GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER; /* keep the VLAN unless all ports are excluded */ + vlan.valid = false; for (i = 0; i < ps->num_ports; ++i) { - if (dsa_is_cpu_port(ds, i)) + if (dsa_is_cpu_port(ds, i) || dsa_is_dsa_port(ds, i)) continue; if (vlan.data[i] != GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER) { - keep = true; + vlan.valid = true; break; } } - vlan.valid = keep; err = _mv88e6xxx_vtu_loadpurge(ds, &vlan); if (err) - goto unlock; - - if (!keep) - clear_bit(vlan.fid, ps->fid_bitmap); - -unlock: - mutex_unlock(&ps->smi_mutex); + return err; - return err; + return _mv88e6xxx_atu_remove(ds, vlan.fid, port, false); } -static int _mv88e6xxx_port_vtu_getnext(struct dsa_switch *ds, int port, u16 vid, - struct mv88e6xxx_vtu_stu_entry *entry) +int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) { - int err; + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + u16 pvid, vid; + int err = 0; - do { - if (vid == 4095) - return -ENOENT; + mutex_lock(&ps->smi_mutex); - err = _mv88e6xxx_vtu_getnext(ds, vid, entry); + err = _mv88e6xxx_port_pvid_get(ds, port, &pvid); + if (err) + goto unlock; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + err = _mv88e6xxx_port_vlan_del(ds, port, vid); if (err) - return err; + goto unlock; - if (!entry->valid) - return -ENOENT; + if (vid == pvid) { + err = _mv88e6xxx_port_pvid_set(ds, port, 0); + if (err) + goto unlock; + } + } - vid = entry->vid; - } while (entry->data[port] != GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED && - entry->data[port] != GLOBAL_VTU_DATA_MEMBER_TAG_UNTAGGED); +unlock: + mutex_unlock(&ps->smi_mutex); - return 0; + return err; } int mv88e6xxx_vlan_getnext(struct dsa_switch *ds, u16 *vid, @@ -1697,7 +1608,12 @@ int mv88e6xxx_vlan_getnext(struct dsa_switch *ds, u16 *vid, return -ENOENT; mutex_lock(&ps->smi_mutex); - err = _mv88e6xxx_vtu_getnext(ds, *vid, &next); + err = _mv88e6xxx_vtu_vid_write(ds, *vid); + if (err) + goto unlock; + + err = _mv88e6xxx_vtu_getnext(ds, &next); +unlock: mutex_unlock(&ps->smi_mutex); if (err) @@ -1712,7 +1628,7 @@ int mv88e6xxx_vlan_getnext(struct dsa_switch *ds, u16 *vid, clear_bit(port, ports); clear_bit(port, untagged); - if (dsa_is_cpu_port(ds, port)) + if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)) continue; if (next.data[port] == GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED || @@ -1761,7 +1677,6 @@ static int _mv88e6xxx_atu_mac_read(struct dsa_switch *ds, unsigned char *addr) static int _mv88e6xxx_atu_load(struct dsa_switch *ds, struct mv88e6xxx_atu_entry *entry) { - u16 reg = 0; int ret; ret = _mv88e6xxx_atu_wait(ds); @@ -1772,47 +1687,15 @@ static int _mv88e6xxx_atu_load(struct dsa_switch *ds, if (ret < 0) return ret; - if (entry->state != GLOBAL_ATU_DATA_STATE_UNUSED) { - unsigned int mask, shift; - - if (entry->trunk) { - reg |= GLOBAL_ATU_DATA_TRUNK; - mask = GLOBAL_ATU_DATA_TRUNK_ID_MASK; - shift = GLOBAL_ATU_DATA_TRUNK_ID_SHIFT; - } else { - mask = GLOBAL_ATU_DATA_PORT_VECTOR_MASK; - shift = GLOBAL_ATU_DATA_PORT_VECTOR_SHIFT; - } - - reg |= (entry->portv_trunkid << shift) & mask; - } - - reg |= entry->state & GLOBAL_ATU_DATA_STATE_MASK; - - ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_ATU_DATA, reg); + ret = _mv88e6xxx_atu_data_write(ds, entry); if (ret < 0) return ret; - return _mv88e6xxx_atu_cmd(ds, entry->fid, GLOBAL_ATU_OP_LOAD_DB); -} - -static int _mv88e6xxx_port_vid_to_fid(struct dsa_switch *ds, int port, u16 vid) -{ - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - struct mv88e6xxx_vtu_stu_entry vlan; - int err; - - if (vid == 0) - return ps->fid[port]; - - err = _mv88e6xxx_port_vtu_getnext(ds, port, vid - 1, &vlan); - if (err) - return err; - - if (vlan.vid == vid) - return vlan.fid; + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_ATU_FID, entry->fid); + if (ret < 0) + return ret; - return -ENOENT; + return _mv88e6xxx_atu_cmd(ds, GLOBAL_ATU_OP_LOAD_DB); } static int _mv88e6xxx_port_fdb_load(struct dsa_switch *ds, int port, @@ -1820,13 +1703,8 @@ static int _mv88e6xxx_port_fdb_load(struct dsa_switch *ds, int port, u8 state) { struct mv88e6xxx_atu_entry entry = { 0 }; - int ret; - ret = _mv88e6xxx_port_vid_to_fid(ds, port, vid); - if (ret < 0) - return ret; - - entry.fid = ret; + entry.fid = vid; /* We use one FID per VLAN */ entry.state = state; ether_addr_copy(entry.mac, addr); if (state != GLOBAL_ATU_DATA_STATE_UNUSED) { @@ -1837,30 +1715,45 @@ static int _mv88e6xxx_port_fdb_load(struct dsa_switch *ds, int port, return _mv88e6xxx_atu_load(ds, &entry); } +int mv88e6xxx_port_fdb_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_fdb *fdb, + struct switchdev_trans *trans) +{ + /* We don't use per-port FDB */ + if (fdb->vid == 0) + return -EOPNOTSUPP; + + /* We don't need any dynamic resource from the kernel (yet), + * so skip the prepare phase. + */ + return 0; +} + int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port, - const unsigned char *addr, u16 vid) + const struct switchdev_obj_port_fdb *fdb, + struct switchdev_trans *trans) { - int state = is_multicast_ether_addr(addr) ? + int state = is_multicast_ether_addr(fdb->addr) ? GLOBAL_ATU_DATA_STATE_MC_STATIC : GLOBAL_ATU_DATA_STATE_UC_STATIC; struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); int ret; mutex_lock(&ps->smi_mutex); - ret = _mv88e6xxx_port_fdb_load(ds, port, addr, vid, state); + ret = _mv88e6xxx_port_fdb_load(ds, port, fdb->addr, fdb->vid, 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) + const struct switchdev_obj_port_fdb *fdb) { struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); int ret; mutex_lock(&ps->smi_mutex); - ret = _mv88e6xxx_port_fdb_load(ds, port, addr, vid, + ret = _mv88e6xxx_port_fdb_load(ds, port, fdb->addr, fdb->vid, GLOBAL_ATU_DATA_STATE_UNUSED); mutex_unlock(&ps->smi_mutex); @@ -1868,7 +1761,6 @@ int mv88e6xxx_port_fdb_del(struct dsa_switch *ds, int port, } static int _mv88e6xxx_atu_getnext(struct dsa_switch *ds, u16 fid, - const unsigned char *addr, struct mv88e6xxx_atu_entry *entry) { struct mv88e6xxx_atu_entry next = { 0 }; @@ -1880,11 +1772,11 @@ static int _mv88e6xxx_atu_getnext(struct dsa_switch *ds, u16 fid, if (ret < 0) return ret; - ret = _mv88e6xxx_atu_mac_write(ds, addr); + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_ATU_FID, fid); if (ret < 0) return ret; - ret = _mv88e6xxx_atu_cmd(ds, fid, GLOBAL_ATU_OP_GET_NEXT_DB); + ret = _mv88e6xxx_atu_cmd(ds, GLOBAL_ATU_OP_GET_NEXT_DB); if (ret < 0) return ret; @@ -1917,51 +1809,99 @@ static int _mv88e6xxx_atu_getnext(struct dsa_switch *ds, u16 fid, return 0; } -/* get next entry for port */ -int mv88e6xxx_port_fdb_getnext(struct dsa_switch *ds, int port, - unsigned char *addr, u16 *vid, bool *is_static) +int mv88e6xxx_port_fdb_dump(struct dsa_switch *ds, int port, + struct switchdev_obj_port_fdb *fdb, + int (*cb)(struct switchdev_obj *obj)) { struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - struct mv88e6xxx_atu_entry next; - u16 fid; - int ret; + struct mv88e6xxx_vtu_stu_entry vlan = { + .vid = GLOBAL_VTU_VID_MASK, /* all ones */ + }; + int err; mutex_lock(&ps->smi_mutex); - ret = _mv88e6xxx_port_vid_to_fid(ds, port, *vid); - if (ret < 0) + err = _mv88e6xxx_vtu_vid_write(ds, vlan.vid); + if (err) goto unlock; - fid = ret; do { - if (is_broadcast_ether_addr(addr)) { - struct mv88e6xxx_vtu_stu_entry vtu; + struct mv88e6xxx_atu_entry addr = { + .mac = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; - ret = _mv88e6xxx_port_vtu_getnext(ds, port, *vid, &vtu); - if (ret < 0) - goto unlock; + err = _mv88e6xxx_vtu_getnext(ds, &vlan); + if (err) + goto unlock; - *vid = vtu.vid; - fid = vtu.fid; - } + if (!vlan.valid) + break; - ret = _mv88e6xxx_atu_getnext(ds, fid, addr, &next); - if (ret < 0) + err = _mv88e6xxx_atu_mac_write(ds, addr.mac); + if (err) goto unlock; - ether_addr_copy(addr, next.mac); + do { + err = _mv88e6xxx_atu_getnext(ds, vlan.fid, &addr); + if (err) + goto unlock; + + if (addr.state == GLOBAL_ATU_DATA_STATE_UNUSED) + break; + + if (!addr.trunk && addr.portv_trunkid & BIT(port)) { + bool is_static = addr.state == + (is_multicast_ether_addr(addr.mac) ? + GLOBAL_ATU_DATA_STATE_MC_STATIC : + GLOBAL_ATU_DATA_STATE_UC_STATIC); - if (next.state == GLOBAL_ATU_DATA_STATE_UNUSED) - continue; - } while (next.trunk || (next.portv_trunkid & BIT(port)) == 0); + fdb->vid = vlan.vid; + ether_addr_copy(fdb->addr, addr.mac); + fdb->ndm_state = is_static ? NUD_NOARP : + NUD_REACHABLE; + + err = cb(&fdb->obj); + if (err) + goto unlock; + } + } while (!is_broadcast_ether_addr(addr.mac)); + + } while (vlan.vid < GLOBAL_VTU_VID_MASK); - *is_static = next.state == (is_multicast_ether_addr(addr) ? - GLOBAL_ATU_DATA_STATE_MC_STATIC : - GLOBAL_ATU_DATA_STATE_UC_STATIC); unlock: mutex_unlock(&ps->smi_mutex); - return ret; + return err; +} + +int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, u32 members) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + const u16 pvid = 4000 + ds->index * DSA_MAX_PORTS + port; + int err; + + /* The port joined a bridge, so leave its reserved VLAN */ + mutex_lock(&ps->smi_mutex); + err = _mv88e6xxx_port_vlan_del(ds, port, pvid); + if (!err) + err = _mv88e6xxx_port_pvid_set(ds, port, 0); + mutex_unlock(&ps->smi_mutex); + return err; +} + +int mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port, u32 members) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + const u16 pvid = 4000 + ds->index * DSA_MAX_PORTS + port; + int err; + + /* The port left the bridge, so join its reserved VLAN */ + mutex_lock(&ps->smi_mutex); + err = _mv88e6xxx_port_vlan_add(ds, port, pvid, true); + if (!err) + err = _mv88e6xxx_port_pvid_set(ds, port, pvid); + mutex_unlock(&ps->smi_mutex); + return err; } static void mv88e6xxx_bridge_work(struct work_struct *work) @@ -1983,7 +1923,7 @@ static void mv88e6xxx_bridge_work(struct work_struct *work) static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port) { struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - int ret, fid; + int ret; u16 reg; mutex_lock(&ps->smi_mutex); @@ -2109,7 +2049,7 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port) reg |= PORT_CONTROL_2_FORWARD_UNKNOWN; } - reg |= PORT_CONTROL_2_8021Q_FALLBACK; + reg |= PORT_CONTROL_2_8021Q_SECURE; if (reg) { ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), @@ -2123,8 +2063,12 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port) * a port bitmap that has only the bit for this port set and * the other bits clear. */ - ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), PORT_ASSOC_VECTOR, - 1 << port); + reg = 1 << port; + /* Disable learning for DSA and CPU ports */ + if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)) + reg = PORT_ASSOC_VECTOR_LOCKED_PORT; + + ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), PORT_ASSOC_VECTOR, reg); if (ret) goto abort; @@ -2202,19 +2146,11 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port) 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. + /* Port based VLAN map: do not give each port its own address + * database, and allow every port to egress frames on all other ports. */ - fid = port + 1; - ps->fid[port] = fid; - set_bit(fid, ps->fid_bitmap); - - if (!dsa_is_cpu_port(ds, port)) - ps->bridge_mask[fid] = 1 << port; - - ret = _mv88e6xxx_update_port_config(ds, port); + reg = BIT(ps->num_ports) - 1; /* all ports */ + ret = _mv88e6xxx_port_vlan_map_set(ds, port, reg & ~port); if (ret) goto abort; @@ -2238,271 +2174,21 @@ int mv88e6xxx_setup_ports(struct dsa_switch *ds) ret = mv88e6xxx_setup_port(ds, i); if (ret < 0) return ret; - } - return 0; -} - -static int mv88e6xxx_regs_show(struct seq_file *s, void *p) -{ - struct dsa_switch *ds = s->private; - - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - int reg, port; - - seq_puts(s, " GLOBAL GLOBAL2 "); - for (port = 0 ; port < ps->num_ports; port++) - seq_printf(s, " %2d ", port); - seq_puts(s, "\n"); - - for (reg = 0; reg < 32; reg++) { - seq_printf(s, "%2x: ", reg); - seq_printf(s, " %4x %4x ", - mv88e6xxx_reg_read(ds, REG_GLOBAL, reg), - mv88e6xxx_reg_read(ds, REG_GLOBAL2, reg)); - for (port = 0 ; port < ps->num_ports; port++) - seq_printf(s, "%4x ", - mv88e6xxx_reg_read(ds, REG_PORT(port), reg)); - seq_puts(s, "\n"); - } - - return 0; -} - -static int mv88e6xxx_regs_open(struct inode *inode, struct file *file) -{ - return single_open(file, mv88e6xxx_regs_show, inode->i_private); -} - -static const struct file_operations mv88e6xxx_regs_fops = { - .open = mv88e6xxx_regs_open, - .read = seq_read, - .llseek = no_llseek, - .release = single_release, - .owner = THIS_MODULE, -}; - -static void mv88e6xxx_atu_show_header(struct seq_file *s) -{ - seq_puts(s, "DB T/P Vec State Addr\n"); -} - -static void mv88e6xxx_atu_show_entry(struct seq_file *s, int dbnum, - unsigned char *addr, int data) -{ - bool trunk = !!(data & GLOBAL_ATU_DATA_TRUNK); - int portvec = ((data & GLOBAL_ATU_DATA_PORT_VECTOR_MASK) >> - GLOBAL_ATU_DATA_PORT_VECTOR_SHIFT); - int state = data & GLOBAL_ATU_DATA_STATE_MASK; - - seq_printf(s, "%03x %5s %10pb %x %pM\n", - dbnum, (trunk ? "Trunk" : "Port"), &portvec, state, addr); -} - -static int mv88e6xxx_atu_show_db(struct seq_file *s, struct dsa_switch *ds, - int dbnum) -{ - unsigned char bcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; - unsigned char addr[6]; - int ret, data, state; - - ret = _mv88e6xxx_atu_mac_write(ds, bcast); - if (ret < 0) - return ret; - - do { - ret = _mv88e6xxx_atu_cmd(ds, dbnum, GLOBAL_ATU_OP_GET_NEXT_DB); - if (ret < 0) - return ret; - data = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_ATU_DATA); - if (data < 0) - return data; + if (dsa_is_cpu_port(ds, i) || dsa_is_dsa_port(ds, i)) + continue; - state = data & GLOBAL_ATU_DATA_STATE_MASK; - if (state == GLOBAL_ATU_DATA_STATE_UNUSED) - break; - ret = _mv88e6xxx_atu_mac_read(ds, addr); + /* setup the unbridged state */ + ret = mv88e6xxx_port_bridge_leave(ds, i, 0); if (ret < 0) return ret; - mv88e6xxx_atu_show_entry(s, dbnum, addr, data); - } while (state != GLOBAL_ATU_DATA_STATE_UNUSED); - - return 0; -} - -static int mv88e6xxx_atu_show(struct seq_file *s, void *p) -{ - struct dsa_switch *ds = s->private; - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - int dbnum; - - mv88e6xxx_atu_show_header(s); - - for (dbnum = 0; dbnum < 255; dbnum++) { - mutex_lock(&ps->smi_mutex); - mv88e6xxx_atu_show_db(s, ds, dbnum); - mutex_unlock(&ps->smi_mutex); - } - - return 0; -} - -static int mv88e6xxx_atu_open(struct inode *inode, struct file *file) -{ - return single_open(file, mv88e6xxx_atu_show, inode->i_private); -} - -static const struct file_operations mv88e6xxx_atu_fops = { - .open = mv88e6xxx_atu_open, - .read = seq_read, - .llseek = no_llseek, - .release = single_release, - .owner = THIS_MODULE, -}; - -static void mv88e6xxx_stats_show_header(struct seq_file *s, - struct mv88e6xxx_priv_state *ps) -{ - int port; - - seq_puts(s, " Statistic "); - for (port = 0 ; port < ps->num_ports; port++) - seq_printf(s, "Port %2d ", port); - seq_puts(s, "\n"); -} - -static int mv88e6xxx_stats_show(struct seq_file *s, void *p) -{ - struct dsa_switch *ds = s->private; - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - struct mv88e6xxx_hw_stat *stats = mv88e6xxx_hw_stats; - int port, stat, max_stats; - uint64_t value; - - if (have_sw_in_discards(ds)) - max_stats = ARRAY_SIZE(mv88e6xxx_hw_stats); - else - max_stats = ARRAY_SIZE(mv88e6xxx_hw_stats) - 3; - - mv88e6xxx_stats_show_header(s, ps); - - mutex_lock(&ps->smi_mutex); - - for (stat = 0; stat < max_stats; stat++) { - seq_printf(s, "%19s: ", stats[stat].string); - for (port = 0 ; port < ps->num_ports; port++) { - _mv88e6xxx_stats_snapshot(ds, port); - value = _mv88e6xxx_get_ethtool_stat(ds, stat, stats, - port); - seq_printf(s, "%8llu ", value); - } - seq_puts(s, "\n"); } - mutex_unlock(&ps->smi_mutex); - return 0; } -static int mv88e6xxx_stats_open(struct inode *inode, struct file *file) -{ - return single_open(file, mv88e6xxx_stats_show, inode->i_private); -} - -static const struct file_operations mv88e6xxx_stats_fops = { - .open = mv88e6xxx_stats_open, - .read = seq_read, - .llseek = no_llseek, - .release = single_release, - .owner = THIS_MODULE, -}; - -static int mv88e6xxx_device_map_show(struct seq_file *s, void *p) -{ - struct dsa_switch *ds = s->private; - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - int target, ret; - - seq_puts(s, "Target Port\n"); - - mutex_lock(&ps->smi_mutex); - for (target = 0; target < 32; target++) { - ret = _mv88e6xxx_reg_write( - ds, REG_GLOBAL2, GLOBAL2_DEVICE_MAPPING, - target << GLOBAL2_DEVICE_MAPPING_TARGET_SHIFT); - if (ret < 0) - goto out; - ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL2, - GLOBAL2_DEVICE_MAPPING); - seq_printf(s, " %2d %2d\n", target, - ret & GLOBAL2_DEVICE_MAPPING_PORT_MASK); - } -out: - mutex_unlock(&ps->smi_mutex); - - return 0; -} - -static int mv88e6xxx_device_map_open(struct inode *inode, struct file *file) -{ - return single_open(file, mv88e6xxx_device_map_show, inode->i_private); -} - -static const struct file_operations mv88e6xxx_device_map_fops = { - .open = mv88e6xxx_device_map_open, - .read = seq_read, - .llseek = no_llseek, - .release = single_release, - .owner = THIS_MODULE, -}; - -static int mv88e6xxx_scratch_show(struct seq_file *s, void *p) -{ - struct dsa_switch *ds = s->private; - struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - int reg, ret; - - seq_puts(s, "Register Value\n"); - - mutex_lock(&ps->smi_mutex); - for (reg = 0; reg < 0x80; reg++) { - ret = _mv88e6xxx_reg_write( - ds, REG_GLOBAL2, GLOBAL2_SCRATCH_MISC, - reg << GLOBAL2_SCRATCH_REGISTER_SHIFT); - if (ret < 0) - goto out; - - ret = _mv88e6xxx_scratch_wait(ds); - if (ret < 0) - goto out; - - ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL2, - GLOBAL2_SCRATCH_MISC); - seq_printf(s, " %2x %2x\n", reg, - ret & GLOBAL2_SCRATCH_VALUE_MASK); - } -out: - mutex_unlock(&ps->smi_mutex); - - return 0; -} - -static int mv88e6xxx_scratch_open(struct inode *inode, struct file *file) -{ - return single_open(file, mv88e6xxx_scratch_show, inode->i_private); -} - -static const struct file_operations mv88e6xxx_scratch_fops = { - .open = mv88e6xxx_scratch_open, - .read = seq_read, - .llseek = no_llseek, - .release = single_release, - .owner = THIS_MODULE, -}; - int mv88e6xxx_setup_common(struct dsa_switch *ds) { struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); - char *name; mutex_init(&ps->smi_mutex); @@ -2510,24 +2196,6 @@ int mv88e6xxx_setup_common(struct dsa_switch *ds) INIT_WORK(&ps->bridge_work, mv88e6xxx_bridge_work); - name = kasprintf(GFP_KERNEL, "dsa%d", ds->index); - ps->dbgfs = debugfs_create_dir(name, NULL); - kfree(name); - - debugfs_create_file("regs", S_IRUGO, ps->dbgfs, ds, - &mv88e6xxx_regs_fops); - - debugfs_create_file("atu", S_IRUGO, ps->dbgfs, ds, - &mv88e6xxx_atu_fops); - - debugfs_create_file("stats", S_IRUGO, ps->dbgfs, ds, - &mv88e6xxx_stats_fops); - - debugfs_create_file("device_map", S_IRUGO, ps->dbgfs, ds, - &mv88e6xxx_device_map_fops); - - debugfs_create_file("scratch", S_IRUGO, ps->dbgfs, ds, - &mv88e6xxx_scratch_fops); return 0; } @@ -2638,6 +2306,11 @@ int mv88e6xxx_setup_global(struct dsa_switch *ds) if (ret < 0) goto unlock; + /* Clear all ATU entries */ + ret = _mv88e6xxx_atu_flush(ds, 0, true); + if (ret < 0) + goto unlock; + /* Clear all the VTU and STU entries */ ret = _mv88e6xxx_vtu_stu_flush(ds); unlock: @@ -2920,6 +2593,38 @@ int mv88e6xxx_get_temp_alarm(struct dsa_switch *ds, bool *alarm) } #endif /* CONFIG_NET_DSA_HWMON */ +char *mv88e6xxx_lookup_name(struct device *host_dev, int sw_addr, + const struct mv88e6xxx_switch_id *table, + unsigned int num) +{ + struct mii_bus *bus = dsa_host_dev_to_mii_bus(host_dev); + int i, ret; + + if (!bus) + return NULL; + + ret = __mv88e6xxx_reg_read(bus, sw_addr, REG_PORT(0), PORT_SWITCH_ID); + if (ret < 0) + return NULL; + + /* Look up the exact switch ID */ + for (i = 0; i < num; ++i) + if (table[i].id == ret) + return table[i].name; + + /* Look up only the product number */ + for (i = 0; i < num; ++i) { + if (table[i].id == (ret & PORT_SWITCH_ID_PROD_NUM_MASK)) { + dev_warn(host_dev, "unknown revision %d, using base switch 0x%x\n", + ret & PORT_SWITCH_ID_REV_MASK, + ret & PORT_SWITCH_ID_PROD_NUM_MASK); + return table[i].name; + } + } + + return NULL; +} + static int __init mv88e6xxx_init(void) { #if IS_ENABLED(CONFIG_NET_DSA_MV88E6131) |