net: dsa: reference count the FDB addresses at the cross-chip notifier level

The same concerns expressed for host MDB entries are valid for host FDBs
just as well:

- in the case of multiple bridges spanning the same switch chip, deleting
  a host FDB entry that belongs to one bridge will result in breakage to
  the other bridge
- not deleting FDB entries across DSA links means that the switch's
  hardware tables will eventually run out, given enough wear&tear

So do the same thing and introduce reference counting for CPU ports and
DSA links using the same data structures as we have for MDB entries.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Vladimir Oltean 2021-06-29 17:06:52 +03:00 committed by David S. Miller
parent 3dc80afc50
commit 3f6e32f92a
3 changed files with 88 additions and 7 deletions

View File

@ -288,6 +288,7 @@ struct dsa_port {
/* List of MAC addresses that must be forwarded on this port. /* List of MAC addresses that must be forwarded on this port.
* These are only valid on CPU ports and DSA links. * These are only valid on CPU ports and DSA links.
*/ */
struct list_head fdbs;
struct list_head mdbs; struct list_head mdbs;
bool setup; bool setup;

View File

@ -348,6 +348,7 @@ static int dsa_port_setup(struct dsa_port *dp)
if (dp->setup) if (dp->setup)
return 0; return 0;
INIT_LIST_HEAD(&dp->fdbs);
INIT_LIST_HEAD(&dp->mdbs); INIT_LIST_HEAD(&dp->mdbs);
switch (dp->type) { switch (dp->type) {
@ -471,6 +472,11 @@ static void dsa_port_teardown(struct dsa_port *dp)
break; break;
} }
list_for_each_entry_safe(a, tmp, &dp->fdbs, list) {
list_del(&a->list);
kfree(a);
}
list_for_each_entry_safe(a, tmp, &dp->mdbs, list) { list_for_each_entry_safe(a, tmp, &dp->mdbs, list) {
list_del(&a->list); list_del(&a->list);
kfree(a); kfree(a);

View File

@ -253,6 +253,71 @@ static int dsa_switch_do_mdb_del(struct dsa_switch *ds, int port,
return 0; return 0;
} }
static int dsa_switch_do_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid)
{
struct dsa_port *dp = dsa_to_port(ds, port);
struct dsa_mac_addr *a;
int err;
/* No need to bother with refcounting for user ports */
if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp)))
return ds->ops->port_fdb_add(ds, port, addr, vid);
a = dsa_mac_addr_find(&dp->fdbs, addr, vid);
if (a) {
refcount_inc(&a->refcount);
return 0;
}
a = kzalloc(sizeof(*a), GFP_KERNEL);
if (!a)
return -ENOMEM;
err = ds->ops->port_fdb_add(ds, port, addr, vid);
if (err) {
kfree(a);
return err;
}
ether_addr_copy(a->addr, addr);
a->vid = vid;
refcount_set(&a->refcount, 1);
list_add_tail(&a->list, &dp->fdbs);
return 0;
}
static int dsa_switch_do_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid)
{
struct dsa_port *dp = dsa_to_port(ds, port);
struct dsa_mac_addr *a;
int err;
/* No need to bother with refcounting for user ports */
if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp)))
return ds->ops->port_fdb_del(ds, port, addr, vid);
a = dsa_mac_addr_find(&dp->fdbs, addr, vid);
if (!a)
return -ENOENT;
if (!refcount_dec_and_test(&a->refcount))
return 0;
err = ds->ops->port_fdb_del(ds, port, addr, vid);
if (err) {
refcount_inc(&a->refcount);
return err;
}
list_del(&a->list);
kfree(a);
return 0;
}
static int dsa_switch_host_fdb_add(struct dsa_switch *ds, static int dsa_switch_host_fdb_add(struct dsa_switch *ds,
struct dsa_notifier_fdb_info *info) struct dsa_notifier_fdb_info *info)
{ {
@ -265,7 +330,7 @@ static int dsa_switch_host_fdb_add(struct dsa_switch *ds,
for (port = 0; port < ds->num_ports; port++) { for (port = 0; port < ds->num_ports; port++) {
if (dsa_switch_host_address_match(ds, port, info->sw_index, if (dsa_switch_host_address_match(ds, port, info->sw_index,
info->port)) { info->port)) {
err = ds->ops->port_fdb_add(ds, port, info->addr, err = dsa_switch_do_fdb_add(ds, port, info->addr,
info->vid); info->vid);
if (err) if (err)
break; break;
@ -278,14 +343,23 @@ static int dsa_switch_host_fdb_add(struct dsa_switch *ds,
static int dsa_switch_host_fdb_del(struct dsa_switch *ds, static int dsa_switch_host_fdb_del(struct dsa_switch *ds,
struct dsa_notifier_fdb_info *info) struct dsa_notifier_fdb_info *info)
{ {
int err = 0;
int port;
if (!ds->ops->port_fdb_del) if (!ds->ops->port_fdb_del)
return -EOPNOTSUPP; return -EOPNOTSUPP;
if (ds->index == info->sw_index) for (port = 0; port < ds->num_ports; port++) {
return ds->ops->port_fdb_del(ds, info->port, info->addr, if (dsa_switch_host_address_match(ds, port, info->sw_index,
info->vid); info->port)) {
err = dsa_switch_do_fdb_del(ds, port, info->addr,
info->vid);
if (err)
break;
}
}
return 0; return err;
} }
static int dsa_switch_fdb_add(struct dsa_switch *ds, static int dsa_switch_fdb_add(struct dsa_switch *ds,
@ -296,7 +370,7 @@ static int dsa_switch_fdb_add(struct dsa_switch *ds,
if (!ds->ops->port_fdb_add) if (!ds->ops->port_fdb_add)
return -EOPNOTSUPP; return -EOPNOTSUPP;
return ds->ops->port_fdb_add(ds, port, info->addr, info->vid); return dsa_switch_do_fdb_add(ds, port, info->addr, info->vid);
} }
static int dsa_switch_fdb_del(struct dsa_switch *ds, static int dsa_switch_fdb_del(struct dsa_switch *ds,
@ -307,7 +381,7 @@ static int dsa_switch_fdb_del(struct dsa_switch *ds,
if (!ds->ops->port_fdb_del) if (!ds->ops->port_fdb_del)
return -EOPNOTSUPP; return -EOPNOTSUPP;
return ds->ops->port_fdb_del(ds, port, info->addr, info->vid); return dsa_switch_do_fdb_del(ds, port, info->addr, info->vid);
} }
static int dsa_switch_hsr_join(struct dsa_switch *ds, static int dsa_switch_hsr_join(struct dsa_switch *ds,