aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/dsa/mv88e6xxx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa/mv88e6xxx.c')
-rw-r--r--drivers/net/dsa/mv88e6xxx.c1087
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)