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:
parent
e212fa7c54
commit
961d8b6990
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue