All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 net-next 0/4] net: dsa: Link aggregation support
@ 2020-11-30 14:06 Tobias Waldekranz
  2020-11-30 14:06 ` [PATCH v2 net-next 1/4] net: bonding: Notify ports about their initial state Tobias Waldekranz
                   ` (3 more replies)
  0 siblings, 4 replies; 17+ messages in thread
From: Tobias Waldekranz @ 2020-11-30 14:06 UTC (permalink / raw)
  To: davem, kuba
  Cc: andrew, vivien.didelot, f.fainelli, olteanv, j.vosburgh, vfalico,
	andy, netdev

Start of by adding an extra notification when adding a port to a bond,
this allows static LAGs to be offloaded using the bonding driver.

Then add the generic support required to offload link aggregates to
drivers built on top of the DSA subsystem.

Finally, implement offloading for the mv88e6xxx driver, i.e. Marvell's
LinkStreet family.

Supported LAG implementations:
- Bonding
- Team

Supported modes:
- Isolated. The LAG may be used as a regular interface outside of any
  bridge.
- Bridged. The LAG may be added to a bridge, in which case switching
  is offloaded between the LAG and any other switch ports. I.e. the
  LAG behaves just like a port from this perspective.

In bridged mode, the following is supported:
- STP filtering.
- VLAN filtering.
- Multicast filtering. The bridge correctly snoops IGMP and configures
  the proper groups if snooping is enabled. Static groups can also be
  configured. MLD seems to work, but has not been extensively tested.
- Unicast filtering. Automatic learning works. Static entries are
  _not_ supported. This will be added in a later series as it requires
  some more general refactoring in mv88e6xxx before I can test it.

v1 -> v2:
- Allocate LAGs from a static pool to avoid late errors under memory
  pressure, as suggested by Andrew.

RFC -> v1:
- Properly propagate MDB operations.
- Support for bonding in addition to team.
- Fixed a locking bug in mv88e6xxx.
- Make sure ports are disabled-by-default in mv88e6xxx.
- Support for both DSA and EDSA tagging.

Tobias Waldekranz (4):
  net: bonding: Notify ports about their initial state
  net: dsa: Link aggregation support
  net: dsa: mv88e6xxx: Link aggregation support
  net: dsa: tag_dsa: Support reception of packets from LAG devices

 drivers/net/bonding/bond_main.c     |   2 +
 drivers/net/dsa/mv88e6xxx/chip.c    | 234 +++++++++++++++++++++++++++-
 drivers/net/dsa/mv88e6xxx/chip.h    |   4 +
 drivers/net/dsa/mv88e6xxx/global2.c |   8 +-
 drivers/net/dsa/mv88e6xxx/global2.h |   5 +
 drivers/net/dsa/mv88e6xxx/port.c    |  21 +++
 drivers/net/dsa/mv88e6xxx/port.h    |   5 +
 include/net/dsa.h                   |  97 ++++++++++++
 net/dsa/dsa.c                       |  12 +-
 net/dsa/dsa2.c                      |  51 ++++++
 net/dsa/dsa_priv.h                  |  31 ++++
 net/dsa/port.c                      | 141 +++++++++++++++++
 net/dsa/slave.c                     |  83 +++++++++-
 net/dsa/switch.c                    |  49 ++++++
 net/dsa/tag_dsa.c                   |  17 +-
 15 files changed, 744 insertions(+), 16 deletions(-)

-- 
2.17.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 net-next 1/4] net: bonding: Notify ports about their initial state
  2020-11-30 14:06 [PATCH v2 net-next 0/4] net: dsa: Link aggregation support Tobias Waldekranz
@ 2020-11-30 14:06 ` Tobias Waldekranz
  2020-11-30 14:06 ` [PATCH v2 net-next 2/4] net: dsa: Link aggregation support Tobias Waldekranz
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 17+ messages in thread
From: Tobias Waldekranz @ 2020-11-30 14:06 UTC (permalink / raw)
  To: davem, kuba
  Cc: andrew, vivien.didelot, f.fainelli, olteanv, j.vosburgh, vfalico,
	andy, netdev

When creating a static bond (e.g. balance-xor), all ports will always
be enabled. This is set, and the corresponding notification is sent
out, before the port is linked to the bond upper.

In the offloaded case, this ordering is hard to deal with.

The lower will first see a notification that it can not associate with
any bond. Then the bond is joined. After that point no more
notifications are sent, so all ports remain disabled.

This change simply sends an extra notification once the port has been
linked to the upper to synchronize the initial state.

Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
 drivers/net/bonding/bond_main.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
index e0880a3840d7..d6e1f9cf28d5 100644
--- a/drivers/net/bonding/bond_main.c
+++ b/drivers/net/bonding/bond_main.c
@@ -1922,6 +1922,8 @@ int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev,
 		goto err_unregister;
 	}
 
+	bond_lower_state_changed(new_slave);
+
 	res = bond_sysfs_slave_add(new_slave);
 	if (res) {
 		slave_dbg(bond_dev, slave_dev, "Error %d calling bond_sysfs_slave_add\n", res);
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-11-30 14:06 [PATCH v2 net-next 0/4] net: dsa: Link aggregation support Tobias Waldekranz
  2020-11-30 14:06 ` [PATCH v2 net-next 1/4] net: bonding: Notify ports about their initial state Tobias Waldekranz
@ 2020-11-30 14:06 ` Tobias Waldekranz
  2020-12-01  1:37   ` Vladimir Oltean
  2020-12-01 14:03   ` Vladimir Oltean
  2020-11-30 14:06 ` [PATCH v2 net-next 3/4] net: dsa: mv88e6xxx: " Tobias Waldekranz
  2020-11-30 14:06 ` [PATCH v2 net-next 4/4] net: dsa: tag_dsa: Support reception of packets from LAG devices Tobias Waldekranz
  3 siblings, 2 replies; 17+ messages in thread
From: Tobias Waldekranz @ 2020-11-30 14:06 UTC (permalink / raw)
  To: davem, kuba
  Cc: andrew, vivien.didelot, f.fainelli, olteanv, j.vosburgh, vfalico,
	andy, netdev

Monitor the following events and notify the driver when:

- A DSA port joins/leaves a LAG.
- A LAG, made up of DSA ports, joins/leaves a bridge.
- A DSA port in a LAG is enabled/disabled (enabled meaning
  "distributing" in 802.3ad LACP terms).

Each LAG interface to which a DSA port is attached is represented by a
`struct dsa_lag` which is globally reachable from the switch tree and
from each associated port.

When a LAG joins a bridge, the DSA subsystem will treat that as each
individual port joining the bridge. The driver may look at the port's
LAG pointer to see if it is associated with any LAG, if that is
required. This is analogue to how switchdev events are replicated out
to all lower devices when reaching e.g. a LAG.

Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
 include/net/dsa.h  |  97 +++++++++++++++++++++++++++++++
 net/dsa/dsa2.c     |  51 ++++++++++++++++
 net/dsa/dsa_priv.h |  31 ++++++++++
 net/dsa/port.c     | 141 +++++++++++++++++++++++++++++++++++++++++++++
 net/dsa/slave.c    |  83 ++++++++++++++++++++++++--
 net/dsa/switch.c   |  49 ++++++++++++++++
 6 files changed, 446 insertions(+), 6 deletions(-)

diff --git a/include/net/dsa.h b/include/net/dsa.h
index 4e60d2610f20..efd7d2bca806 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -7,6 +7,7 @@
 #ifndef __LINUX_NET_DSA_H
 #define __LINUX_NET_DSA_H
 
+#include <linux/bitmap.h>
 #include <linux/if.h>
 #include <linux/if_ether.h>
 #include <linux/list.h>
@@ -71,6 +72,7 @@ enum dsa_tag_protocol {
 
 struct packet_type;
 struct dsa_switch;
+struct dsa_lag;
 
 struct dsa_device_ops {
 	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
@@ -149,6 +151,13 @@ struct dsa_switch_tree {
 
 	/* List of DSA links composing the routing table */
 	struct list_head rtable;
+
+	/* Link aggregates */
+	struct {
+		struct dsa_lag *pool;
+		unsigned long *busy;
+		unsigned int num;
+	} lags;
 };
 
 /* TC matchall action types */
@@ -180,6 +189,69 @@ struct dsa_mall_tc_entry {
 	};
 };
 
+struct dsa_lag {
+	struct net_device __rcu *dev;
+	int id;
+
+	struct list_head ports;
+
+	/* For multichip systems, we must ensure that each hash bucket
+	 * is only enabled on a single egress port throughout the
+	 * whole tree, lest we send duplicates. Therefore we must
+	 * maintain a global list of active tx ports, so that each
+	 * switch can figure out which buckets to enable on which
+	 * ports.
+	 */
+	struct list_head tx_ports;
+	int num_tx;
+
+	struct kref refcount;
+};
+
+#define dsa_lag_foreach(_id, _dst) \
+	for_each_set_bit(_id, (_dst)->lags.busy, (_dst)->lags.num)
+
+static inline bool dsa_lag_offloading(struct dsa_switch_tree *dst)
+{
+	return dst->lags.num > 0;
+}
+
+static inline bool dsa_lag_available(struct dsa_switch_tree *dst)
+{
+	return !bitmap_full(dst->lags.busy, dst->lags.num);
+}
+
+static inline struct dsa_lag *dsa_lag_by_id(struct dsa_switch_tree *dst, int id)
+{
+	if (!test_bit(id, dst->lags.busy))
+		return NULL;
+
+	return &dst->lags.pool[id];
+}
+
+static inline struct net_device *dsa_lag_dev_by_id(struct dsa_switch_tree *dst,
+						   int id)
+{
+	struct dsa_lag *lag = dsa_lag_by_id(dst, id);
+
+	return lag ? rcu_dereference(lag->dev) : NULL;
+}
+
+static inline struct dsa_lag *dsa_lag_by_dev(struct dsa_switch_tree *dst,
+					     struct net_device *dev)
+{
+	struct dsa_lag *lag;
+	int id;
+
+	dsa_lag_foreach(id, dst) {
+		lag = dsa_lag_by_id(dst, id);
+
+		if (rtnl_dereference(lag->dev) == dev)
+			return lag;
+	}
+
+	return NULL;
+}
 
 struct dsa_port {
 	/* A CPU port is physically connected to a master device.
@@ -220,6 +292,9 @@ struct dsa_port {
 	bool			devlink_port_setup;
 	struct phylink		*pl;
 	struct phylink_config	pl_config;
+	struct dsa_lag		*lag;
+	struct list_head	lag_list;
+	struct list_head	lag_tx_list;
 
 	struct list_head list;
 
@@ -335,6 +410,11 @@ struct dsa_switch {
 	 */
 	bool			mtu_enforcement_ingress;
 
+	/* The maximum number of LAGs that can be configured. A value of zero
+	 * is used to indicate that LAG offloading is not supported.
+	 */
+	unsigned int		num_lags;
+
 	size_t num_ports;
 };
 
@@ -624,6 +704,13 @@ struct dsa_switch_ops {
 	void	(*crosschip_bridge_leave)(struct dsa_switch *ds, int tree_index,
 					  int sw_index, int port,
 					  struct net_device *br);
+	int	(*crosschip_lag_change)(struct dsa_switch *ds, int sw_index,
+					int port, struct net_device *lag_dev,
+					struct netdev_lag_lower_state_info *info);
+	int	(*crosschip_lag_join)(struct dsa_switch *ds, int sw_index,
+				      int port, struct net_device *lag_dev);
+	void	(*crosschip_lag_leave)(struct dsa_switch *ds, int sw_index,
+				       int port, struct net_device *lag_dev);
 
 	/*
 	 * PTP functionality
@@ -655,6 +742,16 @@ struct dsa_switch_ops {
 	int	(*port_change_mtu)(struct dsa_switch *ds, int port,
 				   int new_mtu);
 	int	(*port_max_mtu)(struct dsa_switch *ds, int port);
+
+	/*
+	 * LAG integration
+	 */
+	int	(*port_lag_change)(struct dsa_switch *ds, int port,
+				   struct netdev_lag_lower_state_info *info);
+	int	(*port_lag_join)(struct dsa_switch *ds, int port,
+				 struct net_device *lag_dev);
+	void	(*port_lag_leave)(struct dsa_switch *ds, int port,
+				  struct net_device *lag_dev);
 };
 
 #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes)		\
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index 183003e45762..786277a21955 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -578,6 +578,47 @@ static void dsa_tree_teardown_master(struct dsa_switch_tree *dst)
 			dsa_master_teardown(dp->master);
 }
 
+static int dsa_tree_setup_lags(struct dsa_switch_tree *dst)
+{
+	struct dsa_port *dp;
+	unsigned int num;
+
+	list_for_each_entry(dp, &dst->ports, list)
+		num = dp->ds->num_lags;
+
+	list_for_each_entry(dp, &dst->ports, list)
+		num = min(num, dp->ds->num_lags);
+
+	if (num == 0)
+		return 0;
+
+	dst->lags.pool = kcalloc(num, sizeof(*dst->lags.pool), GFP_KERNEL);
+	if (!dst->lags.pool)
+		goto err;
+
+	dst->lags.busy = bitmap_zalloc(num, GFP_KERNEL);
+	if (!dst->lags.busy)
+		goto err_free_pool;
+
+	dst->lags.num = num;
+	return 0;
+
+err_free_pool:
+	kfree(dst->lags.pool);
+err:
+	return -ENOMEM;
+}
+
+static void dsa_tree_teardown_lags(struct dsa_switch_tree *dst)
+{
+	if (dst->lags.num == 0)
+		return;
+
+	kfree(dst->lags.busy);
+	kfree(dst->lags.pool);
+	dst->lags.num = 0;
+}
+
 static int dsa_tree_setup(struct dsa_switch_tree *dst)
 {
 	bool complete;
@@ -605,12 +646,18 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)
 	if (err)
 		goto teardown_switches;
 
+	err = dsa_tree_setup_lags(dst);
+	if (err)
+		goto teardown_master;
+
 	dst->setup = true;
 
 	pr_info("DSA: tree %d setup\n", dst->index);
 
 	return 0;
 
+teardown_master:
+	dsa_tree_teardown_master(dst);
 teardown_switches:
 	dsa_tree_teardown_switches(dst);
 teardown_default_cpu:
@@ -626,6 +673,8 @@ static void dsa_tree_teardown(struct dsa_switch_tree *dst)
 	if (!dst->setup)
 		return;
 
+	dsa_tree_teardown_lags(dst);
+
 	dsa_tree_teardown_master(dst);
 
 	dsa_tree_teardown_switches(dst);
@@ -659,6 +708,8 @@ static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
 	dp->index = index;
 
 	INIT_LIST_HEAD(&dp->list);
+	INIT_LIST_HEAD(&dp->lag_list);
+	INIT_LIST_HEAD(&dp->lag_tx_list);
 	list_add_tail(&dp->list, &dst->ports);
 
 	return dp;
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index 7c96aae9062c..77e07a0cff29 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -20,6 +20,9 @@ enum {
 	DSA_NOTIFIER_BRIDGE_LEAVE,
 	DSA_NOTIFIER_FDB_ADD,
 	DSA_NOTIFIER_FDB_DEL,
+	DSA_NOTIFIER_LAG_CHANGE,
+	DSA_NOTIFIER_LAG_JOIN,
+	DSA_NOTIFIER_LAG_LEAVE,
 	DSA_NOTIFIER_MDB_ADD,
 	DSA_NOTIFIER_MDB_DEL,
 	DSA_NOTIFIER_VLAN_ADD,
@@ -57,6 +60,14 @@ struct dsa_notifier_mdb_info {
 	int port;
 };
 
+/* DSA_NOTIFIER_LAG_* */
+struct dsa_notifier_lag_info {
+	struct netdev_lag_lower_state_info *info;
+	struct net_device *lag;
+	int sw_index;
+	int port;
+};
+
 /* DSA_NOTIFIER_VLAN_* */
 struct dsa_notifier_vlan_info {
 	const struct switchdev_obj_port_vlan *vlan;
@@ -135,6 +146,10 @@ void dsa_port_disable_rt(struct dsa_port *dp);
 void dsa_port_disable(struct dsa_port *dp);
 int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br);
 void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br);
+int dsa_port_lag_change(struct dsa_port *dp,
+			struct netdev_lag_lower_state_info *linfo);
+int dsa_port_lag_join(struct dsa_port *dp, struct net_device *lag_dev);
+void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag_dev);
 int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
 			    struct switchdev_trans *trans);
 bool dsa_port_skip_vlan_configuration(struct dsa_port *dp);
@@ -167,6 +182,22 @@ int dsa_port_link_register_of(struct dsa_port *dp);
 void dsa_port_link_unregister_of(struct dsa_port *dp);
 extern const struct phylink_mac_ops dsa_port_phylink_mac_ops;
 
+static inline bool dsa_port_can_offload(struct dsa_port *dp,
+					struct net_device *dev)
+{
+	/* Switchdev offloading can be configured on: */
+
+	if (dev == dp->slave)
+		/* DSA ports directly connected to a bridge. */
+		return true;
+
+	if (dp->lag && dev == rtnl_dereference(dp->lag->dev))
+		/* DSA ports connected to a bridge via a LAG */
+		return true;
+
+	return false;
+}
+
 /* slave.c */
 extern const struct dsa_device_ops notag_netdev_ops;
 void dsa_slave_mii_bus_init(struct dsa_switch *ds);
diff --git a/net/dsa/port.c b/net/dsa/port.c
index 73569c9af3cc..c2332ee5f5c7 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -193,6 +193,147 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
 	dsa_port_set_state_now(dp, BR_STATE_FORWARDING);
 }
 
+static struct dsa_lag *dsa_lag_get(struct dsa_switch_tree *dst,
+				   struct net_device *dev)
+{
+	struct dsa_lag *lag;
+	int id;
+
+	lag = dsa_lag_by_dev(dst, dev);
+	if (lag) {
+		kref_get(&lag->refcount);
+		return lag;
+	}
+
+	id = find_first_zero_bit(dst->lags.busy, dst->lags.num);
+	if (id >= dst->lags.num) {
+		WARN(1, "No LAGs available");
+		return NULL;
+	}
+
+	set_bit(id, dst->lags.busy);
+
+	lag = &dst->lags.pool[id];
+	kref_init(&lag->refcount);
+	lag->id = id;
+	INIT_LIST_HEAD(&lag->ports);
+	INIT_LIST_HEAD(&lag->tx_ports);
+
+	rcu_assign_pointer(lag->dev, dev);
+	return lag;
+}
+
+static void dsa_lag_release(struct kref *refcount)
+{
+	struct dsa_lag *lag = container_of(refcount, struct dsa_lag, refcount);
+
+	rcu_assign_pointer(lag->dev, NULL);
+	synchronize_rcu();
+	memset(lag, 0, sizeof(*lag));
+}
+
+static void dsa_lag_put(struct dsa_switch_tree *dst, struct dsa_lag *lag)
+{
+	int id = lag->id;
+
+	if (kref_put(&lag->refcount, dsa_lag_release))
+		clear_bit(id, dst->lags.busy);
+}
+
+int dsa_port_lag_change(struct dsa_port *dp,
+			struct netdev_lag_lower_state_info *linfo)
+{
+	struct dsa_notifier_lag_info info = {
+		.sw_index = dp->ds->index,
+		.port = dp->index,
+		.info = linfo,
+	};
+	bool old, new;
+
+	if (!dp->lag)
+		return 0;
+
+	info.lag = rtnl_dereference(dp->lag->dev);
+
+	/* If this port is on the tx list, it is already enabled. */
+	old = !list_empty(&dp->lag_tx_list);
+
+	/* On statically configured aggregates (e.g. loadbalance
+	 * without LACP) ports will always be tx_enabled, even if the
+	 * link is down. Thus we require both link_up and tx_enabled
+	 * in order to include it in the tx set.
+	 */
+	new = linfo->link_up && linfo->tx_enabled;
+
+	if (new == old)
+		return 0;
+
+	if (new) {
+		dp->lag->num_tx++;
+		list_add_tail(&dp->lag_tx_list, &dp->lag->tx_ports);
+	} else {
+		list_del_init(&dp->lag_tx_list);
+		dp->lag->num_tx--;
+	}
+
+	return dsa_port_notify(dp, DSA_NOTIFIER_LAG_CHANGE, &info);
+}
+
+int dsa_port_lag_join(struct dsa_port *dp, struct net_device *lag_dev)
+{
+	struct dsa_notifier_lag_info info = {
+		.sw_index = dp->ds->index,
+		.port = dp->index,
+		.lag = lag_dev,
+	};
+	struct dsa_lag *lag;
+	int err;
+
+	lag = dsa_lag_get(dp->ds->dst, lag_dev);
+	if (!lag)
+		return -ENODEV;
+
+	dp->lag = lag;
+	list_add_tail(&dp->lag_list, &lag->ports);
+
+	err = dsa_port_notify(dp, DSA_NOTIFIER_LAG_JOIN, &info);
+	if (err) {
+		dp->lag = NULL;
+		list_del_init(&dp->lag_list);
+		dsa_lag_put(dp->ds->dst, lag);
+	}
+
+	return err;
+}
+
+void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag_dev)
+{
+	struct dsa_notifier_lag_info info = {
+		.sw_index = dp->ds->index,
+		.port = dp->index,
+		.lag = lag_dev,
+	};
+	int err;
+
+	/* Port might have been part of a LAG that in turn was
+	 * attached to a bridge.
+	 */
+	if (dp->bridge_dev)
+		dsa_port_bridge_leave(dp, dp->bridge_dev);
+
+	list_del_init(&dp->lag_list);
+	list_del_init(&dp->lag_tx_list);
+
+	err = dsa_port_notify(dp, DSA_NOTIFIER_LAG_LEAVE, &info);
+	if (err)
+		pr_err("DSA: failed to notify DSA_NOTIFIER_LAG_LEAVE: %d\n",
+		       err);
+
+	dsa_lag_put(dp->ds->dst, dp->lag);
+
+	dp->lag = NULL;
+}
+
 /* Must be called under rcu_read_lock() */
 static bool dsa_port_can_apply_vlan_filtering(struct dsa_port *dp,
 					      bool vlan_filtering)
diff --git a/net/dsa/slave.c b/net/dsa/slave.c
index 7efc753e4d9d..6d7878cc7f3d 100644
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -334,7 +334,7 @@ static int dsa_slave_vlan_add(struct net_device *dev,
 	struct switchdev_obj_port_vlan vlan;
 	int vid, err;
 
-	if (obj->orig_dev != dev)
+	if (!dsa_port_can_offload(dp, obj->orig_dev))
 		return -EOPNOTSUPP;
 
 	if (dsa_port_skip_vlan_configuration(dp))
@@ -391,7 +391,7 @@ static int dsa_slave_port_obj_add(struct net_device *dev,
 
 	switch (obj->id) {
 	case SWITCHDEV_OBJ_ID_PORT_MDB:
-		if (obj->orig_dev != dev)
+		if (!dsa_port_can_offload(dp, obj->orig_dev))
 			return -EOPNOTSUPP;
 		err = dsa_port_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj), trans);
 		break;
@@ -421,7 +421,7 @@ static int dsa_slave_vlan_del(struct net_device *dev,
 	struct switchdev_obj_port_vlan *vlan;
 	int vid, err;
 
-	if (obj->orig_dev != dev)
+	if (!dsa_port_can_offload(dp, obj->orig_dev))
 		return -EOPNOTSUPP;
 
 	if (dsa_port_skip_vlan_configuration(dp))
@@ -450,7 +450,7 @@ static int dsa_slave_port_obj_del(struct net_device *dev,
 
 	switch (obj->id) {
 	case SWITCHDEV_OBJ_ID_PORT_MDB:
-		if (obj->orig_dev != dev)
+		if (!dsa_port_can_offload(dp, obj->orig_dev))
 			return -EOPNOTSUPP;
 		err = dsa_port_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
 		break;
@@ -1941,6 +1941,40 @@ static int dsa_slave_changeupper(struct net_device *dev,
 			dsa_port_bridge_leave(dp, info->upper_dev);
 			err = NOTIFY_OK;
 		}
+	} else if (netif_is_lag_master(info->upper_dev) &&
+		   dsa_lag_offloading(dp->ds->dst)) {
+		if (info->linking) {
+			err = dsa_port_lag_join(dp, info->upper_dev);
+			err = notifier_from_errno(err);
+		} else {
+			dsa_port_lag_leave(dp, info->upper_dev);
+			err = NOTIFY_OK;
+		}
+	}
+
+	return err;
+}
+
+static int
+dsa_slave_lag_changeupper(struct net_device *dev,
+			  struct netdev_notifier_changeupper_info *info)
+{
+	struct net_device *lower;
+	struct list_head *iter;
+	int err = NOTIFY_DONE;
+	struct dsa_port *dp;
+
+	netdev_for_each_lower_dev(dev, lower, iter) {
+		if (!dsa_slave_dev_check(lower))
+			continue;
+
+		dp = dsa_slave_to_port(lower);
+		if (!dsa_lag_offloading(dp->ds->dst))
+			break;
+
+		err = dsa_slave_changeupper(lower, info);
+		if (notifier_to_errno(err))
+			break;
 	}
 
 	return err;
@@ -2009,6 +2043,23 @@ dsa_slave_check_8021q_upper(struct net_device *dev,
 	return NOTIFY_DONE;
 }
 
+static int dsa_slave_check_lag_upper(struct net_device *dev)
+{
+	struct dsa_port *dp = dsa_slave_to_port(dev);
+	struct dsa_switch_tree *dst = dp->ds->dst;
+
+	if (!dsa_lag_offloading(dst))
+		return NOTIFY_DONE;
+
+	if (dsa_lag_by_dev(dst, dev))
+		return NOTIFY_OK;
+
+	if (!dsa_lag_available(dst))
+		return notifier_from_errno(-EBUSY);
+
+	return NOTIFY_OK;
+}
+
 static int dsa_slave_netdevice_event(struct notifier_block *nb,
 				     unsigned long event, void *ptr)
 {
@@ -2035,13 +2086,33 @@ static int dsa_slave_netdevice_event(struct notifier_block *nb,
 
 		if (is_vlan_dev(info->upper_dev))
 			return dsa_slave_check_8021q_upper(dev, ptr);
+
+		if (netif_is_lag_master(info->upper_dev))
+			return dsa_slave_check_lag_upper(dev);
+
 		break;
 	}
 	case NETDEV_CHANGEUPPER:
+		if (dsa_slave_dev_check(dev))
+			return dsa_slave_changeupper(dev, ptr);
+
+		if (netif_is_lag_master(dev))
+			return dsa_slave_lag_changeupper(dev, ptr);
+
+		break;
+	case NETDEV_CHANGELOWERSTATE: {
+		struct netdev_notifier_changelowerstate_info *info = ptr;
+		struct dsa_port *dp;
+		int err;
+
 		if (!dsa_slave_dev_check(dev))
-			return NOTIFY_DONE;
+			break;
+
+		dp = dsa_slave_to_port(dev);
 
-		return dsa_slave_changeupper(dev, ptr);
+		err = dsa_port_lag_change(dp, info->lower_state_info);
+		return notifier_from_errno(err);
+	}
 	}
 
 	return NOTIFY_DONE;
diff --git a/net/dsa/switch.c b/net/dsa/switch.c
index 3fb362b6874e..3e518df7cd1f 100644
--- a/net/dsa/switch.c
+++ b/net/dsa/switch.c
@@ -178,6 +178,46 @@ static int dsa_switch_fdb_del(struct dsa_switch *ds,
 	return ds->ops->port_fdb_del(ds, port, info->addr, info->vid);
 }
 
+static int dsa_switch_lag_change(struct dsa_switch *ds,
+				 struct dsa_notifier_lag_info *info)
+{
+	if (ds->index == info->sw_index && ds->ops->port_lag_change)
+		return ds->ops->port_lag_change(ds, info->port, info->info);
+
+	if (ds->index != info->sw_index && ds->ops->crosschip_lag_change)
+		return ds->ops->crosschip_lag_change(ds, info->sw_index,
+						     info->port, info->lag,
+						     info->info);
+
+	return 0;
+}
+
+static int dsa_switch_lag_join(struct dsa_switch *ds,
+			       struct dsa_notifier_lag_info *info)
+{
+	if (ds->index == info->sw_index && ds->ops->port_lag_join)
+		return ds->ops->port_lag_join(ds, info->port, info->lag);
+
+	if (ds->index != info->sw_index && ds->ops->crosschip_lag_join)
+		return ds->ops->crosschip_lag_join(ds, info->sw_index,
+						   info->port, info->lag);
+
+	return 0;
+}
+
+static int dsa_switch_lag_leave(struct dsa_switch *ds,
+				struct dsa_notifier_lag_info *info)
+{
+	if (ds->index == info->sw_index && ds->ops->port_lag_leave)
+		ds->ops->port_lag_leave(ds, info->port, info->lag);
+
+	if (ds->index != info->sw_index && ds->ops->crosschip_lag_leave)
+		ds->ops->crosschip_lag_leave(ds, info->sw_index,
+					     info->port, info->lag);
+
+	return 0;
+}
+
 static bool dsa_switch_mdb_match(struct dsa_switch *ds, int port,
 				 struct dsa_notifier_mdb_info *info)
 {
@@ -325,6 +365,15 @@ static int dsa_switch_event(struct notifier_block *nb,
 	case DSA_NOTIFIER_FDB_DEL:
 		err = dsa_switch_fdb_del(ds, info);
 		break;
+	case DSA_NOTIFIER_LAG_CHANGE:
+		err = dsa_switch_lag_change(ds, info);
+		break;
+	case DSA_NOTIFIER_LAG_JOIN:
+		err = dsa_switch_lag_join(ds, info);
+		break;
+	case DSA_NOTIFIER_LAG_LEAVE:
+		err = dsa_switch_lag_leave(ds, info);
+		break;
 	case DSA_NOTIFIER_MDB_ADD:
 		err = dsa_switch_mdb_add(ds, info);
 		break;
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 net-next 3/4] net: dsa: mv88e6xxx: Link aggregation support
  2020-11-30 14:06 [PATCH v2 net-next 0/4] net: dsa: Link aggregation support Tobias Waldekranz
  2020-11-30 14:06 ` [PATCH v2 net-next 1/4] net: bonding: Notify ports about their initial state Tobias Waldekranz
  2020-11-30 14:06 ` [PATCH v2 net-next 2/4] net: dsa: Link aggregation support Tobias Waldekranz
@ 2020-11-30 14:06 ` Tobias Waldekranz
  2020-11-30 14:06 ` [PATCH v2 net-next 4/4] net: dsa: tag_dsa: Support reception of packets from LAG devices Tobias Waldekranz
  3 siblings, 0 replies; 17+ messages in thread
From: Tobias Waldekranz @ 2020-11-30 14:06 UTC (permalink / raw)
  To: davem, kuba
  Cc: andrew, vivien.didelot, f.fainelli, olteanv, j.vosburgh, vfalico,
	andy, netdev

Support offloading of LAGs to hardware. LAGs may be attached to a
bridge in which case VLANs, multicast groups, etc. are also offloaded
as usual.

Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c    | 234 +++++++++++++++++++++++++++-
 drivers/net/dsa/mv88e6xxx/chip.h    |   4 +
 drivers/net/dsa/mv88e6xxx/global2.c |   8 +-
 drivers/net/dsa/mv88e6xxx/global2.h |   5 +
 drivers/net/dsa/mv88e6xxx/port.c    |  21 +++
 drivers/net/dsa/mv88e6xxx/port.h    |   5 +
 6 files changed, 269 insertions(+), 8 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index e7f68ac0c7e3..3c4b795ac7e4 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1189,7 +1189,8 @@ static int mv88e6xxx_set_mac_eee(struct dsa_switch *ds, int port,
 }
 
 /* Mask of the local ports allowed to receive frames from a given fabric port */
-static u16 mv88e6xxx_port_vlan(struct mv88e6xxx_chip *chip, int dev, int port)
+static u16 mv88e6xxx_port_vlan(struct mv88e6xxx_chip *chip, int dev, int port,
+			       struct dsa_lag **lag)
 {
 	struct dsa_switch *ds = chip->ds;
 	struct dsa_switch_tree *dst = ds->dst;
@@ -1201,6 +1202,9 @@ static u16 mv88e6xxx_port_vlan(struct mv88e6xxx_chip *chip, int dev, int port)
 	list_for_each_entry(dp, &dst->ports, list) {
 		if (dp->ds->index == dev && dp->index == port) {
 			found = true;
+
+			if (dp->lag && lag)
+				*lag = dp->lag;
 			break;
 		}
 	}
@@ -1231,7 +1235,9 @@ static u16 mv88e6xxx_port_vlan(struct mv88e6xxx_chip *chip, int dev, int port)
 
 static int mv88e6xxx_port_vlan_map(struct mv88e6xxx_chip *chip, int port)
 {
-	u16 output_ports = mv88e6xxx_port_vlan(chip, chip->ds->index, port);
+	u16 output_ports;
+
+	output_ports = mv88e6xxx_port_vlan(chip, chip->ds->index, port, NULL);
 
 	/* prevent frames from going back out of the port they came in on */
 	output_ports &= ~BIT(port);
@@ -1389,14 +1395,21 @@ static int mv88e6xxx_mac_setup(struct mv88e6xxx_chip *chip)
 
 static int mv88e6xxx_pvt_map(struct mv88e6xxx_chip *chip, int dev, int port)
 {
+	struct dsa_lag *lag = NULL;
 	u16 pvlan = 0;
 
 	if (!mv88e6xxx_has_pvt(chip))
 		return 0;
 
 	/* Skip the local source device, which uses in-chip port VLAN */
-	if (dev != chip->ds->index)
-		pvlan = mv88e6xxx_port_vlan(chip, dev, port);
+	if (dev != chip->ds->index) {
+		pvlan = mv88e6xxx_port_vlan(chip, dev, port, &lag);
+
+		if (lag) {
+			dev = MV88E6XXX_G2_PVT_ADRR_DEV_TRUNK;
+			port = lag->id;
+		}
+	}
 
 	return mv88e6xxx_g2_pvt_write(chip, dev, port, pvlan);
 }
@@ -5368,6 +5381,207 @@ static int mv88e6xxx_port_egress_floods(struct dsa_switch *ds, int port,
 	return err;
 }
 
+static int mv88e6xxx_lag_sync_map(struct dsa_switch *ds, struct dsa_lag *lag)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	struct dsa_port *dp;
+	u16 map = 0;
+
+	/* Build the map of all ports to distribute flows destined for
+	 * this LAG. This can be either a local user port, or a DSA
+	 * port if the LAG port is on a remote chip.
+	 */
+	list_for_each_entry(dp, &lag->ports, lag_list) {
+		map |= BIT(dsa_towards_port(ds, dp->ds->index, dp->index));
+	}
+
+	return mv88e6xxx_g2_trunk_mapping_write(chip, lag->id, map);
+}
+
+static const u8 mv88e6xxx_lag_mask_table[8][8] = {
+	/* Row number corresponds to the number of active members in a
+	 * LAG. Each column states which of the eight hash buckets are
+	 * mapped to the column:th port in the LAG.
+	 *
+	 * Example: In a LAG with three active ports, the second port
+	 * ([2][1]) would be selected for traffic mapped to buckets
+	 * 3,4,5 (0x38).
+	 */
+	{ 0xff,    0,    0,    0,    0,    0,    0,    0 },
+	{ 0x0f, 0xf0,    0,    0,    0,    0,    0,    0 },
+	{ 0x07, 0x38, 0xc0,    0,    0,    0,    0,    0 },
+	{ 0x03, 0x0c, 0x30, 0xc0,    0,    0,    0,    0 },
+	{ 0x03, 0x0c, 0x30, 0x40, 0x80,    0,    0,    0 },
+	{ 0x03, 0x0c, 0x10, 0x20, 0x40, 0x80,    0,    0 },
+	{ 0x03, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,    0 },
+	{ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 },
+};
+
+static void mv88e6xxx_lag_set_port_mask(u16 *mask, int port,
+					int num_tx, int nth)
+{
+	u8 active = 0;
+	int i;
+
+	num_tx = num_tx <= 8 ? num_tx : 8;
+	if (nth < num_tx)
+		active = mv88e6xxx_lag_mask_table[num_tx - 1][nth];
+
+	for (i = 0; i < 8; i++) {
+		if (BIT(i) & active)
+			mask[i] |= BIT(port);
+	}
+}
+
+static int mv88e6xxx_lag_sync_masks(struct dsa_switch *ds)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	struct dsa_port *dp;
+	struct dsa_lag *lag;
+	int i, id, err, nth;
+	u16 mask[8] = { 0 };
+	u16 ivec;
+
+	/* Assume no port is a member of any LAG. */
+	ivec = BIT(mv88e6xxx_num_ports(chip)) - 1;
+
+	/* Disable all masks for ports that _are_ members of a LAG. */
+	dsa_lag_foreach(id, ds->dst) {
+		lag = dsa_lag_by_id(ds->dst, id);
+		list_for_each_entry(dp, &lag->ports, lag_list) {
+			if (dp->ds != ds)
+				continue;
+
+			ivec &= ~BIT(dp->index);
+		}
+	}
+
+	for (i = 0; i < 8; i++)
+		mask[i] = ivec;
+
+	/* Enable the correct subset of masks for all LAG ports that
+	 * are in the Tx set.
+	 */
+	dsa_lag_foreach(id, ds->dst) {
+		lag = dsa_lag_by_id(ds->dst, id);
+		if (!lag->num_tx)
+			continue;
+
+		nth = 0;
+		list_for_each_entry(dp, &lag->tx_ports, lag_tx_list) {
+			if (dp->ds == ds)
+				mv88e6xxx_lag_set_port_mask(mask, dp->index,
+							    lag->num_tx, nth);
+
+			nth++;
+		}
+	}
+
+	for (i = 0; i < 8; i++) {
+		err = mv88e6xxx_g2_trunk_mask_write(chip, i, true, mask[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int mv88e6xxx_lag_sync_masks_map(struct dsa_switch *ds,
+					struct dsa_lag *lag)
+{
+	int err;
+
+	err = mv88e6xxx_lag_sync_masks(ds);
+
+	if (!err)
+		err = mv88e6xxx_lag_sync_map(ds, lag);
+
+	return err;
+}
+
+static int mv88e6xxx_port_lag_change(struct dsa_switch *ds, int port,
+				     struct netdev_lag_lower_state_info *info)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	int err;
+
+	mv88e6xxx_reg_lock(chip);
+	err = mv88e6xxx_lag_sync_masks(ds);
+	mv88e6xxx_reg_unlock(chip);
+	return err;
+}
+
+static int mv88e6xxx_port_lag_join(struct dsa_switch *ds, int port,
+				   struct net_device *lag_dev)
+{
+	struct dsa_lag *lag = dsa_to_port(ds, port)->lag;
+	struct mv88e6xxx_chip *chip = ds->priv;
+	int err;
+
+	mv88e6xxx_reg_lock(chip);
+
+	err = mv88e6xxx_port_set_trunk(chip, port, true, lag->id);
+	if (err)
+		goto unlock;
+
+	err = mv88e6xxx_lag_sync_masks_map(ds, lag);
+	if (err)
+		mv88e6xxx_port_set_trunk(chip, port, false, 0);
+
+unlock:
+	mv88e6xxx_reg_unlock(chip);
+	return err;
+}
+
+static void mv88e6xxx_port_lag_leave(struct dsa_switch *ds, int port,
+				     struct net_device *lag_dev)
+{
+	struct dsa_lag *lag = dsa_to_port(ds, port)->lag;
+	struct mv88e6xxx_chip *chip = ds->priv;
+
+	mv88e6xxx_reg_lock(chip);
+	mv88e6xxx_lag_sync_masks_map(ds, lag);
+	mv88e6xxx_port_set_trunk(chip, port, false, 0);
+	mv88e6xxx_reg_unlock(chip);
+}
+
+static int mv88e6xxx_crosschip_lag_change(struct dsa_switch *ds, int sw_index,
+					  int port, struct net_device *lag_dev,
+					  struct netdev_lag_lower_state_info *info)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	int err;
+
+	mv88e6xxx_reg_lock(chip);
+	err = mv88e6xxx_lag_sync_masks(ds);
+	mv88e6xxx_reg_unlock(chip);
+	return err;
+}
+
+static int mv88e6xxx_crosschip_lag_join(struct dsa_switch *ds, int sw_index,
+					int port, struct net_device *lag_dev)
+{
+	struct dsa_lag *lag = dsa_lag_by_dev(ds->dst, lag_dev);
+	struct mv88e6xxx_chip *chip = ds->priv;
+	int err;
+
+	mv88e6xxx_reg_lock(chip);
+	err = mv88e6xxx_lag_sync_masks_map(ds, lag);
+	mv88e6xxx_reg_unlock(chip);
+	return err;
+}
+
+static void mv88e6xxx_crosschip_lag_leave(struct dsa_switch *ds, int sw_index,
+					  int port, struct net_device *lag_dev)
+{
+	struct dsa_lag *lag = dsa_lag_by_dev(ds->dst, lag_dev);
+	struct mv88e6xxx_chip *chip = ds->priv;
+
+	mv88e6xxx_reg_lock(chip);
+	mv88e6xxx_lag_sync_masks_map(ds, lag);
+	mv88e6xxx_reg_unlock(chip);
+}
+
 static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
 	.get_tag_protocol	= mv88e6xxx_get_tag_protocol,
 	.setup			= mv88e6xxx_setup,
@@ -5422,6 +5636,12 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
 	.devlink_param_get	= mv88e6xxx_devlink_param_get,
 	.devlink_param_set	= mv88e6xxx_devlink_param_set,
 	.devlink_info_get	= mv88e6xxx_devlink_info_get,
+	.port_lag_change	= mv88e6xxx_port_lag_change,
+	.port_lag_join		= mv88e6xxx_port_lag_join,
+	.port_lag_leave		= mv88e6xxx_port_lag_leave,
+	.crosschip_lag_change	= mv88e6xxx_crosschip_lag_change,
+	.crosschip_lag_join	= mv88e6xxx_crosschip_lag_join,
+	.crosschip_lag_leave	= mv88e6xxx_crosschip_lag_leave,
 };
 
 static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
@@ -5441,6 +5661,12 @@ static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
 	ds->ageing_time_min = chip->info->age_time_coeff;
 	ds->ageing_time_max = chip->info->age_time_coeff * U8_MAX;
 
+	/* Some chips support up to 32, but that requires enabling the
+	 * 5-bit port mode, which we do not support. 640k^W16 ought to
+	 * be enough for anyone.
+	 */
+	ds->num_lags = 16;
+
 	dev_set_drvdata(dev, ds);
 
 	return dsa_register_switch(ds);
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e196d7270606..fc7625593c81 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -580,6 +580,10 @@ struct mv88e6xxx_ops {
 
 	/* Max Frame Size */
 	int (*set_max_frame_size)(struct mv88e6xxx_chip *chip, int mtu);
+
+	/* Link aggregation */
+	int (*lag_set_map)(struct mv88e6xxx_chip *chip, struct dsa_lag *lag);
+	int (*lag_set_masks)(struct mv88e6xxx_chip *chip, struct dsa_lag *lag);
 };
 
 struct mv88e6xxx_irq_ops {
diff --git a/drivers/net/dsa/mv88e6xxx/global2.c b/drivers/net/dsa/mv88e6xxx/global2.c
index 8dbb1ae723ae..fa65ecd9cb85 100644
--- a/drivers/net/dsa/mv88e6xxx/global2.c
+++ b/drivers/net/dsa/mv88e6xxx/global2.c
@@ -126,8 +126,8 @@ int mv88e6xxx_g2_device_mapping_write(struct mv88e6xxx_chip *chip, int target,
 
 /* Offset 0x07: Trunk Mask Table register */
 
-static int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,
-					 bool hash, u16 mask)
+int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,
+				  bool hash, u16 mask)
 {
 	u16 val = (num << 12) | (mask & mv88e6xxx_port_mask(chip));
 
@@ -140,8 +140,8 @@ static int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,
 
 /* Offset 0x08: Trunk Mapping Table register */
 
-static int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id,
-					    u16 map)
+int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id,
+				     u16 map)
 {
 	const u16 port_mask = BIT(mv88e6xxx_num_ports(chip)) - 1;
 	u16 val = (id << 11) | (map & port_mask);
diff --git a/drivers/net/dsa/mv88e6xxx/global2.h b/drivers/net/dsa/mv88e6xxx/global2.h
index 1510a1c810c8..621bbc4a1d80 100644
--- a/drivers/net/dsa/mv88e6xxx/global2.h
+++ b/drivers/net/dsa/mv88e6xxx/global2.h
@@ -101,6 +101,7 @@
 #define MV88E6XXX_G2_PVT_ADDR_OP_WRITE_PVLAN	0x3000
 #define MV88E6XXX_G2_PVT_ADDR_OP_READ		0x4000
 #define MV88E6XXX_G2_PVT_ADDR_PTR_MASK		0x01ff
+#define MV88E6XXX_G2_PVT_ADRR_DEV_TRUNK		0x1f
 
 /* Offset 0x0C: Cross-chip Port VLAN Data Register */
 #define MV88E6XXX_G2_PVT_DATA		0x0c
@@ -347,6 +348,10 @@ int mv88e6352_g2_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip);
 
 int mv88e6xxx_g2_pot_clear(struct mv88e6xxx_chip *chip);
 
+int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,
+				  bool hash, u16 mask);
+int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id,
+				     u16 map);
 int mv88e6xxx_g2_trunk_clear(struct mv88e6xxx_chip *chip);
 
 int mv88e6xxx_g2_device_mapping_write(struct mv88e6xxx_chip *chip, int target,
diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c
index 77a5fd1798cd..4b46e10a2dde 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -851,6 +851,27 @@ int mv88e6xxx_port_set_message_port(struct mv88e6xxx_chip *chip, int port,
 	return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL1, val);
 }
 
+int mv88e6xxx_port_set_trunk(struct mv88e6xxx_chip *chip, int port,
+			     bool trunk, u8 id)
+{
+	u16 val;
+	int err;
+
+	err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_CTL1, &val);
+	if (err)
+		return err;
+
+	val &= ~MV88E6XXX_PORT_CTL1_TRUNK_ID_MASK;
+
+	if (trunk)
+		val |= MV88E6XXX_PORT_CTL1_TRUNK_PORT |
+			(id << MV88E6XXX_PORT_CTL1_TRUNK_ID_SHIFT);
+	else
+		val &= ~MV88E6XXX_PORT_CTL1_TRUNK_PORT;
+
+	return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL1, val);
+}
+
 /* Offset 0x06: Port Based VLAN Map */
 
 int mv88e6xxx_port_set_vlan_map(struct mv88e6xxx_chip *chip, int port, u16 map)
diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h
index 500e1d4896ff..a729bba050df 100644
--- a/drivers/net/dsa/mv88e6xxx/port.h
+++ b/drivers/net/dsa/mv88e6xxx/port.h
@@ -168,6 +168,9 @@
 /* Offset 0x05: Port Control 1 */
 #define MV88E6XXX_PORT_CTL1			0x05
 #define MV88E6XXX_PORT_CTL1_MESSAGE_PORT	0x8000
+#define MV88E6XXX_PORT_CTL1_TRUNK_PORT		0x4000
+#define MV88E6XXX_PORT_CTL1_TRUNK_ID_MASK	0x0f00
+#define MV88E6XXX_PORT_CTL1_TRUNK_ID_SHIFT	8
 #define MV88E6XXX_PORT_CTL1_FID_11_4_MASK	0x00ff
 
 /* Offset 0x06: Port Based VLAN Map */
@@ -351,6 +354,8 @@ int mv88e6351_port_set_ether_type(struct mv88e6xxx_chip *chip, int port,
 				  u16 etype);
 int mv88e6xxx_port_set_message_port(struct mv88e6xxx_chip *chip, int port,
 				    bool message_port);
+int mv88e6xxx_port_set_trunk(struct mv88e6xxx_chip *chip, int port,
+			     bool trunk, u8 id);
 int mv88e6165_port_set_jumbo_size(struct mv88e6xxx_chip *chip, int port,
 				  size_t size);
 int mv88e6095_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port);
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 net-next 4/4] net: dsa: tag_dsa: Support reception of packets from LAG devices
  2020-11-30 14:06 [PATCH v2 net-next 0/4] net: dsa: Link aggregation support Tobias Waldekranz
                   ` (2 preceding siblings ...)
  2020-11-30 14:06 ` [PATCH v2 net-next 3/4] net: dsa: mv88e6xxx: " Tobias Waldekranz
@ 2020-11-30 14:06 ` Tobias Waldekranz
  2020-12-01 21:24   ` Vladimir Oltean
  3 siblings, 1 reply; 17+ messages in thread
From: Tobias Waldekranz @ 2020-11-30 14:06 UTC (permalink / raw)
  To: davem, kuba
  Cc: andrew, vivien.didelot, f.fainelli, olteanv, j.vosburgh, vfalico,
	andy, netdev

Packets ingressing on a LAG that egress on the CPU port, which are not
classified as management, will have a FORWARD tag that does not
contain the normal source device/port tuple. Instead the trunk bit
will be set, and the port field holds the LAG id.

Since the exact source port information is not available in the tag,
frames are injected directly on the LAG interface and thus do never
pass through any DSA port interface on ingress.

Management frames (TO_CPU) are not affected and will pass through the
DSA port interface as usual.

Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
 net/dsa/dsa.c     | 12 +++++++++++-
 net/dsa/tag_dsa.c | 17 ++++++++++++++++-
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index a1b1dc8a4d87..7325bf4608e9 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -219,11 +219,21 @@ static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev,
 	}
 
 	skb = nskb;
-	p = netdev_priv(skb->dev);
 	skb_push(skb, ETH_HLEN);
 	skb->pkt_type = PACKET_HOST;
 	skb->protocol = eth_type_trans(skb, skb->dev);
 
+	if (unlikely(!dsa_slave_dev_check(skb->dev))) {
+		/* Packet is to be injected directly on an upper
+		 * device, e.g. a team/bond, so skip all DSA-port
+		 * specific actions.
+		 */
+		netif_rx(skb);
+		return 0;
+	}
+
+	p = netdev_priv(skb->dev);
+
 	if (unlikely(cpu_dp->ds->untag_bridge_pvid)) {
 		nskb = dsa_untag_bridge_pvid(skb);
 		if (!nskb) {
diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
index 112c7c6dd568..be7271de8d0b 100644
--- a/net/dsa/tag_dsa.c
+++ b/net/dsa/tag_dsa.c
@@ -163,6 +163,7 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 				  u8 extra)
 {
 	int source_device, source_port;
+	bool trunk = false;
 	enum dsa_code code;
 	enum dsa_cmd cmd;
 	u8 *dsa_header;
@@ -174,6 +175,8 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 	switch (cmd) {
 	case DSA_CMD_FORWARD:
 		skb->offload_fwd_mark = 1;
+
+		trunk = !!(dsa_header[1] & 7);
 		break;
 
 	case DSA_CMD_TO_CPU:
@@ -216,7 +219,19 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 	source_device = dsa_header[0] & 0x1f;
 	source_port = (dsa_header[1] >> 3) & 0x1f;
 
-	skb->dev = dsa_master_find_slave(dev, source_device, source_port);
+	if (trunk) {
+		struct dsa_port *cpu_dp = dev->dsa_ptr;
+
+		/* The exact source port is not available in the tag,
+		 * so we inject the frame directly on the upper
+		 * team/bond.
+		 */
+		skb->dev = dsa_lag_dev_by_id(cpu_dp->dst, source_port);
+	} else {
+		skb->dev = dsa_master_find_slave(dev, source_device,
+						 source_port);
+	}
+
 	if (!skb->dev)
 		return NULL;
 
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-11-30 14:06 ` [PATCH v2 net-next 2/4] net: dsa: Link aggregation support Tobias Waldekranz
@ 2020-12-01  1:37   ` Vladimir Oltean
  2020-12-01  8:13     ` Tobias Waldekranz
  2020-12-01 14:03   ` Vladimir Oltean
  1 sibling, 1 reply; 17+ messages in thread
From: Vladimir Oltean @ 2020-12-01  1:37 UTC (permalink / raw)
  To: Tobias Waldekranz
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

Hi Tobias,

On Mon, Nov 30, 2020 at 03:06:08PM +0100, Tobias Waldekranz wrote:
> Monitor the following events and notify the driver when:
> 
> - A DSA port joins/leaves a LAG.
> - A LAG, made up of DSA ports, joins/leaves a bridge.
> - A DSA port in a LAG is enabled/disabled (enabled meaning
>   "distributing" in 802.3ad LACP terms).
> 
> Each LAG interface to which a DSA port is attached is represented by a
> `struct dsa_lag` which is globally reachable from the switch tree and
> from each associated port.
> 
> When a LAG joins a bridge, the DSA subsystem will treat that as each
> individual port joining the bridge. The driver may look at the port's
> LAG pointer to see if it is associated with any LAG, if that is
> required. This is analogue to how switchdev events are replicated out
> to all lower devices when reaching e.g. a LAG.
> 
> Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
> ---
>  include/net/dsa.h  |  97 +++++++++++++++++++++++++++++++
>  net/dsa/dsa2.c     |  51 ++++++++++++++++
>  net/dsa/dsa_priv.h |  31 ++++++++++
>  net/dsa/port.c     | 141 +++++++++++++++++++++++++++++++++++++++++++++
>  net/dsa/slave.c    |  83 ++++++++++++++++++++++++--
>  net/dsa/switch.c   |  49 ++++++++++++++++
>  6 files changed, 446 insertions(+), 6 deletions(-)
> 
> +static inline struct dsa_lag *dsa_lag_by_id(struct dsa_switch_tree *dst, int id)
> +{
> +	if (!test_bit(id, dst->lags.busy))
> +		return NULL;
> +
> +	return &dst->lags.pool[id];
> +}
> +
> +static inline struct net_device *dsa_lag_dev_by_id(struct dsa_switch_tree *dst,
> +						   int id)
> +{
> +	struct dsa_lag *lag = dsa_lag_by_id(dst, id);
> +
> +	return lag ? rcu_dereference(lag->dev) : NULL;
> +}
> +
> +static inline struct dsa_lag *dsa_lag_by_dev(struct dsa_switch_tree *dst,
> +					     struct net_device *dev)
> +{
> +	struct dsa_lag *lag;
> +	int id;
> +
> +	dsa_lag_foreach(id, dst) {
> +		lag = dsa_lag_by_id(dst, id);
> +
> +		if (rtnl_dereference(lag->dev) == dev)
> +			return lag;
> +	}
> +
> +	return NULL;
> +}
>  
> diff --git a/net/dsa/port.c b/net/dsa/port.c
> index 73569c9af3cc..c2332ee5f5c7 100644
> --- a/net/dsa/port.c
> +++ b/net/dsa/port.c
> @@ -193,6 +193,147 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
>  	dsa_port_set_state_now(dp, BR_STATE_FORWARDING);
>  }
>  
> +static struct dsa_lag *dsa_lag_get(struct dsa_switch_tree *dst,
> +				   struct net_device *dev)
> +{
> +	struct dsa_lag *lag;
> +	int id;
> +
> +	lag = dsa_lag_by_dev(dst, dev);
> +	if (lag) {
> +		kref_get(&lag->refcount);
> +		return lag;
> +	}
> +
> +	id = find_first_zero_bit(dst->lags.busy, dst->lags.num);
> +	if (id >= dst->lags.num) {
> +		WARN(1, "No LAGs available");
> +		return NULL;
> +	}
> +
> +	set_bit(id, dst->lags.busy);
> +
> +	lag = &dst->lags.pool[id];
> +	kref_init(&lag->refcount);
> +	lag->id = id;
> +	INIT_LIST_HEAD(&lag->ports);
> +	INIT_LIST_HEAD(&lag->tx_ports);
> +
> +	rcu_assign_pointer(lag->dev, dev);
> +	return lag;
> +}
> +
> +static void dsa_lag_release(struct kref *refcount)
> +{
> +	struct dsa_lag *lag = container_of(refcount, struct dsa_lag, refcount);
> +
> +	rcu_assign_pointer(lag->dev, NULL);
> +	synchronize_rcu();
> +	memset(lag, 0, sizeof(*lag));
> +}

What difference does it make if lag->dev is set to NULL right away or
after a grace period? Squeezing one last packet from that bonding interface?
Pointer updates are atomic operations on all architectures that the
kernel supports, and, as long as you use WRITE_ONCE and READ_ONCE memory
barriers, there should be no reason for RCU protection that I can see.
And unlike typical uses of RCU, you do not free lag->dev, because you do
not own lag->dev. Instead, the bonding interface pointed to by lag->dev
is going to be freed (in case of a deletion using ip link) after an RCU
grace period anyway. And the receive data path is under an RCU read-side
critical section anyway. So even if you set lag->dev to NULL using
WRITE_ONCE, the existing in-flight readers from the RX data path that
had called dsa_lag_dev_by_id() will still hold a reference to a valid
bonding interface.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-12-01  1:37   ` Vladimir Oltean
@ 2020-12-01  8:13     ` Tobias Waldekranz
  2020-12-01 13:29       ` Vladimir Oltean
  0 siblings, 1 reply; 17+ messages in thread
From: Tobias Waldekranz @ 2020-12-01  8:13 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Tue, Dec 01, 2020 at 03:37, Vladimir Oltean <olteanv@gmail.com> wrote:
> On Mon, Nov 30, 2020 at 03:06:08PM +0100, Tobias Waldekranz wrote:
>> +static void dsa_lag_release(struct kref *refcount)
>> +{
>> +	struct dsa_lag *lag = container_of(refcount, struct dsa_lag, refcount);
>> +
>> +	rcu_assign_pointer(lag->dev, NULL);
>> +	synchronize_rcu();
>> +	memset(lag, 0, sizeof(*lag));
>> +}
>
> What difference does it make if lag->dev is set to NULL right away or
> after a grace period? Squeezing one last packet from that bonding interface?
> Pointer updates are atomic operations on all architectures that the
> kernel supports, and, as long as you use WRITE_ONCE and READ_ONCE memory
> barriers, there should be no reason for RCU protection that I can see.
> And unlike typical uses of RCU, you do not free lag->dev, because you do
> not own lag->dev. Instead, the bonding interface pointed to by lag->dev
> is going to be freed (in case of a deletion using ip link) after an RCU
> grace period anyway. And the receive data path is under an RCU read-side
> critical section anyway. So even if you set lag->dev to NULL using
> WRITE_ONCE, the existing in-flight readers from the RX data path that
> had called dsa_lag_dev_by_id() will still hold a reference to a valid
> bonding interface.

I completely agree with your analysis. I will remove all the RCU
primitives in v3. Thank you.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-12-01  8:13     ` Tobias Waldekranz
@ 2020-12-01 13:29       ` Vladimir Oltean
  2020-12-01 14:22         ` Tobias Waldekranz
  0 siblings, 1 reply; 17+ messages in thread
From: Vladimir Oltean @ 2020-12-01 13:29 UTC (permalink / raw)
  To: Tobias Waldekranz
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Tue, Dec 01, 2020 at 09:13:57AM +0100, Tobias Waldekranz wrote:
> I completely agree with your analysis. I will remove all the RCU
> primitives in v3. Thank you.

I expect that this also gives us a simple refcount_t instead of the
struct kref?

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-11-30 14:06 ` [PATCH v2 net-next 2/4] net: dsa: Link aggregation support Tobias Waldekranz
  2020-12-01  1:37   ` Vladimir Oltean
@ 2020-12-01 14:03   ` Vladimir Oltean
  2020-12-01 14:29     ` Tobias Waldekranz
  1 sibling, 1 reply; 17+ messages in thread
From: Vladimir Oltean @ 2020-12-01 14:03 UTC (permalink / raw)
  To: Tobias Waldekranz
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Mon, Nov 30, 2020 at 03:06:08PM +0100, Tobias Waldekranz wrote:
> When a LAG joins a bridge, the DSA subsystem will treat that as each
> individual port joining the bridge. The driver may look at the port's
> LAG pointer to see if it is associated with any LAG, if that is
> required. This is analogue to how switchdev events are replicated out
> to all lower devices when reaching e.g. a LAG.

Agree with the principle. But doesn't that mean that this code:

static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
					      unsigned long event, void *ptr)
{
	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
	int err;

	switch (event) {
	case SWITCHDEV_PORT_OBJ_ADD:
		err = switchdev_handle_port_obj_add(dev, ptr,
						    dsa_slave_dev_check,
						    dsa_slave_port_obj_add);
		return notifier_from_errno(err);
	case SWITCHDEV_PORT_OBJ_DEL:
		err = switchdev_handle_port_obj_del(dev, ptr,
						    dsa_slave_dev_check,
						    dsa_slave_port_obj_del);
		return notifier_from_errno(err);
	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);
	}

	return NOTIFY_DONE;
}

should be replaced with something that also reacts to the case where
"dev" is a LAG? Like, for example, I imagine that a VLAN installed on a
bridge port that is a LAG should be propagated to the switch ports
beneath that LAG. Similarly for all bridge attributes.

As for FDB and MDB addresses, I think they should be propagated towards
a "logical port" corresponding to the LAG upper. I don't know how the
mv88e6xxx handles this.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-12-01 13:29       ` Vladimir Oltean
@ 2020-12-01 14:22         ` Tobias Waldekranz
  0 siblings, 0 replies; 17+ messages in thread
From: Tobias Waldekranz @ 2020-12-01 14:22 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Tue, Dec 01, 2020 at 15:29, Vladimir Oltean <olteanv@gmail.com> wrote:
> On Tue, Dec 01, 2020 at 09:13:57AM +0100, Tobias Waldekranz wrote:
>> I completely agree with your analysis. I will remove all the RCU
>> primitives in v3. Thank you.
>
> I expect that this also gives us a simple refcount_t instead of the
> struct kref?

Yeah sure, I was just trying to be consistent with what was being used
in other dsa-related structs. I will change it.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-12-01 14:03   ` Vladimir Oltean
@ 2020-12-01 14:29     ` Tobias Waldekranz
  2020-12-01 20:04       ` Vladimir Oltean
  0 siblings, 1 reply; 17+ messages in thread
From: Tobias Waldekranz @ 2020-12-01 14:29 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Tue, Dec 01, 2020 at 16:03, Vladimir Oltean <olteanv@gmail.com> wrote:
> On Mon, Nov 30, 2020 at 03:06:08PM +0100, Tobias Waldekranz wrote:
>> When a LAG joins a bridge, the DSA subsystem will treat that as each
>> individual port joining the bridge. The driver may look at the port's
>> LAG pointer to see if it is associated with any LAG, if that is
>> required. This is analogue to how switchdev events are replicated out
>> to all lower devices when reaching e.g. a LAG.
>
> Agree with the principle. But doesn't that mean that this code:
>
> static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
> 					      unsigned long event, void *ptr)
> {
> 	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
> 	int err;
>
> 	switch (event) {
> 	case SWITCHDEV_PORT_OBJ_ADD:
> 		err = switchdev_handle_port_obj_add(dev, ptr,
> 						    dsa_slave_dev_check,
> 						    dsa_slave_port_obj_add);
> 		return notifier_from_errno(err);
> 	case SWITCHDEV_PORT_OBJ_DEL:
> 		err = switchdev_handle_port_obj_del(dev, ptr,
> 						    dsa_slave_dev_check,
> 						    dsa_slave_port_obj_del);
> 		return notifier_from_errno(err);
> 	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);
> 	}
>
> 	return NOTIFY_DONE;
> }
>
> should be replaced with something that also reacts to the case where
> "dev" is a LAG? Like, for example, I imagine that a VLAN installed on a
> bridge port that is a LAG should be propagated to the switch ports
> beneath that LAG. Similarly for all bridge attributes.

That is exactly what switchdev_handle_* does, no? It is this exact
behavior that my statement about switchdev event replication references.

> As for FDB and MDB addresses, I think they should be propagated towards
> a "logical port" corresponding to the LAG upper. I don't know how the
> mv88e6xxx handles this.

mv88e6xxx differentiates between multicast and unicast entries. So MDB
entries fit very well with the obj_add/del+replication. Unicast entries
will have use "lagX" as its destination, so in that case we need a new
dsa op along the lines of "lag_fdb_add/del".

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-12-01 14:29     ` Tobias Waldekranz
@ 2020-12-01 20:04       ` Vladimir Oltean
  2020-12-01 21:48         ` Tobias Waldekranz
  0 siblings, 1 reply; 17+ messages in thread
From: Vladimir Oltean @ 2020-12-01 20:04 UTC (permalink / raw)
  To: Tobias Waldekranz
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Tue, Dec 01, 2020 at 03:29:53PM +0100, Tobias Waldekranz wrote:
> On Tue, Dec 01, 2020 at 16:03, Vladimir Oltean <olteanv@gmail.com> wrote:
> > On Mon, Nov 30, 2020 at 03:06:08PM +0100, Tobias Waldekranz wrote:
> >> When a LAG joins a bridge, the DSA subsystem will treat that as each
> >> individual port joining the bridge. The driver may look at the port's
> >> LAG pointer to see if it is associated with any LAG, if that is
> >> required. This is analogue to how switchdev events are replicated out
> >> to all lower devices when reaching e.g. a LAG.
> >
> > Agree with the principle. But doesn't that mean that this code:
> >
> > static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
> > 					      unsigned long event, void *ptr)
> > {
> > 	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
> > 	int err;
> >
> > 	switch (event) {
> > 	case SWITCHDEV_PORT_OBJ_ADD:
> > 		err = switchdev_handle_port_obj_add(dev, ptr,
> > 						    dsa_slave_dev_check,
> > 						    dsa_slave_port_obj_add);
> > 		return notifier_from_errno(err);
> > 	case SWITCHDEV_PORT_OBJ_DEL:
> > 		err = switchdev_handle_port_obj_del(dev, ptr,
> > 						    dsa_slave_dev_check,
> > 						    dsa_slave_port_obj_del);
> > 		return notifier_from_errno(err);
> > 	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);
> > 	}
> >
> > 	return NOTIFY_DONE;
> > }
> >
> > should be replaced with something that also reacts to the case where
> > "dev" is a LAG? Like, for example, I imagine that a VLAN installed on a
> > bridge port that is a LAG should be propagated to the switch ports
> > beneath that LAG. Similarly for all bridge attributes.
>
> That is exactly what switchdev_handle_* does, no? It is this exact
> behavior that my statement about switchdev event replication references.

I'm sorry, I don't mean to be overly obtuse, but _how_ does the current
code propagate a VLAN to a physical port located below a bond? Through
magic? The dsa_slave_dev_check is passed as a parameter to
switchdev_handle_port_obj_add _exactly_ because the code has needed so
far to match only on DSA interfaces and not on bonding interfaces. So
the code does not react to VLANs added on a bonding interface. Hence my
question.

ip link del bond0
ip link add bond0 type bond mode 802.3ad
ip link set swp1 down && ip link set swp1 master bond0 && ip link set swp1 up
ip link set swp2 down && ip link set swp2 master bond0 && ip link set swp2 up
ip link del br0
ip link add br0 type bridge
ip link set bond0 master br0
ip link set swp0 master br0

This should propagate the VLANs to swp1 and swp2 but doesn't:
bridge vlan add dev bond0 vid 100

It's perfectly acceptable to say that this patch set doesn't deal with
that. But your commit message seems to suggest that it's me who's
misunderstanding something.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 4/4] net: dsa: tag_dsa: Support reception of packets from LAG devices
  2020-11-30 14:06 ` [PATCH v2 net-next 4/4] net: dsa: tag_dsa: Support reception of packets from LAG devices Tobias Waldekranz
@ 2020-12-01 21:24   ` Vladimir Oltean
  2020-12-01 22:31     ` Tobias Waldekranz
  0 siblings, 1 reply; 17+ messages in thread
From: Vladimir Oltean @ 2020-12-01 21:24 UTC (permalink / raw)
  To: Tobias Waldekranz
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Mon, Nov 30, 2020 at 03:06:10PM +0100, Tobias Waldekranz wrote:
> Packets ingressing on a LAG that egress on the CPU port, which are not
> classified as management, will have a FORWARD tag that does not
> contain the normal source device/port tuple. Instead the trunk bit
> will be set, and the port field holds the LAG id.
> 
> Since the exact source port information is not available in the tag,
> frames are injected directly on the LAG interface and thus do never
> pass through any DSA port interface on ingress.
> 
> Management frames (TO_CPU) are not affected and will pass through the
> DSA port interface as usual.
> 
> Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
> ---
>  net/dsa/dsa.c     | 12 +++++++++++-
>  net/dsa/tag_dsa.c | 17 ++++++++++++++++-
>  2 files changed, 27 insertions(+), 2 deletions(-)
> 
> diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
> index a1b1dc8a4d87..7325bf4608e9 100644
> --- a/net/dsa/dsa.c
> +++ b/net/dsa/dsa.c
> @@ -219,11 +219,21 @@ static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev,
>  	}
>  
>  	skb = nskb;
> -	p = netdev_priv(skb->dev);
>  	skb_push(skb, ETH_HLEN);
>  	skb->pkt_type = PACKET_HOST;
>  	skb->protocol = eth_type_trans(skb, skb->dev);
>  
> +	if (unlikely(!dsa_slave_dev_check(skb->dev))) {
> +		/* Packet is to be injected directly on an upper
> +		 * device, e.g. a team/bond, so skip all DSA-port
> +		 * specific actions.
> +		 */
> +		netif_rx(skb);
> +		return 0;

netif_rx returns an int code, it seems odd to ignore it.

> +	}
> +
> +	p = netdev_priv(skb->dev);
> +
>  	if (unlikely(cpu_dp->ds->untag_bridge_pvid)) {
>  		nskb = dsa_untag_bridge_pvid(skb);
>  		if (!nskb) {

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-12-01 20:04       ` Vladimir Oltean
@ 2020-12-01 21:48         ` Tobias Waldekranz
  2020-12-01 22:23           ` Vladimir Oltean
  0 siblings, 1 reply; 17+ messages in thread
From: Tobias Waldekranz @ 2020-12-01 21:48 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Tue, Dec 01, 2020 at 22:04, Vladimir Oltean <olteanv@gmail.com> wrote:
> On Tue, Dec 01, 2020 at 03:29:53PM +0100, Tobias Waldekranz wrote:
>> On Tue, Dec 01, 2020 at 16:03, Vladimir Oltean <olteanv@gmail.com> wrote:
>> > On Mon, Nov 30, 2020 at 03:06:08PM +0100, Tobias Waldekranz wrote:
>> >> When a LAG joins a bridge, the DSA subsystem will treat that as each
>> >> individual port joining the bridge. The driver may look at the port's
>> >> LAG pointer to see if it is associated with any LAG, if that is
>> >> required. This is analogue to how switchdev events are replicated out
>> >> to all lower devices when reaching e.g. a LAG.
>> >
>> > Agree with the principle. But doesn't that mean that this code:
>> >
>> > static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
>> > 					      unsigned long event, void *ptr)
>> > {
>> > 	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
>> > 	int err;
>> >
>> > 	switch (event) {
>> > 	case SWITCHDEV_PORT_OBJ_ADD:
>> > 		err = switchdev_handle_port_obj_add(dev, ptr,
>> > 						    dsa_slave_dev_check,
>> > 						    dsa_slave_port_obj_add);
>> > 		return notifier_from_errno(err);
>> > 	case SWITCHDEV_PORT_OBJ_DEL:
>> > 		err = switchdev_handle_port_obj_del(dev, ptr,
>> > 						    dsa_slave_dev_check,
>> > 						    dsa_slave_port_obj_del);
>> > 		return notifier_from_errno(err);
>> > 	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);
>> > 	}
>> >
>> > 	return NOTIFY_DONE;
>> > }
>> >
>> > should be replaced with something that also reacts to the case where
>> > "dev" is a LAG? Like, for example, I imagine that a VLAN installed on a
>> > bridge port that is a LAG should be propagated to the switch ports
>> > beneath that LAG. Similarly for all bridge attributes.
>>
>> That is exactly what switchdev_handle_* does, no? It is this exact
>> behavior that my statement about switchdev event replication references.
>
> I'm sorry, I don't mean to be overly obtuse, but _how_ does the current
> code propagate a VLAN to a physical port located below a bond? Through
> magic? The dsa_slave_dev_check is passed as a parameter to
> switchdev_handle_port_obj_add _exactly_ because the code has needed so
> far to match only on DSA interfaces and not on bonding interfaces. So
> the code does not react to VLANs added on a bonding interface. Hence my
> question.

There is no magic involved, here is the relevant snippet from
__switchdev_handle_port_obj_add:

	/* Switch ports might be stacked under e.g. a LAG. Ignore the
	 * unsupported devices, another driver might be able to handle them. But
	 * propagate to the callers any hard errors.
	 *
	 * If the driver does its own bookkeeping of stacked ports, it's not
	 * necessary to go through this helper.
	 */
	netdev_for_each_lower_dev(dev, lower_dev, iter) {
		if (netif_is_bridge_master(lower_dev))
			continue;

		err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info,
						      check_cb, add_cb);
		if (err && err != -EOPNOTSUPP)
			return err;
	}


> ip link del bond0
> ip link add bond0 type bond mode 802.3ad
> ip link set swp1 down && ip link set swp1 master bond0 && ip link set swp1 up
> ip link set swp2 down && ip link set swp2 master bond0 && ip link set swp2 up
> ip link del br0
> ip link add br0 type bridge
> ip link set bond0 master br0
> ip link set swp0 master br0
>
> This should propagate the VLANs to swp1 and swp2 but doesn't:
> bridge vlan add dev bond0 vid 100

I ran through this on my setup and it is indeed propagated to all ports.

Just a thought, when you rebased the ocelot specific stuff to v2, did
you add the number of supported LAGs to ds->num_lags? If not, DSA will
assume that the hardware does not support offloading.

> It's perfectly acceptable to say that this patch set doesn't deal with
> that. But your commit message seems to suggest that it's me who's
> misunderstanding something.

I understand, that is why I explicitly mentioned the lack of static FDB
support for example. But it absolutely should deal with the full list I
specified, so thanks for testing it.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 2/4] net: dsa: Link aggregation support
  2020-12-01 21:48         ` Tobias Waldekranz
@ 2020-12-01 22:23           ` Vladimir Oltean
  0 siblings, 0 replies; 17+ messages in thread
From: Vladimir Oltean @ 2020-12-01 22:23 UTC (permalink / raw)
  To: Tobias Waldekranz
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Tue, Dec 01, 2020 at 10:48:34PM +0100, Tobias Waldekranz wrote:
> On Tue, Dec 01, 2020 at 22:04, Vladimir Oltean <olteanv@gmail.com> wrote:
> > On Tue, Dec 01, 2020 at 03:29:53PM +0100, Tobias Waldekranz wrote:
> >> On Tue, Dec 01, 2020 at 16:03, Vladimir Oltean <olteanv@gmail.com> wrote:
> >> > On Mon, Nov 30, 2020 at 03:06:08PM +0100, Tobias Waldekranz wrote:
> >> >> When a LAG joins a bridge, the DSA subsystem will treat that as each
> >> >> individual port joining the bridge. The driver may look at the port's
> >> >> LAG pointer to see if it is associated with any LAG, if that is
> >> >> required. This is analogue to how switchdev events are replicated out
> >> >> to all lower devices when reaching e.g. a LAG.
> >> >
> >> > Agree with the principle. But doesn't that mean that this code:
> >> >
> >> > static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
> >> > 					      unsigned long event, void *ptr)
> >> > {
> >> > 	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
> >> > 	int err;
> >> >
> >> > 	switch (event) {
> >> > 	case SWITCHDEV_PORT_OBJ_ADD:
> >> > 		err = switchdev_handle_port_obj_add(dev, ptr,
> >> > 						    dsa_slave_dev_check,
> >> > 						    dsa_slave_port_obj_add);
> >> > 		return notifier_from_errno(err);
> >> > 	case SWITCHDEV_PORT_OBJ_DEL:
> >> > 		err = switchdev_handle_port_obj_del(dev, ptr,
> >> > 						    dsa_slave_dev_check,
> >> > 						    dsa_slave_port_obj_del);
> >> > 		return notifier_from_errno(err);
> >> > 	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);
> >> > 	}
> >> >
> >> > 	return NOTIFY_DONE;
> >> > }
> >> >
> >> > should be replaced with something that also reacts to the case where
> >> > "dev" is a LAG? Like, for example, I imagine that a VLAN installed on a
> >> > bridge port that is a LAG should be propagated to the switch ports
> >> > beneath that LAG. Similarly for all bridge attributes.
> >>
> >> That is exactly what switchdev_handle_* does, no? It is this exact
> >> behavior that my statement about switchdev event replication references.
> >
> > I'm sorry, I don't mean to be overly obtuse, but _how_ does the current
> > code propagate a VLAN to a physical port located below a bond? Through
> > magic? The dsa_slave_dev_check is passed as a parameter to
> > switchdev_handle_port_obj_add _exactly_ because the code has needed so
> > far to match only on DSA interfaces and not on bonding interfaces. So
> > the code does not react to VLANs added on a bonding interface. Hence my
> > question.
>
> There is no magic involved, here is the relevant snippet from
> __switchdev_handle_port_obj_add:
>
> 	/* Switch ports might be stacked under e.g. a LAG. Ignore the
> 	 * unsupported devices, another driver might be able to handle them. But
> 	 * propagate to the callers any hard errors.
> 	 *
> 	 * If the driver does its own bookkeeping of stacked ports, it's not
> 	 * necessary to go through this helper.
> 	 */
> 	netdev_for_each_lower_dev(dev, lower_dev, iter) {
> 		if (netif_is_bridge_master(lower_dev))
> 			continue;
>
> 		err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info,
> 						      check_cb, add_cb);
> 		if (err && err != -EOPNOTSUPP)
> 			return err;
> 	}
>

Oh wow, such an odd place to put that. Especially since the entire
reason why switchdev uses notifiers is that you as a switchdev driver
can now explicitly intercept and offload switchdev objects that the
bridge emitted towards a driver that was "not you", such as a vxlan
interface. I guess that's still what's happening now, just that it's
completely non-obvious since it's hidden behind an opaque function.

Very interesting, thanks, I didn't know that.

> > ip link del bond0
> > ip link add bond0 type bond mode 802.3ad
> > ip link set swp1 down && ip link set swp1 master bond0 && ip link set swp1 up
> > ip link set swp2 down && ip link set swp2 master bond0 && ip link set swp2 up
> > ip link del br0
> > ip link add br0 type bridge
> > ip link set bond0 master br0
> > ip link set swp0 master br0
> >
> > This should propagate the VLANs to swp1 and swp2 but doesn't:
> > bridge vlan add dev bond0 vid 100
>
> I ran through this on my setup and it is indeed propagated to all ports.
>
> Just a thought, when you rebased the ocelot specific stuff to v2, did
> you add the number of supported LAGs to ds->num_lags? If not, DSA will
> assume that the hardware does not support offloading.

Ah, yes, that makes sense and that's what was happening. So DSA does the
right thing and does not offload bridge attributes to these ports,
because bonding needs to be done in software, and therefore even
bridging on swp1 and swp2 needs to be done in software. So as far as DSA
is concerned, swp1 and swp2 are standalone ports. This reminds me that I
need to do more testing for switches that can't offload bonding, to make
sure that they do the right thing.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 4/4] net: dsa: tag_dsa: Support reception of packets from LAG devices
  2020-12-01 21:24   ` Vladimir Oltean
@ 2020-12-01 22:31     ` Tobias Waldekranz
  2020-12-02  0:33       ` Vladimir Oltean
  0 siblings, 1 reply; 17+ messages in thread
From: Tobias Waldekranz @ 2020-12-01 22:31 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Tue, Dec 01, 2020 at 23:24, Vladimir Oltean <olteanv@gmail.com> wrote:
> On Mon, Nov 30, 2020 at 03:06:10PM +0100, Tobias Waldekranz wrote:
>> Packets ingressing on a LAG that egress on the CPU port, which are not
>> classified as management, will have a FORWARD tag that does not
>> contain the normal source device/port tuple. Instead the trunk bit
>> will be set, and the port field holds the LAG id.
>> 
>> Since the exact source port information is not available in the tag,
>> frames are injected directly on the LAG interface and thus do never
>> pass through any DSA port interface on ingress.
>> 
>> Management frames (TO_CPU) are not affected and will pass through the
>> DSA port interface as usual.
>> 
>> Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
>> ---
>>  net/dsa/dsa.c     | 12 +++++++++++-
>>  net/dsa/tag_dsa.c | 17 ++++++++++++++++-
>>  2 files changed, 27 insertions(+), 2 deletions(-)
>> 
>> diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
>> index a1b1dc8a4d87..7325bf4608e9 100644
>> --- a/net/dsa/dsa.c
>> +++ b/net/dsa/dsa.c
>> @@ -219,11 +219,21 @@ static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev,
>>  	}
>>  
>>  	skb = nskb;
>> -	p = netdev_priv(skb->dev);
>>  	skb_push(skb, ETH_HLEN);
>>  	skb->pkt_type = PACKET_HOST;
>>  	skb->protocol = eth_type_trans(skb, skb->dev);
>>  
>> +	if (unlikely(!dsa_slave_dev_check(skb->dev))) {
>> +		/* Packet is to be injected directly on an upper
>> +		 * device, e.g. a team/bond, so skip all DSA-port
>> +		 * specific actions.
>> +		 */
>> +		netif_rx(skb);
>> +		return 0;
>
> netif_rx returns an int code, it seems odd to ignore it.

This is exactly the same treatment that the return code from
gro_cells_receive gets just a few lines down. They return the same set
of codes (NET_RX_{SUCCESS,DROP}).

Looking through the source base, there are a few callers that look at
the return value (the overwhelming majority ignore it). Actions vary
from printing warnings (without rate-limit, yikes), setting variables
that are otherwise unused, or bumping a counter (the only reasonable
thing I have seen).

But looking through enqueue_to_backlog, it seems like there already is a
counter for this that is accessible from /proc/net/softnet_data.

>> +	}
>> +
>> +	p = netdev_priv(skb->dev);
>> +
>>  	if (unlikely(cpu_dp->ds->untag_bridge_pvid)) {
>>  		nskb = dsa_untag_bridge_pvid(skb);
>>  		if (!nskb) {

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 net-next 4/4] net: dsa: tag_dsa: Support reception of packets from LAG devices
  2020-12-01 22:31     ` Tobias Waldekranz
@ 2020-12-02  0:33       ` Vladimir Oltean
  0 siblings, 0 replies; 17+ messages in thread
From: Vladimir Oltean @ 2020-12-02  0:33 UTC (permalink / raw)
  To: Tobias Waldekranz
  Cc: davem, kuba, andrew, vivien.didelot, f.fainelli, j.vosburgh,
	vfalico, andy, netdev

On Tue, Dec 01, 2020 at 11:31:02PM +0100, Tobias Waldekranz wrote:
> >> +	if (unlikely(!dsa_slave_dev_check(skb->dev))) {
> >> +		/* Packet is to be injected directly on an upper
> >> +		 * device, e.g. a team/bond, so skip all DSA-port
> >> +		 * specific actions.
> >> +		 */
> >> +		netif_rx(skb);
> >> +		return 0;
> >
> > netif_rx returns an int code, it seems odd to ignore it.
>
> This is exactly the same treatment that the return code from
> gro_cells_receive gets just a few lines down. They return the same set
> of codes (NET_RX_{SUCCESS,DROP}).
>
> Looking through the source base, there are a few callers that look at
> the return value (the overwhelming majority ignore it). Actions vary
> from printing warnings (without rate-limit, yikes), setting variables
> that are otherwise unused, or bumping a counter (the only reasonable
> thing I have seen).
>
> But looking through enqueue_to_backlog, it seems like there already is a
> counter for this that is accessible from /proc/net/softnet_data.

And also, more obviously, in ndo_get_stats64 (ip -s -s link).

Ok, I think you can ignore the return value.

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2020-12-02  0:34 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-30 14:06 [PATCH v2 net-next 0/4] net: dsa: Link aggregation support Tobias Waldekranz
2020-11-30 14:06 ` [PATCH v2 net-next 1/4] net: bonding: Notify ports about their initial state Tobias Waldekranz
2020-11-30 14:06 ` [PATCH v2 net-next 2/4] net: dsa: Link aggregation support Tobias Waldekranz
2020-12-01  1:37   ` Vladimir Oltean
2020-12-01  8:13     ` Tobias Waldekranz
2020-12-01 13:29       ` Vladimir Oltean
2020-12-01 14:22         ` Tobias Waldekranz
2020-12-01 14:03   ` Vladimir Oltean
2020-12-01 14:29     ` Tobias Waldekranz
2020-12-01 20:04       ` Vladimir Oltean
2020-12-01 21:48         ` Tobias Waldekranz
2020-12-01 22:23           ` Vladimir Oltean
2020-11-30 14:06 ` [PATCH v2 net-next 3/4] net: dsa: mv88e6xxx: " Tobias Waldekranz
2020-11-30 14:06 ` [PATCH v2 net-next 4/4] net: dsa: tag_dsa: Support reception of packets from LAG devices Tobias Waldekranz
2020-12-01 21:24   ` Vladimir Oltean
2020-12-01 22:31     ` Tobias Waldekranz
2020-12-02  0:33       ` Vladimir Oltean

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.