diff options
Diffstat (limited to 'net/switchdev/switchdev.c')
| -rw-r--r-- | net/switchdev/switchdev.c | 213 | 
1 files changed, 167 insertions, 46 deletions
diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c index 74b9d916a58b..5df9d1138ac9 100644 --- a/net/switchdev/switchdev.c +++ b/net/switchdev/switchdev.c @@ -353,34 +353,35 @@ static size_t switchdev_obj_size(const struct switchdev_obj *obj)  	return 0;  } -static int __switchdev_port_obj_add(struct net_device *dev, -				    const struct switchdev_obj *obj, -				    struct switchdev_trans *trans) +static int switchdev_port_obj_notify(enum switchdev_notifier_type nt, +				     struct net_device *dev, +				     const struct switchdev_obj *obj, +				     struct switchdev_trans *trans, +				     struct netlink_ext_ack *extack)  { -	const struct switchdev_ops *ops = dev->switchdev_ops; -	struct net_device *lower_dev; -	struct list_head *iter; -	int err = -EOPNOTSUPP; - -	if (ops && ops->switchdev_port_obj_add) -		return ops->switchdev_port_obj_add(dev, obj, trans); +	int rc; +	int err; -	/* Switch device port(s) may be stacked under -	 * bond/team/vlan dev, so recurse down to add object on -	 * each port. -	 */ +	struct switchdev_notifier_port_obj_info obj_info = { +		.obj = obj, +		.trans = trans, +		.handled = false, +	}; -	netdev_for_each_lower_dev(dev, lower_dev, iter) { -		err = __switchdev_port_obj_add(lower_dev, obj, trans); -		if (err) -			break; +	rc = call_switchdev_blocking_notifiers(nt, dev, &obj_info.info, extack); +	err = notifier_to_errno(rc); +	if (err) { +		WARN_ON(!obj_info.handled); +		return err;  	} - -	return err; +	if (!obj_info.handled) +		return -EOPNOTSUPP; +	return 0;  }  static int switchdev_port_obj_add_now(struct net_device *dev, -				      const struct switchdev_obj *obj) +				      const struct switchdev_obj *obj, +				      struct netlink_ext_ack *extack)  {  	struct switchdev_trans trans;  	int err; @@ -397,7 +398,8 @@ static int switchdev_port_obj_add_now(struct net_device *dev,  	 */  	trans.ph_prepare = true; -	err = __switchdev_port_obj_add(dev, obj, &trans); +	err = switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD, +					dev, obj, &trans, extack);  	if (err) {  		/* Prepare phase failed: abort the transaction.  Any  		 * resources reserved in the prepare phase are @@ -416,7 +418,8 @@ static int switchdev_port_obj_add_now(struct net_device *dev,  	 */  	trans.ph_prepare = false; -	err = __switchdev_port_obj_add(dev, obj, &trans); +	err = switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD, +					dev, obj, &trans, extack);  	WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, obj->id);  	switchdev_trans_items_warn_destroy(dev, &trans); @@ -429,7 +432,7 @@ static void switchdev_port_obj_add_deferred(struct net_device *dev,  	const struct switchdev_obj *obj = data;  	int err; -	err = switchdev_port_obj_add_now(dev, obj); +	err = switchdev_port_obj_add_now(dev, obj, NULL);  	if (err && err != -EOPNOTSUPP)  		netdev_err(dev, "failed (err=%d) to add object (id=%d)\n",  			   err, obj->id); @@ -459,38 +462,21 @@ static int switchdev_port_obj_add_defer(struct net_device *dev,   *	in case SWITCHDEV_F_DEFER flag is not set.   */  int switchdev_port_obj_add(struct net_device *dev, -			   const struct switchdev_obj *obj) +			   const struct switchdev_obj *obj, +			   struct netlink_ext_ack *extack)  {  	if (obj->flags & SWITCHDEV_F_DEFER)  		return switchdev_port_obj_add_defer(dev, obj);  	ASSERT_RTNL(); -	return switchdev_port_obj_add_now(dev, obj); +	return switchdev_port_obj_add_now(dev, obj, extack);  }  EXPORT_SYMBOL_GPL(switchdev_port_obj_add);  static int switchdev_port_obj_del_now(struct net_device *dev,  				      const struct switchdev_obj *obj)  { -	const struct switchdev_ops *ops = dev->switchdev_ops; -	struct net_device *lower_dev; -	struct list_head *iter; -	int err = -EOPNOTSUPP; - -	if (ops && ops->switchdev_port_obj_del) -		return ops->switchdev_port_obj_del(dev, obj); - -	/* Switch device port(s) may be stacked under -	 * bond/team/vlan dev, so recurse down to delete object on -	 * each port. -	 */ - -	netdev_for_each_lower_dev(dev, lower_dev, iter) { -		err = switchdev_port_obj_del_now(lower_dev, obj); -		if (err) -			break; -	} - -	return err; +	return switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_DEL, +					 dev, obj, NULL, NULL);  }  static void switchdev_port_obj_del_deferred(struct net_device *dev, @@ -535,6 +521,7 @@ int switchdev_port_obj_del(struct net_device *dev,  EXPORT_SYMBOL_GPL(switchdev_port_obj_del);  static ATOMIC_NOTIFIER_HEAD(switchdev_notif_chain); +static BLOCKING_NOTIFIER_HEAD(switchdev_blocking_notif_chain);  /**   *	register_switchdev_notifier - Register notifier @@ -572,10 +559,38 @@ int call_switchdev_notifiers(unsigned long val, struct net_device *dev,  			     struct switchdev_notifier_info *info)  {  	info->dev = dev; +	info->extack = NULL;  	return atomic_notifier_call_chain(&switchdev_notif_chain, val, info);  }  EXPORT_SYMBOL_GPL(call_switchdev_notifiers); +int register_switchdev_blocking_notifier(struct notifier_block *nb) +{ +	struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain; + +	return blocking_notifier_chain_register(chain, nb); +} +EXPORT_SYMBOL_GPL(register_switchdev_blocking_notifier); + +int unregister_switchdev_blocking_notifier(struct notifier_block *nb) +{ +	struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain; + +	return blocking_notifier_chain_unregister(chain, nb); +} +EXPORT_SYMBOL_GPL(unregister_switchdev_blocking_notifier); + +int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev, +				      struct switchdev_notifier_info *info, +				      struct netlink_ext_ack *extack) +{ +	info->dev = dev; +	info->extack = extack; +	return blocking_notifier_call_chain(&switchdev_blocking_notif_chain, +					    val, info); +} +EXPORT_SYMBOL_GPL(call_switchdev_blocking_notifiers); +  bool switchdev_port_same_parent_id(struct net_device *a,  				   struct net_device *b)  { @@ -595,3 +610,109 @@ bool switchdev_port_same_parent_id(struct net_device *a,  	return netdev_phys_item_id_same(&a_attr.u.ppid, &b_attr.u.ppid);  }  EXPORT_SYMBOL_GPL(switchdev_port_same_parent_id); + +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, +				      const struct switchdev_obj *obj, +				      struct switchdev_trans *trans, +				      struct netlink_ext_ack *extack)) +{ +	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); + +	if (check_cb(dev)) { +		/* This flag is only checked if the return value is success. */ +		port_obj_info->handled = true; +		return add_cb(dev, port_obj_info->obj, port_obj_info->trans, +			      extack); +	} + +	/* 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) { +		err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info, +						      check_cb, add_cb); +		if (err && err != -EOPNOTSUPP) +			return err; +	} + +	return err; +} + +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, +				      const struct switchdev_obj *obj, +				      struct switchdev_trans *trans, +				      struct netlink_ext_ack *extack)) +{ +	int err; + +	err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb, +					      add_cb); +	if (err == -EOPNOTSUPP) +		err = 0; +	return err; +} +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, +				      const struct switchdev_obj *obj)) +{ +	struct net_device *lower_dev; +	struct list_head *iter; +	int err = -EOPNOTSUPP; + +	if (check_cb(dev)) { +		/* This flag is only checked if the return value is success. */ +		port_obj_info->handled = true; +		return del_cb(dev, port_obj_info->obj); +	} + +	/* 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) { +		err = __switchdev_handle_port_obj_del(lower_dev, port_obj_info, +						      check_cb, del_cb); +		if (err && err != -EOPNOTSUPP) +			return err; +	} + +	return err; +} + +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, +				      const struct switchdev_obj *obj)) +{ +	int err; + +	err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb, +					      del_cb); +	if (err == -EOPNOTSUPP) +		err = 0; +	return err; +} +EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del);  | 
