mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-03 19:55:31 +00:00
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.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
c214cc3aa8
@ -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 {
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
/* 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 {
|
|||||||
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,
|
||||||
|
176
net/dsa/slave.c
176
net/dsa/slave.c
@ -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 *
|
|
||||||
fdb_info)
|
|
||||||
{
|
{
|
||||||
memcpy(&switchdev_work->fdb_info, fdb_info,
|
if (dsa_slave_dev_check(lower_dev)) {
|
||||||
sizeof(switchdev_work->fdb_info));
|
priv->data = (void *)netdev_priv(lower_dev);
|
||||||
switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
|
return 1;
|
||||||
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);
|
||||||
}
|
|
||||||
|
|
||||||
if (!dsa_slave_dev_check(dev))
|
|
||||||
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->dev = dev;
|
|
||||||
switchdev_work->event = event;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case SWITCHDEV_FDB_ADD_TO_DEVICE:
|
case SWITCHDEV_FDB_ADD_TO_DEVICE:
|
||||||
case SWITCHDEV_FDB_DEL_TO_DEVICE:
|
case SWITCHDEV_FDB_DEL_TO_DEVICE:
|
||||||
if (dsa_slave_switchdev_fdb_work_init(switchdev_work, ptr))
|
fdb_info = ptr;
|
||||||
goto err_fdb_work_init;
|
|
||||||
dev_hold(dev);
|
if (dsa_slave_dev_check(dev)) {
|
||||||
|
if (!fdb_info->added_by_user)
|
||||||
|
return NOTIFY_OK;
|
||||||
|
|
||||||
|
dp = dsa_slave_to_port(dev);
|
||||||
|
} else {
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
br_dev = netdev_master_upper_dev_get_rcu(dev);
|
||||||
|
if (!br_dev)
|
||||||
|
return NOTIFY_DONE;
|
||||||
|
|
||||||
|
if (!netif_is_bridge_master(br_dev))
|
||||||
|
return NOTIFY_DONE;
|
||||||
|
|
||||||
|
p = dsa_slave_dev_lower_find(br_dev);
|
||||||
|
if (!p)
|
||||||
|
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,
|
||||||
|
Loading…
Reference in New Issue
Block a user