#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) { #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); }