Vladimir Oltean fd292c189a net: dsa: tear down devlink port regions when tearing down the devlink port on error
Commit 86f8b1c01a0a ("net: dsa: Do not make user port errors fatal")
decided it was fine to ignore errors on certain ports that fail to
probe, and go on with the ports that do probe fine.

Commit fb6ec87f7229 ("net: dsa: Fix type was not set for devlink port")
noticed that devlink_port_type_eth_set(dlp, dp->slave); does not get
called, and devlink notices after a timeout of 3600 seconds and prints a
WARN_ON. So it went ahead to unregister the devlink port. And because
there exists an UNUSED port flavour, we actually re-register the devlink
port as UNUSED.

Commit 08156ba430b4 ("net: dsa: Add devlink port regions support to
DSA") added devlink port regions, which are set up by the driver and not
by DSA.

When we trigger the devlink port deregistration and reregistration as
unused, devlink now prints another WARN_ON, from here:

devlink_port_unregister:
	WARN_ON(!list_empty(&devlink_port->region_list));

So the port still has regions, which makes sense, because they were set
up by the driver, and the driver doesn't know we're unregistering the
devlink port.

Somebody needs to tear them down, and optionally (actually it would be
nice, to be consistent) set them up again for the new devlink port.

But DSA's layering stays in our way quite badly here.

The options I've considered are:

1. Introduce a function in devlink to just change a port's type and
   flavour. No dice, devlink keeps a lot of state, it really wants the
   port to not be registered when you set its parameters, so changing
   anything can only be done by destroying what we currently have and
   recreating it.

2. Make DSA cache the parameters passed to dsa_devlink_port_region_create,
   and the region returned, keep those in a list, then when the devlink
   port unregister needs to take place, the existing devlink regions are
   destroyed by DSA, and we replay the creation of new regions using the
   cached parameters. Problem: mv88e6xxx keeps the region pointers in
   chip->ports[port].region, and these will remain stale after DSA frees
   them. There are many things DSA can do, but updating mv88e6xxx's
   private pointers is not one of them.

3. Just let the driver do it (i.e. introduce a very specific method
   called ds->ops->port_reinit_as_unused, which unregisters its devlink
   port devlink regions, then the old devlink port, then registers the
   new one, then the devlink port regions for it). While it does work,
   as opposed to the others, it's pretty horrible from an API
   perspective and we can do better.

4. Introduce a new pair of methods, ->port_setup and ->port_teardown,
   which in the case of mv88e6xxx must register and unregister the
   devlink port regions. Call these 2 methods when the port must be
   reinitialized as unused.

Naturally, I went for the 4th approach.

Fixes: 08156ba430b4 ("net: dsa: Add devlink port regions support to DSA")
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-19 13:05:44 +01:00

740 lines
17 KiB
C

// 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, &registers[i]);
break;
case MV88E6XXX_REGION_GLOBAL2:
err = mv88e6xxx_g2_read(chip, i, &registers[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;
}
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, &registers[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_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_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;
}
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;
int err;
err = devlink_info_driver_name_put(req, "mv88e6xxx");
if (err)
return err;
return devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
chip->info->name);
}