realtek: Backport LAG functionality for DSA
Add the LAG configuration API for DSA as found in Linux 5.12 so that we can implement it in the dsa driver. Signed-off-by: Sebastian Gottschall <s.gottschall@dd-wrt.com> Signed-off-by: Birger Koblitz <git@birger-koblitz.de>
This commit is contained in:
		 Birger Koblitz
					Birger Koblitz
				
			
				
					committed by
					
						 Daniel Golle
						Daniel Golle
					
				
			
			
				
	
			
			
			 Daniel Golle
						Daniel Golle
					
				
			
						parent
						
							77f3e2ea17
						
					
				
				
					commit
					8557b458e2
				
			
							
								
								
									
										759
									
								
								target/linux/realtek/patches-5.10/709-lag-offloading.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										759
									
								
								target/linux/realtek/patches-5.10/709-lag-offloading.patch
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,759 @@ | |||||||
|  | --- a/drivers/net/bonding/bond_main.c | ||||||
|  | +++ b/drivers/net/bonding/bond_main.c | ||||||
|  | @@ -2046,6 +2046,8 @@ int bond_enslave(struct net_device *bond | ||||||
|  |  		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); | ||||||
|  | --- a/include/net/dsa.h | ||||||
|  | +++ b/include/net/dsa.h | ||||||
|  | @@ -149,8 +149,41 @@ struct dsa_switch_tree { | ||||||
|  |   | ||||||
|  |  	/* List of DSA links composing the routing table */ | ||||||
|  |  	struct list_head rtable; | ||||||
|  | + | ||||||
|  | +	/* Maps offloaded LAG netdevs to a zero-based linear ID for | ||||||
|  | +	 * drivers that need it. | ||||||
|  | +	 */ | ||||||
|  | +	struct net_device **lags; | ||||||
|  | +	unsigned int lags_len; | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  | +#define dsa_lags_foreach_id(_id, _dst)				\ | ||||||
|  | +	for ((_id) = 0; (_id) < (_dst)->lags_len; (_id)++)	\ | ||||||
|  | +		if ((_dst)->lags[(_id)]) | ||||||
|  | + | ||||||
|  | +#define dsa_lag_foreach_port(_dp, _dst, _lag)			\ | ||||||
|  | +	list_for_each_entry((_dp), &(_dst)->ports, list)	\ | ||||||
|  | +		if ((_dp)->lag_dev == (_lag)) | ||||||
|  | + | ||||||
|  | +static inline struct net_device *dsa_lag_dev(struct dsa_switch_tree *dst, | ||||||
|  | +					     unsigned int id) | ||||||
|  | +{ | ||||||
|  | +	return dst->lags[id]; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static inline int dsa_lag_id(struct dsa_switch_tree *dst, | ||||||
|  | +			     struct net_device *lag) | ||||||
|  | +{ | ||||||
|  | +	unsigned int id; | ||||||
|  | + | ||||||
|  | +	dsa_lags_foreach_id(id, dst) { | ||||||
|  | +		if (dsa_lag_dev(dst, id) == lag) | ||||||
|  | +			return id; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	return -ENODEV; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  |  /* TC matchall action types */ | ||||||
|  |  enum dsa_port_mall_action_type { | ||||||
|  |  	DSA_PORT_MALL_MIRROR, | ||||||
|  | @@ -220,6 +253,8 @@ struct dsa_port { | ||||||
|  |  	bool			devlink_port_setup; | ||||||
|  |  	struct phylink		*pl; | ||||||
|  |  	struct phylink_config	pl_config; | ||||||
|  | +	struct net_device	*lag_dev; | ||||||
|  | +	bool			lag_tx_enabled; | ||||||
|  |   | ||||||
|  |  	struct list_head list; | ||||||
|  |   | ||||||
|  | @@ -340,6 +375,14 @@ struct dsa_switch { | ||||||
|  |  	 */ | ||||||
|  |  	bool			mtu_enforcement_ingress; | ||||||
|  |   | ||||||
|  | +	/* Drivers that benefit from having an ID associated with each | ||||||
|  | +	 * offloaded LAG should set this to the maximum number of | ||||||
|  | +	 * supported IDs. DSA will then maintain a mapping of _at | ||||||
|  | +	 * least_ these many IDs, accessible to drivers via | ||||||
|  | +	 * dsa_lag_id(). | ||||||
|  | +	 */ | ||||||
|  | +	unsigned int		num_lag_ids; | ||||||
|  | + | ||||||
|  |  	size_t num_ports; | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  | @@ -432,6 +475,18 @@ static inline bool dsa_port_is_vlan_filt | ||||||
|  |  		return dp->vlan_filtering; | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +static inline | ||||||
|  | +struct net_device *dsa_port_to_bridge_port(const struct dsa_port *dp) | ||||||
|  | +{ | ||||||
|  | +	if (!dp->bridge_dev) | ||||||
|  | +		return NULL; | ||||||
|  | + | ||||||
|  | +	if (dp->lag_dev) | ||||||
|  | +		return dp->lag_dev; | ||||||
|  | + | ||||||
|  | +	return dp->slave; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  |  typedef int dsa_fdb_dump_cb_t(const unsigned char *addr, u16 vid, | ||||||
|  |  			      bool is_static, void *data); | ||||||
|  |  struct dsa_switch_ops { | ||||||
|  | @@ -629,6 +684,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); | ||||||
|  | +	int	(*crosschip_lag_join)(struct dsa_switch *ds, int sw_index, | ||||||
|  | +				      int port, struct net_device *lag, | ||||||
|  | +				      struct netdev_lag_upper_info *info); | ||||||
|  | +	int	(*crosschip_lag_leave)(struct dsa_switch *ds, int sw_index, | ||||||
|  | +				       int port, struct net_device *lag); | ||||||
|  |   | ||||||
|  |  	/* | ||||||
|  |  	 * PTP functionality | ||||||
|  | @@ -660,6 +722,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); | ||||||
|  | +	int	(*port_lag_join)(struct dsa_switch *ds, int port, | ||||||
|  | +				 struct net_device *lag, | ||||||
|  | +				 struct netdev_lag_upper_info *info); | ||||||
|  | +	int	(*port_lag_leave)(struct dsa_switch *ds, int port, | ||||||
|  | +				  struct net_device *lag); | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  |  #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes)		\ | ||||||
|  | --- a/net/dsa/dsa.c | ||||||
|  | +++ b/net/dsa/dsa.c | ||||||
|  | @@ -220,11 +220,21 @@ static int dsa_switch_rcv(struct sk_buff | ||||||
|  |  	} | ||||||
|  |   | ||||||
|  |  	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) { | ||||||
|  | --- a/net/dsa/dsa2.c | ||||||
|  | +++ b/net/dsa/dsa2.c | ||||||
|  | @@ -21,6 +21,65 @@ | ||||||
|  |  static DEFINE_MUTEX(dsa2_mutex); | ||||||
|  |  LIST_HEAD(dsa_tree_list); | ||||||
|  |   | ||||||
|  | +/** | ||||||
|  | + * dsa_lag_map() - Map LAG netdev to a linear LAG ID | ||||||
|  | + * @dst: Tree in which to record the mapping. | ||||||
|  | + * @lag: Netdev that is to be mapped to an ID. | ||||||
|  | + * | ||||||
|  | + * dsa_lag_id/dsa_lag_dev can then be used to translate between the | ||||||
|  | + * two spaces. The size of the mapping space is determined by the | ||||||
|  | + * driver by setting ds->num_lag_ids. It is perfectly legal to leave | ||||||
|  | + * it unset if it is not needed, in which case these functions become | ||||||
|  | + * no-ops. | ||||||
|  | + */ | ||||||
|  | +void dsa_lag_map(struct dsa_switch_tree *dst, struct net_device *lag) | ||||||
|  | +{ | ||||||
|  | +	unsigned int id; | ||||||
|  | + | ||||||
|  | +	if (dsa_lag_id(dst, lag) >= 0) | ||||||
|  | +		/* Already mapped */ | ||||||
|  | +		return; | ||||||
|  | + | ||||||
|  | +	for (id = 0; id < dst->lags_len; id++) { | ||||||
|  | +		if (!dsa_lag_dev(dst, id)) { | ||||||
|  | +			dst->lags[id] = lag; | ||||||
|  | +			return; | ||||||
|  | +		} | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	/* No IDs left, which is OK. Some drivers do not need it. The | ||||||
|  | +	 * ones that do, e.g. mv88e6xxx, will discover that dsa_lag_id | ||||||
|  | +	 * returns an error for this device when joining the LAG. The | ||||||
|  | +	 * driver can then return -EOPNOTSUPP back to DSA, which will | ||||||
|  | +	 * fall back to a software LAG. | ||||||
|  | +	 */ | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +/** | ||||||
|  | + * dsa_lag_unmap() - Remove a LAG ID mapping | ||||||
|  | + * @dst: Tree in which the mapping is recorded. | ||||||
|  | + * @lag: Netdev that was mapped. | ||||||
|  | + * | ||||||
|  | + * As there may be multiple users of the mapping, it is only removed | ||||||
|  | + * if there are no other references to it. | ||||||
|  | + */ | ||||||
|  | +void dsa_lag_unmap(struct dsa_switch_tree *dst, struct net_device *lag) | ||||||
|  | +{ | ||||||
|  | +	struct dsa_port *dp; | ||||||
|  | +	unsigned int id; | ||||||
|  | + | ||||||
|  | +	dsa_lag_foreach_port(dp, dst, lag) | ||||||
|  | +		/* There are remaining users of this mapping */ | ||||||
|  | +		return; | ||||||
|  | + | ||||||
|  | +	dsa_lags_foreach_id(id, dst) { | ||||||
|  | +		if (dsa_lag_dev(dst, id) == lag) { | ||||||
|  | +			dst->lags[id] = NULL; | ||||||
|  | +			break; | ||||||
|  | +		} | ||||||
|  | +	} | ||||||
|  | +} | ||||||
|  | + | ||||||
|  |  struct dsa_switch *dsa_switch_find(int tree_index, int sw_index) | ||||||
|  |  { | ||||||
|  |  	struct dsa_switch_tree *dst; | ||||||
|  | @@ -597,6 +656,32 @@ static void dsa_tree_teardown_master(str | ||||||
|  |  			dsa_master_teardown(dp->master); | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +static int dsa_tree_setup_lags(struct dsa_switch_tree *dst) | ||||||
|  | +{ | ||||||
|  | +	unsigned int len = 0; | ||||||
|  | +	struct dsa_port *dp; | ||||||
|  | + | ||||||
|  | +	list_for_each_entry(dp, &dst->ports, list) { | ||||||
|  | +		if (dp->ds->num_lag_ids > len) | ||||||
|  | +			len = dp->ds->num_lag_ids; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	if (!len) | ||||||
|  | +		return 0; | ||||||
|  | + | ||||||
|  | +	dst->lags = kcalloc(len, sizeof(*dst->lags), GFP_KERNEL); | ||||||
|  | +	if (!dst->lags) | ||||||
|  | +		return -ENOMEM; | ||||||
|  | + | ||||||
|  | +	dst->lags_len = len; | ||||||
|  | +	return 0; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static void dsa_tree_teardown_lags(struct dsa_switch_tree *dst) | ||||||
|  | +{ | ||||||
|  | +	kfree(dst->lags); | ||||||
|  | +} | ||||||
|  | + | ||||||
|  |  static int dsa_tree_setup(struct dsa_switch_tree *dst) | ||||||
|  |  { | ||||||
|  |  	bool complete; | ||||||
|  | @@ -624,12 +709,18 @@ static int dsa_tree_setup(struct dsa_swi | ||||||
|  |  	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: | ||||||
|  | @@ -645,6 +736,8 @@ static void dsa_tree_teardown(struct dsa | ||||||
|  |  	if (!dst->setup) | ||||||
|  |  		return; | ||||||
|  |   | ||||||
|  | +	dsa_tree_teardown_lags(dst); | ||||||
|  | + | ||||||
|  |  	dsa_tree_teardown_master(dst); | ||||||
|  |   | ||||||
|  |  	dsa_tree_teardown_switches(dst); | ||||||
|  | --- 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,15 @@ struct dsa_notifier_mdb_info { | ||||||
|  |  	int port; | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  | +/* DSA_NOTIFIER_LAG_* */ | ||||||
|  | +struct dsa_notifier_lag_info { | ||||||
|  | +	struct net_device *lag; | ||||||
|  | +	int sw_index; | ||||||
|  | +	int port; | ||||||
|  | + | ||||||
|  | +	struct netdev_lag_upper_info *info; | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  |  /* DSA_NOTIFIER_VLAN_* */ | ||||||
|  |  struct dsa_notifier_vlan_info { | ||||||
|  |  	const struct switchdev_obj_port_vlan *vlan; | ||||||
|  | @@ -149,6 +161,11 @@ void dsa_port_disable_rt(struct dsa_port | ||||||
|  |  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, | ||||||
|  | +		      struct netdev_lag_upper_info *uinfo); | ||||||
|  | +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); | ||||||
|  | @@ -181,6 +198,71 @@ int dsa_port_link_register_of(struct dsa | ||||||
|  |  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_offloads_netdev(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, and event | ||||||
|  | +		 * was emitted for the ports themselves. | ||||||
|  | +		 */ | ||||||
|  | +		return true; | ||||||
|  | + | ||||||
|  | +	if (dp->bridge_dev == dev) | ||||||
|  | +		/* DSA ports connected to a bridge, and event was emitted | ||||||
|  | +		 * for the bridge. | ||||||
|  | +		 */ | ||||||
|  | +		return true; | ||||||
|  | + | ||||||
|  | +	if (dp->lag_dev == dev) | ||||||
|  | +		/* DSA ports connected to a bridge via a LAG */ | ||||||
|  | +		return true; | ||||||
|  | + | ||||||
|  | +	return false; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp, | ||||||
|  | +						 struct net_device *dev) | ||||||
|  | +{ | ||||||
|  | +	return dsa_port_to_bridge_port(dp) == dev; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static inline bool dsa_port_offloads_bridge(struct dsa_port *dp, | ||||||
|  | +					    struct net_device *bridge_dev) | ||||||
|  | +{ | ||||||
|  | +	/* DSA ports connected to a bridge, and event was emitted | ||||||
|  | +	 * for the bridge. | ||||||
|  | +	 */ | ||||||
|  | +	return dp->bridge_dev == bridge_dev; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +/* Returns true if any port of this tree offloads the given net_device */ | ||||||
|  | +static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst, | ||||||
|  | +						 struct net_device *dev) | ||||||
|  | +{ | ||||||
|  | +	struct dsa_port *dp; | ||||||
|  | + | ||||||
|  | +	list_for_each_entry(dp, &dst->ports, list) | ||||||
|  | +		if (dsa_port_offloads_bridge_port(dp, dev)) | ||||||
|  | +			return true; | ||||||
|  | + | ||||||
|  | +	return false; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +/* Returns true if any port of this tree offloads the given net_device */ | ||||||
|  | +static inline bool dsa_tree_offloads_netdev(struct dsa_switch_tree *dst, | ||||||
|  | +					    struct net_device *dev) | ||||||
|  | +{ | ||||||
|  | +	struct dsa_port *dp; | ||||||
|  | + | ||||||
|  | +	list_for_each_entry(dp, &dst->ports, list) | ||||||
|  | +		if (dsa_port_offloads_netdev(dp, dev)) | ||||||
|  | +			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); | ||||||
|  | @@ -285,6 +367,9 @@ int dsa_switch_register_notifier(struct | ||||||
|  |  void dsa_switch_unregister_notifier(struct dsa_switch *ds); | ||||||
|  |   | ||||||
|  |  /* dsa2.c */ | ||||||
|  | +void dsa_lag_map(struct dsa_switch_tree *dst, struct net_device *lag); | ||||||
|  | +void dsa_lag_unmap(struct dsa_switch_tree *dst, struct net_device *lag); | ||||||
|  | + | ||||||
|  |  extern struct list_head dsa_tree_list; | ||||||
|  |   | ||||||
|  |  #endif | ||||||
|  | --- a/net/dsa/port.c | ||||||
|  | +++ b/net/dsa/port.c | ||||||
|  | @@ -193,6 +193,99 @@ void dsa_port_bridge_leave(struct dsa_po | ||||||
|  |  	dsa_port_set_state_now(dp, BR_STATE_FORWARDING); | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +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, | ||||||
|  | +	}; | ||||||
|  | +	bool tx_enabled; | ||||||
|  | + | ||||||
|  | +	if (!dp->lag_dev) | ||||||
|  | +		return 0; | ||||||
|  | + | ||||||
|  | +	/* 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. | ||||||
|  | +	 */ | ||||||
|  | +	tx_enabled = linfo->link_up && linfo->tx_enabled; | ||||||
|  | + | ||||||
|  | +	if (tx_enabled == dp->lag_tx_enabled) | ||||||
|  | +		return 0; | ||||||
|  | + | ||||||
|  | +	dp->lag_tx_enabled = tx_enabled; | ||||||
|  | + | ||||||
|  | +	return dsa_port_notify(dp, DSA_NOTIFIER_LAG_CHANGE, &info); | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +int dsa_port_lag_join(struct dsa_port *dp, struct net_device *lag, | ||||||
|  | +		      struct netdev_lag_upper_info *uinfo) | ||||||
|  | +{ | ||||||
|  | +	struct dsa_notifier_lag_info info = { | ||||||
|  | +		.sw_index = dp->ds->index, | ||||||
|  | +		.port = dp->index, | ||||||
|  | +		.lag = lag, | ||||||
|  | +		.info = uinfo, | ||||||
|  | +	}; | ||||||
|  | +	struct net_device *bridge_dev; | ||||||
|  | +	int err; | ||||||
|  | + | ||||||
|  | +	dsa_lag_map(dp->ds->dst, lag); | ||||||
|  | +	dp->lag_dev = lag; | ||||||
|  | + | ||||||
|  | +	err = dsa_port_notify(dp, DSA_NOTIFIER_LAG_JOIN, &info); | ||||||
|  | +	if (err) | ||||||
|  | +		goto err_lag_join; | ||||||
|  | + | ||||||
|  | +	bridge_dev = netdev_master_upper_dev_get(lag); | ||||||
|  | +	if (!bridge_dev || !netif_is_bridge_master(bridge_dev)) | ||||||
|  | +		return 0; | ||||||
|  | + | ||||||
|  | +	err = dsa_port_bridge_join(dp, bridge_dev); | ||||||
|  | +	if (err) | ||||||
|  | +		goto err_bridge_join; | ||||||
|  | + | ||||||
|  | +	return 0; | ||||||
|  | + | ||||||
|  | +err_bridge_join: | ||||||
|  | +	dsa_port_notify(dp, DSA_NOTIFIER_LAG_LEAVE, &info); | ||||||
|  | +err_lag_join: | ||||||
|  | +	dp->lag_dev = NULL; | ||||||
|  | +	dsa_lag_unmap(dp->ds->dst, lag); | ||||||
|  | +	return err; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag) | ||||||
|  | +{ | ||||||
|  | +	struct dsa_notifier_lag_info info = { | ||||||
|  | +		.sw_index = dp->ds->index, | ||||||
|  | +		.port = dp->index, | ||||||
|  | +		.lag = lag, | ||||||
|  | +	}; | ||||||
|  | +	int err; | ||||||
|  | + | ||||||
|  | +	if (!dp->lag_dev) | ||||||
|  | +		return; | ||||||
|  | + | ||||||
|  | +	/* 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); | ||||||
|  | + | ||||||
|  | +	dp->lag_tx_enabled = false; | ||||||
|  | +	dp->lag_dev = NULL; | ||||||
|  | + | ||||||
|  | +	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_unmap(dp->ds->dst, lag); | ||||||
|  | +} | ||||||
|  | + | ||||||
|  |  /* Must be called under rcu_read_lock() */ | ||||||
|  |  static bool dsa_port_can_apply_vlan_filtering(struct dsa_port *dp, | ||||||
|  |  					      bool vlan_filtering) | ||||||
|  | --- a/net/dsa/slave.c | ||||||
|  | +++ b/net/dsa/slave.c | ||||||
|  | @@ -337,9 +337,6 @@ static int dsa_slave_vlan_add(struct net | ||||||
|  |  	struct switchdev_obj_port_vlan vlan; | ||||||
|  |  	int vid, err; | ||||||
|  |   | ||||||
|  | -	if (obj->orig_dev != dev) | ||||||
|  | -		return -EOPNOTSUPP; | ||||||
|  | - | ||||||
|  |  	if (dsa_port_skip_vlan_configuration(dp)) | ||||||
|  |  		return 0; | ||||||
|  |   | ||||||
|  | @@ -394,11 +391,13 @@ static int dsa_slave_port_obj_add(struct | ||||||
|  |   | ||||||
|  |  	switch (obj->id) { | ||||||
|  |  	case SWITCHDEV_OBJ_ID_PORT_MDB: | ||||||
|  | -		if (obj->orig_dev != dev) | ||||||
|  | +		if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev)) | ||||||
|  |  			return -EOPNOTSUPP; | ||||||
|  |  		err = dsa_port_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj), trans); | ||||||
|  |  		break; | ||||||
|  |  	case SWITCHDEV_OBJ_ID_HOST_MDB: | ||||||
|  | +		if (!dsa_port_offloads_bridge(dp, obj->orig_dev)) | ||||||
|  | +			return -EOPNOTSUPP; | ||||||
|  |  		/* DSA can directly translate this to a normal MDB add, | ||||||
|  |  		 * but on the CPU port. | ||||||
|  |  		 */ | ||||||
|  | @@ -406,6 +405,9 @@ static int dsa_slave_port_obj_add(struct | ||||||
|  |  				       trans); | ||||||
|  |  		break; | ||||||
|  |  	case SWITCHDEV_OBJ_ID_PORT_VLAN: | ||||||
|  | +		if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev)) | ||||||
|  | +			return -EOPNOTSUPP; | ||||||
|  | + | ||||||
|  |  		err = dsa_slave_vlan_add(dev, obj, trans); | ||||||
|  |  		break; | ||||||
|  |  	default: | ||||||
|  | @@ -424,9 +426,6 @@ static int dsa_slave_vlan_del(struct net | ||||||
|  |  	struct switchdev_obj_port_vlan *vlan; | ||||||
|  |  	int vid, err; | ||||||
|  |   | ||||||
|  | -	if (obj->orig_dev != dev) | ||||||
|  | -		return -EOPNOTSUPP; | ||||||
|  | - | ||||||
|  |  	if (dsa_port_skip_vlan_configuration(dp)) | ||||||
|  |  		return 0; | ||||||
|  |   | ||||||
|  | @@ -453,17 +452,22 @@ static int dsa_slave_port_obj_del(struct | ||||||
|  |   | ||||||
|  |  	switch (obj->id) { | ||||||
|  |  	case SWITCHDEV_OBJ_ID_PORT_MDB: | ||||||
|  | -		if (obj->orig_dev != dev) | ||||||
|  | +		if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev)) | ||||||
|  |  			return -EOPNOTSUPP; | ||||||
|  |  		err = dsa_port_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj)); | ||||||
|  |  		break; | ||||||
|  |  	case SWITCHDEV_OBJ_ID_HOST_MDB: | ||||||
|  | +		if (!dsa_port_offloads_bridge(dp, obj->orig_dev)) | ||||||
|  | +			return -EOPNOTSUPP; | ||||||
|  |  		/* DSA can directly translate this to a normal MDB add, | ||||||
|  |  		 * but on the CPU port. | ||||||
|  |  		 */ | ||||||
|  |  		err = dsa_port_mdb_del(dp->cpu_dp, SWITCHDEV_OBJ_PORT_MDB(obj)); | ||||||
|  |  		break; | ||||||
|  |  	case SWITCHDEV_OBJ_ID_PORT_VLAN: | ||||||
|  | +		if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev)) | ||||||
|  | +			return -EOPNOTSUPP; | ||||||
|  | + | ||||||
|  |  		err = dsa_slave_vlan_del(dev, obj); | ||||||
|  |  		break; | ||||||
|  |  	default: | ||||||
|  | @@ -1993,6 +1997,46 @@ static int dsa_slave_changeupper(struct | ||||||
|  |  			dsa_port_bridge_leave(dp, info->upper_dev); | ||||||
|  |  			err = NOTIFY_OK; | ||||||
|  |  		} | ||||||
|  | +	} else if (netif_is_lag_master(info->upper_dev)) { | ||||||
|  | +		if (info->linking) { | ||||||
|  | +			err = dsa_port_lag_join(dp, info->upper_dev, | ||||||
|  | +						info->upper_info); | ||||||
|  | +			if (err == -EOPNOTSUPP) { | ||||||
|  | +				NL_SET_ERR_MSG_MOD(info->info.extack, | ||||||
|  | +						   "Offloading not supported"); | ||||||
|  | +				err = 0; | ||||||
|  | +			} | ||||||
|  | +			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 (!dp->lag_dev) | ||||||
|  | +			/* Software LAG */ | ||||||
|  | +			continue; | ||||||
|  | + | ||||||
|  | +		err = dsa_slave_changeupper(lower, info); | ||||||
|  | +		if (notifier_to_errno(err)) | ||||||
|  | +			break; | ||||||
|  |  	} | ||||||
|  |   | ||||||
|  |  	return err; | ||||||
|  | @@ -2078,10 +2122,26 @@ static int dsa_slave_netdevice_event(str | ||||||
|  |  		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; | ||||||
|  |   | ||||||
|  | -		return dsa_slave_changeupper(dev, ptr); | ||||||
|  | +		dp = dsa_slave_to_port(dev); | ||||||
|  | + | ||||||
|  | +		err = dsa_port_lag_change(dp, info->lower_state_info); | ||||||
|  | +		return notifier_from_errno(err); | ||||||
|  | +	} | ||||||
|  |  	} | ||||||
|  |   | ||||||
|  |  	return NOTIFY_DONE; | ||||||
|  | @@ -2229,6 +2289,15 @@ static int dsa_slave_switchdev_event(str | ||||||
|  |  			if (!fdb_info->added_by_user && | ||||||
|  |  			    !dp->ds->assisted_learning_on_cpu_port) | ||||||
|  |  				return NOTIFY_DONE; | ||||||
|  | + | ||||||
|  | +			/* When the bridge learns an address on an offloaded | ||||||
|  | +			 * LAG we don't want to send traffic to the CPU, the | ||||||
|  | +			 * other ports bridged with the LAG should be able to | ||||||
|  | +			 * autonomously forward towards it. | ||||||
|  | +			 */ | ||||||
|  | +			if (dsa_tree_offloads_netdev(dp->ds->dst, dev)) | ||||||
|  | +				return NOTIFY_DONE; | ||||||
|  | + | ||||||
|  |  		} | ||||||
|  |   | ||||||
|  |  		if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del) | ||||||
|  | --- a/net/dsa/switch.c | ||||||
|  | +++ b/net/dsa/switch.c | ||||||
|  | @@ -178,6 +178,47 @@ static int dsa_switch_fdb_del(struct dsa | ||||||
|  |  	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); | ||||||
|  | + | ||||||
|  | +	if (ds->index != info->sw_index && ds->ops->crosschip_lag_change) | ||||||
|  | +		return ds->ops->crosschip_lag_change(ds, info->sw_index, | ||||||
|  | +						     info->port); | ||||||
|  | + | ||||||
|  | +	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, | ||||||
|  | +					      info->info); | ||||||
|  | + | ||||||
|  | +	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, | ||||||
|  | +						   info->info); | ||||||
|  | + | ||||||
|  | +	return -EOPNOTSUPP; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +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) | ||||||
|  | +		return ds->ops->port_lag_leave(ds, info->port, info->lag); | ||||||
|  | + | ||||||
|  | +	if (ds->index != info->sw_index && ds->ops->crosschip_lag_leave) | ||||||
|  | +		return ds->ops->crosschip_lag_leave(ds, info->sw_index, | ||||||
|  | +						    info->port, info->lag); | ||||||
|  | + | ||||||
|  | +	return -EOPNOTSUPP; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  |  static bool dsa_switch_mdb_match(struct dsa_switch *ds, int port, | ||||||
|  |  				 struct dsa_notifier_mdb_info *info) | ||||||
|  |  { | ||||||
|  | @@ -325,6 +366,15 @@ static int dsa_switch_event(struct notif | ||||||
|  |  	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; | ||||||
|  | --- a/net/dsa/tag_dsa.c | ||||||
|  | +++ b/net/dsa/tag_dsa.c | ||||||
|  | @@ -82,7 +82,19 @@ static struct sk_buff *dsa_rcv(struct sk | ||||||
|  |  	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(cpu_dp->dst, source_port); | ||||||
|  | +	} else { | ||||||
|  | +		skb->dev = dsa_master_find_slave(dev, source_device, | ||||||
|  | +						 source_port); | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  |  	if (!skb->dev) | ||||||
|  |  		return NULL; | ||||||
|  |   | ||||||
		Reference in New Issue
	
	Block a user