Commit c214cc3a authored by Jakub Kicinski's avatar Jakub Kicinski

Merge branch 'offload-software-learnt-bridge-addresses-to-dsa'

Vladimir Oltean says:

====================
Offload software learnt bridge addresses to DSA

This series tries to make DSA behave a bit more sanely when bridged with
"foreign" (non-DSA) interfaces and source address learning is not
supported on the hardware CPU port (which would make things work more
seamlessly without software intervention). When a station A connected to
a DSA switch port needs to talk to another station B connected to a
non-DSA port through the Linux bridge, DSA must explicitly add a route
for station B towards its CPU port.

Initial RFC was posted here:
https://patchwork.ozlabs.org/project/netdev/cover/20201108131953.2462644-1-olteanv@gmail.com/

v2 was posted here:
https://patchwork.kernel.org/project/netdevbpf/cover/20201213024018.772586-1-vladimir.oltean@nxp.com/

v3 was posted here:
https://patchwork.kernel.org/project/netdevbpf/cover/20201213140710.1198050-1-vladimir.oltean@nxp.com/

This is a resend of the previous v3 with some added Reviewed-by tags.
====================

Link: https://lore.kernel.org/r/20210106095136.224739-1-olteanv@gmail.comSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents dd15c4a0 c54913c1
...@@ -629,6 +629,7 @@ static int felix_setup(struct dsa_switch *ds) ...@@ -629,6 +629,7 @@ static int felix_setup(struct dsa_switch *ds)
ds->mtu_enforcement_ingress = true; ds->mtu_enforcement_ingress = true;
ds->configure_vlan_while_not_filtering = true; ds->configure_vlan_while_not_filtering = true;
ds->assisted_learning_on_cpu_port = true;
return 0; return 0;
} }
......
...@@ -319,6 +319,11 @@ struct dsa_switch { ...@@ -319,6 +319,11 @@ struct dsa_switch {
*/ */
bool untag_bridge_pvid; bool untag_bridge_pvid;
/* Let DSA manage the FDB entries towards the CPU, based on the
* software bridge database.
*/
bool assisted_learning_on_cpu_port;
/* In case vlan_filtering_is_global is set, the VLAN awareness state /* In case vlan_filtering_is_global is set, the VLAN awareness state
* should be retrieved from here and not from the per-port settings. * should be retrieved from here and not from the per-port settings.
*/ */
......
...@@ -602,6 +602,7 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, ...@@ -602,6 +602,7 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
/* fastpath: update of existing entry */ /* fastpath: update of existing entry */
if (unlikely(source != fdb->dst && if (unlikely(source != fdb->dst &&
!test_bit(BR_FDB_STICKY, &fdb->flags))) { !test_bit(BR_FDB_STICKY, &fdb->flags))) {
br_switchdev_fdb_notify(fdb, RTM_DELNEIGH);
fdb->dst = source; fdb->dst = source;
fdb_modified = true; fdb_modified = true;
/* Take over HW learned entry */ /* Take over HW learned entry */
......
...@@ -73,6 +73,18 @@ struct dsa_notifier_mtu_info { ...@@ -73,6 +73,18 @@ struct dsa_notifier_mtu_info {
int mtu; int mtu;
}; };
struct dsa_switchdev_event_work {
struct dsa_switch *ds;
int port;
struct work_struct work;
unsigned long event;
/* Specific for SWITCHDEV_FDB_ADD_TO_DEVICE and
* SWITCHDEV_FDB_DEL_TO_DEVICE
*/
unsigned char addr[ETH_ALEN];
u16 vid;
};
struct dsa_slave_priv { struct dsa_slave_priv {
/* Copy of CPU port xmit for faster access in slave transmit hot path */ /* Copy of CPU port xmit for faster access in slave transmit hot path */
struct sk_buff * (*xmit)(struct sk_buff *skb, struct sk_buff * (*xmit)(struct sk_buff *skb,
......
...@@ -2047,119 +2047,167 @@ static int dsa_slave_netdevice_event(struct notifier_block *nb, ...@@ -2047,119 +2047,167 @@ static int dsa_slave_netdevice_event(struct notifier_block *nb,
return NOTIFY_DONE; return NOTIFY_DONE;
} }
struct dsa_switchdev_event_work { static void
struct work_struct work; dsa_fdb_offload_notify(struct dsa_switchdev_event_work *switchdev_work)
struct switchdev_notifier_fdb_info fdb_info; {
struct net_device *dev; struct dsa_switch *ds = switchdev_work->ds;
unsigned long event; struct switchdev_notifier_fdb_info info;
}; struct dsa_port *dp;
if (!dsa_is_user_port(ds, switchdev_work->port))
return;
info.addr = switchdev_work->addr;
info.vid = switchdev_work->vid;
info.offloaded = true;
dp = dsa_to_port(ds, switchdev_work->port);
call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
dp->slave, &info.info, NULL);
}
static void dsa_slave_switchdev_event_work(struct work_struct *work) static void dsa_slave_switchdev_event_work(struct work_struct *work)
{ {
struct dsa_switchdev_event_work *switchdev_work = struct dsa_switchdev_event_work *switchdev_work =
container_of(work, struct dsa_switchdev_event_work, work); container_of(work, struct dsa_switchdev_event_work, work);
struct net_device *dev = switchdev_work->dev; struct dsa_switch *ds = switchdev_work->ds;
struct switchdev_notifier_fdb_info *fdb_info; struct dsa_port *dp;
struct dsa_port *dp = dsa_slave_to_port(dev);
int err; int err;
dp = dsa_to_port(ds, switchdev_work->port);
rtnl_lock(); rtnl_lock();
switch (switchdev_work->event) { switch (switchdev_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_ADD_TO_DEVICE:
fdb_info = &switchdev_work->fdb_info; err = dsa_port_fdb_add(dp, switchdev_work->addr,
if (!fdb_info->added_by_user) switchdev_work->vid);
break;
err = dsa_port_fdb_add(dp, fdb_info->addr, fdb_info->vid);
if (err) { if (err) {
netdev_dbg(dev, "fdb add failed err=%d\n", 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);
break; break;
} }
fdb_info->offloaded = true; dsa_fdb_offload_notify(switchdev_work);
call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev,
&fdb_info->info, NULL);
break; break;
case SWITCHDEV_FDB_DEL_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE:
fdb_info = &switchdev_work->fdb_info; err = dsa_port_fdb_del(dp, switchdev_work->addr,
if (!fdb_info->added_by_user) switchdev_work->vid);
break;
err = dsa_port_fdb_del(dp, fdb_info->addr, fdb_info->vid);
if (err) { if (err) {
netdev_dbg(dev, "fdb del failed err=%d\n", err); dev_err(ds->dev,
dev_close(dev); "port %d failed to delete %pM vid %d from fdb: %d\n",
dp->index, switchdev_work->addr,
switchdev_work->vid, err);
} }
break; break;
} }
rtnl_unlock(); rtnl_unlock();
kfree(switchdev_work->fdb_info.addr);
kfree(switchdev_work); kfree(switchdev_work);
dev_put(dev); if (dsa_is_user_port(ds, dp->index))
dev_put(dp->slave);
} }
static int static int dsa_lower_dev_walk(struct net_device *lower_dev,
dsa_slave_switchdev_fdb_work_init(struct dsa_switchdev_event_work * struct netdev_nested_priv *priv)
switchdev_work, {
const struct switchdev_notifier_fdb_info * if (dsa_slave_dev_check(lower_dev)) {
fdb_info) priv->data = (void *)netdev_priv(lower_dev);
{ return 1;
memcpy(&switchdev_work->fdb_info, fdb_info, }
sizeof(switchdev_work->fdb_info));
switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
if (!switchdev_work->fdb_info.addr)
return -ENOMEM;
ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
fdb_info->addr);
return 0; return 0;
} }
static struct dsa_slave_priv *dsa_slave_dev_lower_find(struct net_device *dev)
{
struct netdev_nested_priv priv = {
.data = NULL,
};
netdev_walk_all_lower_dev_rcu(dev, dsa_lower_dev_walk, &priv);
return (struct dsa_slave_priv *)priv.data;
}
/* Called under rcu_read_lock() */ /* Called under rcu_read_lock() */
static int dsa_slave_switchdev_event(struct notifier_block *unused, static int dsa_slave_switchdev_event(struct notifier_block *unused,
unsigned long event, void *ptr) unsigned long event, void *ptr)
{ {
struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
const struct switchdev_notifier_fdb_info *fdb_info;
struct dsa_switchdev_event_work *switchdev_work; struct dsa_switchdev_event_work *switchdev_work;
struct dsa_port *dp;
int err; int err;
if (event == SWITCHDEV_PORT_ATTR_SET) { switch (event) {
case SWITCHDEV_PORT_ATTR_SET:
err = switchdev_handle_port_attr_set(dev, ptr, err = switchdev_handle_port_attr_set(dev, ptr,
dsa_slave_dev_check, dsa_slave_dev_check,
dsa_slave_port_attr_set); dsa_slave_port_attr_set);
return notifier_from_errno(err); return notifier_from_errno(err);
} case SWITCHDEV_FDB_ADD_TO_DEVICE:
case SWITCHDEV_FDB_DEL_TO_DEVICE:
fdb_info = ptr;
if (!dsa_slave_dev_check(dev)) if (dsa_slave_dev_check(dev)) {
return NOTIFY_DONE; if (!fdb_info->added_by_user)
return NOTIFY_OK;
switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC); dp = dsa_slave_to_port(dev);
if (!switchdev_work) } else {
return NOTIFY_BAD; /* Snoop addresses learnt on foreign interfaces
* bridged with us, for switches that don't
* automatically learn SA from CPU-injected traffic
*/
struct net_device *br_dev;
struct dsa_slave_priv *p;
INIT_WORK(&switchdev_work->work, br_dev = netdev_master_upper_dev_get_rcu(dev);
dsa_slave_switchdev_event_work); if (!br_dev)
switchdev_work->dev = dev; return NOTIFY_DONE;
switchdev_work->event = event;
switch (event) { if (!netif_is_bridge_master(br_dev))
case SWITCHDEV_FDB_ADD_TO_DEVICE: return NOTIFY_DONE;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (dsa_slave_switchdev_fdb_work_init(switchdev_work, ptr)) p = dsa_slave_dev_lower_find(br_dev);
goto err_fdb_work_init; if (!p)
dev_hold(dev); return NOTIFY_DONE;
dp = p->dp->cpu_dp;
if (!dp->ds->assisted_learning_on_cpu_port)
return NOTIFY_DONE;
}
if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del)
return NOTIFY_DONE;
switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
if (!switchdev_work)
return NOTIFY_BAD;
INIT_WORK(&switchdev_work->work,
dsa_slave_switchdev_event_work);
switchdev_work->ds = dp->ds;
switchdev_work->port = dp->index;
switchdev_work->event = event;
ether_addr_copy(switchdev_work->addr,
fdb_info->addr);
switchdev_work->vid = fdb_info->vid;
/* Hold a reference on the slave for dsa_fdb_offload_notify */
if (dsa_is_user_port(dp->ds, dp->index))
dev_hold(dev);
dsa_schedule_work(&switchdev_work->work);
break; break;
default: default:
kfree(switchdev_work);
return NOTIFY_DONE; return NOTIFY_DONE;
} }
dsa_schedule_work(&switchdev_work->work);
return NOTIFY_OK; return NOTIFY_OK;
err_fdb_work_init:
kfree(switchdev_work);
return NOTIFY_BAD;
} }
static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused, static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment