|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | #include <net/dsa.h> | 
|  |  | 
|  | #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); | 
|  | } |