diff options
| author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2022-01-15 12:09:44 -0800 | 
|---|---|---|
| committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2022-01-15 12:09:44 -0800 | 
| commit | 762f99f4f3cb41a775b5157dd761217beba65873 (patch) | |
| tree | 2e93410a577dfff1202ea5b0d109460b97650e58 /net/switchdev/switchdev.c | |
| parent | cf73ed894ee939d6706d65e0cd186e4a64e3af6d (diff) | |
| parent | b6e43dddaea3dbfa93327f986beb3ec5e8157c4c (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 5.17 merge window.
Diffstat (limited to 'net/switchdev/switchdev.c')
| -rw-r--r-- | net/switchdev/switchdev.c | 333 | 
1 files changed, 322 insertions, 11 deletions
| diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c index 89a36db47ab4..0b2c18efc079 100644 --- a/net/switchdev/switchdev.c +++ b/net/switchdev/switchdev.c @@ -378,22 +378,283 @@ int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev,  }  EXPORT_SYMBOL_GPL(call_switchdev_blocking_notifiers); +struct switchdev_nested_priv { +	bool (*check_cb)(const struct net_device *dev); +	bool (*foreign_dev_check_cb)(const struct net_device *dev, +				     const struct net_device *foreign_dev); +	const struct net_device *dev; +	struct net_device *lower_dev; +}; + +static int switchdev_lower_dev_walk(struct net_device *lower_dev, +				    struct netdev_nested_priv *priv) +{ +	struct switchdev_nested_priv *switchdev_priv = priv->data; +	bool (*foreign_dev_check_cb)(const struct net_device *dev, +				     const struct net_device *foreign_dev); +	bool (*check_cb)(const struct net_device *dev); +	const struct net_device *dev; + +	check_cb = switchdev_priv->check_cb; +	foreign_dev_check_cb = switchdev_priv->foreign_dev_check_cb; +	dev = switchdev_priv->dev; + +	if (check_cb(lower_dev) && !foreign_dev_check_cb(lower_dev, dev)) { +		switchdev_priv->lower_dev = lower_dev; +		return 1; +	} + +	return 0; +} + +static struct net_device * +switchdev_lower_dev_find(struct net_device *dev, +			 bool (*check_cb)(const struct net_device *dev), +			 bool (*foreign_dev_check_cb)(const struct net_device *dev, +						      const struct net_device *foreign_dev)) +{ +	struct switchdev_nested_priv switchdev_priv = { +		.check_cb = check_cb, +		.foreign_dev_check_cb = foreign_dev_check_cb, +		.dev = dev, +		.lower_dev = NULL, +	}; +	struct netdev_nested_priv priv = { +		.data = &switchdev_priv, +	}; + +	netdev_walk_all_lower_dev_rcu(dev, switchdev_lower_dev_walk, &priv); + +	return switchdev_priv.lower_dev; +} + +static int __switchdev_handle_fdb_add_to_device(struct net_device *dev, +		const struct net_device *orig_dev, +		const struct switchdev_notifier_fdb_info *fdb_info, +		bool (*check_cb)(const struct net_device *dev), +		bool (*foreign_dev_check_cb)(const struct net_device *dev, +					     const struct net_device *foreign_dev), +		int (*add_cb)(struct net_device *dev, +			      const struct net_device *orig_dev, const void *ctx, +			      const struct switchdev_notifier_fdb_info *fdb_info), +		int (*lag_add_cb)(struct net_device *dev, +				  const struct net_device *orig_dev, const void *ctx, +				  const struct switchdev_notifier_fdb_info *fdb_info)) +{ +	const struct switchdev_notifier_info *info = &fdb_info->info; +	struct net_device *br, *lower_dev; +	struct list_head *iter; +	int err = -EOPNOTSUPP; + +	if (check_cb(dev)) +		return add_cb(dev, orig_dev, info->ctx, fdb_info); + +	if (netif_is_lag_master(dev)) { +		if (!switchdev_lower_dev_find(dev, check_cb, foreign_dev_check_cb)) +			goto maybe_bridged_with_us; + +		/* This is a LAG interface that we offload */ +		if (!lag_add_cb) +			return -EOPNOTSUPP; + +		return lag_add_cb(dev, orig_dev, info->ctx, fdb_info); +	} + +	/* Recurse through lower interfaces in case the FDB entry is pointing +	 * towards a bridge device. +	 */ +	if (netif_is_bridge_master(dev)) { +		if (!switchdev_lower_dev_find(dev, check_cb, foreign_dev_check_cb)) +			return 0; + +		/* This is a bridge interface that we offload */ +		netdev_for_each_lower_dev(dev, lower_dev, iter) { +			/* Do not propagate FDB entries across bridges */ +			if (netif_is_bridge_master(lower_dev)) +				continue; + +			/* Bridge ports might be either us, or LAG interfaces +			 * that we offload. +			 */ +			if (!check_cb(lower_dev) && +			    !switchdev_lower_dev_find(lower_dev, check_cb, +						      foreign_dev_check_cb)) +				continue; + +			err = __switchdev_handle_fdb_add_to_device(lower_dev, orig_dev, +								   fdb_info, check_cb, +								   foreign_dev_check_cb, +								   add_cb, lag_add_cb); +			if (err && err != -EOPNOTSUPP) +				return err; +		} + +		return 0; +	} + +maybe_bridged_with_us: +	/* Event is neither on a bridge nor a LAG. Check whether it is on an +	 * interface that is in a bridge with us. +	 */ +	br = netdev_master_upper_dev_get_rcu(dev); +	if (!br || !netif_is_bridge_master(br)) +		return 0; + +	if (!switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb)) +		return 0; + +	return __switchdev_handle_fdb_add_to_device(br, orig_dev, fdb_info, +						    check_cb, foreign_dev_check_cb, +						    add_cb, lag_add_cb); +} + +int switchdev_handle_fdb_add_to_device(struct net_device *dev, +		const struct switchdev_notifier_fdb_info *fdb_info, +		bool (*check_cb)(const struct net_device *dev), +		bool (*foreign_dev_check_cb)(const struct net_device *dev, +					     const struct net_device *foreign_dev), +		int (*add_cb)(struct net_device *dev, +			      const struct net_device *orig_dev, const void *ctx, +			      const struct switchdev_notifier_fdb_info *fdb_info), +		int (*lag_add_cb)(struct net_device *dev, +				  const struct net_device *orig_dev, const void *ctx, +				  const struct switchdev_notifier_fdb_info *fdb_info)) +{ +	int err; + +	err = __switchdev_handle_fdb_add_to_device(dev, dev, fdb_info, +						   check_cb, +						   foreign_dev_check_cb, +						   add_cb, lag_add_cb); +	if (err == -EOPNOTSUPP) +		err = 0; + +	return err; +} +EXPORT_SYMBOL_GPL(switchdev_handle_fdb_add_to_device); + +static int __switchdev_handle_fdb_del_to_device(struct net_device *dev, +		const struct net_device *orig_dev, +		const struct switchdev_notifier_fdb_info *fdb_info, +		bool (*check_cb)(const struct net_device *dev), +		bool (*foreign_dev_check_cb)(const struct net_device *dev, +					     const struct net_device *foreign_dev), +		int (*del_cb)(struct net_device *dev, +			      const struct net_device *orig_dev, const void *ctx, +			      const struct switchdev_notifier_fdb_info *fdb_info), +		int (*lag_del_cb)(struct net_device *dev, +				  const struct net_device *orig_dev, const void *ctx, +				  const struct switchdev_notifier_fdb_info *fdb_info)) +{ +	const struct switchdev_notifier_info *info = &fdb_info->info; +	struct net_device *br, *lower_dev; +	struct list_head *iter; +	int err = -EOPNOTSUPP; + +	if (check_cb(dev)) +		return del_cb(dev, orig_dev, info->ctx, fdb_info); + +	if (netif_is_lag_master(dev)) { +		if (!switchdev_lower_dev_find(dev, check_cb, foreign_dev_check_cb)) +			goto maybe_bridged_with_us; + +		/* This is a LAG interface that we offload */ +		if (!lag_del_cb) +			return -EOPNOTSUPP; + +		return lag_del_cb(dev, orig_dev, info->ctx, fdb_info); +	} + +	/* Recurse through lower interfaces in case the FDB entry is pointing +	 * towards a bridge device. +	 */ +	if (netif_is_bridge_master(dev)) { +		if (!switchdev_lower_dev_find(dev, check_cb, foreign_dev_check_cb)) +			return 0; + +		/* This is a bridge interface that we offload */ +		netdev_for_each_lower_dev(dev, lower_dev, iter) { +			/* Do not propagate FDB entries across bridges */ +			if (netif_is_bridge_master(lower_dev)) +				continue; + +			/* Bridge ports might be either us, or LAG interfaces +			 * that we offload. +			 */ +			if (!check_cb(lower_dev) && +			    !switchdev_lower_dev_find(lower_dev, check_cb, +						      foreign_dev_check_cb)) +				continue; + +			err = __switchdev_handle_fdb_del_to_device(lower_dev, orig_dev, +								   fdb_info, check_cb, +								   foreign_dev_check_cb, +								   del_cb, lag_del_cb); +			if (err && err != -EOPNOTSUPP) +				return err; +		} + +		return 0; +	} + +maybe_bridged_with_us: +	/* Event is neither on a bridge nor a LAG. Check whether it is on an +	 * interface that is in a bridge with us. +	 */ +	br = netdev_master_upper_dev_get_rcu(dev); +	if (!br || !netif_is_bridge_master(br)) +		return 0; + +	if (!switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb)) +		return 0; + +	return __switchdev_handle_fdb_del_to_device(br, orig_dev, fdb_info, +						    check_cb, foreign_dev_check_cb, +						    del_cb, lag_del_cb); +} + +int switchdev_handle_fdb_del_to_device(struct net_device *dev, +		const struct switchdev_notifier_fdb_info *fdb_info, +		bool (*check_cb)(const struct net_device *dev), +		bool (*foreign_dev_check_cb)(const struct net_device *dev, +					     const struct net_device *foreign_dev), +		int (*del_cb)(struct net_device *dev, +			      const struct net_device *orig_dev, const void *ctx, +			      const struct switchdev_notifier_fdb_info *fdb_info), +		int (*lag_del_cb)(struct net_device *dev, +				  const struct net_device *orig_dev, const void *ctx, +				  const struct switchdev_notifier_fdb_info *fdb_info)) +{ +	int err; + +	err = __switchdev_handle_fdb_del_to_device(dev, dev, fdb_info, +						   check_cb, +						   foreign_dev_check_cb, +						   del_cb, lag_del_cb); +	if (err == -EOPNOTSUPP) +		err = 0; + +	return err; +} +EXPORT_SYMBOL_GPL(switchdev_handle_fdb_del_to_device); +  static int __switchdev_handle_port_obj_add(struct net_device *dev,  			struct switchdev_notifier_port_obj_info *port_obj_info,  			bool (*check_cb)(const struct net_device *dev), -			int (*add_cb)(struct net_device *dev, +			int (*add_cb)(struct net_device *dev, const void *ctx,  				      const struct switchdev_obj *obj,  				      struct netlink_ext_ack *extack))  { +	struct switchdev_notifier_info *info = &port_obj_info->info;  	struct netlink_ext_ack *extack;  	struct net_device *lower_dev;  	struct list_head *iter;  	int err = -EOPNOTSUPP; -	extack = switchdev_notifier_info_to_extack(&port_obj_info->info); +	extack = switchdev_notifier_info_to_extack(info);  	if (check_cb(dev)) { -		err = add_cb(dev, port_obj_info->obj, extack); +		err = add_cb(dev, info->ctx, port_obj_info->obj, extack);  		if (err != -EOPNOTSUPP)  			port_obj_info->handled = true;  		return err; @@ -422,7 +683,7 @@ static int __switchdev_handle_port_obj_add(struct net_device *dev,  int switchdev_handle_port_obj_add(struct net_device *dev,  			struct switchdev_notifier_port_obj_info *port_obj_info,  			bool (*check_cb)(const struct net_device *dev), -			int (*add_cb)(struct net_device *dev, +			int (*add_cb)(struct net_device *dev, const void *ctx,  				      const struct switchdev_obj *obj,  				      struct netlink_ext_ack *extack))  { @@ -439,15 +700,16 @@ EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add);  static int __switchdev_handle_port_obj_del(struct net_device *dev,  			struct switchdev_notifier_port_obj_info *port_obj_info,  			bool (*check_cb)(const struct net_device *dev), -			int (*del_cb)(struct net_device *dev, +			int (*del_cb)(struct net_device *dev, const void *ctx,  				      const struct switchdev_obj *obj))  { +	struct switchdev_notifier_info *info = &port_obj_info->info;  	struct net_device *lower_dev;  	struct list_head *iter;  	int err = -EOPNOTSUPP;  	if (check_cb(dev)) { -		err = del_cb(dev, port_obj_info->obj); +		err = del_cb(dev, info->ctx, port_obj_info->obj);  		if (err != -EOPNOTSUPP)  			port_obj_info->handled = true;  		return err; @@ -476,7 +738,7 @@ static int __switchdev_handle_port_obj_del(struct net_device *dev,  int switchdev_handle_port_obj_del(struct net_device *dev,  			struct switchdev_notifier_port_obj_info *port_obj_info,  			bool (*check_cb)(const struct net_device *dev), -			int (*del_cb)(struct net_device *dev, +			int (*del_cb)(struct net_device *dev, const void *ctx,  				      const struct switchdev_obj *obj))  {  	int err; @@ -492,19 +754,20 @@ EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del);  static int __switchdev_handle_port_attr_set(struct net_device *dev,  			struct switchdev_notifier_port_attr_info *port_attr_info,  			bool (*check_cb)(const struct net_device *dev), -			int (*set_cb)(struct net_device *dev, +			int (*set_cb)(struct net_device *dev, const void *ctx,  				      const struct switchdev_attr *attr,  				      struct netlink_ext_ack *extack))  { +	struct switchdev_notifier_info *info = &port_attr_info->info;  	struct netlink_ext_ack *extack;  	struct net_device *lower_dev;  	struct list_head *iter;  	int err = -EOPNOTSUPP; -	extack = switchdev_notifier_info_to_extack(&port_attr_info->info); +	extack = switchdev_notifier_info_to_extack(info);  	if (check_cb(dev)) { -		err = set_cb(dev, port_attr_info->attr, extack); +		err = set_cb(dev, info->ctx, port_attr_info->attr, extack);  		if (err != -EOPNOTSUPP)  			port_attr_info->handled = true;  		return err; @@ -533,7 +796,7 @@ static int __switchdev_handle_port_attr_set(struct net_device *dev,  int switchdev_handle_port_attr_set(struct net_device *dev,  			struct switchdev_notifier_port_attr_info *port_attr_info,  			bool (*check_cb)(const struct net_device *dev), -			int (*set_cb)(struct net_device *dev, +			int (*set_cb)(struct net_device *dev, const void *ctx,  				      const struct switchdev_attr *attr,  				      struct netlink_ext_ack *extack))  { @@ -546,3 +809,51 @@ int switchdev_handle_port_attr_set(struct net_device *dev,  	return err;  }  EXPORT_SYMBOL_GPL(switchdev_handle_port_attr_set); + +int switchdev_bridge_port_offload(struct net_device *brport_dev, +				  struct net_device *dev, const void *ctx, +				  struct notifier_block *atomic_nb, +				  struct notifier_block *blocking_nb, +				  bool tx_fwd_offload, +				  struct netlink_ext_ack *extack) +{ +	struct switchdev_notifier_brport_info brport_info = { +		.brport = { +			.dev = dev, +			.ctx = ctx, +			.atomic_nb = atomic_nb, +			.blocking_nb = blocking_nb, +			.tx_fwd_offload = tx_fwd_offload, +		}, +	}; +	int err; + +	ASSERT_RTNL(); + +	err = call_switchdev_blocking_notifiers(SWITCHDEV_BRPORT_OFFLOADED, +						brport_dev, &brport_info.info, +						extack); +	return notifier_to_errno(err); +} +EXPORT_SYMBOL_GPL(switchdev_bridge_port_offload); + +void switchdev_bridge_port_unoffload(struct net_device *brport_dev, +				     const void *ctx, +				     struct notifier_block *atomic_nb, +				     struct notifier_block *blocking_nb) +{ +	struct switchdev_notifier_brport_info brport_info = { +		.brport = { +			.ctx = ctx, +			.atomic_nb = atomic_nb, +			.blocking_nb = blocking_nb, +		}, +	}; + +	ASSERT_RTNL(); + +	call_switchdev_blocking_notifiers(SWITCHDEV_BRPORT_UNOFFLOADED, +					  brport_dev, &brport_info.info, +					  NULL); +} +EXPORT_SYMBOL_GPL(switchdev_bridge_port_unoffload); | 
