summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2022-03-03 14:15:31 +0000
committerDavid S. Miller <davem@davemloft.net>2022-03-03 14:15:31 +0000
commit6fb8661c8f97bb062d2ecc7a57591d38e6c4f8c8 (patch)
tree30247092495e3758a688f96dc30c4bf53eeaa0d0
parent43113ff73453c231af2c8e700cb4ff8bca97cff6 (diff)
parentac45520960234cfa4be89b5aa072ac0fc7e57c07 (diff)
downloadlinux-6fb8661c8f97bb062d2ecc7a57591d38e6c4f8c8.tar.bz2
Merge branch 'dsa-unicast-filtering'
Vladimir Oltean says: ==================== DSA unicast filtering This series doesn't attempt anything extremely brave, it just changes the way in which standalone ports which support FDB isolation work. Up until now, DSA has recommended that switch drivers configure standalone ports in a separate VID/FID with learning disabled, and with the CPU port as the only destination, reached trivially via flooding. That works, except that standalone ports will deliver all packets to the CPU. We can leverage the hardware FDB as a MAC DA filter, and disable flooding towards the CPU port, to force the dropping of packets with unknown MAC DA. We handle port promiscuity by re-enabling flooding towards the CPU port. This is relevant because the bridge puts its automatic (learning + flooding) ports in promiscuous mode, and this makes some things work automagically, like for example bridging with a foreign interface. We don't delve yet into the territory of managing CPU flooding more aggressively while under a bridge. The only switch driver that benefits from this work right now is the NXP LS1028A switch (felix). The others need to implement FDB isolation first, before DSA is going to install entries to the port's standalone database. Otherwise, these entries might collide with bridge FDB/MDB entries. This work was done mainly to have all the required features in place before somebody starts seriously architecting DSA support for multiple CPU ports. Otherwise it is much more difficult to bolt these features on top of multiple CPU ports. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--drivers/net/dsa/ocelot/felix.c241
-rw-r--r--drivers/net/ethernet/mscc/ocelot.c3
-rw-r--r--include/net/dsa.h7
-rw-r--r--net/dsa/dsa.c40
-rw-r--r--net/dsa/dsa_priv.h53
-rw-r--r--net/dsa/port.c160
-rw-r--r--net/dsa/slave.c261
7 files changed, 609 insertions, 156 deletions
diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c
index badb5b9ba790..638f420bf599 100644
--- a/drivers/net/dsa/ocelot/felix.c
+++ b/drivers/net/dsa/ocelot/felix.c
@@ -25,6 +25,141 @@
#include <net/dsa.h>
#include "felix.h"
+/* Translate the DSA database API into the ocelot switch library API,
+ * which uses VID 0 for all ports that aren't part of a bridge,
+ * and expects the bridge_dev to be NULL in that case.
+ */
+static struct net_device *felix_classify_db(struct dsa_db db)
+{
+ switch (db.type) {
+ case DSA_DB_PORT:
+ case DSA_DB_LAG:
+ return NULL;
+ case DSA_DB_BRIDGE:
+ return db.bridge.dev;
+ default:
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+}
+
+/* We are called before felix_npi_port_init(), so ocelot->npi is -1. */
+static int felix_migrate_fdbs_to_npi_port(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct net_device *bridge_dev = felix_classify_db(db);
+ struct ocelot *ocelot = ds->priv;
+ int cpu = ocelot->num_phys_ports;
+ int err;
+
+ err = ocelot_fdb_del(ocelot, port, addr, vid, bridge_dev);
+ if (err)
+ return err;
+
+ return ocelot_fdb_add(ocelot, cpu, addr, vid, bridge_dev);
+}
+
+static int felix_migrate_mdbs_to_npi_port(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct net_device *bridge_dev = felix_classify_db(db);
+ struct switchdev_obj_port_mdb mdb;
+ struct ocelot *ocelot = ds->priv;
+ int cpu = ocelot->num_phys_ports;
+ int err;
+
+ memset(&mdb, 0, sizeof(mdb));
+ ether_addr_copy(mdb.addr, addr);
+ mdb.vid = vid;
+
+ err = ocelot_port_mdb_del(ocelot, port, &mdb, bridge_dev);
+ if (err)
+ return err;
+
+ return ocelot_port_mdb_add(ocelot, cpu, &mdb, bridge_dev);
+}
+
+static void felix_migrate_pgid_bit(struct dsa_switch *ds, int from, int to,
+ int pgid)
+{
+ struct ocelot *ocelot = ds->priv;
+ bool on;
+ u32 val;
+
+ val = ocelot_read_rix(ocelot, ANA_PGID_PGID, pgid);
+ on = !!(val & BIT(from));
+ val &= ~BIT(from);
+ if (on)
+ val |= BIT(to);
+ else
+ val &= ~BIT(to);
+
+ ocelot_write_rix(ocelot, val, ANA_PGID_PGID, pgid);
+}
+
+static void felix_migrate_flood_to_npi_port(struct dsa_switch *ds, int port)
+{
+ struct ocelot *ocelot = ds->priv;
+
+ felix_migrate_pgid_bit(ds, port, ocelot->num_phys_ports, PGID_UC);
+ felix_migrate_pgid_bit(ds, port, ocelot->num_phys_ports, PGID_MC);
+ felix_migrate_pgid_bit(ds, port, ocelot->num_phys_ports, PGID_BC);
+}
+
+static void
+felix_migrate_flood_to_tag_8021q_port(struct dsa_switch *ds, int port)
+{
+ struct ocelot *ocelot = ds->priv;
+
+ felix_migrate_pgid_bit(ds, ocelot->num_phys_ports, port, PGID_UC);
+ felix_migrate_pgid_bit(ds, ocelot->num_phys_ports, port, PGID_MC);
+ felix_migrate_pgid_bit(ds, ocelot->num_phys_ports, port, PGID_BC);
+}
+
+/* ocelot->npi was already set to -1 by felix_npi_port_deinit, so
+ * ocelot_fdb_add() will not redirect FDB entries towards the
+ * CPU port module here, which is what we want.
+ */
+static int
+felix_migrate_fdbs_to_tag_8021q_port(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct net_device *bridge_dev = felix_classify_db(db);
+ struct ocelot *ocelot = ds->priv;
+ int cpu = ocelot->num_phys_ports;
+ int err;
+
+ err = ocelot_fdb_del(ocelot, cpu, addr, vid, bridge_dev);
+ if (err)
+ return err;
+
+ return ocelot_fdb_add(ocelot, port, addr, vid, bridge_dev);
+}
+
+static int
+felix_migrate_mdbs_to_tag_8021q_port(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct net_device *bridge_dev = felix_classify_db(db);
+ struct switchdev_obj_port_mdb mdb;
+ struct ocelot *ocelot = ds->priv;
+ int cpu = ocelot->num_phys_ports;
+ int err;
+
+ memset(&mdb, 0, sizeof(mdb));
+ ether_addr_copy(mdb.addr, addr);
+ mdb.vid = vid;
+
+ err = ocelot_port_mdb_del(ocelot, cpu, &mdb, bridge_dev);
+ if (err)
+ return err;
+
+ return ocelot_port_mdb_add(ocelot, port, &mdb, bridge_dev);
+}
+
/* Set up VCAP ES0 rules for pushing a tag_8021q VLAN towards the CPU such that
* the tagger can perform RX source port identification.
*/
@@ -327,10 +462,9 @@ static int felix_update_trapping_destinations(struct dsa_switch *ds,
return 0;
}
-static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu)
+static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu, bool change)
{
struct ocelot *ocelot = ds->priv;
- unsigned long cpu_flood;
struct dsa_port *dp;
int err;
@@ -352,22 +486,27 @@ static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu)
ANA_PORT_CPU_FWD_BPDU_CFG, dp->index);
}
- /* In tag_8021q mode, the CPU port module is unused, except for PTP
- * frames. So we want to disable flooding of any kind to the CPU port
- * module, since packets going there will end in a black hole.
- */
- 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);
- ocelot_rmw_rix(ocelot, 0, cpu_flood, ANA_PGID_PGID, PGID_BC);
-
err = dsa_tag_8021q_register(ds, htons(ETH_P_8021AD));
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_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);
+ }
+
err = felix_update_trapping_destinations(ds, true);
if (err)
- goto out_tag_8021q_unregister;
+ goto out_migrate_flood;
/* The ownership of the CPU port module's queues might have just been
* transferred to the tag_8021q tagger from the NPI-based tagger.
@@ -380,6 +519,14 @@ static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu)
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);
+out_migrate_fdbs:
+ if (change)
+ dsa_port_walk_fdbs(ds, cpu, felix_migrate_fdbs_to_npi_port);
out_tag_8021q_unregister:
dsa_tag_8021q_unregister(ds);
return err;
@@ -454,30 +601,35 @@ 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)
+static int felix_setup_tag_npi(struct dsa_switch *ds, int cpu, bool change)
{
struct ocelot *ocelot = ds->priv;
- unsigned long cpu_flood;
+ int err;
- felix_npi_port_init(ocelot, cpu);
+ if (change) {
+ err = dsa_port_walk_fdbs(ds, cpu,
+ felix_migrate_fdbs_to_npi_port);
+ if (err)
+ return err;
- /* Include the CPU port module (and indirectly, the NPI port)
- * in the forwarding mask for unknown unicast - the hardware
- * default value for ANA_FLOODING_FLD_UNICAST excludes
- * BIT(ocelot->num_phys_ports), and so does ocelot_init,
- * since Ocelot relies on whitelisting MAC addresses towards
- * PGID_CPU.
- * We do this because DSA does not yet perform RX filtering,
- * and the NPI port does not perform source address learning,
- * so traffic sent to Linux is effectively unknown from the
- * switch's perspective.
- */
- cpu_flood = ANA_PGID_PGID_PGID(BIT(ocelot->num_phys_ports));
- ocelot_rmw_rix(ocelot, cpu_flood, cpu_flood, ANA_PGID_PGID, PGID_UC);
- ocelot_rmw_rix(ocelot, cpu_flood, cpu_flood, ANA_PGID_PGID, PGID_MC);
- ocelot_rmw_rix(ocelot, cpu_flood, cpu_flood, ANA_PGID_PGID, PGID_BC);
+ 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_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);
+
+ return err;
}
static void felix_teardown_tag_npi(struct dsa_switch *ds, int cpu)
@@ -488,17 +640,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)
+ enum dsa_tag_protocol proto, bool change)
{
int err;
switch (proto) {
case DSA_TAG_PROTO_SEVILLE:
case DSA_TAG_PROTO_OCELOT:
- err = felix_setup_tag_npi(ds, cpu);
+ err = felix_setup_tag_npi(ds, cpu, change);
break;
case DSA_TAG_PROTO_OCELOT_8021Q:
- err = felix_setup_tag_8021q(ds, cpu);
+ err = felix_setup_tag_8021q(ds, cpu, change);
break;
default:
err = -EPROTONOSUPPORT;
@@ -542,9 +694,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);
+ err = felix_set_tag_protocol(ds, cpu, proto, true);
if (err) {
- felix_set_tag_protocol(ds, cpu, old_proto);
+ felix_set_tag_protocol(ds, cpu, old_proto, true);
return err;
}
@@ -592,23 +744,6 @@ static int felix_fdb_dump(struct dsa_switch *ds, int port,
return ocelot_fdb_dump(ocelot, port, cb, data);
}
-/* Translate the DSA database API into the ocelot switch library API,
- * which uses VID 0 for all ports that aren't part of a bridge,
- * and expects the bridge_dev to be NULL in that case.
- */
-static struct net_device *felix_classify_db(struct dsa_db db)
-{
- switch (db.type) {
- case DSA_DB_PORT:
- case DSA_DB_LAG:
- return NULL;
- case DSA_DB_BRIDGE:
- return db.bridge.dev;
- default:
- return ERR_PTR(-EOPNOTSUPP);
- }
-}
-
static int felix_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
@@ -1260,7 +1395,7 @@ 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);
+ felix_set_tag_protocol(ds, dp->index, felix->tag_proto, false);
break;
}
diff --git a/drivers/net/ethernet/mscc/ocelot.c b/drivers/net/ethernet/mscc/ocelot.c
index 0af321f6fb54..21134125a6e4 100644
--- a/drivers/net/ethernet/mscc/ocelot.c
+++ b/drivers/net/ethernet/mscc/ocelot.c
@@ -2886,6 +2886,9 @@ EXPORT_SYMBOL(ocelot_port_pre_bridge_flags);
void ocelot_port_bridge_flags(struct ocelot *ocelot, int port,
struct switchdev_brport_flags flags)
{
+ if (port == ocelot->npi)
+ port = ocelot->num_phys_ports;
+
if (flags.mask & BR_LEARNING)
ocelot_port_set_learning(ocelot, port,
!!(flags.val & BR_LEARNING));
diff --git a/include/net/dsa.h b/include/net/dsa.h
index cfedcfb86350..71cc363dbbd4 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -1219,6 +1219,13 @@ struct dsa_switch_driver {
struct net_device *dsa_dev_to_net_device(struct device *dev);
+typedef int dsa_fdb_walk_cb_t(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db);
+
+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);
+
/* 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 c43f7446a75d..06d5de28a43e 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -467,6 +467,46 @@ struct dsa_port *dsa_port_from_netdev(struct net_device *netdev)
}
EXPORT_SYMBOL_GPL(dsa_port_from_netdev);
+int dsa_port_walk_fdbs(struct dsa_switch *ds, int port, dsa_fdb_walk_cb_t cb)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct dsa_mac_addr *a;
+ int err;
+
+ mutex_lock(&dp->addr_lists_lock);
+
+ list_for_each_entry(a, &dp->fdbs, list) {
+ err = cb(ds, port, a->addr, a->vid, a->db);
+ if (err)
+ break;
+ }
+
+ mutex_unlock(&dp->addr_lists_lock);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(dsa_port_walk_fdbs);
+
+int dsa_port_walk_mdbs(struct dsa_switch *ds, int port, dsa_fdb_walk_cb_t cb)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct dsa_mac_addr *a;
+ int err;
+
+ mutex_lock(&dp->addr_lists_lock);
+
+ list_for_each_entry(a, &dp->mdbs, list) {
+ err = cb(ds, port, a->addr, a->vid, a->db);
+ if (err)
+ break;
+ }
+
+ mutex_unlock(&dp->addr_lists_lock);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(dsa_port_walk_mdbs);
+
static int __init dsa_init_module(void)
{
int rc;
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index 07c0ad52395a..c3c7491ace72 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -144,6 +144,21 @@ struct dsa_switchdev_event_work {
bool host_addr;
};
+enum dsa_standalone_event {
+ DSA_UC_ADD,
+ DSA_UC_DEL,
+ DSA_MC_ADD,
+ DSA_MC_DEL,
+};
+
+struct dsa_standalone_event_work {
+ struct work_struct work;
+ struct net_device *dev;
+ enum dsa_standalone_event event;
+ unsigned char addr[ETH_ALEN];
+ u16 vid;
+};
+
struct dsa_slave_priv {
/* Copy of CPU port xmit for faster access in slave transmit hot path */
struct sk_buff * (*xmit)(struct sk_buff *skb,
@@ -223,10 +238,14 @@ int dsa_port_fdb_add(struct dsa_port *dp, const unsigned char *addr,
u16 vid);
int dsa_port_fdb_del(struct dsa_port *dp, const unsigned char *addr,
u16 vid);
-int dsa_port_host_fdb_add(struct dsa_port *dp, const unsigned char *addr,
- u16 vid);
-int dsa_port_host_fdb_del(struct dsa_port *dp, const unsigned char *addr,
- u16 vid);
+int dsa_port_standalone_host_fdb_add(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid);
+int dsa_port_standalone_host_fdb_del(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid);
+int dsa_port_bridge_host_fdb_add(struct dsa_port *dp, const unsigned char *addr,
+ u16 vid);
+int dsa_port_bridge_host_fdb_del(struct dsa_port *dp, const unsigned char *addr,
+ u16 vid);
int dsa_port_lag_fdb_add(struct dsa_port *dp, const unsigned char *addr,
u16 vid);
int dsa_port_lag_fdb_del(struct dsa_port *dp, const unsigned char *addr,
@@ -236,10 +255,14 @@ int dsa_port_mdb_add(const struct dsa_port *dp,
const struct switchdev_obj_port_mdb *mdb);
int dsa_port_mdb_del(const struct dsa_port *dp,
const struct switchdev_obj_port_mdb *mdb);
-int dsa_port_host_mdb_add(const struct dsa_port *dp,
- const struct switchdev_obj_port_mdb *mdb);
-int dsa_port_host_mdb_del(const struct dsa_port *dp,
- const struct switchdev_obj_port_mdb *mdb);
+int dsa_port_standalone_host_mdb_add(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb);
+int dsa_port_standalone_host_mdb_del(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb);
+int dsa_port_bridge_host_mdb_add(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb);
+int dsa_port_bridge_host_mdb_del(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb);
int dsa_port_pre_bridge_flags(const struct dsa_port *dp,
struct switchdev_brport_flags flags,
struct netlink_ext_ack *extack);
@@ -502,6 +525,20 @@ static inline void *dsa_etype_header_pos_tx(struct sk_buff *skb)
int dsa_switch_register_notifier(struct dsa_switch *ds);
void dsa_switch_unregister_notifier(struct dsa_switch *ds);
+static inline bool dsa_switch_supports_uc_filtering(struct dsa_switch *ds)
+{
+ return ds->ops->port_fdb_add && ds->ops->port_fdb_del &&
+ ds->fdb_isolation && !ds->vlan_filtering_is_global &&
+ !ds->needs_standalone_vlan_filtering;
+}
+
+static inline bool dsa_switch_supports_mc_filtering(struct dsa_switch *ds)
+{
+ return ds->ops->port_mdb_add && ds->ops->port_mdb_del &&
+ ds->fdb_isolation && !ds->vlan_filtering_is_global &&
+ !ds->needs_standalone_vlan_filtering;
+}
+
/* dsa2.c */
void dsa_lag_map(struct dsa_switch_tree *dst, struct dsa_lag *lag);
void dsa_lag_unmap(struct dsa_switch_tree *dst, struct dsa_lag *lag);
diff --git a/net/dsa/port.c b/net/dsa/port.c
index d9da425a17fb..58291df14cdb 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -835,20 +835,43 @@ int dsa_port_fdb_del(struct dsa_port *dp, const unsigned char *addr,
return dsa_port_notify(dp, DSA_NOTIFIER_FDB_DEL, &info);
}
-int dsa_port_host_fdb_add(struct dsa_port *dp, const unsigned char *addr,
- u16 vid)
+static int dsa_port_host_fdb_add(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
{
struct dsa_notifier_fdb_info info = {
.sw_index = dp->ds->index,
.port = dp->index,
.addr = addr,
.vid = vid,
- .db = {
- .type = DSA_DB_BRIDGE,
- .bridge = *dp->bridge,
- },
+ .db = db,
+ };
+
+ if (!dp->ds->fdb_isolation)
+ info.db.bridge.num = 0;
+
+ return dsa_port_notify(dp, DSA_NOTIFIER_HOST_FDB_ADD, &info);
+}
+
+int dsa_port_standalone_host_fdb_add(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid)
+{
+ struct dsa_db db = {
+ .type = DSA_DB_PORT,
+ .dp = dp,
};
+
+ return dsa_port_host_fdb_add(dp, addr, vid, db);
+}
+
+int dsa_port_bridge_host_fdb_add(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid)
+{
struct dsa_port *cpu_dp = dp->cpu_dp;
+ struct dsa_db db = {
+ .type = DSA_DB_BRIDGE,
+ .bridge = *dp->bridge,
+ };
int err;
/* Avoid a call to __dev_set_promiscuity() on the master, which
@@ -861,26 +884,46 @@ int dsa_port_host_fdb_add(struct dsa_port *dp, const unsigned char *addr,
return err;
}
- if (!dp->ds->fdb_isolation)
- info.db.bridge.num = 0;
-
- return dsa_port_notify(dp, DSA_NOTIFIER_HOST_FDB_ADD, &info);
+ return dsa_port_host_fdb_add(dp, addr, vid, db);
}
-int dsa_port_host_fdb_del(struct dsa_port *dp, const unsigned char *addr,
- u16 vid)
+static int dsa_port_host_fdb_del(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
{
struct dsa_notifier_fdb_info info = {
.sw_index = dp->ds->index,
.port = dp->index,
.addr = addr,
.vid = vid,
- .db = {
- .type = DSA_DB_BRIDGE,
- .bridge = *dp->bridge,
- },
+ .db = db,
+ };
+
+ if (!dp->ds->fdb_isolation)
+ info.db.bridge.num = 0;
+
+ return dsa_port_notify(dp, DSA_NOTIFIER_HOST_FDB_DEL, &info);
+}
+
+int dsa_port_standalone_host_fdb_del(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid)
+{
+ struct dsa_db db = {
+ .type = DSA_DB_PORT,
+ .dp = dp,
};
+
+ return dsa_port_host_fdb_del(dp, addr, vid, db);
+}
+
+int dsa_port_bridge_host_fdb_del(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid)
+{
struct dsa_port *cpu_dp = dp->cpu_dp;
+ struct dsa_db db = {
+ .type = DSA_DB_BRIDGE,
+ .bridge = *dp->bridge,
+ };
int err;
if (cpu_dp->master->priv_flags & IFF_UNICAST_FLT) {
@@ -889,10 +932,7 @@ int dsa_port_host_fdb_del(struct dsa_port *dp, const unsigned char *addr,
return err;
}
- if (!dp->ds->fdb_isolation)
- info.db.bridge.num = 0;
-
- return dsa_port_notify(dp, DSA_NOTIFIER_HOST_FDB_DEL, &info);
+ return dsa_port_host_fdb_del(dp, addr, vid, db);
}
int dsa_port_lag_fdb_add(struct dsa_port *dp, const unsigned char *addr,
@@ -982,54 +1022,94 @@ int dsa_port_mdb_del(const struct dsa_port *dp,
return dsa_port_notify(dp, DSA_NOTIFIER_MDB_DEL, &info);
}
-int dsa_port_host_mdb_add(const struct dsa_port *dp,
- const struct switchdev_obj_port_mdb *mdb)
+static int dsa_port_host_mdb_add(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
{
struct dsa_notifier_mdb_info info = {
.sw_index = dp->ds->index,
.port = dp->index,
.mdb = mdb,
- .db = {
- .type = DSA_DB_BRIDGE,
- .bridge = *dp->bridge,
- },
+ .db = db,
+ };
+
+ if (!dp->ds->fdb_isolation)
+ info.db.bridge.num = 0;
+
+ return dsa_port_notify(dp, DSA_NOTIFIER_HOST_MDB_ADD, &info);
+}
+
+int dsa_port_standalone_host_mdb_add(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct dsa_db db = {
+ .type = DSA_DB_PORT,
+ .dp = dp,
};
+
+ return dsa_port_host_mdb_add(dp, mdb, db);
+}
+
+int dsa_port_bridge_host_mdb_add(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb)
+{
struct dsa_port *cpu_dp = dp->cpu_dp;
+ struct dsa_db db = {
+ .type = DSA_DB_BRIDGE,
+ .bridge = *dp->bridge,
+ };
int err;
err = dev_mc_add(cpu_dp->master, mdb->addr);
if (err)
return err;
- if (!dp->ds->fdb_isolation)
- info.db.bridge.num = 0;
-
- return dsa_port_notify(dp, DSA_NOTIFIER_HOST_MDB_ADD, &info);
+ return dsa_port_host_mdb_add(dp, mdb, db);
}
-int dsa_port_host_mdb_del(const struct dsa_port *dp,
- const struct switchdev_obj_port_mdb *mdb)
+static int dsa_port_host_mdb_del(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
{
struct dsa_notifier_mdb_info info = {
.sw_index = dp->ds->index,
.port = dp->index,
.mdb = mdb,
- .db = {
- .type = DSA_DB_BRIDGE,
- .bridge = *dp->bridge,
- },
+ .db = db,
+ };
+
+ if (!dp->ds->fdb_isolation)
+ info.db.bridge.num = 0;
+
+ return dsa_port_notify(dp, DSA_NOTIFIER_HOST_MDB_DEL, &info);
+}
+
+int dsa_port_standalone_host_mdb_del(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct dsa_db db = {
+ .type = DSA_DB_PORT,
+ .dp = dp,
};
+
+ return dsa_port_host_mdb_del(dp, mdb, db);
+}
+
+int dsa_port_bridge_host_mdb_del(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb)
+{
struct dsa_port *cpu_dp = dp->cpu_dp;
+ struct dsa_db db = {
+ .type = DSA_DB_BRIDGE,
+ .bridge = *dp->bridge,
+ };
int err;
err = dev_mc_del(cpu_dp->master, mdb->addr);
if (err)
return err;
- if (!dp->ds->fdb_isolation)
- info.db.bridge.num = 0;
-
- return dsa_port_notify(dp, DSA_NOTIFIER_HOST_MDB_DEL, &info);
+ return dsa_port_host_mdb_del(dp, mdb, db);
}
int dsa_port_vlan_add(struct dsa_port *dp,
diff --git a/net/dsa/slave.c b/net/dsa/slave.c
index 089616206b11..42436ac6993b 100644
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -23,6 +23,114 @@
#include "dsa_priv.h"
+static void dsa_slave_standalone_event_work(struct work_struct *work)
+{
+ struct dsa_standalone_event_work *standalone_work =
+ container_of(work, struct dsa_standalone_event_work, work);
+ const unsigned char *addr = standalone_work->addr;
+ struct net_device *dev = standalone_work->dev;
+ struct dsa_port *dp = dsa_slave_to_port(dev);
+ struct switchdev_obj_port_mdb mdb;
+ struct dsa_switch *ds = dp->ds;
+ u16 vid = standalone_work->vid;
+ int err;
+
+ switch (standalone_work->event) {
+ case DSA_UC_ADD:
+ err = dsa_port_standalone_host_fdb_add(dp, addr, vid);
+ if (err) {
+ dev_err(ds->dev,
+ "port %d failed to add %pM vid %d to fdb: %d\n",
+ dp->index, addr, vid, err);
+ break;
+ }
+ break;
+
+ case DSA_UC_DEL:
+ err = dsa_port_standalone_host_fdb_del(dp, addr, vid);
+ if (err) {
+ dev_err(ds->dev,
+ "port %d failed to delete %pM vid %d from fdb: %d\n",
+ dp->index, addr, vid, err);
+ }
+
+ break;
+ case DSA_MC_ADD:
+ ether_addr_copy(mdb.addr, addr);
+ mdb.vid = vid;
+
+ err = dsa_port_standalone_host_mdb_add(dp, &mdb);
+ if (err) {
+ dev_err(ds->dev,
+ "port %d failed to add %pM vid %d to mdb: %d\n",
+ dp->index, addr, vid, err);
+ break;
+ }
+ break;
+ case DSA_MC_DEL:
+ ether_addr_copy(mdb.addr, addr);
+ mdb.vid = vid;
+
+ err = dsa_port_standalone_host_mdb_del(dp, &mdb);
+ if (err) {
+ dev_err(ds->dev,
+ "port %d failed to delete %pM vid %d from mdb: %d\n",
+ dp->index, addr, vid, err);
+ }
+
+ break;
+ }
+
+ kfree(standalone_work);
+}
+
+static int dsa_slave_schedule_standalone_work(struct net_device *dev,
+ enum dsa_standalone_event event,
+ const unsigned char *addr,
+ u16 vid)
+{
+ struct dsa_standalone_event_work *standalone_work;
+
+ standalone_work = kzalloc(sizeof(*standalone_work), GFP_ATOMIC);
+ if (!standalone_work)
+ return -ENOMEM;
+
+ INIT_WORK(&standalone_work->work, dsa_slave_standalone_event_work);
+ standalone_work->event = event;
+ standalone_work->dev = dev;
+
+ ether_addr_copy(standalone_work->addr, addr);
+ standalone_work->vid = vid;
+
+ dsa_schedule_work(&standalone_work->work);
+
+ return 0;
+}
+
+static int dsa_slave_sync_uc(struct net_device *dev,
+ const unsigned char *addr)
+{
+ return dsa_slave_schedule_standalone_work(dev, DSA_UC_ADD, addr, 0);
+}
+
+static int dsa_slave_unsync_uc(struct net_device *dev,
+ const unsigned char *addr)
+{
+ return dsa_slave_schedule_standalone_work(dev, DSA_UC_DEL, addr, 0);
+}
+
+static int dsa_slave_sync_mc(struct net_device *dev,
+ const unsigned char *addr)
+{
+ return dsa_slave_schedule_standalone_work(dev, DSA_MC_ADD, addr, 0);
+}
+
+static int dsa_slave_unsync_mc(struct net_device *dev,
+ const unsigned char *addr)
+{
+ return dsa_slave_schedule_standalone_work(dev, DSA_MC_DEL, addr, 0);
+}
+
/* slave mii_bus handling ***************************************************/
static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg)
{
@@ -67,6 +175,7 @@ static int dsa_slave_open(struct net_device *dev)
{
struct net_device *master = dsa_slave_to_master(dev);
struct dsa_port *dp = dsa_slave_to_port(dev);
+ struct dsa_switch *ds = dp->ds;
int err;
err = dev_open(master, NULL);
@@ -75,38 +184,30 @@ static int dsa_slave_open(struct net_device *dev)
goto out;
}
- if (!ether_addr_equal(dev->dev_addr, master->dev_addr)) {
- err = dev_uc_add(master, dev->dev_addr);
- if (err < 0)
+ if (dsa_switch_supports_uc_filtering(ds)) {
+ err = dsa_port_standalone_host_fdb_add(dp, dev->dev_addr, 0);
+ if (err)
goto out;
}
- if (dev->flags & IFF_ALLMULTI) {
- err = dev_set_allmulti(master, 1);
- if (err < 0)
- goto del_unicast;
- }
- if (dev->flags & IFF_PROMISC) {
- err = dev_set_promiscuity(master, 1);
+ if (!ether_addr_equal(dev->dev_addr, master->dev_addr)) {
+ err = dev_uc_add(master, dev->dev_addr);
if (err < 0)
- goto clear_allmulti;
+ goto del_host_addr;
}
err = dsa_port_enable_rt(dp, dev->phydev);
if (err)
- goto clear_promisc;
+ goto del_unicast;
return 0;
-clear_promisc:
- if (dev->flags & IFF_PROMISC)
- dev_set_promiscuity(master, -1);
-clear_allmulti:
- if (dev->flags & IFF_ALLMULTI)
- dev_set_allmulti(master, -1);
del_unicast:
if (!ether_addr_equal(dev->dev_addr, master->dev_addr))
dev_uc_del(master, dev->dev_addr);
+del_host_addr:
+ if (dsa_switch_supports_uc_filtering(ds))
+ dsa_port_standalone_host_fdb_del(dp, dev->dev_addr, 0);
out:
return err;
}
@@ -115,68 +216,122 @@ static int dsa_slave_close(struct net_device *dev)
{
struct net_device *master = dsa_slave_to_master(dev);
struct dsa_port *dp = dsa_slave_to_port(dev);
+ struct dsa_switch *ds = dp->ds;
dsa_port_disable_rt(dp);
- dev_mc_unsync(master, dev);
- dev_uc_unsync(master, dev);
- if (dev->flags & IFF_ALLMULTI)
- dev_set_allmulti(master, -1);
- if (dev->flags & IFF_PROMISC)
- dev_set_promiscuity(master, -1);
-
if (!ether_addr_equal(dev->dev_addr, master->dev_addr))
dev_uc_del(master, dev->dev_addr);
+ if (dsa_switch_supports_uc_filtering(ds))
+ dsa_port_standalone_host_fdb_del(dp, dev->dev_addr, 0);
+
return 0;
}
+/* Keep flooding enabled towards this port's CPU port as long as it serves at
+ * least one port in the tree that requires it.
+ */
+static void dsa_port_manage_cpu_flood(struct dsa_port *dp)
+{
+ struct switchdev_brport_flags flags = {
+ .mask = BR_FLOOD | BR_MCAST_FLOOD,
+ };
+ struct dsa_switch_tree *dst = dp->ds->dst;
+ struct dsa_port *cpu_dp = dp->cpu_dp;
+ struct dsa_port *other_dp;
+ int err;
+
+ list_for_each_entry(other_dp, &dst->ports, list) {
+ if (!dsa_port_is_user(other_dp))
+ continue;
+
+ if (other_dp->cpu_dp != cpu_dp)
+ continue;
+
+ if (other_dp->slave->flags & IFF_ALLMULTI)
+ flags.val |= BR_MCAST_FLOOD;
+ if (other_dp->slave->flags & IFF_PROMISC)
+ flags.val |= BR_FLOOD;
+ }
+
+ err = dsa_port_pre_bridge_flags(dp, flags, NULL);
+ if (err)
+ return;
+
+ dsa_port_bridge_flags(cpu_dp, flags, NULL);
+}
+
static void dsa_slave_change_rx_flags(struct net_device *dev, int change)
{
struct net_device *master = dsa_slave_to_master(dev);
- if (dev->flags & IFF_UP) {
- if (change & IFF_ALLMULTI)
- dev_set_allmulti(master,
- dev->flags & IFF_ALLMULTI ? 1 : -1);
- if (change & IFF_PROMISC)
- dev_set_promiscuity(master,
- dev->flags & IFF_PROMISC ? 1 : -1);
- }
+ struct dsa_port *dp = dsa_slave_to_port(dev);
+ struct dsa_switch *ds = dp->ds;
+
+ if (change & IFF_ALLMULTI)
+ dev_set_allmulti(master,
+ dev->flags & IFF_ALLMULTI ? 1 : -1);
+ if (change & IFF_PROMISC)
+ dev_set_promiscuity(master,
+ dev->flags & IFF_PROMISC ? 1 : -1);
+
+ if (dsa_switch_supports_uc_filtering(ds) &&
+ dsa_switch_supports_mc_filtering(ds))
+ dsa_port_manage_cpu_flood(dp);
}
static void dsa_slave_set_rx_mode(struct net_device *dev)
{
struct net_device *master = dsa_slave_to_master(dev);
+ struct dsa_port *dp = dsa_slave_to_port(dev);
+ struct dsa_switch *ds = dp->ds;
dev_mc_sync(master, dev);
dev_uc_sync(master, dev);
+ if (dsa_switch_supports_mc_filtering(ds))
+ __dev_mc_sync(dev, dsa_slave_sync_mc, dsa_slave_unsync_mc);
+ if (dsa_switch_supports_uc_filtering(ds))
+ __dev_uc_sync(dev, dsa_slave_sync_uc, dsa_slave_unsync_uc);
}
static int dsa_slave_set_mac_address(struct net_device *dev, void *a)
{
struct net_device *master = dsa_slave_to_master(dev);
+ struct dsa_port *dp = dsa_slave_to_port(dev);
+ struct dsa_switch *ds = dp->ds;
struct sockaddr *addr = a;
int err;
if (!is_valid_ether_addr(addr->sa_data))
return -EADDRNOTAVAIL;
- if (!(dev->flags & IFF_UP))
- goto out;
+ if (dsa_switch_supports_uc_filtering(ds)) {
+ err = dsa_port_standalone_host_fdb_add(dp, addr->sa_data, 0);
+ if (err)
+ return err;
+ }
if (!ether_addr_equal(addr->sa_data, master->dev_addr)) {
err = dev_uc_add(master, addr->sa_data);
if (err < 0)
- return err;
+ goto del_unicast;
}
if (!ether_addr_equal(dev->dev_addr, master->dev_addr))
dev_uc_del(master, dev->dev_addr);
-out:
+ if (dsa_switch_supports_uc_filtering(ds))
+ dsa_port_standalone_host_fdb_del(dp, dev->dev_addr, 0);
+
eth_hw_addr_set(dev, addr->sa_data);
return 0;
+
+del_unicast:
+ if (dsa_switch_supports_uc_filtering(ds))
+ dsa_port_standalone_host_fdb_del(dp, addr->sa_data, 0);
+
+ return err;
}
struct dsa_slave_dump_ctx {
@@ -426,7 +581,7 @@ static int dsa_slave_port_obj_add(struct net_device *dev, const void *ctx,
if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev))
return -EOPNOTSUPP;
- err = dsa_port_host_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
+ err = dsa_port_bridge_host_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
break;
case SWITCHDEV_OBJ_ID_PORT_VLAN:
if (dsa_port_offloads_bridge_port(dp, obj->orig_dev))
@@ -507,7 +662,7 @@ static int dsa_slave_port_obj_del(struct net_device *dev, const void *ctx,
if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev))
return -EOPNOTSUPP;
- err = dsa_port_host_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
+ err = dsa_port_bridge_host_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
break;
case SWITCHDEV_OBJ_ID_PORT_VLAN:
if (dsa_port_offloads_bridge_port(dp, obj->orig_dev))
@@ -1948,6 +2103,8 @@ int dsa_slave_create(struct dsa_port *port)
else
eth_hw_addr_inherit(slave_dev, master);
slave_dev->priv_flags |= IFF_NO_QUEUE;
+ if (dsa_switch_supports_uc_filtering(ds))
+ slave_dev->priv_flags |= IFF_UNICAST_FLT;
slave_dev->netdev_ops = &dsa_slave_netdev_ops;
if (ds->ops->port_max_mtu)
slave_dev->max_mtu = ds->ops->port_max_mtu(ds, port->index);
@@ -2385,7 +2542,9 @@ static void dsa_slave_switchdev_event_work(struct work_struct *work)
{
struct dsa_switchdev_event_work *switchdev_work =
container_of(work, struct dsa_switchdev_event_work, work);
+ const unsigned char *addr = switchdev_work->addr;
struct net_device *dev = switchdev_work->dev;
+ u16 vid = switchdev_work->vid;
struct dsa_switch *ds;
struct dsa_port *dp;
int err;
@@ -2396,19 +2555,15 @@ static void dsa_slave_switchdev_event_work(struct work_struct *work)
switch (switchdev_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
if (switchdev_work->host_addr)
- err = dsa_port_host_fdb_add(dp, switchdev_work->addr,
- switchdev_work->vid);
+ err = dsa_port_bridge_host_fdb_add(dp, addr, vid);
else if (dp->lag)
- err = dsa_port_lag_fdb_add(dp, switchdev_work->addr,
- switchdev_work->vid);
+ err = dsa_port_lag_fdb_add(dp, addr, vid);
else
- err = dsa_port_fdb_add(dp, switchdev_work->addr,
- switchdev_work->vid);
+ err = dsa_port_fdb_add(dp, addr, vid);
if (err) {
dev_err(ds->dev,
"port %d failed to add %pM vid %d to fdb: %d\n",
- dp->index, switchdev_work->addr,
- switchdev_work->vid, err);
+ dp->index, addr, vid, err);
break;
}
dsa_fdb_offload_notify(switchdev_work);
@@ -2416,19 +2571,15 @@ static void dsa_slave_switchdev_event_work(struct work_struct *work)
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (switchdev_work->host_addr)
- err = dsa_port_host_fdb_del(dp, switchdev_work->addr,
- switchdev_work->vid);
+ err = dsa_port_bridge_host_fdb_del(dp, addr, vid);
else if (dp->lag)
- err = dsa_port_lag_fdb_del(dp, switchdev_work->addr,
- switchdev_work->vid);
+ err = dsa_port_lag_fdb_del(dp, addr, vid);
else
- err = dsa_port_fdb_del(dp, switchdev_work->addr,
- switchdev_work->vid);
+ err = dsa_port_fdb_del(dp, addr, vid);
if (err) {
dev_err(ds->dev,
"port %d failed to delete %pM vid %d from fdb: %d\n",
- dp->index, switchdev_work->addr,
- switchdev_work->vid, err);
+ dp->index, addr, vid, err);
}
break;