Commit c214cc3a authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

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: default avatarJakub Kicinski <kuba@kernel.org>
parents dd15c4a0 c54913c1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -629,6 +629,7 @@ static int felix_setup(struct dsa_switch *ds)

	ds->mtu_enforcement_ingress = true;
	ds->configure_vlan_while_not_filtering = true;
	ds->assisted_learning_on_cpu_port = true;

	return 0;
}
+5 −0
Original line number Diff line number Diff line
@@ -319,6 +319,11 @@ struct dsa_switch {
	 */
	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
	 * should be retrieved from here and not from the per-port settings.
	 */
+1 −0
Original line number Diff line number Diff line
@@ -602,6 +602,7 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
			/* fastpath: update of existing entry */
			if (unlikely(source != fdb->dst &&
				     !test_bit(BR_FDB_STICKY, &fdb->flags))) {
				br_switchdev_fdb_notify(fdb, RTM_DELNEIGH);
				fdb->dst = source;
				fdb_modified = true;
				/* Take over HW learned entry */
+12 −0
Original line number Diff line number Diff line
@@ -73,6 +73,18 @@ struct dsa_notifier_mtu_info {
	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 {
	/* Copy of CPU port xmit for faster access in slave transmit hot path */
	struct sk_buff *	(*xmit)(struct sk_buff *skb,
+111 −63
Original line number Diff line number Diff line
@@ -2047,90 +2047,141 @@ static int dsa_slave_netdevice_event(struct notifier_block *nb,
	return NOTIFY_DONE;
}

struct dsa_switchdev_event_work {
	struct work_struct work;
	struct switchdev_notifier_fdb_info fdb_info;
	struct net_device *dev;
	unsigned long event;
};
static void
dsa_fdb_offload_notify(struct dsa_switchdev_event_work *switchdev_work)
{
	struct dsa_switch *ds = switchdev_work->ds;
	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)
{
	struct dsa_switchdev_event_work *switchdev_work =
		container_of(work, struct dsa_switchdev_event_work, work);
	struct net_device *dev = switchdev_work->dev;
	struct switchdev_notifier_fdb_info *fdb_info;
	struct dsa_port *dp = dsa_slave_to_port(dev);
	struct dsa_switch *ds = switchdev_work->ds;
	struct dsa_port *dp;
	int err;

	dp = dsa_to_port(ds, switchdev_work->port);

	rtnl_lock();
	switch (switchdev_work->event) {
	case SWITCHDEV_FDB_ADD_TO_DEVICE:
		fdb_info = &switchdev_work->fdb_info;
		if (!fdb_info->added_by_user)
			break;

		err = dsa_port_fdb_add(dp, fdb_info->addr, fdb_info->vid);
		err = dsa_port_fdb_add(dp, switchdev_work->addr,
				       switchdev_work->vid);
		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;
		}
		fdb_info->offloaded = true;
		call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev,
					 &fdb_info->info, NULL);
		dsa_fdb_offload_notify(switchdev_work);
		break;

	case SWITCHDEV_FDB_DEL_TO_DEVICE:
		fdb_info = &switchdev_work->fdb_info;
		if (!fdb_info->added_by_user)
			break;

		err = dsa_port_fdb_del(dp, fdb_info->addr, fdb_info->vid);
		err = dsa_port_fdb_del(dp, switchdev_work->addr,
				       switchdev_work->vid);
		if (err) {
			netdev_dbg(dev, "fdb del failed err=%d\n", err);
			dev_close(dev);
			dev_err(ds->dev,
				"port %d failed to delete %pM vid %d from fdb: %d\n",
				dp->index, switchdev_work->addr,
				switchdev_work->vid, err);
		}

		break;
	}
	rtnl_unlock();

	kfree(switchdev_work->fdb_info.addr);
	kfree(switchdev_work);
	dev_put(dev);
	if (dsa_is_user_port(ds, dp->index))
		dev_put(dp->slave);
}

static int dsa_lower_dev_walk(struct net_device *lower_dev,
			      struct netdev_nested_priv *priv)
{
	if (dsa_slave_dev_check(lower_dev)) {
		priv->data = (void *)netdev_priv(lower_dev);
		return 1;
	}

static int
dsa_slave_switchdev_fdb_work_init(struct dsa_switchdev_event_work *
				  switchdev_work,
				  const struct switchdev_notifier_fdb_info *
				  fdb_info)
{
	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;
}

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() */
static int dsa_slave_switchdev_event(struct notifier_block *unused,
				     unsigned long event, void *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_port *dp;
	int err;

	if (event == SWITCHDEV_PORT_ATTR_SET) {
	switch (event) {
	case SWITCHDEV_PORT_ATTR_SET:
		err = switchdev_handle_port_attr_set(dev, ptr,
						     dsa_slave_dev_check,
						     dsa_slave_port_attr_set);
		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 (!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 (!dsa_slave_dev_check(dev))
		if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del)
			return NOTIFY_DONE;

		switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
@@ -2139,27 +2190,24 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused,

		INIT_WORK(&switchdev_work->work,
			  dsa_slave_switchdev_event_work);
	switchdev_work->dev = dev;
		switchdev_work->ds = dp->ds;
		switchdev_work->port = dp->index;
		switchdev_work->event = event;

	switch (event) {
	case SWITCHDEV_FDB_ADD_TO_DEVICE:
	case SWITCHDEV_FDB_DEL_TO_DEVICE:
		if (dsa_slave_switchdev_fdb_work_init(switchdev_work, ptr))
			goto err_fdb_work_init;
		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;
	default:
		kfree(switchdev_work);
		return NOTIFY_DONE;
	}

	dsa_schedule_work(&switchdev_work->work);
	return NOTIFY_OK;

err_fdb_work_init:
	kfree(switchdev_work);
	return NOTIFY_BAD;
}

static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,