Commit 3f6e32f9 authored by Vladimir Oltean's avatar Vladimir Oltean Committed by David S. Miller
Browse files

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: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 3dc80afc
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -288,6 +288,7 @@ struct dsa_port {
	/* List of MAC addresses that must be forwarded on this port.
	 * These are only valid on CPU ports and DSA links.
	 */
	struct list_head	fdbs;
	struct list_head	mdbs;

	bool setup;
+6 −0
Original line number Diff line number Diff line
@@ -348,6 +348,7 @@ static int dsa_port_setup(struct dsa_port *dp)
	if (dp->setup)
		return 0;

	INIT_LIST_HEAD(&dp->fdbs);
	INIT_LIST_HEAD(&dp->mdbs);

	switch (dp->type) {
@@ -471,6 +472,11 @@ static void dsa_port_teardown(struct dsa_port *dp)
		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_del(&a->list);
		kfree(a);
+81 −7
Original line number Diff line number Diff line
@@ -253,6 +253,71 @@ static int dsa_switch_do_mdb_del(struct dsa_switch *ds, int port,
	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,
				   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++) {
		if (dsa_switch_host_address_match(ds, port, info->sw_index,
						  info->port)) {
			err = ds->ops->port_fdb_add(ds, port, info->addr,
			err = dsa_switch_do_fdb_add(ds, port, info->addr,
						    info->vid);
			if (err)
				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,
				   struct dsa_notifier_fdb_info *info)
{
	int err = 0;
	int port;

	if (!ds->ops->port_fdb_del)
		return -EOPNOTSUPP;

	if (ds->index == info->sw_index)
		return ds->ops->port_fdb_del(ds, info->port, info->addr,
	for (port = 0; port < ds->num_ports; port++) {
		if (dsa_switch_host_address_match(ds, port, info->sw_index,
						  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,
@@ -296,7 +370,7 @@ static int dsa_switch_fdb_add(struct dsa_switch *ds,
	if (!ds->ops->port_fdb_add)
		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,
@@ -307,7 +381,7 @@ static int dsa_switch_fdb_del(struct dsa_switch *ds,
	if (!ds->ops->port_fdb_del)
		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,