Subject: Re: bug: net: dsa: mv88e6xxx: unable to tx or rx with Clearfog GT 8K (with git bisect)

From: Russell King - ARM Linux admin <[email-masked]>
Date: Fri, 17 Jul 2020 10:21:54 +0100

On Fri, Jul 17, 2020 at 05:56:22AM +0000, Martin Rowe wrote:
> On Sun, 12 Jul 2020 at 13:26, Russell King - ARM Linux admin
> <[email-masked]> wrote:
> > If it is a port issue, that should help pinpoint it - if it's a problem
> > with the CPU port configuration, then ethtool can't read those registers
> > (and the only way to get them is to apply some debugfs patch that was
> > refused from being merged into mainline.)
> 
> It looks like this is the next step then. Do you have a link to the
> patch? I'll give it a go. What outputs would be useful for me to
> provide?

The patch below is what I'm still using - a forward ported version of
a Vivien's very old debugfs patch (but with bits of it disabled.) I
prefer the combined format of the "regs" file which dumps all the
switch registers in one place.

The key file is /sys/kernel/debug/mv88e6xxx.0/regs - please send the
contents of that file.

Thanks.

8<===
From: Vivien Didelot <[email-masked]>
Subject: [PATCH] net: dsa: mv88e6xxx: add debugfs interface

Add a debugfs directory named mv88e6xxx.X where X is the DSA switch
index. Mount the debugfs file system with:

    # mount -t debugfs none /sys/kernel/debug

Signed-off-by: Vivien Didelot <[email-masked]>
[Modified by rmk for current kernels.]
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/dsa/mv88e6xxx/chip.c              |    7 +
 drivers/net/dsa/mv88e6xxx/chip.h              |    2 +
 drivers/net/dsa/mv88e6xxx/mv88e6xxx_debugfs.c | 1102 +++++++++++++++++
 3 files changed, 1111 insertions(+)
 create mode 100644 drivers/net/dsa/mv88e6xxx/mv88e6xxx_debugfs.c

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 7627ea61e0ea..0bce26f1df93 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -3030,8 +3030,13 @@ static int mv88e6xxx_setup_devlink_resources(struct dsa_switch *ds)
 	return err;
 }
 
+#include "mv88e6xxx_debugfs.c"
+
 static void mv88e6xxx_teardown(struct dsa_switch *ds)
 {
+	struct mv88e6xxx_chip *chip = ds->priv;
+
+	mv88e6xxx_remove_debugfs(chip);
 	mv88e6xxx_teardown_devlink_params(ds);
 	dsa_devlink_resources_unregister(ds);
 }
@@ -3149,6 +3154,8 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
 	if (err)
 		goto unlock;
 
+	mv88e6xxx_init_debugfs(chip);
+
 unlock:
 	mv88e6xxx_reg_unlock(chip);
 
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e5430cf2ad71..f78536bdfe39 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -333,6 +333,8 @@ struct mv88e6xxx_chip {
 
 	/* Array of port structures. */
 	struct mv88e6xxx_port ports[DSA_MAX_PORTS];
+
+	struct dentry *dbgfs;
 };
 
 struct mv88e6xxx_bus_ops {
diff --git a/drivers/net/dsa/mv88e6xxx/mv88e6xxx_debugfs.c b/drivers/net/dsa/mv88e6xxx/mv88e6xxx_debugfs.c
new file mode 100644
index 000000000000..4005a4760884
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/mv88e6xxx_debugfs.c
@@ -0,0 +1,1102 @@
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#define GLOBAL2_PVT_ADDR	0x0b
+#define GLOBAL2_PVT_ADDR_BUSY	BIT(15)
+#define GLOBAL2_PVT_ADDR_OP_INIT_ONES	((0x01 << 12) | GLOBAL2_PVT_ADDR_BUSY)
+#define GLOBAL2_PVT_ADDR_OP_WRITE_PVLAN	((0x03 << 12) | GLOBAL2_PVT_ADDR_BUSY)
+#define GLOBAL2_PVT_ADDR_OP_READ	((0x04 << 12) | GLOBAL2_PVT_ADDR_BUSY)
+#define GLOBAL2_PVT_DATA	0x0c
+
+#define ADDR_GLOBAL2	0x1c
+
+static int mv88e6xxx_serdes_read(struct mv88e6xxx_chip *chip, int reg, u16 *val)
+{
+	return mv88e6xxx_phy_page_read(chip, MV88E6352_ADDR_SERDES,
+				       MV88E6352_SERDES_PAGE_FIBER,
+				       reg, val);
+}
+
+static int mv88e6xxx_serdes_write(struct mv88e6xxx_chip *chip, int reg, u16 val)
+{
+	return mv88e6xxx_phy_page_write(chip, MV88E6352_ADDR_SERDES,
+					MV88E6352_SERDES_PAGE_FIBER,
+					reg, val);
+}
+
+static int _mv88e6xxx_pvt_wait(struct mv88e6xxx_chip *chip)
+{
+	return mv88e6xxx_wait_mask(chip, ADDR_GLOBAL2, GLOBAL2_PVT_ADDR,
+				   GLOBAL2_PVT_ADDR_BUSY, 0);
+}
+
+static int _mv88e6xxx_pvt_cmd(struct mv88e6xxx_chip *chip, int src_dev,
+			      int src_port, u16 op)
+{
+	u16 reg = op;
+	int err;
+
+	/* 9-bit Cross-chip PVT pointer: with GLOBAL2_MISC_5_BIT_PORT cleared,
+	 * source device is 5-bit, source port is 4-bit.
+	 */
+	reg |= (src_dev & 0x1f) << 4;
+	reg |= (src_port & 0xf);
+
+	err = mv88e6xxx_g2_write(chip, GLOBAL2_PVT_ADDR, reg);
+	if (err)
+		return err;
+
+	return _mv88e6xxx_pvt_wait(chip);
+}
+
+static int _mv88e6xxx_pvt_read(struct mv88e6xxx_chip *chip, int src_dev,
+			       int src_port, u16 *data)
+{
+	int ret;
+
+	ret = _mv88e6xxx_pvt_wait(chip);
+	if (ret < 0)
+		return ret;
+
+	ret = _mv88e6xxx_pvt_cmd(chip, src_dev, src_port,
+				GLOBAL2_PVT_ADDR_OP_READ);
+	if (ret < 0)
+		return ret;
+
+	return mv88e6xxx_g2_read(chip, GLOBAL2_PVT_DATA, data);
+}
+
+static int _mv88e6xxx_pvt_write(struct mv88e6xxx_chip *chip, int src_dev,
+				int src_port, u16 data)
+{
+	int err;
+
+	err = _mv88e6xxx_pvt_wait(chip);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_g2_write(chip, GLOBAL2_PVT_DATA, data);
+	if (err)
+		return err;
+
+        return _mv88e6xxx_pvt_cmd(chip, src_dev, src_port,
+				  GLOBAL2_PVT_ADDR_OP_WRITE_PVLAN);
+}
+
+static int mv88e6xxx_regs_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	int port, reg, ret;
+	u16 data;
+
+	seq_puts(s, "    GLOBAL GLOBAL2 SERDES   ");
+	for (port = 0; port < mv88e6xxx_num_ports(chip); port++)
+		seq_printf(s, " %2d  ", port);
+	seq_puts(s, "\n");
+
+	mutex_lock(&chip->reg_lock);
+
+	for (reg = 0; reg < 32; reg++) {
+		seq_printf(s, "%2x:", reg);
+
+		ret = mv88e6xxx_g1_read(chip, reg, &data);
+		if (ret < 0)
+			goto unlock;
+		seq_printf(s, "  %4x  ", data);
+
+		ret = mv88e6xxx_g2_read(chip, reg, &data);
+		if (ret < 0)
+			goto unlock;
+		seq_printf(s, "  %4x  ", data);
+
+		if (reg != MV88E6XXX_PHY_PAGE) {
+			ret = mv88e6xxx_serdes_read(chip, reg, &data);
+			if (ret < 0)
+				goto unlock;
+		} else {
+			data = 0;
+		}
+		seq_printf(s, "  %4x  ", data);
+
+		/* Port regs 0x1a-0x1f are reserved in 6185 family */
+		if (chip->info->family == MV88E6XXX_FAMILY_6185 && reg > 25) {
+			for (port = 0; port < mv88e6xxx_num_ports(chip); ++port)
+				seq_printf(s, "%4c ", '-');
+			seq_puts(s, "\n");
+			continue;
+		}
+
+		for (port = 0; port < mv88e6xxx_num_ports(chip); ++port) {
+			ret = mv88e6xxx_port_read(chip, port, reg, &data);
+			if (ret < 0)
+				goto unlock;
+
+			seq_printf(s, "%4x ", data);
+		}
+
+		seq_puts(s, "\n");
+	}
+
+	ret = 0;
+unlock:
+	mutex_unlock(&chip->reg_lock);
+
+	return ret;
+}
+
+static ssize_t mv88e6xxx_regs_write(struct file *file, const char __user *buf,
+				    size_t count, loff_t *ppos)
+{
+	struct seq_file *s = file->private_data;
+	struct mv88e6xxx_chip *chip = s->private;
+	char cmd[32], name[32] = { 0 };
+	unsigned int port, reg, val;
+	int ret;
+
+	if (count > sizeof(name) - 1)
+		return -EINVAL;
+
+	if (copy_from_user(cmd, buf, sizeof(cmd)))
+		return -EFAULT;
+
+	ret = sscanf(cmd, "%s %x %x", name, &reg, &val);
+	if (ret != 3)
+		return -EINVAL;
+
+	if (reg > 0x1f || val > 0xffff)
+		return -ERANGE;
+
+	mutex_lock(&chip->reg_lock);
+
+	if (strcasecmp(name, "GLOBAL") == 0)
+		ret = mv88e6xxx_g1_write(chip, reg, val);
+	else if (strcasecmp(name, "GLOBAL2") == 0)
+		ret = mv88e6xxx_g2_write(chip, reg, val);
+	else if (strcasecmp(name, "SERDES") == 0)
+		ret = mv88e6xxx_serdes_write(chip, reg, val);
+	else if (kstrtouint(name, 10, &port) == 0 && port < mv88e6xxx_num_ports(chip))
+		ret = mv88e6xxx_port_write(chip, port, reg, val);
+	else
+		ret = -EINVAL;
+
+	mutex_unlock(&chip->reg_lock);
+
+	return ret < 0 ? ret : count;
+}
+
+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,
+	.write  = mv88e6xxx_regs_write,
+	.llseek = no_llseek,
+	.release = single_release,
+	.owner  = THIS_MODULE,
+};
+
+static int mv88e6xxx_name_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	struct dsa_switch *ds = chip->ds;
+	struct dsa_switch_tree *dst = ds->dst;
+	struct dsa_port *dp;
+	int i;
+
+	if (!ds->cd)
+		return 0;
+
+	seq_puts(s, " Port  Name\n");
+
+	list_for_each_entry(dp, &dst->ports, list) {
+		if (dp->ds != ds)
+			continue;
+
+		i = dp->index;
+		if (!ds->cd->port_names[i])
+			continue;
+
+		seq_printf(s, "%4d   %s", i, ds->cd->port_names[i]);
+
+		if (dp->slave)
+			seq_printf(s, " (%s)", netdev_name(dp->slave));
+
+		seq_puts(s, "\n");
+	}
+
+	return 0;
+}
+
+static int mv88e6xxx_name_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mv88e6xxx_name_show, inode->i_private);
+}
+
+static const struct file_operations mv88e6xxx_name_fops = {
+	.open		= mv88e6xxx_name_open,
+	.read		= seq_read,
+	.llseek		= no_llseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+
+static int mv88e6xxx_atu_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	struct mv88e6xxx_atu_entry addr;
+	const char *state;
+	int fid, i, err;
+
+	seq_puts(s, " FID  MAC Addr                  State         Trunk?  DPV/Trunk ID\n");
+
+	for (fid = 0; fid < mv88e6xxx_num_databases(chip); ++fid) {
+		addr.state = 0;
+		eth_broadcast_addr(addr.mac);
+
+		do {
+			mutex_lock(&chip->reg_lock);
+			err = mv88e6xxx_g1_atu_getnext(chip, fid, &addr);
+			mutex_unlock(&chip->reg_lock);
+			if (err)
+				return err;
+
+			if (addr.state == 0)
+				break;
+
+			/* print ATU entry */
+			seq_printf(s, "%4d  %pM", fid, addr.mac);
+
+			if (is_multicast_ether_addr(addr.mac)) {
+				switch (addr.state) {
+				case MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_PO:
+					state = "MC_STATIC_PO";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_DA_MGMT_PO:
+					state = "MC_STATIC_MGMT_PO";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_AVB_NRL_PO:
+					state = "MC_STATIC_NRL_PO";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_POLICY_PO:
+					state = "MC_STATIC_POLICY_PO";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC:
+					state = "MC_STATIC";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_DA_MGMT:
+					state = "MC_STATIC_MGMT";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_AVB_NRL:
+					state = "MC_STATIC_NRL";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_POLICY:
+					state = "MC_STATIC_POLICY";
+					break;
+				case 0xb: case 0xa: case 0x9: case 0x8:
+					/* Reserved for future use */
+				case 0x3: case 0x2: case 0x1:
+					/* Reserved for future use */
+				case MV88E6XXX_G1_ATU_DATA_STATE_MC_UNUSED:
+				default:
+					state = "???";
+					break;
+				}
+			} else {
+				switch (addr.state) {
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_PO:
+					state = "UC_STATIC_PO";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC:
+					state = "UC_STATIC";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_DA_MGMT_PO:
+					state = "UC_STATIC_MGMT_PO";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_DA_MGMT:
+					state = "UC_STATIC_MGMT";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_AVB_NRL_PO:
+					state = "UC_STATIC_NRL_PO";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_AVB_NRL:
+					state = "UC_STATIC_NRL";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_POLICY_PO:
+					state = "UC_STATIC_POLICY_PO";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_POLICY:
+					state = "UC_STATIC_POLICY";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_7_NEWEST:
+					state = "Age 7 (newest)";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_6:
+					state = "Age 6";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_5:
+					state = "Age 5";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_4:
+					state = "Age 4";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_3:
+					state = "Age 3";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_2:
+					state = "Age 2";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_1_OLDEST:
+					state = "Age 1 (oldest)";
+					break;
+				case MV88E6XXX_G1_ATU_DATA_STATE_UC_UNUSED:
+				default:
+					state = "???";
+					break;
+				}
+			}
+
+			seq_printf(s, "  %19s", state);
+
+			if (addr.trunk) {
+				seq_printf(s, "       y  %d",
+					   addr.portvec);
+			} else {
+				seq_puts(s, "       n ");
+				for (i = 0; i < mv88e6xxx_num_ports(chip); ++i)
+					seq_printf(s, " %c",
+						   addr.portvec & BIT(i) ?
+						   48 + i : '-');
+			}
+
+			seq_puts(s, "\n");
+		} while (!is_broadcast_ether_addr(addr.mac));
+	}
+
+	return 0;
+}
+
+static ssize_t mv88e6xxx_atu_write(struct file *file, const char __user *buf,
+				   size_t count, loff_t *ppos)
+{
+	struct seq_file *s = file->private_data;
+	struct mv88e6xxx_chip *chip = s->private;
+	char cmd[64];
+	unsigned int fid;
+	int ret;
+
+	if (copy_from_user(cmd, buf, sizeof(cmd)))
+		return -EFAULT;
+
+	ret = sscanf(cmd, "%u", &fid);
+	if (ret != 1)
+		return -EINVAL;
+
+	if (fid >= mv88e6xxx_num_databases(chip))
+		return -ERANGE;
+
+	mutex_lock(&chip->reg_lock);
+	ret = mv88e6xxx_g1_atu_flush(chip, fid, true);
+	mutex_unlock(&chip->reg_lock);
+
+	return ret < 0 ? ret : count;
+}
+
+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,
+	.write   = mv88e6xxx_atu_write,
+	.llseek = no_llseek,
+	.release = single_release,
+	.owner  = THIS_MODULE,
+};
+
+static int mv88e6xxx_default_vid_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	u16 pvid;
+	int i, err;
+
+	seq_puts(s, " Port  DefaultVID\n");
+
+	mutex_lock(&chip->reg_lock);
+
+	for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
+		err = mv88e6xxx_port_get_pvid(chip, i, &pvid);
+		if (err)
+			break;
+
+		seq_printf(s, "%4d  %d\n", i, pvid);
+	}
+
+	mutex_unlock(&chip->reg_lock);
+
+	return err;
+}
+
+static ssize_t mv88e6xxx_default_vid_write(struct file *file,
+					   const char __user *buf, size_t count,
+					   loff_t *ppos)
+{
+	struct seq_file *s = file->private_data;
+	struct mv88e6xxx_chip *chip = s->private;
+	char cmd[32];
+	unsigned int port, pvid;
+	int ret;
+
+	if (copy_from_user(cmd, buf, sizeof(cmd)))
+		return -EFAULT;
+
+	ret = sscanf(cmd, "%u %u", &port, &pvid);
+	if (ret != 2)
+		return -EINVAL;
+
+	if (port >= mv88e6xxx_num_ports(chip) || pvid > 0xfff)
+		return -ERANGE;
+
+	mutex_lock(&chip->reg_lock);
+	ret = mv88e6xxx_port_set_pvid(chip, port, pvid);
+	mutex_unlock(&chip->reg_lock);
+
+	return ret < 0 ? ret : count;
+}
+
+static int mv88e6xxx_default_vid_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mv88e6xxx_default_vid_show, inode->i_private);
+}
+
+static const struct file_operations mv88e6xxx_default_vid_fops = {
+	.open		= mv88e6xxx_default_vid_open,
+	.read		= seq_read,
+	.write		= mv88e6xxx_default_vid_write,
+	.llseek		= no_llseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+
+static int mv88e6xxx_fid_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	u16 fid;
+	int i, err;
+
+	seq_puts(s, " Port  FID\n");
+
+	mutex_lock(&chip->reg_lock);
+
+	for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
+		err = mv88e6xxx_port_get_fid(chip, i, &fid);
+		if (err)
+			break;
+
+		seq_printf(s, "%4d  %d\n", i, fid);
+	}
+
+	mutex_unlock(&chip->reg_lock);
+
+	return err;
+}
+
+static int mv88e6xxx_fid_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mv88e6xxx_fid_show, inode->i_private);
+}
+
+static const struct file_operations mv88e6xxx_fid_fops = {
+	.open		= mv88e6xxx_fid_open,
+	.read		= seq_read,
+	.llseek		= no_llseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+
+static const char * const mv88e6xxx_port_state_names[] = {
+	[MV88E6XXX_PORT_CTL0_STATE_DISABLED] = "Disabled",
+	[MV88E6XXX_PORT_CTL0_STATE_BLOCKING] = "Blocking/Listening",
+	[MV88E6XXX_PORT_CTL0_STATE_LEARNING] = "Learning",
+	[MV88E6XXX_PORT_CTL0_STATE_FORWARDING] = "Forwarding",
+};
+
+static int mv88e6xxx_state_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	int i, ret;
+	u16 data;
+
+	/* header */
+	seq_puts(s, " Port  Mode\n");
+
+	mutex_lock(&chip->reg_lock);
+
+	/* One line per input port */
+	for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
+		seq_printf(s, "%4d ", i);
+
+		ret = mv88e6xxx_port_read(chip, i, MV88E6XXX_PORT_CTL0, &data);
+		if (ret < 0)
+			goto unlock;
+
+		data &= MV88E6XXX_PORT_CTL0_STATE_MASK;
+		seq_printf(s, " %s\n", mv88e6xxx_port_state_names[data]);
+		ret = 0;
+	}
+
+unlock:
+	mutex_unlock(&chip->reg_lock);
+
+	return ret;
+}
+
+static int mv88e6xxx_state_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mv88e6xxx_state_show, inode->i_private);
+}
+
+static const struct file_operations mv88e6xxx_state_fops = {
+	.open		= mv88e6xxx_state_open,
+	.read		= seq_read,
+	.llseek		= no_llseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+
+static const char * const mv88e6xxx_port_8021q_mode_names[] = {
+	[MV88E6XXX_PORT_CTL2_8021Q_MODE_DISABLED] = "Disabled",
+	[MV88E6XXX_PORT_CTL2_8021Q_MODE_FALLBACK] = "Fallback",
+	[MV88E6XXX_PORT_CTL2_8021Q_MODE_CHECK] = "Check",
+	[MV88E6XXX_PORT_CTL2_8021Q_MODE_SECURE] = "Secure",
+};
+
+static int mv88e6xxx_8021q_mode_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	int i, ret;
+	u16 data;
+
+	/* header */
+	seq_puts(s, " Port  Mode\n");
+
+	mutex_lock(&chip->reg_lock);
+
+	/* One line per input port */
+	for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
+		seq_printf(s, "%4d ", i);
+
+		ret = mv88e6xxx_port_read(chip, i, MV88E6XXX_PORT_CTL2, &data);
+		if (ret < 0)
+			goto unlock;
+
+		data &= MV88E6XXX_PORT_CTL2_8021Q_MODE_MASK;
+		seq_printf(s, " %s\n", mv88e6xxx_port_8021q_mode_names[data]);
+		ret = 0;
+	}
+
+unlock:
+	mutex_unlock(&chip->reg_lock);
+
+	return ret;
+}
+
+static int mv88e6xxx_8021q_mode_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mv88e6xxx_8021q_mode_show, inode->i_private);
+}
+
+static const struct file_operations mv88e6xxx_8021q_mode_fops = {
+	.open		= mv88e6xxx_8021q_mode_open,
+	.read		= seq_read,
+	.llseek		= no_llseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+
+static int mv88e6xxx_vlan_table_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	int i, j, ret;
+	u16 data;
+
+	/* header */
+	seq_puts(s, " Port");
+	for (i = 0; i < mv88e6xxx_num_ports(chip); ++i)
+		seq_printf(s, " %2d", i);
+	seq_puts(s, "\n");
+
+	mutex_lock(&chip->reg_lock);
+
+	/* One line per input port */
+	for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
+		seq_printf(s, "%4d ", i);
+
+		ret = mv88e6xxx_port_read(chip, i, MV88E6XXX_PORT_BASE_VLAN, &data);
+		if (ret < 0)
+			goto unlock;
+
+		/* One column per output port */
+		for (j = 0; j < mv88e6xxx_num_ports(chip); ++j)
+			seq_printf(s, "  %c", data & BIT(j) ? '*' : '-');
+		seq_puts(s, "\n");
+	}
+
+	ret = 0;
+unlock:
+	mutex_unlock(&chip->reg_lock);
+
+	return ret;
+}
+
+static int mv88e6xxx_vlan_table_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mv88e6xxx_vlan_table_show, inode->i_private);
+}
+
+static const struct file_operations mv88e6xxx_vlan_table_fops = {
+	.open		= mv88e6xxx_vlan_table_open,
+	.read		= seq_read,
+	.llseek		= no_llseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+
+static int mv88e6xxx_pvt_show(struct seq_file *s, void *p)
+{
+#if 0
+	struct mv88e6xxx_chip *chip = s->private;
+	struct dsa_switch_tree *dst = chip->ds->dst;
+	int port, src_dev, src_port;
+	u16 pvlan;
+	int err = 0;
+
+	if (chip->info->family == MV88E6XXX_FAMILY_6185)
+		return -ENODEV;
+
+	/* header */
+	seq_puts(s, " Dev Port PVLAN");
+	for (port = 0; port < mv88e6xxx_num_ports(chip); ++port)
+		seq_printf(s, " %2d", port);
+	seq_puts(s, "\n");
+
+	mutex_lock(&chip->reg_lock);
+
+	/* One line per external port */
+	for (src_dev = 0; src_dev < DSA_MAX_SWITCHES; ++src_dev) {
+		if (!dst->ds[src_dev])
+			break;
+
+		if (src_dev == chip->ds->index)
+			continue;
+
+		seq_puts(s, "\n");
+		for (src_port = 0; src_port < 16; ++src_port) {
+			if (src_port >= DSA_MAX_PORTS)
+				break;
+
+			err = _mv88e6xxx_pvt_read(chip, src_dev, src_port,
+						  &pvlan);
+			if (err)
+				goto unlock;
+
+			seq_printf(s, "  %d   %2d   %03hhx ", src_dev, src_port,
+				   pvlan);
+
+			/* One column per internal output port */
+			for (port = 0; port < mv88e6xxx_num_ports(chip); ++port)
+				seq_printf(s, "  %c",
+					   pvlan & BIT(port) ? '*' : '-');
+			seq_puts(s, "\n");
+		}
+	}
+
+unlock:
+	mutex_unlock(&chip->reg_lock);
+	return err;
+#else
+	return 0;
+#endif
+}
+
+static ssize_t mv88e6xxx_pvt_write(struct file *file, const char __user *buf,
+				    size_t count, loff_t *ppos)
+{
+	struct seq_file *s = file->private_data;
+	struct mv88e6xxx_chip *chip = s->private;
+	const u16 mask = (1 << mv88e6xxx_num_ports(chip)) - 1;
+	char cmd[32];
+	unsigned int src_dev, src_port, pvlan;
+	int ret;
+
+	if (copy_from_user(cmd, buf, sizeof(cmd)))
+		return -EFAULT;
+
+	if (sscanf(cmd, "%d %d %x", &src_dev, &src_port, &pvlan) != 3)
+		return -EINVAL;
+
+	if (src_dev >= 32 || src_port >= 16 || pvlan & ~mask)
+		return -ERANGE;
+
+	mutex_lock(&chip->reg_lock);
+	ret = _mv88e6xxx_pvt_write(chip, src_dev, src_port, pvlan);
+	mutex_unlock(&chip->reg_lock);
+
+	return ret < 0 ? ret : count;
+}
+
+static int mv88e6xxx_pvt_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mv88e6xxx_pvt_show, inode->i_private);
+}
+
+static const struct file_operations mv88e6xxx_pvt_fops = {
+	.open		= mv88e6xxx_pvt_open,
+	.read		= seq_read,
+	.write		= mv88e6xxx_pvt_write,
+	.llseek		= no_llseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+
+static int mv88e6xxx_vtu_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	struct mv88e6xxx_vtu_entry next = { 0 };
+	int port, ret = 0;
+
+	seq_puts(s, " VID  FID  SID");
+	for (port = 0; port < mv88e6xxx_num_ports(chip); ++port)
+		seq_printf(s, " %2d", port);
+	seq_puts(s, "\n");
+
+	if (!chip->info->ops->vtu_getnext)
+		return 0;
+
+	next.vid = chip->info->max_vid; /* first or lowest VID */
+
+	do {
+		mutex_lock(&chip->reg_lock);
+		ret = chip->info->ops->vtu_getnext(chip, &next);
+		mutex_unlock(&chip->reg_lock);
+		if (ret < 0)
+			break;
+
+		if (!next.valid)
+			break;
+
+		seq_printf(s, "%4d %4d   %2d", next.vid, next.fid, next.sid);
+		for (port = 0; port < mv88e6xxx_num_ports(chip); ++port) {
+			switch (next.member[port]) {
+			case MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNMODIFIED:
+				seq_puts(s, "  =");
+				break;
+			case MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNTAGGED:
+				seq_puts(s, "  u");
+				break;
+			case MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_TAGGED:
+				seq_puts(s, "  t");
+				break;
+			case MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_NON_MEMBER:
+				seq_puts(s, "  x");
+				break;
+			default:
+				seq_puts(s, " ??");
+				break;
+			}
+		}
+		seq_puts(s, "\n");
+	} while (next.vid < chip->info->max_vid);
+
+	return ret;
+}
+
+static ssize_t mv88e6xxx_vtu_write(struct file *file, const char __user *buf,
+				   size_t count, loff_t *ppos)
+{
+	struct seq_file *s = file->private_data;
+	struct mv88e6xxx_chip *chip = s->private;
+	struct mv88e6xxx_vtu_entry entry = { 0 };
+	bool valid = true;
+	char cmd[64], tags[12]; /* DSA_MAX_PORTS */
+	int vid, fid, sid, port, ret;
+
+	if (!chip->info->ops->vtu_loadpurge)
+		return -EOPNOTSUPP;
+
+	if (copy_from_user(cmd, buf, sizeof(cmd)))
+		return -EFAULT;
+
+	/* scan 12 chars instead of num_ports to avoid dynamic scanning... */
+	ret = sscanf(cmd, "%d %d %d %c %c %c %c %c %c %c %c %c %c %c %c", &vid,
+		     &fid, &sid, &tags[0], &tags[1], &tags[2], &tags[3],
+		     &tags[4], &tags[5], &tags[6], &tags[7], &tags[8], &tags[9],
+		     &tags[10], &tags[11]);
+	if (ret == 1)
+		valid = false;
+	else if (ret != 3 + mv88e6xxx_num_ports(chip))
+		return -EINVAL;
+
+	entry.vid = vid;
+	entry.valid = valid;
+
+	if (valid) {
+		entry.fid = fid;
+		entry.sid = sid;
+		/* Note: The VTU entry pointed by VID will be loaded but not
+		 * considered valid until the STU entry pointed by SID is valid.
+		 */
+
+		for (port = 0; port < mv88e6xxx_num_ports(chip); ++port) {
+			u8 tag;
+
+			switch (tags[port]) {
+			case 'u':
+				tag = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNTAGGED;
+				break;
+			case 't':
+				tag = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_TAGGED;
+				break;
+			case 'x':
+				tag = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_NON_MEMBER;
+				break;
+			case '=':
+				tag = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNMODIFIED;
+				break;
+			default:
+				return -EINVAL;
+			}
+
+			entry.member[port] = tag;
+		}
+	}
+
+	mutex_lock(&chip->reg_lock);
+	ret = chip->info->ops->vtu_loadpurge(chip, &entry);
+	mutex_unlock(&chip->reg_lock);
+
+	return ret < 0 ? ret : count;
+}
+
+static int mv88e6xxx_vtu_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mv88e6xxx_vtu_show, inode->i_private);
+}
+
+static const struct file_operations mv88e6xxx_vtu_fops = {
+	.open		= mv88e6xxx_vtu_open,
+	.read		= seq_read,
+	.write		= mv88e6xxx_vtu_write,
+	.llseek		= no_llseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+#if 0
+static int mv88e6xxx_stats_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	char *strs;
+	u64 *stats;
+	int stat, port, num_stats, num_ports;
+	int err = 0;
+
+	num_stats = mv88e6xxx_get_sset_count(chip->ds);
+	if (num_stats == 0)
+		return 0;
+
+	num_ports = mv88e6xxx_num_ports(chip);
+
+	strs = kcalloc(num_stats, ETH_GSTRING_LEN, GFP_KERNEL);
+	stats = kcalloc(num_stats, num_ports * sizeof(*stats), GFP_KERNEL);
+	if (!strs || !strs) {
+		kfree(strs);
+		kfree(stats);
+		return -ENOMEM;
+	}
+
+	mv88e6xxx_get_strings(chip->ds, 0, strs);
+
+	for (port = 0; port < num_ports; port++)
+		mv88e6xxx_get_ethtool_stats(chip->ds, port, stats + (port * num_stats));
+
+	seq_puts(s, "          Statistic  ");
+	for (port = 0; port < mv88e6xxx_num_ports(chip); port++)
+		seq_printf(s, " Port %2d ", port);
+	seq_puts(s, "\n");
+
+	for (stat = 0; stat < num_stats; stat++) {
+		seq_printf(s, "%19s: ", strs + stat * ETH_GSTRING_LEN);
+		for (port = 0 ; port < num_ports; port++) {
+			u64 value = stats[stat + port * num_stats];
+
+			seq_printf(s, "%8llu ", value);
+		}
+		seq_puts(s, "\n");
+	}
+
+	kfree(stats);
+	kfree(strs);
+
+	return err;
+}
+
+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,
+};
+#endif
+static int mv88e6xxx_device_map_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	int target, ret;
+	u16 data, port_mask;
+
+	seq_puts(s, "Target Port\n");
+
+	/* FIXME */
+	port_mask = MV88E6390_G2_DEVICE_MAPPING_PORT_MASK;
+
+	mutex_lock(&chip->reg_lock);
+	for (target = 0; target < 32; target++) {
+		ret = mv88e6xxx_g2_write(chip, MV88E6XXX_G2_DEVICE_MAPPING,
+			target << 8 /* MV88E6XXX_G2_DEVICE_MAPPING_DEV_MASK */);
+		if (ret < 0)
+			goto out;
+		ret = mv88e6xxx_g2_read(chip, MV88E6XXX_G2_DEVICE_MAPPING, &data);
+		if (ret < 0)
+			goto out;
+		seq_printf(s, "  %2d   %2d\n", target, data & port_mask);
+	}
+out:
+	mutex_unlock(&chip->reg_lock);
+
+	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,
+};
+
+/* Must be called with SMI lock held */
+static int _mv88e6xxx_scratch_wait(struct mv88e6xxx_chip *chip)
+{
+	return mv88e6xxx_wait_mask(chip, ADDR_GLOBAL2,
+				   MV88E6XXX_G2_SCRATCH_MISC_MISC,
+			           MV88E6XXX_G2_SCRATCH_MISC_UPDATE, 0);
+}
+
+static int mv88e6xxx_scratch_show(struct seq_file *s, void *p)
+{
+	struct mv88e6xxx_chip *chip = s->private;
+	int reg, ret;
+	u16 data;
+
+	seq_puts(s, "Register Value\n");
+
+	mutex_lock(&chip->reg_lock);
+	for (reg = 0; reg < 0x80; reg++) {
+		ret = mv88e6xxx_g2_write(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC,
+			reg << 8 /* MV88E6XXX_G2_SCRATCH_MISC_PTR_MASK */);
+		if (ret < 0)
+			goto out;
+
+		ret = _mv88e6xxx_scratch_wait(chip);
+		if (ret < 0)
+			goto out;
+
+		ret = mv88e6xxx_g2_read(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC, &data);
+		seq_printf(s, "  %2x   %2x\n", reg,
+			   data & MV88E6XXX_G2_SCRATCH_MISC_DATA_MASK);
+	}
+out:
+	mutex_unlock(&chip->reg_lock);
+
+	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,
+};
+
+static void mv88e6xxx_init_debugfs(struct mv88e6xxx_chip *chip)
+{
+	char *name;
+
+	name = kasprintf(GFP_KERNEL, "mv88e6xxx.%d", chip->ds->index);
+	chip->dbgfs = debugfs_create_dir(name, NULL);
+
+	kfree(name);
+
+	debugfs_create_file("regs", S_IRUGO | S_IWUSR, chip->dbgfs, chip,
+			    &mv88e6xxx_regs_fops);
+
+	debugfs_create_file("name", S_IRUGO, chip->dbgfs, chip,
+			    &mv88e6xxx_name_fops);
+
+	debugfs_create_file("atu", S_IRUGO | S_IWUSR, chip->dbgfs, chip,
+			    &mv88e6xxx_atu_fops);
+
+	debugfs_create_file("default_vid", S_IRUGO | S_IWUSR, chip->dbgfs, chip,
+			    &mv88e6xxx_default_vid_fops);
+
+	debugfs_create_file("fid", S_IRUGO, chip->dbgfs, chip, &mv88e6xxx_fid_fops);
+
+	debugfs_create_file("state", S_IRUGO, chip->dbgfs, chip,
+			    &mv88e6xxx_state_fops);
+
+	debugfs_create_file("8021q_mode", S_IRUGO, chip->dbgfs, chip,
+			    &mv88e6xxx_8021q_mode_fops);
+
+	debugfs_create_file("vlan_table", S_IRUGO, chip->dbgfs, chip,
+			    &mv88e6xxx_vlan_table_fops);
+
+	debugfs_create_file("pvt", S_IRUGO | S_IWUSR, chip->dbgfs, chip,
+			    &mv88e6xxx_pvt_fops);
+
+	debugfs_create_file("vtu", S_IRUGO | S_IWUSR, chip->dbgfs, chip,
+			    &mv88e6xxx_vtu_fops);
+#if 0
+	debugfs_create_file("stats", S_IRUGO, chip->dbgfs, chip,
+			    &mv88e6xxx_stats_fops);
+#endif
+	debugfs_create_file("device_map", S_IRUGO, chip->dbgfs, chip,
+			    &mv88e6xxx_device_map_fops);
+
+	debugfs_create_file("scratch", S_IRUGO, chip->dbgfs, chip,
+			    &mv88e6xxx_scratch_fops);
+}
+
+static void mv88e6xxx_remove_debugfs(struct mv88e6xxx_chip *chip)
+{
+	debugfs_remove_recursive(chip->dbgfs);
+}
-- 
2.20.1

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 40Mbps down 10Mbps up. Decent connectivity at last!