Skip to content

Commit

Permalink
Merge branch 'dsa-unicast-filtering'
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
David S. Miller committed Mar 3, 2022
2 parents 43113ff + ac45520 commit 6fb8661
Show file tree
Hide file tree
Showing 7 changed files with 609 additions and 156 deletions.
241 changes: 188 additions & 53 deletions drivers/net/dsa/ocelot/felix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;

Expand All @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}

Expand Down
3 changes: 3 additions & 0 deletions drivers/net/ethernet/mscc/ocelot.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
7 changes: 7 additions & 0 deletions include/net/dsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
40 changes: 40 additions & 0 deletions net/dsa/dsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 6fb8661

Please sign in to comment.