1395 lines · 34.8 KB · C
RawDownload
/* * 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> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #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/ip_fib.h>#include <net/switchdev.h> /** *	switchdev_trans_item_enqueue - Enqueue data item to transaction queue * *	@trans: transaction *	@data: pointer to data being queued *	@destructor: data destructor *	@tritem: transaction item being queued * *	Enqeueue data item to transaction queue. tritem is typically placed in *	cointainter pointed at by data pointer. Destructor is called on *	transaction abort and after successful commit phase in case *	the caller did not dequeue the item before. */void switchdev_trans_item_enqueue(struct switchdev_trans *trans,				  void *data, void (*destructor)(void const *),				  struct switchdev_trans_item *tritem){	tritem->data = data;	tritem->destructor = destructor;	list_add_tail(&tritem->list, &trans->item_list);}EXPORT_SYMBOL_GPL(switchdev_trans_item_enqueue); static struct switchdev_trans_item *__switchdev_trans_item_dequeue(struct switchdev_trans *trans){	struct switchdev_trans_item *tritem; 	if (list_empty(&trans->item_list))		return NULL;	tritem = list_first_entry(&trans->item_list,				  struct switchdev_trans_item, list);	list_del(&tritem->list);	return tritem;} /** *	switchdev_trans_item_dequeue - Dequeue data item from transaction queue * *	@trans: transaction */void *switchdev_trans_item_dequeue(struct switchdev_trans *trans){	struct switchdev_trans_item *tritem; 	tritem = __switchdev_trans_item_dequeue(trans);	BUG_ON(!tritem);	return tritem->data;}EXPORT_SYMBOL_GPL(switchdev_trans_item_dequeue); static void switchdev_trans_init(struct switchdev_trans *trans){	INIT_LIST_HEAD(&trans->item_list);} static void switchdev_trans_items_destroy(struct switchdev_trans *trans){	struct switchdev_trans_item *tritem; 	while ((tritem = __switchdev_trans_item_dequeue(trans)))		tritem->destructor(tritem->data);} static void switchdev_trans_items_warn_destroy(struct net_device *dev,					       struct switchdev_trans *trans){	WARN(!list_empty(&trans->item_list), "%s: transaction item queue is not empty.\n",	     dev->name);	switchdev_trans_items_destroy(trans);} 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;	switchdev_deferred_func_t *func;	unsigned long data[0];}; 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);		dev_put(dfitem->dev);		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(sizeof(*dfitem) + data_len, GFP_ATOMIC);	if (!dfitem)		return -ENOMEM;	dfitem->dev = dev;	dfitem->func = func;	memcpy(dfitem->data, data, data_len);	dev_hold(dev);	spin_lock_bh(&deferred_lock);	list_add_tail(&dfitem->list, &deferred);	spin_unlock_bh(&deferred_lock);	schedule_work(&deferred_process_work);	return 0;} /** *	switchdev_port_attr_get - Get port attribute * *	@dev: port device *	@attr: attribute to get */int switchdev_port_attr_get(struct net_device *dev, struct switchdev_attr *attr){	const struct switchdev_ops *ops = dev->switchdev_ops;	struct net_device *lower_dev;	struct list_head *iter;	struct switchdev_attr first = {		.id = SWITCHDEV_ATTR_ID_UNDEFINED	};	int err = -EOPNOTSUPP; 	if (ops && ops->switchdev_port_attr_get)		return ops->switchdev_port_attr_get(dev, attr); 	if (attr->flags & SWITCHDEV_F_NO_RECURSE)		return err; 	/* Switch device port(s) may be stacked under	 * bond/team/vlan dev, so recurse down to get attr on	 * each port.  Return -ENODATA if attr values don't	 * compare across ports.	 */ 	netdev_for_each_lower_dev(dev, lower_dev, iter) {		err = switchdev_port_attr_get(lower_dev, attr);		if (err)			break;		if (first.id == SWITCHDEV_ATTR_ID_UNDEFINED)			first = *attr;		else if (memcmp(&first, attr, sizeof(*attr)))			return -ENODATA;	} 	return err;}EXPORT_SYMBOL_GPL(switchdev_port_attr_get); static int __switchdev_port_attr_set(struct net_device *dev,				     const struct switchdev_attr *attr,				     struct switchdev_trans *trans){	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_attr_set) {		err = ops->switchdev_port_attr_set(dev, attr, trans);		goto done;	} 	if (attr->flags & SWITCHDEV_F_NO_RECURSE)		goto done; 	/* Switch device port(s) may be stacked under	 * bond/team/vlan dev, so recurse down to set attr on	 * each port.	 */ 	netdev_for_each_lower_dev(dev, lower_dev, iter) {		err = __switchdev_port_attr_set(lower_dev, attr, trans);		if (err)			break;	} done:	if (err == -EOPNOTSUPP && attr->flags & SWITCHDEV_F_SKIP_EOPNOTSUPP)		err = 0; 	return err;} static int switchdev_port_attr_set_now(struct net_device *dev,				       const struct switchdev_attr *attr){	struct switchdev_trans trans;	int err; 	switchdev_trans_init(&trans); 	/* Phase I: prepare for attr set. Driver/device should fail	 * here if there are going to be issues in the commit phase,	 * such as lack of resources or support.  The driver/device	 * should reserve resources needed for the commit phase here,	 * but should not commit the attr.	 */ 	trans.ph_prepare = true;	err = __switchdev_port_attr_set(dev, attr, &trans);	if (err) {		/* Prepare phase failed: abort the transaction.  Any		 * resources reserved in the prepare phase are		 * released.		 */ 		if (err != -EOPNOTSUPP)			switchdev_trans_items_destroy(&trans); 		return err;	} 	/* Phase II: commit attr set.  This cannot fail as a fault	 * of driver/device.  If it does, it's a bug in the driver/device	 * because the driver said everythings was OK in phase I.	 */ 	trans.ph_prepare = false;	err = __switchdev_port_attr_set(dev, attr, &trans);	WARN(err, "%s: Commit of attribute (id=%d) failed.\n",	     dev->name, attr->id);	switchdev_trans_items_warn_destroy(dev, &trans); 	return err;} 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);	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 * *	Use a 2-phase prepare-commit transaction model to ensure *	system is not left in a partially updated state due to *	failure from driver/device. * *	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){	if (attr->flags & SWITCHDEV_F_DEFER)		return switchdev_port_attr_set_defer(dev, attr);	ASSERT_RTNL();	return switchdev_port_attr_set_now(dev, attr);}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_IPV4_FIB:		return sizeof(struct switchdev_obj_ipv4_fib);	case SWITCHDEV_OBJ_ID_PORT_FDB:		return sizeof(struct switchdev_obj_port_fdb);	case SWITCHDEV_OBJ_ID_PORT_MDB:		return sizeof(struct switchdev_obj_port_mdb);	default:		BUG();	}	return 0;} static int __switchdev_port_obj_add(struct net_device *dev,				    const struct switchdev_obj *obj,				    struct switchdev_trans *trans){	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); 	/* Switch device port(s) may be stacked under	 * bond/team/vlan dev, so recurse down to add object on	 * each port.	 */ 	netdev_for_each_lower_dev(dev, lower_dev, iter) {		err = __switchdev_port_obj_add(lower_dev, obj, trans);		if (err)			break;	} 	return err;} static int switchdev_port_obj_add_now(struct net_device *dev,				      const struct switchdev_obj *obj){	struct switchdev_trans trans;	int err; 	ASSERT_RTNL(); 	switchdev_trans_init(&trans); 	/* Phase I: prepare for obj add. Driver/device should fail	 * here if there are going to be issues in the commit phase,	 * such as lack of resources or support.  The driver/device	 * should reserve resources needed for the commit phase here,	 * but should not commit the obj.	 */ 	trans.ph_prepare = true;	err = __switchdev_port_obj_add(dev, obj, &trans);	if (err) {		/* Prepare phase failed: abort the transaction.  Any		 * resources reserved in the prepare phase are		 * released.		 */ 		if (err != -EOPNOTSUPP)			switchdev_trans_items_destroy(&trans); 		return err;	} 	/* Phase II: commit obj add.  This cannot fail as a fault	 * of driver/device.  If it does, it's a bug in the driver/device	 * because the driver said everythings was OK in phase I.	 */ 	trans.ph_prepare = false;	err = __switchdev_port_obj_add(dev, obj, &trans);	WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, obj->id);	switchdev_trans_items_warn_destroy(dev, &trans); 	return err;} static void switchdev_port_obj_add_deferred(struct net_device *dev,					    const void *data){	const struct switchdev_obj *obj = data;	int err; 	err = switchdev_port_obj_add_now(dev, obj);	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 *	@id: object ID *	@obj: object to add * *	Use a 2-phase prepare-commit transaction model to ensure *	system is not left in a partially updated state due to *	failure from driver/device. * *	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){	if (obj->flags & SWITCHDEV_F_DEFER)		return switchdev_port_obj_add_defer(dev, obj);	ASSERT_RTNL();	return switchdev_port_obj_add_now(dev, obj);}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;} static void switchdev_port_obj_del_deferred(struct net_device *dev,					    const void *data)

Lines 1–500 of 1395.