net: dsa: reference count the FDB addresses at the cross-chip notifier level
authorVladimir Oltean <vladimir.oltean@nxp.com>
Tue, 29 Jun 2021 14:06:52 +0000 (17:06 +0300)
committerDavid S. Miller <davem@davemloft.net>
Tue, 29 Jun 2021 17:46:23 +0000 (10:46 -0700)
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>
include/net/dsa.h
net/dsa/dsa2.c
net/dsa/switch.c

index 2c50546..33f40c1 100644 (file)
@@ -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;
index 2035d13..185629f 100644 (file)
@@ -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);
index 219fc9b..af71b86 100644 (file)
@@ -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,
-                                            info->vid);
+       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,