summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVivien Didelot <vivien.didelot@savoirfairelinux.com>2015-10-22 14:31:23 -0400
committerRussell King (Oracle) <rmk+kernel@armlinux.org.uk>2021-06-29 15:50:24 +0100
commit45f77cd1bdc7f83687bcb274f7d5467a08bd4a44 (patch)
tree89d6abcb3406251cea70f800e9fde22614418c0a
parent62fb9874f5da54fdb243003b386128037319b219 (diff)
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 <vivien.didelot@savoirfairelinux.com> [Modified by rmk for current kernels.] Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
-rw-r--r--drivers/net/dsa/mv88e6xxx/chip.c7
-rw-r--r--drivers/net/dsa/mv88e6xxx/chip.h2
-rw-r--r--drivers/net/dsa/mv88e6xxx/mv88e6xxx_debugfs.c1099
3 files changed, 1108 insertions, 0 deletions
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index eca285aaf72f..66f8ccc7a5e4 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -3008,8 +3008,13 @@ static int mv88e6390_setup_errata(struct mv88e6xxx_chip *chip)
return mv88e6xxx_software_reset(chip);
}
+#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);
mv88e6xxx_teardown_devlink_regions(ds);
@@ -3128,6 +3133,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 675b1f3e43b7..850706ed2dad 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -377,6 +377,8 @@ struct mv88e6xxx_chip {
/* devlink regions */
struct devlink_region *regions[_MV88E6XXX_REGION_MAX];
+
+ 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..931e769fe9ce
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/mv88e6xxx_debugfs.c
@@ -0,0 +1,1099 @@
+#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)
+{
+ 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;
+}
+
+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);
+}