1064 lines · 31.3 KB · C
RawDownload
/** *	call_switchdev_notifiers - Call notifiers *	@val: value passed unmodified to notifier function *	@dev: port device *	@info: notifier information data *	@extack: netlink extended ack *	Call all network notifier blocks. */int call_switchdev_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 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 raw_notifier_head *chain = &switchdev_blocking_notif_chain;	int err; 	rtnl_lock();	err = raw_notifier_chain_register(chain, nb);	rtnl_unlock(); 	return err;}EXPORT_SYMBOL_GPL(register_switchdev_blocking_notifier); int unregister_switchdev_blocking_notifier(struct notifier_block *nb){	struct raw_notifier_head *chain = &switchdev_blocking_notif_chain;	int err; 	rtnl_lock();	err = raw_notifier_chain_unregister(chain, nb);	rtnl_unlock(); 	return err;}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){	ASSERT_RTNL();	info->dev = dev;	info->extack = extack;	return raw_notifier_call_chain(&switchdev_blocking_notif_chain,				       val, info);}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_rcu(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 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(dev, switchdev_lower_dev_walk, &priv); 	return switchdev_priv.lower_dev;} static int __switchdev_handle_fdb_event_to_device(struct net_device *dev,		struct net_device *orig_dev, unsigned long event,		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 (*mod_cb)(struct net_device *dev, struct net_device *orig_dev,			      unsigned long event, 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, *switchdev;	struct list_head *iter;	int err = -EOPNOTSUPP; 	if (check_cb(dev))		return mod_cb(dev, orig_dev, event, info->ctx, fdb_info); 	/* Recurse through lower interfaces in case the FDB entry is pointing	 * towards a bridge or a LAG device.	 */	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_rcu(lower_dev, check_cb,						  foreign_dev_check_cb))			continue; 		err = __switchdev_handle_fdb_event_to_device(lower_dev, orig_dev,							     event, fdb_info, check_cb,							     foreign_dev_check_cb,							     mod_cb);		if (err && err != -EOPNOTSUPP)			return err;	} 	/* 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; 	switchdev = switchdev_lower_dev_find_rcu(br, check_cb, foreign_dev_check_cb);	if (!switchdev)		return 0; 	if (!foreign_dev_check_cb(switchdev, dev))		return err; 	return __switchdev_handle_fdb_event_to_device(br, orig_dev, event, fdb_info,						      check_cb, foreign_dev_check_cb,						      mod_cb);} int switchdev_handle_fdb_event_to_device(struct net_device *dev, unsigned long event,		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 (*mod_cb)(struct net_device *dev, struct net_device *orig_dev,			      unsigned long event, const void *ctx,			      const struct switchdev_notifier_fdb_info *fdb_info)){	int err; 	err = __switchdev_handle_fdb_event_to_device(dev, dev, event, fdb_info,						     check_cb, foreign_dev_check_cb,						     mod_cb);	if (err == -EOPNOTSUPP)		err = 0; 	return err;}EXPORT_SYMBOL_GPL(switchdev_handle_fdb_event_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),			bool (*foreign_dev_check_cb)(const struct net_device *dev,						     const struct net_device *foreign_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 net_device *br, *lower_dev, *switchdev;	struct netlink_ext_ack *extack;	struct list_head *iter;	int err = -EOPNOTSUPP; 	extack = switchdev_notifier_info_to_extack(info); 	if (check_cb(dev)) {		err = add_cb(dev, info->ctx, port_obj_info->obj, extack);		if (err != -EOPNOTSUPP)			port_obj_info->handled = true;		return err;	} 	/* 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; 		/* When searching for switchdev interfaces that are neighbors		 * of foreign ones, and @dev is a bridge, do not recurse on the		 * foreign interface again, it was already visited.		 */		if (foreign_dev_check_cb && !check_cb(lower_dev) &&		    !switchdev_lower_dev_find(lower_dev, check_cb, foreign_dev_check_cb))			continue; 		err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info,						      check_cb, foreign_dev_check_cb,						      add_cb);		if (err && err != -EOPNOTSUPP)			return err;	} 	/* Event is neither on a bridge nor a LAG. Check whether it is on an	 * interface that is in a bridge with us.	 */	if (!foreign_dev_check_cb || port_obj_info->obj->flags & SWITCHDEV_F_NO_FOREIGN)		return err; 	br = netdev_master_upper_dev_get(dev);	if (!br || !netif_is_bridge_master(br))		return err; 	switchdev = switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb);	if (!switchdev)		return err; 	if (!foreign_dev_check_cb(switchdev, dev))		return err; 	return __switchdev_handle_port_obj_add(br, port_obj_info, check_cb,					       foreign_dev_check_cb, add_cb);} /* Pass through a port object addition, if @dev passes @check_cb, or replicate * it towards all lower interfaces of @dev that pass @check_cb, if @dev is a * bridge or a LAG. */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 void *ctx,				      const struct switchdev_obj *obj,				      struct netlink_ext_ack *extack)){	int err; 	err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb,					      NULL, add_cb);	if (err == -EOPNOTSUPP)		err = 0;	return err;}EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add); /* Same as switchdev_handle_port_obj_add(), except if object is notified on a * @dev that passes @foreign_dev_check_cb, it is replicated towards all devices * that pass @check_cb and are in the same bridge as @dev. */int switchdev_handle_port_obj_add_foreign(struct net_device *dev,			struct switchdev_notifier_port_obj_info *port_obj_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 void *ctx,				      const struct switchdev_obj *obj,				      struct netlink_ext_ack *extack)){	int err; 	err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb,					      foreign_dev_check_cb, add_cb);	if (err == -EOPNOTSUPP)		err = 0;	return err;}EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add_foreign); 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),			bool (*foreign_dev_check_cb)(const struct net_device *dev,						     const struct net_device *foreign_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 *br, *lower_dev, *switchdev;	struct list_head *iter;	int err = -EOPNOTSUPP; 	if (check_cb(dev)) {		err = del_cb(dev, info->ctx, port_obj_info->obj);		if (err != -EOPNOTSUPP)			port_obj_info->handled = true;		return err;	} 	/* 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; 		/* When searching for switchdev interfaces that are neighbors		 * of foreign ones, and @dev is a bridge, do not recurse on the		 * foreign interface again, it was already visited.		 */		if (foreign_dev_check_cb && !check_cb(lower_dev) &&		    !switchdev_lower_dev_find(lower_dev, check_cb, foreign_dev_check_cb))			continue; 		err = __switchdev_handle_port_obj_del(lower_dev, port_obj_info,						      check_cb, foreign_dev_check_cb,						      del_cb);		if (err && err != -EOPNOTSUPP)			return err;	} 	/* Event is neither on a bridge nor a LAG. Check whether it is on an	 * interface that is in a bridge with us.	 */	if (!foreign_dev_check_cb)		return err; 	br = netdev_master_upper_dev_get(dev);	if (!br || !netif_is_bridge_master(br))		return err; 	switchdev = switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb);	if (!switchdev)		return err; 	if (!foreign_dev_check_cb(switchdev, dev))		return err; 	return __switchdev_handle_port_obj_del(br, port_obj_info, check_cb,					       foreign_dev_check_cb, del_cb);} /* Pass through a port object deletion, if @dev passes @check_cb, or replicate * it towards all lower interfaces of @dev that pass @check_cb, if @dev is a * bridge or a LAG. */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 void *ctx,				      const struct switchdev_obj *obj)){	int err; 	err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb,					      NULL, del_cb);	if (err == -EOPNOTSUPP)		err = 0;	return err;}EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del); /* Same as switchdev_handle_port_obj_del(), except if object is notified on a * @dev that passes @foreign_dev_check_cb, it is replicated towards all devices * that pass @check_cb and are in the same bridge as @dev. */int switchdev_handle_port_obj_del_foreign(struct net_device *dev,			struct switchdev_notifier_port_obj_info *port_obj_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 void *ctx,				      const struct switchdev_obj *obj)){	int err; 	err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb,					      foreign_dev_check_cb, del_cb);	if (err == -EOPNOTSUPP)		err = 0;	return err;}EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del_foreign); 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, 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(info); 	if (check_cb(dev)) {		err = set_cb(dev, info->ctx, port_attr_info->attr, extack);		if (err != -EOPNOTSUPP)			port_attr_info->handled = true;		return err;	} 	/* 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_attr_set(lower_dev, port_attr_info,						       check_cb, set_cb);		if (err && err != -EOPNOTSUPP)			return err;	} 	return err;} 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, const void *ctx,				      const struct switchdev_attr *attr,				      struct netlink_ext_ack *extack)){	int err; 	err = __switchdev_handle_port_attr_set(dev, port_attr_info, check_cb,					       set_cb);	if (err == -EOPNOTSUPP)		err = 0;	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 = {

Lines 501–1000 of 1064. Previous lines