From f223a66d966060bfc180299729322797dd2681ef Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Thu, 22 Oct 2015 14:31:23 -0400 Subject: 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 [Modified by rmk for current kernels.] Signed-off-by: Russell King --- drivers/net/dsa/mv88e6xxx/chip.c | 7 + drivers/net/dsa/mv88e6xxx/chip.h | 2 + drivers/net/dsa/mv88e6xxx/mv88e6xxx_debugfs.c | 1099 +++++++++++++++++++++++++ 3 files changed, 1108 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 54aa942eedaa..302cbb52285f 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -2848,8 +2848,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); @@ -2969,6 +2974,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 3543055bcb51..7b77454e31a1 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -351,6 +351,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 +#include + +#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, ®, &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); +} -- cgit