// SPDX-License-Identifier: GPL-2.0-or-later #include #include "chip.h" #include "devlink.h" #include "global1.h" #include "global2.h" #include "port.h" static int mv88e6xxx_atu_get_hash(struct mv88e6xxx_chip *chip, u8 *hash) { if (chip->info->ops->atu_get_hash) return chip->info->ops->atu_get_hash(chip, hash); return -EOPNOTSUPP; } static int mv88e6xxx_atu_set_hash(struct mv88e6xxx_chip *chip, u8 hash) { if (chip->info->ops->atu_set_hash) return chip->info->ops->atu_set_hash(chip, hash); return -EOPNOTSUPP; } enum mv88e6xxx_devlink_param_id { MV88E6XXX_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX, MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH, }; int mv88e6xxx_devlink_param_get(struct dsa_switch *ds, u32 id, struct devlink_param_gset_ctx *ctx) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); switch (id) { case MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH: err = mv88e6xxx_atu_get_hash(chip, &ctx->val.vu8); break; default: err = -EOPNOTSUPP; break; } mv88e6xxx_reg_unlock(chip); return err; } int mv88e6xxx_devlink_param_set(struct dsa_switch *ds, u32 id, struct devlink_param_gset_ctx *ctx) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); switch (id) { case MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH: err = mv88e6xxx_atu_set_hash(chip, ctx->val.vu8); break; default: err = -EOPNOTSUPP; break; } mv88e6xxx_reg_unlock(chip); return err; } static const struct devlink_param mv88e6xxx_devlink_params[] = { DSA_DEVLINK_PARAM_DRIVER(MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH, "ATU_hash", DEVLINK_PARAM_TYPE_U8, BIT(DEVLINK_PARAM_CMODE_RUNTIME)), }; int mv88e6xxx_setup_devlink_params(struct dsa_switch *ds) { return dsa_devlink_params_register(ds, mv88e6xxx_devlink_params, ARRAY_SIZE(mv88e6xxx_devlink_params)); } void mv88e6xxx_teardown_devlink_params(struct dsa_switch *ds) { dsa_devlink_params_unregister(ds, mv88e6xxx_devlink_params, ARRAY_SIZE(mv88e6xxx_devlink_params)); } enum mv88e6xxx_devlink_resource_id { MV88E6XXX_RESOURCE_ID_ATU, MV88E6XXX_RESOURCE_ID_ATU_BIN_0, MV88E6XXX_RESOURCE_ID_ATU_BIN_1, MV88E6XXX_RESOURCE_ID_ATU_BIN_2, MV88E6XXX_RESOURCE_ID_ATU_BIN_3, }; static u64 mv88e6xxx_devlink_atu_bin_get(struct mv88e6xxx_chip *chip, u16 bin) { u16 occupancy = 0; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g2_atu_stats_set(chip, MV88E6XXX_G2_ATU_STATS_MODE_ALL, bin); if (err) { dev_err(chip->dev, "failed to set ATU stats kind/bin\n"); goto unlock; } err = mv88e6xxx_g1_atu_get_next(chip, 0); if (err) { dev_err(chip->dev, "failed to perform ATU get next\n"); goto unlock; } err = mv88e6xxx_g2_atu_stats_get(chip, &occupancy); if (err) { dev_err(chip->dev, "failed to get ATU stats\n"); goto unlock; } occupancy &= MV88E6XXX_G2_ATU_STATS_MASK; unlock: mv88e6xxx_reg_unlock(chip); return occupancy; } static u64 mv88e6xxx_devlink_atu_bin_0_get(void *priv) { struct mv88e6xxx_chip *chip = priv; return mv88e6xxx_devlink_atu_bin_get(chip, MV88E6XXX_G2_ATU_STATS_BIN_0); } static u64 mv88e6xxx_devlink_atu_bin_1_get(void *priv) { struct mv88e6xxx_chip *chip = priv; return mv88e6xxx_devlink_atu_bin_get(chip, MV88E6XXX_G2_ATU_STATS_BIN_1); } static u64 mv88e6xxx_devlink_atu_bin_2_get(void *priv) { struct mv88e6xxx_chip *chip = priv; return mv88e6xxx_devlink_atu_bin_get(chip, MV88E6XXX_G2_ATU_STATS_BIN_2); } static u64 mv88e6xxx_devlink_atu_bin_3_get(void *priv) { struct mv88e6xxx_chip *chip = priv; return mv88e6xxx_devlink_atu_bin_get(chip, MV88E6XXX_G2_ATU_STATS_BIN_3); } static u64 mv88e6xxx_devlink_atu_get(void *priv) { return mv88e6xxx_devlink_atu_bin_0_get(priv) + mv88e6xxx_devlink_atu_bin_1_get(priv) + mv88e6xxx_devlink_atu_bin_2_get(priv) + mv88e6xxx_devlink_atu_bin_3_get(priv); } int mv88e6xxx_setup_devlink_resources(struct dsa_switch *ds) { struct devlink_resource_size_params size_params; struct mv88e6xxx_chip *chip = ds->priv; int err; devlink_resource_size_params_init(&size_params, mv88e6xxx_num_macs(chip), mv88e6xxx_num_macs(chip), 1, DEVLINK_RESOURCE_UNIT_ENTRY); err = dsa_devlink_resource_register(ds, "ATU", mv88e6xxx_num_macs(chip), MV88E6XXX_RESOURCE_ID_ATU, DEVLINK_RESOURCE_ID_PARENT_TOP, &size_params); if (err) goto out; devlink_resource_size_params_init(&size_params, mv88e6xxx_num_macs(chip) / 4, mv88e6xxx_num_macs(chip) / 4, 1, DEVLINK_RESOURCE_UNIT_ENTRY); err = dsa_devlink_resource_register(ds, "ATU_bin_0", mv88e6xxx_num_macs(chip) / 4, MV88E6XXX_RESOURCE_ID_ATU_BIN_0, MV88E6XXX_RESOURCE_ID_ATU, &size_params); if (err) goto out; err = dsa_devlink_resource_register(ds, "ATU_bin_1", mv88e6xxx_num_macs(chip) / 4, MV88E6XXX_RESOURCE_ID_ATU_BIN_1, MV88E6XXX_RESOURCE_ID_ATU, &size_params); if (err) goto out; err = dsa_devlink_resource_register(ds, "ATU_bin_2", mv88e6xxx_num_macs(chip) / 4, MV88E6XXX_RESOURCE_ID_ATU_BIN_2, MV88E6XXX_RESOURCE_ID_ATU, &size_params); if (err) goto out; err = dsa_devlink_resource_register(ds, "ATU_bin_3", mv88e6xxx_num_macs(chip) / 4, MV88E6XXX_RESOURCE_ID_ATU_BIN_3, MV88E6XXX_RESOURCE_ID_ATU, &size_params); if (err) goto out; dsa_devlink_resource_occ_get_register(ds, MV88E6XXX_RESOURCE_ID_ATU, mv88e6xxx_devlink_atu_get, chip); dsa_devlink_resource_occ_get_register(ds, MV88E6XXX_RESOURCE_ID_ATU_BIN_0, mv88e6xxx_devlink_atu_bin_0_get, chip); dsa_devlink_resource_occ_get_register(ds, MV88E6XXX_RESOURCE_ID_ATU_BIN_1, mv88e6xxx_devlink_atu_bin_1_get, chip); dsa_devlink_resource_occ_get_register(ds, MV88E6XXX_RESOURCE_ID_ATU_BIN_2, mv88e6xxx_devlink_atu_bin_2_get, chip); dsa_devlink_resource_occ_get_register(ds, MV88E6XXX_RESOURCE_ID_ATU_BIN_3, mv88e6xxx_devlink_atu_bin_3_get, chip); return 0; out: dsa_devlink_resources_unregister(ds); return err; } static int mv88e6xxx_region_global_snapshot(struct devlink *dl, const struct devlink_region_ops *ops, struct netlink_ext_ack *extack, u8 **data) { struct mv88e6xxx_region_priv *region_priv = ops->priv; struct dsa_switch *ds = dsa_devlink_to_ds(dl); struct mv88e6xxx_chip *chip = ds->priv; u16 *registers; int i, err; registers = kmalloc_array(32, sizeof(u16), GFP_KERNEL); if (!registers) return -ENOMEM; mv88e6xxx_reg_lock(chip); for (i = 0; i < 32; i++) { switch (region_priv->id) { case MV88E6XXX_REGION_GLOBAL1: err = mv88e6xxx_g1_read(chip, i, ®isters[i]); break; case MV88E6XXX_REGION_GLOBAL2: err = mv88e6xxx_g2_read(chip, i, ®isters[i]); break; default: err = -EOPNOTSUPP; } if (err) { kfree(registers); goto out; } } *data = (u8 *)registers; out: mv88e6xxx_reg_unlock(chip); return err; } /* The ATU entry varies between mv88e6xxx chipset generations. Define * a generic format which covers all the current and hopefully future * mv88e6xxx generations */ struct mv88e6xxx_devlink_atu_entry { /* The FID is scattered over multiple registers. */ u16 fid; u16 atu_op; u16 atu_data; u16 atu_01; u16 atu_23; u16 atu_45; }; static int mv88e6xxx_region_atu_snapshot_fid(struct mv88e6xxx_chip *chip, int fid, struct mv88e6xxx_devlink_atu_entry *table, int *count) { u16 atu_op, atu_data, atu_01, atu_23, atu_45; struct mv88e6xxx_atu_entry addr; int err; addr.state = 0; eth_broadcast_addr(addr.mac); do { err = mv88e6xxx_g1_atu_getnext(chip, fid, &addr); if (err) return err; if (!addr.state) break; err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_OP, &atu_op); if (err) return err; err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_DATA, &atu_data); if (err) return err; err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC01, &atu_01); if (err) return err; err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC23, &atu_23); if (err) return err; err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC45, &atu_45); if (err) return err; table[*count].fid = fid; table[*count].atu_op = atu_op; table[*count].atu_data = atu_data; table[*count].atu_01 = atu_01; table[*count].atu_23 = atu_23; table[*count].atu_45 = atu_45; (*count)++; } while (!is_broadcast_ether_addr(addr.mac)); return 0; } static int mv88e6xxx_region_atu_snapshot(struct devlink *dl, const struct devlink_region_ops *ops, struct netlink_ext_ack *extack, u8 **data) { struct dsa_switch *ds = dsa_devlink_to_ds(dl); DECLARE_BITMAP(fid_bitmap, MV88E6XXX_N_FID); struct mv88e6xxx_devlink_atu_entry *table; struct mv88e6xxx_chip *chip = ds->priv; int fid = -1, count, err; table = kmalloc_array(mv88e6xxx_num_databases(chip), sizeof(struct mv88e6xxx_devlink_atu_entry), GFP_KERNEL); if (!table) return -ENOMEM; memset(table, 0, mv88e6xxx_num_databases(chip) * sizeof(struct mv88e6xxx_devlink_atu_entry)); count = 0; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_fid_map(chip, fid_bitmap); if (err) { kfree(table); goto out; } while (1) { fid = find_next_bit(fid_bitmap, MV88E6XXX_N_FID, fid + 1); if (fid == MV88E6XXX_N_FID) break; err = mv88e6xxx_region_atu_snapshot_fid(chip, fid, table, &count); if (err) { kfree(table); goto out; } } *data = (u8 *)table; out: mv88e6xxx_reg_unlock(chip); return err; } /** * struct mv88e6xxx_devlink_vtu_entry - Devlink VTU entry * @fid: Global1/2: FID and VLAN policy. * @sid: Global1/3: SID, unknown filters and learning. * @op: Global1/5: FID (old chipsets). * @vid: Global1/6: VID, valid, and page. * @data: Global1/7-9: Membership data and priority override. * @resvd: Reserved. Also happens to align the size to 16B. * * The VTU entry format varies between chipset generations, the * descriptions above represent the superset of all possible * information, not all fields are valid on all devices. Since this is * a low-level debug interface, copy all data verbatim and defer * parsing to the consumer. */ struct mv88e6xxx_devlink_vtu_entry { u16 fid; u16 sid; u16 op; u16 vid; u16 data[3]; u16 resvd; }; static int mv88e6xxx_region_vtu_snapshot(struct devlink *dl, const struct devlink_region_ops *ops, struct netlink_ext_ack *extack, u8 **data) { struct mv88e6xxx_devlink_vtu_entry *table, *entry; struct dsa_switch *ds = dsa_devlink_to_ds(dl); struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_vtu_entry vlan; int err; table = kcalloc(mv88e6xxx_max_vid(chip) + 1, sizeof(struct mv88e6xxx_devlink_vtu_entry), GFP_KERNEL); if (!table) return -ENOMEM; entry = table; vlan.vid = mv88e6xxx_max_vid(chip); vlan.valid = false; mv88e6xxx_reg_lock(chip); do { err = mv88e6xxx_g1_vtu_getnext(chip, &vlan); if (err) break; if (!vlan.valid) break; err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_FID, &entry->fid); err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_SID, &entry->sid); err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_OP, &entry->op); err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_VID, &entry->vid); err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1, &entry->data[0]); err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA2, &entry->data[1]); err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA3, &entry->data[2]); if (err) break; entry++; } while (vlan.vid < mv88e6xxx_max_vid(chip)); mv88e6xxx_reg_unlock(chip); if (err) { kfree(table); return err; } *data = (u8 *)table; return 0; } /** * struct mv88e6xxx_devlink_stu_entry - Devlink STU entry * @sid: Global1/3: SID, unknown filters and learning. * @vid: Global1/6: Valid bit. * @data: Global1/7-9: Membership data and priority override. * @resvd: Reserved. In case we forgot something. * * The STU entry format varies between chipset generations. Peridot * and Amethyst packs the STU data into Global1/7-8. Older silicon * spreads the information across all three VTU data registers - * inheriting the layout of even older hardware that had no STU at * all. Since this is a low-level debug interface, copy all data * verbatim and defer parsing to the consumer. */ struct mv88e6xxx_devlink_stu_entry { u16 sid; u16 vid; u16 data[3]; u16 resvd; }; static int mv88e6xxx_region_stu_snapshot(struct devlink *dl, const struct devlink_region_ops *ops, struct netlink_ext_ack *extack, u8 **data) { struct mv88e6xxx_devlink_stu_entry *table, *entry; struct dsa_switch *ds = dsa_devlink_to_ds(dl); struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_stu_entry stu; int err; table = kcalloc(mv88e6xxx_max_sid(chip) + 1, sizeof(struct mv88e6xxx_devlink_stu_entry), GFP_KERNEL); if (!table) return -ENOMEM; entry = table; stu.sid = mv88e6xxx_max_sid(chip); stu.valid = false; mv88e6xxx_reg_lock(chip); do { err = mv88e6xxx_g1_stu_getnext(chip, &stu); if (err) break; if (!stu.valid) break; err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_SID, &entry->sid); err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_VID, &entry->vid); err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1, &entry->data[0]); err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA2, &entry->data[1]); err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA3, &entry->data[2]); if (err) break; entry++; } while (stu.sid < mv88e6xxx_max_sid(chip)); mv88e6xxx_reg_unlock(chip); if (err) { kfree(table); return err; } *data = (u8 *)table; return 0; } static int mv88e6xxx_region_pvt_snapshot(struct devlink *dl, const struct devlink_region_ops *ops, struct netlink_ext_ack *extack, u8 **data) { struct dsa_switch *ds = dsa_devlink_to_ds(dl); struct mv88e6xxx_chip *chip = ds->priv; int dev, port, err; u16 *pvt, *cur; pvt = kcalloc(MV88E6XXX_MAX_PVT_ENTRIES, sizeof(*pvt), GFP_KERNEL); if (!pvt) return -ENOMEM; mv88e6xxx_reg_lock(chip); cur = pvt; for (dev = 0; dev < MV88E6XXX_MAX_PVT_SWITCHES; dev++) { for (port = 0; port < MV88E6XXX_MAX_PVT_PORTS; port++) { err = mv88e6xxx_g2_pvt_read(chip, dev, port, cur); if (err) break; cur++; } } mv88e6xxx_reg_unlock(chip); if (err) { kfree(pvt); return err; } *data = (u8 *)pvt; return 0; } static int mv88e6xxx_region_port_snapshot(struct devlink_port *devlink_port, const struct devlink_port_region_ops *ops, struct netlink_ext_ack *extack, u8 **data) { struct dsa_switch *ds = dsa_devlink_port_to_ds(devlink_port); int port = dsa_devlink_port_to_port(devlink_port); struct mv88e6xxx_chip *chip = ds->priv; u16 *registers; int i, err; registers = kmalloc_array(32, sizeof(u16), GFP_KERNEL); if (!registers) return -ENOMEM; mv88e6xxx_reg_lock(chip); for (i = 0; i < 32; i++) { err = mv88e6xxx_port_read(chip, port, i, ®isters[i]); if (err) { kfree(registers); goto out; } } *data = (u8 *)registers; out: mv88e6xxx_reg_unlock(chip); return err; } static struct mv88e6xxx_region_priv mv88e6xxx_region_global1_priv = { .id = MV88E6XXX_REGION_GLOBAL1, }; static struct devlink_region_ops mv88e6xxx_region_global1_ops = { .name = "global1", .snapshot = mv88e6xxx_region_global_snapshot, .destructor = kfree, .priv = &mv88e6xxx_region_global1_priv, }; static struct mv88e6xxx_region_priv mv88e6xxx_region_global2_priv = { .id = MV88E6XXX_REGION_GLOBAL2, }; static struct devlink_region_ops mv88e6xxx_region_global2_ops = { .name = "global2", .snapshot = mv88e6xxx_region_global_snapshot, .destructor = kfree, .priv = &mv88e6xxx_region_global2_priv, }; static struct devlink_region_ops mv88e6xxx_region_atu_ops = { .name = "atu", .snapshot = mv88e6xxx_region_atu_snapshot, .destructor = kfree, }; static struct devlink_region_ops mv88e6xxx_region_vtu_ops = { .name = "vtu", .snapshot = mv88e6xxx_region_vtu_snapshot, .destructor = kfree, }; static struct devlink_region_ops mv88e6xxx_region_stu_ops = { .name = "stu", .snapshot = mv88e6xxx_region_stu_snapshot, .destructor = kfree, }; static struct devlink_region_ops mv88e6xxx_region_pvt_ops = { .name = "pvt", .snapshot = mv88e6xxx_region_pvt_snapshot, .destructor = kfree, }; static const struct devlink_port_region_ops mv88e6xxx_region_port_ops = { .name = "port", .snapshot = mv88e6xxx_region_port_snapshot, .destructor = kfree, }; struct mv88e6xxx_region { struct devlink_region_ops *ops; u64 size; bool (*cond)(struct mv88e6xxx_chip *chip); }; static struct mv88e6xxx_region mv88e6xxx_regions[] = { [MV88E6XXX_REGION_GLOBAL1] = { .ops = &mv88e6xxx_region_global1_ops, .size = 32 * sizeof(u16) }, [MV88E6XXX_REGION_GLOBAL2] = { .ops = &mv88e6xxx_region_global2_ops, .size = 32 * sizeof(u16) }, [MV88E6XXX_REGION_ATU] = { .ops = &mv88e6xxx_region_atu_ops /* calculated at runtime */ }, [MV88E6XXX_REGION_VTU] = { .ops = &mv88e6xxx_region_vtu_ops /* calculated at runtime */ }, [MV88E6XXX_REGION_STU] = { .ops = &mv88e6xxx_region_stu_ops, .cond = mv88e6xxx_has_stu, /* calculated at runtime */ }, [MV88E6XXX_REGION_PVT] = { .ops = &mv88e6xxx_region_pvt_ops, .size = MV88E6XXX_MAX_PVT_ENTRIES * sizeof(u16), .cond = mv88e6xxx_has_pvt, }, }; void mv88e6xxx_teardown_devlink_regions_global(struct dsa_switch *ds) { struct mv88e6xxx_chip *chip = ds->priv; int i; for (i = 0; i < ARRAY_SIZE(mv88e6xxx_regions); i++) dsa_devlink_region_destroy(chip->regions[i]); } void mv88e6xxx_teardown_devlink_regions_port(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; dsa_devlink_region_destroy(chip->ports[port].region); } int mv88e6xxx_setup_devlink_regions_port(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; struct devlink_region *region; region = dsa_devlink_port_region_create(ds, port, &mv88e6xxx_region_port_ops, 1, 32 * sizeof(u16)); if (IS_ERR(region)) return PTR_ERR(region); chip->ports[port].region = region; return 0; } int mv88e6xxx_setup_devlink_regions_global(struct dsa_switch *ds) { bool (*cond)(struct mv88e6xxx_chip *chip); struct mv88e6xxx_chip *chip = ds->priv; struct devlink_region_ops *ops; struct devlink_region *region; u64 size; int i, j; for (i = 0; i < ARRAY_SIZE(mv88e6xxx_regions); i++) { ops = mv88e6xxx_regions[i].ops; size = mv88e6xxx_regions[i].size; cond = mv88e6xxx_regions[i].cond; if (cond && !cond(chip)) continue; switch (i) { case MV88E6XXX_REGION_ATU: size = mv88e6xxx_num_databases(chip) * sizeof(struct mv88e6xxx_devlink_atu_entry); break; case MV88E6XXX_REGION_VTU: size = (mv88e6xxx_max_vid(chip) + 1) * sizeof(struct mv88e6xxx_devlink_vtu_entry); break; case MV88E6XXX_REGION_STU: size = (mv88e6xxx_max_sid(chip) + 1) * sizeof(struct mv88e6xxx_devlink_stu_entry); break; } region = dsa_devlink_region_create(ds, ops, 1, size); if (IS_ERR(region)) goto out; chip->regions[i] = region; } return 0; out: for (j = 0; j < i; j++) dsa_devlink_region_destroy(chip->regions[j]); return PTR_ERR(region); } int mv88e6xxx_devlink_info_get(struct dsa_switch *ds, struct devlink_info_req *req, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; return devlink_info_version_fixed_put(req, DEVLINK_INFO_VERSION_GENERIC_ASIC_ID, chip->info->name); }