net: dsa: felix: support FDB entries on offloaded LAG interfaces

This adds the logic in the Felix DSA driver and Ocelot switch library.
For Ocelot switches, the DEST_IDX that is the output of the MAC table
lookup is a logical port (equal to physical port, if no LAG is used, or
a dynamically allocated number otherwise). The allocation we have in
place for LAG IDs is different from DSA's, so we can't use that:
- DSA allocates a continuous range of LAG IDs starting from 1
- Ocelot appears to require that physical ports and LAG IDs are in the
  same space of [0, num_phys_ports), and additionally, ports that aren't
  in a LAG must have physical port id == logical port id

The implication is that an FDB entry towards a LAG might need to be
deleted and reinstalled when the LAG ID changes.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Vladimir Oltean 2022-02-23 16:00:54 +02:00 committed by Jakub Kicinski
parent e212fa7c54
commit 961d8b6990
3 changed files with 157 additions and 1 deletions

View File

@ -614,6 +614,22 @@ static int felix_fdb_del(struct dsa_switch *ds, int port,
return ocelot_fdb_del(ocelot, port, addr, vid);
}
static int felix_lag_fdb_add(struct dsa_switch *ds, struct dsa_lag lag,
const unsigned char *addr, u16 vid)
{
struct ocelot *ocelot = ds->priv;
return ocelot_lag_fdb_add(ocelot, lag.dev, addr, vid);
}
static int felix_lag_fdb_del(struct dsa_switch *ds, struct dsa_lag lag,
const unsigned char *addr, u16 vid)
{
struct ocelot *ocelot = ds->priv;
return ocelot_lag_fdb_del(ocelot, lag.dev, addr, vid);
}
static int felix_mdb_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb)
{
@ -1579,6 +1595,8 @@ const struct dsa_switch_ops felix_switch_ops = {
.port_fdb_dump = felix_fdb_dump,
.port_fdb_add = felix_fdb_add,
.port_fdb_del = felix_fdb_del,
.lag_fdb_add = felix_lag_fdb_add,
.lag_fdb_del = felix_lag_fdb_del,
.port_mdb_add = felix_mdb_add,
.port_mdb_del = felix_mdb_del,
.port_pre_bridge_flags = felix_pre_bridge_flags,

View File

@ -1907,6 +1907,8 @@ static u32 ocelot_get_bond_mask(struct ocelot *ocelot, struct net_device *bond)
u32 mask = 0;
int port;
lockdep_assert_held(&ocelot->fwd_domain_lock);
for (port = 0; port < ocelot->num_phys_ports; port++) {
struct ocelot_port *ocelot_port = ocelot->ports[port];
@ -1920,6 +1922,19 @@ static u32 ocelot_get_bond_mask(struct ocelot *ocelot, struct net_device *bond)
return mask;
}
/* The logical port number of a LAG is equal to the lowest numbered physical
* port ID present in that LAG. It may change if that port ever leaves the LAG.
*/
static int ocelot_bond_get_id(struct ocelot *ocelot, struct net_device *bond)
{
int bond_mask = ocelot_get_bond_mask(ocelot, bond);
if (!bond_mask)
return -ENOENT;
return __ffs(bond_mask);
}
u32 ocelot_get_bridge_fwd_mask(struct ocelot *ocelot, int src_port)
{
struct ocelot_port *ocelot_port = ocelot->ports[src_port];
@ -2413,7 +2428,7 @@ static void ocelot_setup_logical_port_ids(struct ocelot *ocelot)
bond = ocelot_port->bond;
if (bond) {
int lag = __ffs(ocelot_get_bond_mask(ocelot, bond));
int lag = ocelot_bond_get_id(ocelot, bond);
ocelot_rmw_gix(ocelot,
ANA_PORT_PORT_CFG_PORTID_VAL(lag),
@ -2428,6 +2443,46 @@ static void ocelot_setup_logical_port_ids(struct ocelot *ocelot)
}
}
/* Documentation for PORTID_VAL says:
* Logical port number for front port. If port is not a member of a LLAG,
* then PORTID must be set to the physical port number.
* If port is a member of a LLAG, then PORTID must be set to the common
* PORTID_VAL used for all member ports of the LLAG.
* The value must not exceed the number of physical ports on the device.
*
* This means we have little choice but to migrate FDB entries pointing towards
* a logical port when that changes.
*/
static void ocelot_migrate_lag_fdbs(struct ocelot *ocelot,
struct net_device *bond,
int lag)
{
struct ocelot_lag_fdb *fdb;
int err;
lockdep_assert_held(&ocelot->fwd_domain_lock);
list_for_each_entry(fdb, &ocelot->lag_fdbs, list) {
if (fdb->bond != bond)
continue;
err = ocelot_mact_forget(ocelot, fdb->addr, fdb->vid);
if (err) {
dev_err(ocelot->dev,
"failed to delete LAG %s FDB %pM vid %d: %pe\n",
bond->name, fdb->addr, fdb->vid, ERR_PTR(err));
}
err = ocelot_mact_learn(ocelot, lag, fdb->addr, fdb->vid,
ENTRYTYPE_LOCKED);
if (err) {
dev_err(ocelot->dev,
"failed to migrate LAG %s FDB %pM vid %d: %pe\n",
bond->name, fdb->addr, fdb->vid, ERR_PTR(err));
}
}
}
int ocelot_port_lag_join(struct ocelot *ocelot, int port,
struct net_device *bond,
struct netdev_lag_upper_info *info)
@ -2452,14 +2507,23 @@ EXPORT_SYMBOL(ocelot_port_lag_join);
void ocelot_port_lag_leave(struct ocelot *ocelot, int port,
struct net_device *bond)
{
int old_lag_id, new_lag_id;
mutex_lock(&ocelot->fwd_domain_lock);
old_lag_id = ocelot_bond_get_id(ocelot, bond);
ocelot->ports[port]->bond = NULL;
ocelot_setup_logical_port_ids(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot, false);
ocelot_set_aggr_pgids(ocelot);
new_lag_id = ocelot_bond_get_id(ocelot, bond);
if (new_lag_id >= 0 && old_lag_id != new_lag_id)
ocelot_migrate_lag_fdbs(ocelot, bond, new_lag_id);
mutex_unlock(&ocelot->fwd_domain_lock);
}
EXPORT_SYMBOL(ocelot_port_lag_leave);
@ -2468,13 +2532,74 @@ void ocelot_port_lag_change(struct ocelot *ocelot, int port, bool lag_tx_active)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
mutex_lock(&ocelot->fwd_domain_lock);
ocelot_port->lag_tx_active = lag_tx_active;
/* Rebalance the LAGs */
ocelot_set_aggr_pgids(ocelot);
mutex_unlock(&ocelot->fwd_domain_lock);
}
EXPORT_SYMBOL(ocelot_port_lag_change);
int ocelot_lag_fdb_add(struct ocelot *ocelot, struct net_device *bond,
const unsigned char *addr, u16 vid)
{
struct ocelot_lag_fdb *fdb;
int lag, err;
fdb = kzalloc(sizeof(*fdb), GFP_KERNEL);
if (!fdb)
return -ENOMEM;
ether_addr_copy(fdb->addr, addr);
fdb->vid = vid;
fdb->bond = bond;
mutex_lock(&ocelot->fwd_domain_lock);
lag = ocelot_bond_get_id(ocelot, bond);
err = ocelot_mact_learn(ocelot, lag, addr, vid, ENTRYTYPE_LOCKED);
if (err) {
mutex_unlock(&ocelot->fwd_domain_lock);
kfree(fdb);
return err;
}
list_add_tail(&fdb->list, &ocelot->lag_fdbs);
mutex_unlock(&ocelot->fwd_domain_lock);
return 0;
}
EXPORT_SYMBOL_GPL(ocelot_lag_fdb_add);
int ocelot_lag_fdb_del(struct ocelot *ocelot, struct net_device *bond,
const unsigned char *addr, u16 vid)
{
struct ocelot_lag_fdb *fdb, *tmp;
mutex_lock(&ocelot->fwd_domain_lock);
list_for_each_entry_safe(fdb, tmp, &ocelot->lag_fdbs, list) {
if (!ether_addr_equal(fdb->addr, addr) || fdb->vid != vid ||
fdb->bond != bond)
continue;
ocelot_mact_forget(ocelot, addr, vid);
list_del(&fdb->list);
mutex_unlock(&ocelot->fwd_domain_lock);
kfree(fdb);
return 0;
}
mutex_unlock(&ocelot->fwd_domain_lock);
return -ENOENT;
}
EXPORT_SYMBOL_GPL(ocelot_lag_fdb_del);
/* Configure the maximum SDU (L2 payload) on RX to the value specified in @sdu.
* The length of VLAN tags is accounted for automatically via DEV_MAC_TAGS_CFG.
* In the special case that it's the NPI port that we're configuring, the
@ -2769,6 +2894,7 @@ int ocelot_init(struct ocelot *ocelot)
INIT_LIST_HEAD(&ocelot->multicast);
INIT_LIST_HEAD(&ocelot->pgids);
INIT_LIST_HEAD(&ocelot->vlans);
INIT_LIST_HEAD(&ocelot->lag_fdbs);
ocelot_detect_features(ocelot);
ocelot_mact_init(ocelot);
ocelot_vlan_init(ocelot);

View File

@ -635,6 +635,13 @@ enum macaccess_entry_type {
#define OCELOT_QUIRK_PCS_PERFORMS_RATE_ADAPTATION BIT(0)
#define OCELOT_QUIRK_QSGMII_PORTS_MUST_BE_UP BIT(1)
struct ocelot_lag_fdb {
unsigned char addr[ETH_ALEN];
u16 vid;
struct net_device *bond;
struct list_head list;
};
struct ocelot_port {
struct ocelot *ocelot;
@ -690,6 +697,7 @@ struct ocelot {
struct list_head vlans;
struct list_head traps;
struct list_head lag_fdbs;
/* Switches like VSC9959 have flooding per traffic class */
int num_flooding_pgids;
@ -866,6 +874,10 @@ int ocelot_fdb_add(struct ocelot *ocelot, int port,
const unsigned char *addr, u16 vid);
int ocelot_fdb_del(struct ocelot *ocelot, int port,
const unsigned char *addr, u16 vid);
int ocelot_lag_fdb_add(struct ocelot *ocelot, struct net_device *bond,
const unsigned char *addr, u16 vid);
int ocelot_lag_fdb_del(struct ocelot *ocelot, struct net_device *bond,
const unsigned char *addr, u16 vid);
int ocelot_vlan_prepare(struct ocelot *ocelot, int port, u16 vid, bool pvid,
bool untagged, struct netlink_ext_ack *extack);
int ocelot_vlan_add(struct ocelot *ocelot, int port, u16 vid, bool pvid,