962 lines · 27.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * net/switchdev/switchdev.c - Switch device API * Copyright (c) 2014-2015 Jiri Pirko <jiri@resnulli.us> * Copyright (c) 2014-2015 Scott Feldman <sfeldma@gmail.com> */ #include <linux/kernel.h>#include <linux/types.h>#include <linux/init.h>#include <linux/mutex.h>#include <linux/notifier.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/if_bridge.h>#include <linux/list.h>#include <linux/workqueue.h>#include <linux/if_vlan.h>#include <linux/rtnetlink.h>#include <net/switchdev.h> static bool switchdev_obj_eq(const struct switchdev_obj *a,			     const struct switchdev_obj *b){	const struct switchdev_obj_port_vlan *va, *vb;	const struct switchdev_obj_port_mdb *ma, *mb; 	if (a->id != b->id || a->orig_dev != b->orig_dev)		return false; 	switch (a->id) {	case SWITCHDEV_OBJ_ID_PORT_VLAN:		va = SWITCHDEV_OBJ_PORT_VLAN(a);		vb = SWITCHDEV_OBJ_PORT_VLAN(b);		return va->flags == vb->flags &&			va->vid == vb->vid &&			va->changed == vb->changed;	case SWITCHDEV_OBJ_ID_PORT_MDB:	case SWITCHDEV_OBJ_ID_HOST_MDB:		ma = SWITCHDEV_OBJ_PORT_MDB(a);		mb = SWITCHDEV_OBJ_PORT_MDB(b);		return ma->vid == mb->vid &&			ether_addr_equal(ma->addr, mb->addr);	default:		break;	} 	BUG();} static LIST_HEAD(deferred);static DEFINE_SPINLOCK(deferred_lock); typedef void switchdev_deferred_func_t(struct net_device *dev,				       const void *data); struct switchdev_deferred_item {	struct list_head list;	struct net_device *dev;	netdevice_tracker dev_tracker;	switchdev_deferred_func_t *func;	unsigned long data[];}; static struct switchdev_deferred_item *switchdev_deferred_dequeue(void){	struct switchdev_deferred_item *dfitem; 	spin_lock_bh(&deferred_lock);	if (list_empty(&deferred)) {		dfitem = NULL;		goto unlock;	}	dfitem = list_first_entry(&deferred,				  struct switchdev_deferred_item, list);	list_del(&dfitem->list);unlock:	spin_unlock_bh(&deferred_lock);	return dfitem;} /** *	switchdev_deferred_process - Process ops in deferred queue * *	Called to flush the ops currently queued in deferred ops queue. *	rtnl_lock must be held. */void switchdev_deferred_process(void){	struct switchdev_deferred_item *dfitem; 	ASSERT_RTNL(); 	while ((dfitem = switchdev_deferred_dequeue())) {		dfitem->func(dfitem->dev, dfitem->data);		netdev_put(dfitem->dev, &dfitem->dev_tracker);		kfree(dfitem);	}}EXPORT_SYMBOL_GPL(switchdev_deferred_process); static void switchdev_deferred_process_work(struct work_struct *work){	rtnl_lock();	switchdev_deferred_process();	rtnl_unlock();} static DECLARE_WORK(deferred_process_work, switchdev_deferred_process_work); static int switchdev_deferred_enqueue(struct net_device *dev,				      const void *data, size_t data_len,				      switchdev_deferred_func_t *func){	struct switchdev_deferred_item *dfitem; 	dfitem = kmalloc(struct_size(dfitem, data, data_len), GFP_ATOMIC);	if (!dfitem)		return -ENOMEM;	dfitem->dev = dev;	dfitem->func = func;	memcpy(dfitem->data, data, data_len);	netdev_hold(dev, &dfitem->dev_tracker, GFP_ATOMIC);	spin_lock_bh(&deferred_lock);	list_add_tail(&dfitem->list, &deferred);	spin_unlock_bh(&deferred_lock);	schedule_work(&deferred_process_work);	return 0;} static int switchdev_port_attr_notify(enum switchdev_notifier_type nt,				      struct net_device *dev,				      const struct switchdev_attr *attr,				      struct netlink_ext_ack *extack){	int err;	int rc; 	struct switchdev_notifier_port_attr_info attr_info = {		.attr = attr,		.handled = false,	}; 	rc = call_switchdev_blocking_notifiers(nt, dev,					       &attr_info.info, extack);	err = notifier_to_errno(rc);	if (err) {		WARN_ON(!attr_info.handled);		return err;	} 	if (!attr_info.handled)		return -EOPNOTSUPP; 	return 0;} static int switchdev_port_attr_set_now(struct net_device *dev,				       const struct switchdev_attr *attr,				       struct netlink_ext_ack *extack){	return switchdev_port_attr_notify(SWITCHDEV_PORT_ATTR_SET, dev, attr,					  extack);} static void switchdev_port_attr_set_deferred(struct net_device *dev,					     const void *data){	const struct switchdev_attr *attr = data;	int err; 	err = switchdev_port_attr_set_now(dev, attr, NULL);	if (err && err != -EOPNOTSUPP)		netdev_err(dev, "failed (err=%d) to set attribute (id=%d)\n",			   err, attr->id);	if (attr->complete)		attr->complete(dev, err, attr->complete_priv);} static int switchdev_port_attr_set_defer(struct net_device *dev,					 const struct switchdev_attr *attr){	return switchdev_deferred_enqueue(dev, attr, sizeof(*attr),					  switchdev_port_attr_set_deferred);} /** *	switchdev_port_attr_set - Set port attribute * *	@dev: port device *	@attr: attribute to set *	@extack: netlink extended ack, for error message propagation * *	rtnl_lock must be held and must not be in atomic section, *	in case SWITCHDEV_F_DEFER flag is not set. */int switchdev_port_attr_set(struct net_device *dev,			    const struct switchdev_attr *attr,			    struct netlink_ext_ack *extack){	if (attr->flags & SWITCHDEV_F_DEFER)		return switchdev_port_attr_set_defer(dev, attr);	ASSERT_RTNL();	return switchdev_port_attr_set_now(dev, attr, extack);}EXPORT_SYMBOL_GPL(switchdev_port_attr_set); static size_t switchdev_obj_size(const struct switchdev_obj *obj){	switch (obj->id) {	case SWITCHDEV_OBJ_ID_PORT_VLAN:		return sizeof(struct switchdev_obj_port_vlan);	case SWITCHDEV_OBJ_ID_PORT_MDB:		return sizeof(struct switchdev_obj_port_mdb);	case SWITCHDEV_OBJ_ID_HOST_MDB:		return sizeof(struct switchdev_obj_port_mdb);	default:		BUG();	}	return 0;} static int switchdev_port_obj_notify(enum switchdev_notifier_type nt,				     struct net_device *dev,				     const struct switchdev_obj *obj,				     struct netlink_ext_ack *extack){	int rc;	int err; 	struct switchdev_notifier_port_obj_info obj_info = {		.obj = obj,		.handled = false,	}; 	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;	}	if (!obj_info.handled)		return -EOPNOTSUPP;	return 0;} static void switchdev_port_obj_add_deferred(struct net_device *dev,					    const void *data){	const struct switchdev_obj *obj = data;	int err; 	ASSERT_RTNL();	err = switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD,					dev, obj, NULL);	if (err && err != -EOPNOTSUPP)		netdev_err(dev, "failed (err=%d) to add object (id=%d)\n",			   err, obj->id);	if (obj->complete)		obj->complete(dev, err, obj->complete_priv);} static int switchdev_port_obj_add_defer(struct net_device *dev,					const struct switchdev_obj *obj){	return switchdev_deferred_enqueue(dev, obj, switchdev_obj_size(obj),					  switchdev_port_obj_add_deferred);} /** *	switchdev_port_obj_add - Add port object * *	@dev: port device *	@obj: object to add *	@extack: netlink extended ack * *	rtnl_lock must be held and must not be in atomic section, *	in case SWITCHDEV_F_DEFER flag is not set. */int switchdev_port_obj_add(struct net_device *dev,			   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_notify(SWITCHDEV_PORT_OBJ_ADD,					 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){	return switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_DEL,					 dev, obj, NULL);} static void switchdev_port_obj_del_deferred(struct net_device *dev,					    const void *data){	const struct switchdev_obj *obj = data;	int err; 	err = switchdev_port_obj_del_now(dev, obj);	if (err && err != -EOPNOTSUPP)		netdev_err(dev, "failed (err=%d) to del object (id=%d)\n",			   err, obj->id);	if (obj->complete)		obj->complete(dev, err, obj->complete_priv);} static int switchdev_port_obj_del_defer(struct net_device *dev,					const struct switchdev_obj *obj){	return switchdev_deferred_enqueue(dev, obj, switchdev_obj_size(obj),					  switchdev_port_obj_del_deferred);} /** *	switchdev_port_obj_del - Delete port object * *	@dev: port device *	@obj: object to delete * *	rtnl_lock must be held and must not be in atomic section, *	in case SWITCHDEV_F_DEFER flag is not set. */int switchdev_port_obj_del(struct net_device *dev,			   const struct switchdev_obj *obj){	if (obj->flags & SWITCHDEV_F_DEFER)		return switchdev_port_obj_del_defer(dev, obj);	ASSERT_RTNL();	return switchdev_port_obj_del_now(dev, obj);}EXPORT_SYMBOL_GPL(switchdev_port_obj_del); /** *	switchdev_port_obj_act_is_deferred - Is object action pending? * *	@dev: port device *	@nt: type of action; add or delete *	@obj: object to test * *	Returns true if a deferred item is pending, which is *	equivalent to the action @nt on an object @obj. * *	rtnl_lock must be held. */bool switchdev_port_obj_act_is_deferred(struct net_device *dev,					enum switchdev_notifier_type nt,					const struct switchdev_obj *obj){	struct switchdev_deferred_item *dfitem;	bool found = false; 	ASSERT_RTNL(); 	spin_lock_bh(&deferred_lock); 	list_for_each_entry(dfitem, &deferred, list) {		if (dfitem->dev != dev)			continue; 		if ((dfitem->func == switchdev_port_obj_add_deferred &&		     nt == SWITCHDEV_PORT_OBJ_ADD) ||		    (dfitem->func == switchdev_port_obj_del_deferred &&		     nt == SWITCHDEV_PORT_OBJ_DEL)) {			if (switchdev_obj_eq((const void *)dfitem->data, obj)) {				found = true;				break;			}		}	} 	spin_unlock_bh(&deferred_lock); 	return found;}EXPORT_SYMBOL_GPL(switchdev_port_obj_act_is_deferred); static ATOMIC_NOTIFIER_HEAD(switchdev_notif_chain);static BLOCKING_NOTIFIER_HEAD(switchdev_blocking_notif_chain); /** *	register_switchdev_notifier - Register notifier *	@nb: notifier_block * *	Register switch device notifier. */int register_switchdev_notifier(struct notifier_block *nb){	return atomic_notifier_chain_register(&switchdev_notif_chain, nb);}EXPORT_SYMBOL_GPL(register_switchdev_notifier); /** *	unregister_switchdev_notifier - Unregister notifier *	@nb: notifier_block * *	Unregister switch device notifier. */int unregister_switchdev_notifier(struct notifier_block *nb){	return atomic_notifier_chain_unregister(&switchdev_notif_chain, nb);}EXPORT_SYMBOL_GPL(unregister_switchdev_notifier); /** *	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 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); 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);

Lines 1–500 of 962.