summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2022-03-09 11:12:10 +0000
committerDavid S. Miller <davem@davemloft.net>2022-03-09 11:12:10 +0000
commit1163319993f0abf8092d5f18fdff98096f7a3a73 (patch)
treec7ff3a0357f2e5c86f7fff793fc18240649bbbbb
parentce7ec1b8ec784c7fe0f6180a82725ca577d12aa8 (diff)
parent7e580490ac9819dd55a36be2a9b3380d1391f91b (diff)
downloadlinux-next-1163319993f0abf8092d5f18fdff98096f7a3a73.tar.gz
Merge branch 'dsa-next-fixups'
Vladimir Oltean says: ==================== Incremental fixups for DSA unicast filtering There are some bugs I've discovered in the recently merged "DSA unicast filtering" series: https://patchwork.kernel.org/project/netdevbpf/cover/20220302191417.1288145-1-vladimir.oltean@nxp.com/ First bug is the dereference of an uninitialized list (dp->fdbs) when the "initial" tag protocol is placed in the device tree for the Felix switch driver. This is a scenario I hadn't tested. It is handled by patches 1-3. Second bug is actually a sum of bugs that canceled each other out during my previous testing. The MAC address change of a DSA slave interface breaks termination for the other slave interfaces. But this actually does not happen if the slave interface whose address is changing is down. And even when up, traffic termination is still not broken because we fail to properly disable host flooding. Patches 4-6 handle this for the Felix driver (the only one benefiting from unicast filtering so far). ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--drivers/net/dsa/ocelot/felix.c90
-rw-r--r--include/net/dsa.h6
-rw-r--r--net/dsa/dsa.c60
-rw-r--r--net/dsa/dsa2.c31
-rw-r--r--net/dsa/dsa_priv.h2
-rw-r--r--net/dsa/slave.c7
-rw-r--r--net/dsa/switch.c18
7 files changed, 134 insertions, 80 deletions
diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c
index 7cc67097948b..35b436a491e1 100644
--- a/drivers/net/dsa/ocelot/felix.c
+++ b/drivers/net/dsa/ocelot/felix.c
@@ -460,7 +460,7 @@ static int felix_update_trapping_destinations(struct dsa_switch *ds,
return 0;
}
-static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu, bool change)
+static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu)
{
struct ocelot *ocelot = ds->priv;
struct dsa_port *dp;
@@ -488,19 +488,15 @@ static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu, bool change)
if (err)
return err;
- if (change) {
- err = dsa_port_walk_fdbs(ds, cpu,
- felix_migrate_fdbs_to_tag_8021q_port);
- if (err)
- goto out_tag_8021q_unregister;
+ err = dsa_port_walk_fdbs(ds, cpu, felix_migrate_fdbs_to_tag_8021q_port);
+ if (err)
+ goto out_tag_8021q_unregister;
- err = dsa_port_walk_mdbs(ds, cpu,
- felix_migrate_mdbs_to_tag_8021q_port);
- if (err)
- goto out_migrate_fdbs;
+ err = dsa_port_walk_mdbs(ds, cpu, felix_migrate_mdbs_to_tag_8021q_port);
+ if (err)
+ goto out_migrate_fdbs;
- felix_migrate_flood_to_tag_8021q_port(ds, cpu);
- }
+ felix_migrate_flood_to_tag_8021q_port(ds, cpu);
err = felix_update_trapping_destinations(ds, true);
if (err)
@@ -518,13 +514,10 @@ static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu, bool change)
return 0;
out_migrate_flood:
- if (change)
- felix_migrate_flood_to_npi_port(ds, cpu);
- if (change)
- dsa_port_walk_mdbs(ds, cpu, felix_migrate_mdbs_to_npi_port);
+ felix_migrate_flood_to_npi_port(ds, cpu);
+ dsa_port_walk_mdbs(ds, cpu, felix_migrate_mdbs_to_npi_port);
out_migrate_fdbs:
- if (change)
- dsa_port_walk_fdbs(ds, cpu, felix_migrate_fdbs_to_npi_port);
+ dsa_port_walk_fdbs(ds, cpu, felix_migrate_fdbs_to_npi_port);
out_tag_8021q_unregister:
dsa_tag_8021q_unregister(ds);
return err;
@@ -599,33 +592,27 @@ static void felix_npi_port_deinit(struct ocelot *ocelot, int port)
ocelot_fields_write(ocelot, port, SYS_PAUSE_CFG_PAUSE_ENA, 1);
}
-static int felix_setup_tag_npi(struct dsa_switch *ds, int cpu, bool change)
+static int felix_setup_tag_npi(struct dsa_switch *ds, int cpu)
{
struct ocelot *ocelot = ds->priv;
int err;
- if (change) {
- err = dsa_port_walk_fdbs(ds, cpu,
- felix_migrate_fdbs_to_npi_port);
- if (err)
- return err;
+ err = dsa_port_walk_fdbs(ds, cpu, felix_migrate_fdbs_to_npi_port);
+ if (err)
+ return err;
- err = dsa_port_walk_mdbs(ds, cpu,
- felix_migrate_mdbs_to_npi_port);
- if (err)
- goto out_migrate_fdbs;
+ err = dsa_port_walk_mdbs(ds, cpu, felix_migrate_mdbs_to_npi_port);
+ if (err)
+ goto out_migrate_fdbs;
- felix_migrate_flood_to_npi_port(ds, cpu);
- }
+ felix_migrate_flood_to_npi_port(ds, cpu);
felix_npi_port_init(ocelot, cpu);
return 0;
out_migrate_fdbs:
- if (change)
- dsa_port_walk_fdbs(ds, cpu,
- felix_migrate_fdbs_to_tag_8021q_port);
+ dsa_port_walk_fdbs(ds, cpu, felix_migrate_fdbs_to_tag_8021q_port);
return err;
}
@@ -638,17 +625,17 @@ static void felix_teardown_tag_npi(struct dsa_switch *ds, int cpu)
}
static int felix_set_tag_protocol(struct dsa_switch *ds, int cpu,
- enum dsa_tag_protocol proto, bool change)
+ enum dsa_tag_protocol proto)
{
int err;
switch (proto) {
case DSA_TAG_PROTO_SEVILLE:
case DSA_TAG_PROTO_OCELOT:
- err = felix_setup_tag_npi(ds, cpu, change);
+ err = felix_setup_tag_npi(ds, cpu);
break;
case DSA_TAG_PROTO_OCELOT_8021Q:
- err = felix_setup_tag_8021q(ds, cpu, change);
+ err = felix_setup_tag_8021q(ds, cpu);
break;
default:
err = -EPROTONOSUPPORT;
@@ -692,9 +679,9 @@ static int felix_change_tag_protocol(struct dsa_switch *ds, int cpu,
felix_del_tag_protocol(ds, cpu, old_proto);
- err = felix_set_tag_protocol(ds, cpu, proto, true);
+ err = felix_set_tag_protocol(ds, cpu, proto);
if (err) {
- felix_set_tag_protocol(ds, cpu, old_proto, true);
+ felix_set_tag_protocol(ds, cpu, old_proto);
return err;
}
@@ -752,6 +739,10 @@ static int felix_fdb_add(struct dsa_switch *ds, int port,
if (IS_ERR(bridge_dev))
return PTR_ERR(bridge_dev);
+ if (dsa_is_cpu_port(ds, port) && !bridge_dev &&
+ dsa_fdb_present_in_other_db(ds, port, addr, vid, db))
+ return 0;
+
return ocelot_fdb_add(ocelot, port, addr, vid, bridge_dev);
}
@@ -765,6 +756,10 @@ static int felix_fdb_del(struct dsa_switch *ds, int port,
if (IS_ERR(bridge_dev))
return PTR_ERR(bridge_dev);
+ if (dsa_is_cpu_port(ds, port) && !bridge_dev &&
+ dsa_fdb_present_in_other_db(ds, port, addr, vid, db))
+ return 0;
+
return ocelot_fdb_del(ocelot, port, addr, vid, bridge_dev);
}
@@ -804,6 +799,10 @@ static int felix_mdb_add(struct dsa_switch *ds, int port,
if (IS_ERR(bridge_dev))
return PTR_ERR(bridge_dev);
+ if (dsa_is_cpu_port(ds, port) && !bridge_dev &&
+ dsa_mdb_present_in_other_db(ds, port, mdb, db))
+ return 0;
+
return ocelot_port_mdb_add(ocelot, port, mdb, bridge_dev);
}
@@ -817,6 +816,10 @@ static int felix_mdb_del(struct dsa_switch *ds, int port,
if (IS_ERR(bridge_dev))
return PTR_ERR(bridge_dev);
+ if (dsa_is_cpu_port(ds, port) && !bridge_dev &&
+ dsa_mdb_present_in_other_db(ds, port, mdb, db))
+ return 0;
+
return ocelot_port_mdb_del(ocelot, port, mdb, bridge_dev);
}
@@ -1356,6 +1359,7 @@ static int felix_setup(struct dsa_switch *ds)
{
struct ocelot *ocelot = ds->priv;
struct felix *felix = ocelot_to_felix(ocelot);
+ unsigned long cpu_flood;
struct dsa_port *dp;
int err;
@@ -1393,7 +1397,15 @@ static int felix_setup(struct dsa_switch *ds)
/* The initial tag protocol is NPI which always returns 0, so
* there's no real point in checking for errors.
*/
- felix_set_tag_protocol(ds, dp->index, felix->tag_proto, false);
+ felix_set_tag_protocol(ds, dp->index, felix->tag_proto);
+
+ /* Start off with flooding disabled towards the NPI port
+ * (actually CPU port module).
+ */
+ cpu_flood = ANA_PGID_PGID_PGID(BIT(ocelot->num_phys_ports));
+ ocelot_rmw_rix(ocelot, 0, cpu_flood, ANA_PGID_PGID, PGID_UC);
+ ocelot_rmw_rix(ocelot, 0, cpu_flood, ANA_PGID_PGID, PGID_MC);
+
break;
}
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 759479fe8573..9d16505fc0e2 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -1227,6 +1227,12 @@ typedef int dsa_fdb_walk_cb_t(struct dsa_switch *ds, int port,
int dsa_port_walk_fdbs(struct dsa_switch *ds, int port, dsa_fdb_walk_cb_t cb);
int dsa_port_walk_mdbs(struct dsa_switch *ds, int port, dsa_fdb_walk_cb_t cb);
+bool dsa_fdb_present_in_other_db(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db);
+bool dsa_mdb_present_in_other_db(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db);
/* Keep inline for faster access in hot path */
static inline bool netdev_uses_dsa(const struct net_device *dev)
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index fe971a2c15cd..89c6c86e746f 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -507,6 +507,66 @@ int dsa_port_walk_mdbs(struct dsa_switch *ds, int port, dsa_fdb_walk_cb_t cb)
}
EXPORT_SYMBOL_GPL(dsa_port_walk_mdbs);
+bool dsa_db_equal(const struct dsa_db *a, const struct dsa_db *b)
+{
+ if (a->type != b->type)
+ return false;
+
+ switch (a->type) {
+ case DSA_DB_PORT:
+ return a->dp == b->dp;
+ case DSA_DB_LAG:
+ return a->lag.dev == b->lag.dev;
+ case DSA_DB_BRIDGE:
+ return a->bridge.num == b->bridge.num;
+ default:
+ WARN_ON(1);
+ return false;
+ }
+}
+
+bool dsa_fdb_present_in_other_db(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct dsa_mac_addr *a;
+
+ lockdep_assert_held(&dp->addr_lists_lock);
+
+ list_for_each_entry(a, &dp->fdbs, list) {
+ if (!ether_addr_equal(a->addr, addr) || a->vid != vid)
+ continue;
+
+ if (a->db.type == db.type && !dsa_db_equal(&a->db, &db))
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(dsa_fdb_present_in_other_db);
+
+bool dsa_mdb_present_in_other_db(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct dsa_mac_addr *a;
+
+ lockdep_assert_held(&dp->addr_lists_lock);
+
+ list_for_each_entry(a, &dp->mdbs, list) {
+ if (!ether_addr_equal(a->addr, mdb->addr) || a->vid != mdb->vid)
+ continue;
+
+ if (a->db.type == db.type && !dsa_db_equal(&a->db, &db))
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(dsa_mdb_present_in_other_db);
+
static int __init dsa_init_module(void)
{
int rc;
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index d5f21a770689..39e696185720 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -457,12 +457,6 @@ static int dsa_port_setup(struct dsa_port *dp)
if (dp->setup)
return 0;
- mutex_init(&dp->addr_lists_lock);
- mutex_init(&dp->vlans_lock);
- INIT_LIST_HEAD(&dp->fdbs);
- INIT_LIST_HEAD(&dp->mdbs);
- INIT_LIST_HEAD(&dp->vlans);
-
if (ds->ops->port_setup) {
err = ds->ops->port_setup(ds, dp->index);
if (err)
@@ -568,9 +562,7 @@ static void dsa_port_teardown(struct dsa_port *dp)
{
struct devlink_port *dlp = &dp->devlink_port;
struct dsa_switch *ds = dp->ds;
- struct dsa_mac_addr *a, *tmp;
struct net_device *slave;
- struct dsa_vlan *v, *n;
if (!dp->setup)
return;
@@ -601,21 +593,6 @@ 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);
- }
-
- list_for_each_entry_safe(v, n, &dp->vlans, list) {
- list_del(&v->list);
- kfree(v);
- }
-
dp->setup = false;
}
@@ -1374,6 +1351,11 @@ static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
dp->ds = ds;
dp->index = index;
+ mutex_init(&dp->addr_lists_lock);
+ mutex_init(&dp->vlans_lock);
+ INIT_LIST_HEAD(&dp->fdbs);
+ INIT_LIST_HEAD(&dp->mdbs);
+ INIT_LIST_HEAD(&dp->vlans);
INIT_LIST_HEAD(&dp->list);
list_add_tail(&dp->list, &dst->ports);
@@ -1712,6 +1694,9 @@ static void dsa_switch_release_ports(struct dsa_switch *ds)
struct dsa_port *dp, *next;
dsa_switch_for_each_port_safe(dp, next, ds) {
+ WARN_ON(!list_empty(&dp->fdbs));
+ WARN_ON(!list_empty(&dp->mdbs));
+ WARN_ON(!list_empty(&dp->vlans));
list_del(&dp->list);
kfree(dp);
}
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index c3c7491ace72..f20bdd8ea0a8 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -182,6 +182,8 @@ const struct dsa_device_ops *dsa_tag_driver_get(int tag_protocol);
void dsa_tag_driver_put(const struct dsa_device_ops *ops);
const struct dsa_device_ops *dsa_find_tagger_by_name(const char *buf);
+bool dsa_db_equal(const struct dsa_db *a, const struct dsa_db *b);
+
bool dsa_schedule_work(struct work_struct *work);
const char *dsa_tag_protocol_to_str(const struct dsa_device_ops *ops);
diff --git a/net/dsa/slave.c b/net/dsa/slave.c
index 42436ac6993b..a61a7c54af20 100644
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -305,6 +305,12 @@ static int dsa_slave_set_mac_address(struct net_device *dev, void *a)
if (!is_valid_ether_addr(addr->sa_data))
return -EADDRNOTAVAIL;
+ /* If the port is down, the address isn't synced yet to hardware or
+ * to the DSA master, so there is nothing to change.
+ */
+ if (!(dev->flags & IFF_UP))
+ goto out_change_dev_addr;
+
if (dsa_switch_supports_uc_filtering(ds)) {
err = dsa_port_standalone_host_fdb_add(dp, addr->sa_data, 0);
if (err)
@@ -323,6 +329,7 @@ static int dsa_slave_set_mac_address(struct net_device *dev, void *a)
if (dsa_switch_supports_uc_filtering(ds))
dsa_port_standalone_host_fdb_del(dp, dev->dev_addr, 0);
+out_change_dev_addr:
eth_hw_addr_set(dev, addr->sa_data);
return 0;
diff --git a/net/dsa/switch.c b/net/dsa/switch.c
index 327d66bf7b47..d25cd1da3eb3 100644
--- a/net/dsa/switch.c
+++ b/net/dsa/switch.c
@@ -212,24 +212,6 @@ static bool dsa_port_host_address_match(struct dsa_port *dp,
return false;
}
-static bool dsa_db_equal(const struct dsa_db *a, const struct dsa_db *b)
-{
- if (a->type != b->type)
- return false;
-
- switch (a->type) {
- case DSA_DB_PORT:
- return a->dp == b->dp;
- case DSA_DB_LAG:
- return a->lag.dev == b->lag.dev;
- case DSA_DB_BRIDGE:
- return a->bridge.num == b->bridge.num;
- default:
- WARN_ON(1);
- return false;
- }
-}
-
static struct dsa_mac_addr *dsa_mac_addr_find(struct list_head *addr_list,
const unsigned char *addr, u16 vid,
struct dsa_db db)