xref: /linux-6.15/net/switchdev/switchdev.c (revision 0bc05d58)
1007f790cSJiri Pirko /*
2007f790cSJiri Pirko  * net/switchdev/switchdev.c - Switch device API
37ea6eb3fSJiri Pirko  * Copyright (c) 2014-2015 Jiri Pirko <[email protected]>
4f8f21471SScott Feldman  * Copyright (c) 2014-2015 Scott Feldman <[email protected]>
5007f790cSJiri Pirko  *
6007f790cSJiri Pirko  * This program is free software; you can redistribute it and/or modify
7007f790cSJiri Pirko  * it under the terms of the GNU General Public License as published by
8007f790cSJiri Pirko  * the Free Software Foundation; either version 2 of the License, or
9007f790cSJiri Pirko  * (at your option) any later version.
10007f790cSJiri Pirko  */
11007f790cSJiri Pirko 
12007f790cSJiri Pirko #include <linux/kernel.h>
13007f790cSJiri Pirko #include <linux/types.h>
14007f790cSJiri Pirko #include <linux/init.h>
1503bf0c28SJiri Pirko #include <linux/mutex.h>
1603bf0c28SJiri Pirko #include <linux/notifier.h>
17007f790cSJiri Pirko #include <linux/netdevice.h>
1847f8328bSScott Feldman #include <linux/if_bridge.h>
197ea6eb3fSJiri Pirko #include <linux/list.h>
20793f4014SJiri Pirko #include <linux/workqueue.h>
215e8d9049SScott Feldman #include <net/ip_fib.h>
22007f790cSJiri Pirko #include <net/switchdev.h>
23007f790cSJiri Pirko 
24007f790cSJiri Pirko /**
257ea6eb3fSJiri Pirko  *	switchdev_trans_item_enqueue - Enqueue data item to transaction queue
267ea6eb3fSJiri Pirko  *
277ea6eb3fSJiri Pirko  *	@trans: transaction
287ea6eb3fSJiri Pirko  *	@data: pointer to data being queued
297ea6eb3fSJiri Pirko  *	@destructor: data destructor
307ea6eb3fSJiri Pirko  *	@tritem: transaction item being queued
317ea6eb3fSJiri Pirko  *
327ea6eb3fSJiri Pirko  *	Enqeueue data item to transaction queue. tritem is typically placed in
337ea6eb3fSJiri Pirko  *	cointainter pointed at by data pointer. Destructor is called on
347ea6eb3fSJiri Pirko  *	transaction abort and after successful commit phase in case
357ea6eb3fSJiri Pirko  *	the caller did not dequeue the item before.
367ea6eb3fSJiri Pirko  */
377ea6eb3fSJiri Pirko void switchdev_trans_item_enqueue(struct switchdev_trans *trans,
387ea6eb3fSJiri Pirko 				  void *data, void (*destructor)(void const *),
397ea6eb3fSJiri Pirko 				  struct switchdev_trans_item *tritem)
407ea6eb3fSJiri Pirko {
417ea6eb3fSJiri Pirko 	tritem->data = data;
427ea6eb3fSJiri Pirko 	tritem->destructor = destructor;
437ea6eb3fSJiri Pirko 	list_add_tail(&tritem->list, &trans->item_list);
447ea6eb3fSJiri Pirko }
457ea6eb3fSJiri Pirko EXPORT_SYMBOL_GPL(switchdev_trans_item_enqueue);
467ea6eb3fSJiri Pirko 
477ea6eb3fSJiri Pirko static struct switchdev_trans_item *
487ea6eb3fSJiri Pirko __switchdev_trans_item_dequeue(struct switchdev_trans *trans)
497ea6eb3fSJiri Pirko {
507ea6eb3fSJiri Pirko 	struct switchdev_trans_item *tritem;
517ea6eb3fSJiri Pirko 
527ea6eb3fSJiri Pirko 	if (list_empty(&trans->item_list))
537ea6eb3fSJiri Pirko 		return NULL;
547ea6eb3fSJiri Pirko 	tritem = list_first_entry(&trans->item_list,
557ea6eb3fSJiri Pirko 				  struct switchdev_trans_item, list);
567ea6eb3fSJiri Pirko 	list_del(&tritem->list);
577ea6eb3fSJiri Pirko 	return tritem;
587ea6eb3fSJiri Pirko }
597ea6eb3fSJiri Pirko 
607ea6eb3fSJiri Pirko /**
617ea6eb3fSJiri Pirko  *	switchdev_trans_item_dequeue - Dequeue data item from transaction queue
627ea6eb3fSJiri Pirko  *
637ea6eb3fSJiri Pirko  *	@trans: transaction
647ea6eb3fSJiri Pirko  */
657ea6eb3fSJiri Pirko void *switchdev_trans_item_dequeue(struct switchdev_trans *trans)
667ea6eb3fSJiri Pirko {
677ea6eb3fSJiri Pirko 	struct switchdev_trans_item *tritem;
687ea6eb3fSJiri Pirko 
697ea6eb3fSJiri Pirko 	tritem = __switchdev_trans_item_dequeue(trans);
707ea6eb3fSJiri Pirko 	BUG_ON(!tritem);
717ea6eb3fSJiri Pirko 	return tritem->data;
727ea6eb3fSJiri Pirko }
737ea6eb3fSJiri Pirko EXPORT_SYMBOL_GPL(switchdev_trans_item_dequeue);
747ea6eb3fSJiri Pirko 
757ea6eb3fSJiri Pirko static void switchdev_trans_init(struct switchdev_trans *trans)
767ea6eb3fSJiri Pirko {
777ea6eb3fSJiri Pirko 	INIT_LIST_HEAD(&trans->item_list);
787ea6eb3fSJiri Pirko }
797ea6eb3fSJiri Pirko 
807ea6eb3fSJiri Pirko static void switchdev_trans_items_destroy(struct switchdev_trans *trans)
817ea6eb3fSJiri Pirko {
827ea6eb3fSJiri Pirko 	struct switchdev_trans_item *tritem;
837ea6eb3fSJiri Pirko 
847ea6eb3fSJiri Pirko 	while ((tritem = __switchdev_trans_item_dequeue(trans)))
857ea6eb3fSJiri Pirko 		tritem->destructor(tritem->data);
867ea6eb3fSJiri Pirko }
877ea6eb3fSJiri Pirko 
887ea6eb3fSJiri Pirko static void switchdev_trans_items_warn_destroy(struct net_device *dev,
897ea6eb3fSJiri Pirko 					       struct switchdev_trans *trans)
907ea6eb3fSJiri Pirko {
917ea6eb3fSJiri Pirko 	WARN(!list_empty(&trans->item_list), "%s: transaction item queue is not empty.\n",
927ea6eb3fSJiri Pirko 	     dev->name);
937ea6eb3fSJiri Pirko 	switchdev_trans_items_destroy(trans);
947ea6eb3fSJiri Pirko }
957ea6eb3fSJiri Pirko 
96793f4014SJiri Pirko static LIST_HEAD(deferred);
97793f4014SJiri Pirko static DEFINE_SPINLOCK(deferred_lock);
98793f4014SJiri Pirko 
99793f4014SJiri Pirko typedef void switchdev_deferred_func_t(struct net_device *dev,
100793f4014SJiri Pirko 				       const void *data);
101793f4014SJiri Pirko 
102793f4014SJiri Pirko struct switchdev_deferred_item {
103793f4014SJiri Pirko 	struct list_head list;
104793f4014SJiri Pirko 	struct net_device *dev;
105793f4014SJiri Pirko 	switchdev_deferred_func_t *func;
106793f4014SJiri Pirko 	unsigned long data[0];
107793f4014SJiri Pirko };
108793f4014SJiri Pirko 
109793f4014SJiri Pirko static struct switchdev_deferred_item *switchdev_deferred_dequeue(void)
110793f4014SJiri Pirko {
111793f4014SJiri Pirko 	struct switchdev_deferred_item *dfitem;
112793f4014SJiri Pirko 
113793f4014SJiri Pirko 	spin_lock_bh(&deferred_lock);
114793f4014SJiri Pirko 	if (list_empty(&deferred)) {
115793f4014SJiri Pirko 		dfitem = NULL;
116793f4014SJiri Pirko 		goto unlock;
117793f4014SJiri Pirko 	}
118793f4014SJiri Pirko 	dfitem = list_first_entry(&deferred,
119793f4014SJiri Pirko 				  struct switchdev_deferred_item, list);
120793f4014SJiri Pirko 	list_del(&dfitem->list);
121793f4014SJiri Pirko unlock:
122793f4014SJiri Pirko 	spin_unlock_bh(&deferred_lock);
123793f4014SJiri Pirko 	return dfitem;
124793f4014SJiri Pirko }
125793f4014SJiri Pirko 
126793f4014SJiri Pirko /**
127793f4014SJiri Pirko  *	switchdev_deferred_process - Process ops in deferred queue
128793f4014SJiri Pirko  *
129793f4014SJiri Pirko  *	Called to flush the ops currently queued in deferred ops queue.
130793f4014SJiri Pirko  *	rtnl_lock must be held.
131793f4014SJiri Pirko  */
132793f4014SJiri Pirko void switchdev_deferred_process(void)
133793f4014SJiri Pirko {
134793f4014SJiri Pirko 	struct switchdev_deferred_item *dfitem;
135793f4014SJiri Pirko 
136793f4014SJiri Pirko 	ASSERT_RTNL();
137793f4014SJiri Pirko 
138793f4014SJiri Pirko 	while ((dfitem = switchdev_deferred_dequeue())) {
139793f4014SJiri Pirko 		dfitem->func(dfitem->dev, dfitem->data);
140793f4014SJiri Pirko 		dev_put(dfitem->dev);
141793f4014SJiri Pirko 		kfree(dfitem);
142793f4014SJiri Pirko 	}
143793f4014SJiri Pirko }
144793f4014SJiri Pirko EXPORT_SYMBOL_GPL(switchdev_deferred_process);
145793f4014SJiri Pirko 
146793f4014SJiri Pirko static void switchdev_deferred_process_work(struct work_struct *work)
147793f4014SJiri Pirko {
148793f4014SJiri Pirko 	rtnl_lock();
149793f4014SJiri Pirko 	switchdev_deferred_process();
150793f4014SJiri Pirko 	rtnl_unlock();
151793f4014SJiri Pirko }
152793f4014SJiri Pirko 
153793f4014SJiri Pirko static DECLARE_WORK(deferred_process_work, switchdev_deferred_process_work);
154793f4014SJiri Pirko 
155793f4014SJiri Pirko static int switchdev_deferred_enqueue(struct net_device *dev,
156793f4014SJiri Pirko 				      const void *data, size_t data_len,
157793f4014SJiri Pirko 				      switchdev_deferred_func_t *func)
158793f4014SJiri Pirko {
159793f4014SJiri Pirko 	struct switchdev_deferred_item *dfitem;
160793f4014SJiri Pirko 
161793f4014SJiri Pirko 	dfitem = kmalloc(sizeof(*dfitem) + data_len, GFP_ATOMIC);
162793f4014SJiri Pirko 	if (!dfitem)
163793f4014SJiri Pirko 		return -ENOMEM;
164793f4014SJiri Pirko 	dfitem->dev = dev;
165793f4014SJiri Pirko 	dfitem->func = func;
166793f4014SJiri Pirko 	memcpy(dfitem->data, data, data_len);
167793f4014SJiri Pirko 	dev_hold(dev);
168793f4014SJiri Pirko 	spin_lock_bh(&deferred_lock);
169793f4014SJiri Pirko 	list_add_tail(&dfitem->list, &deferred);
170793f4014SJiri Pirko 	spin_unlock_bh(&deferred_lock);
171793f4014SJiri Pirko 	schedule_work(&deferred_process_work);
172793f4014SJiri Pirko 	return 0;
173793f4014SJiri Pirko }
174793f4014SJiri Pirko 
1757ea6eb3fSJiri Pirko /**
1763094333dSScott Feldman  *	switchdev_port_attr_get - Get port attribute
1773094333dSScott Feldman  *
1783094333dSScott Feldman  *	@dev: port device
1793094333dSScott Feldman  *	@attr: attribute to get
1803094333dSScott Feldman  */
1813094333dSScott Feldman int switchdev_port_attr_get(struct net_device *dev, struct switchdev_attr *attr)
1823094333dSScott Feldman {
1833094333dSScott Feldman 	const struct switchdev_ops *ops = dev->switchdev_ops;
1843094333dSScott Feldman 	struct net_device *lower_dev;
1853094333dSScott Feldman 	struct list_head *iter;
1863094333dSScott Feldman 	struct switchdev_attr first = {
1871f868398SJiri Pirko 		.id = SWITCHDEV_ATTR_ID_UNDEFINED
1883094333dSScott Feldman 	};
1893094333dSScott Feldman 	int err = -EOPNOTSUPP;
1903094333dSScott Feldman 
1913094333dSScott Feldman 	if (ops && ops->switchdev_port_attr_get)
1923094333dSScott Feldman 		return ops->switchdev_port_attr_get(dev, attr);
1933094333dSScott Feldman 
1943094333dSScott Feldman 	if (attr->flags & SWITCHDEV_F_NO_RECURSE)
1953094333dSScott Feldman 		return err;
1963094333dSScott Feldman 
1973094333dSScott Feldman 	/* Switch device port(s) may be stacked under
1983094333dSScott Feldman 	 * bond/team/vlan dev, so recurse down to get attr on
1993094333dSScott Feldman 	 * each port.  Return -ENODATA if attr values don't
2003094333dSScott Feldman 	 * compare across ports.
2013094333dSScott Feldman 	 */
2023094333dSScott Feldman 
2033094333dSScott Feldman 	netdev_for_each_lower_dev(dev, lower_dev, iter) {
2043094333dSScott Feldman 		err = switchdev_port_attr_get(lower_dev, attr);
2053094333dSScott Feldman 		if (err)
2063094333dSScott Feldman 			break;
2071f868398SJiri Pirko 		if (first.id == SWITCHDEV_ATTR_ID_UNDEFINED)
2083094333dSScott Feldman 			first = *attr;
2093094333dSScott Feldman 		else if (memcmp(&first, attr, sizeof(*attr)))
2103094333dSScott Feldman 			return -ENODATA;
2113094333dSScott Feldman 	}
2123094333dSScott Feldman 
2133094333dSScott Feldman 	return err;
2143094333dSScott Feldman }
2153094333dSScott Feldman EXPORT_SYMBOL_GPL(switchdev_port_attr_get);
2163094333dSScott Feldman 
2173094333dSScott Feldman static int __switchdev_port_attr_set(struct net_device *dev,
218f7fadf30SJiri Pirko 				     const struct switchdev_attr *attr,
2197ea6eb3fSJiri Pirko 				     struct switchdev_trans *trans)
2203094333dSScott Feldman {
2213094333dSScott Feldman 	const struct switchdev_ops *ops = dev->switchdev_ops;
2223094333dSScott Feldman 	struct net_device *lower_dev;
2233094333dSScott Feldman 	struct list_head *iter;
2243094333dSScott Feldman 	int err = -EOPNOTSUPP;
2253094333dSScott Feldman 
2263094333dSScott Feldman 	if (ops && ops->switchdev_port_attr_set)
2277ea6eb3fSJiri Pirko 		return ops->switchdev_port_attr_set(dev, attr, trans);
2283094333dSScott Feldman 
2293094333dSScott Feldman 	if (attr->flags & SWITCHDEV_F_NO_RECURSE)
230464314eaSScott Feldman 		goto done;
2313094333dSScott Feldman 
2323094333dSScott Feldman 	/* Switch device port(s) may be stacked under
2333094333dSScott Feldman 	 * bond/team/vlan dev, so recurse down to set attr on
2343094333dSScott Feldman 	 * each port.
2353094333dSScott Feldman 	 */
2363094333dSScott Feldman 
2373094333dSScott Feldman 	netdev_for_each_lower_dev(dev, lower_dev, iter) {
2387ea6eb3fSJiri Pirko 		err = __switchdev_port_attr_set(lower_dev, attr, trans);
239464314eaSScott Feldman 		if (err == -EOPNOTSUPP &&
240464314eaSScott Feldman 		    attr->flags & SWITCHDEV_F_SKIP_EOPNOTSUPP)
241464314eaSScott Feldman 			continue;
2423094333dSScott Feldman 		if (err)
2433094333dSScott Feldman 			break;
2443094333dSScott Feldman 	}
2453094333dSScott Feldman 
246464314eaSScott Feldman done:
247464314eaSScott Feldman 	if (err == -EOPNOTSUPP && attr->flags & SWITCHDEV_F_SKIP_EOPNOTSUPP)
248464314eaSScott Feldman 		err = 0;
249464314eaSScott Feldman 
2503094333dSScott Feldman 	return err;
2513094333dSScott Feldman }
2523094333dSScott Feldman 
253*0bc05d58SJiri Pirko static int switchdev_port_attr_set_now(struct net_device *dev,
254f7fadf30SJiri Pirko 				       const struct switchdev_attr *attr)
2553094333dSScott Feldman {
2567ea6eb3fSJiri Pirko 	struct switchdev_trans trans;
2573094333dSScott Feldman 	int err;
2583094333dSScott Feldman 
2597ea6eb3fSJiri Pirko 	switchdev_trans_init(&trans);
2607ea6eb3fSJiri Pirko 
2613094333dSScott Feldman 	/* Phase I: prepare for attr set. Driver/device should fail
2623094333dSScott Feldman 	 * here if there are going to be issues in the commit phase,
2633094333dSScott Feldman 	 * such as lack of resources or support.  The driver/device
2643094333dSScott Feldman 	 * should reserve resources needed for the commit phase here,
2653094333dSScott Feldman 	 * but should not commit the attr.
2663094333dSScott Feldman 	 */
2673094333dSScott Feldman 
268f623ab7fSJiri Pirko 	trans.ph_prepare = true;
2697ea6eb3fSJiri Pirko 	err = __switchdev_port_attr_set(dev, attr, &trans);
2703094333dSScott Feldman 	if (err) {
2713094333dSScott Feldman 		/* Prepare phase failed: abort the transaction.  Any
2723094333dSScott Feldman 		 * resources reserved in the prepare phase are
2733094333dSScott Feldman 		 * released.
2743094333dSScott Feldman 		 */
2753094333dSScott Feldman 
2769f6467cfSJiri Pirko 		if (err != -EOPNOTSUPP)
2777ea6eb3fSJiri Pirko 			switchdev_trans_items_destroy(&trans);
2783094333dSScott Feldman 
2793094333dSScott Feldman 		return err;
2803094333dSScott Feldman 	}
2813094333dSScott Feldman 
2823094333dSScott Feldman 	/* Phase II: commit attr set.  This cannot fail as a fault
2833094333dSScott Feldman 	 * of driver/device.  If it does, it's a bug in the driver/device
2843094333dSScott Feldman 	 * because the driver said everythings was OK in phase I.
2853094333dSScott Feldman 	 */
2863094333dSScott Feldman 
287f623ab7fSJiri Pirko 	trans.ph_prepare = false;
2887ea6eb3fSJiri Pirko 	err = __switchdev_port_attr_set(dev, attr, &trans);
289e9fdaec0SScott Feldman 	WARN(err, "%s: Commit of attribute (id=%d) failed.\n",
290e9fdaec0SScott Feldman 	     dev->name, attr->id);
2917ea6eb3fSJiri Pirko 	switchdev_trans_items_warn_destroy(dev, &trans);
2923094333dSScott Feldman 
2933094333dSScott Feldman 	return err;
2943094333dSScott Feldman }
295*0bc05d58SJiri Pirko 
296*0bc05d58SJiri Pirko static void switchdev_port_attr_set_deferred(struct net_device *dev,
297*0bc05d58SJiri Pirko 					     const void *data)
298*0bc05d58SJiri Pirko {
299*0bc05d58SJiri Pirko 	const struct switchdev_attr *attr = data;
300*0bc05d58SJiri Pirko 	int err;
301*0bc05d58SJiri Pirko 
302*0bc05d58SJiri Pirko 	err = switchdev_port_attr_set_now(dev, attr);
303*0bc05d58SJiri Pirko 	if (err && err != -EOPNOTSUPP)
304*0bc05d58SJiri Pirko 		netdev_err(dev, "failed (err=%d) to set attribute (id=%d)\n",
305*0bc05d58SJiri Pirko 			   err, attr->id);
306*0bc05d58SJiri Pirko }
307*0bc05d58SJiri Pirko 
308*0bc05d58SJiri Pirko static int switchdev_port_attr_set_defer(struct net_device *dev,
309*0bc05d58SJiri Pirko 					 const struct switchdev_attr *attr)
310*0bc05d58SJiri Pirko {
311*0bc05d58SJiri Pirko 	return switchdev_deferred_enqueue(dev, attr, sizeof(*attr),
312*0bc05d58SJiri Pirko 					  switchdev_port_attr_set_deferred);
313*0bc05d58SJiri Pirko }
314*0bc05d58SJiri Pirko 
315*0bc05d58SJiri Pirko /**
316*0bc05d58SJiri Pirko  *	switchdev_port_attr_set - Set port attribute
317*0bc05d58SJiri Pirko  *
318*0bc05d58SJiri Pirko  *	@dev: port device
319*0bc05d58SJiri Pirko  *	@attr: attribute to set
320*0bc05d58SJiri Pirko  *
321*0bc05d58SJiri Pirko  *	Use a 2-phase prepare-commit transaction model to ensure
322*0bc05d58SJiri Pirko  *	system is not left in a partially updated state due to
323*0bc05d58SJiri Pirko  *	failure from driver/device.
324*0bc05d58SJiri Pirko  *
325*0bc05d58SJiri Pirko  *	rtnl_lock must be held and must not be in atomic section,
326*0bc05d58SJiri Pirko  *	in case SWITCHDEV_F_DEFER flag is not set.
327*0bc05d58SJiri Pirko  */
328*0bc05d58SJiri Pirko int switchdev_port_attr_set(struct net_device *dev,
329*0bc05d58SJiri Pirko 			    const struct switchdev_attr *attr)
330*0bc05d58SJiri Pirko {
331*0bc05d58SJiri Pirko 	if (attr->flags & SWITCHDEV_F_DEFER)
332*0bc05d58SJiri Pirko 		return switchdev_port_attr_set_defer(dev, attr);
333*0bc05d58SJiri Pirko 	ASSERT_RTNL();
334*0bc05d58SJiri Pirko 	return switchdev_port_attr_set_now(dev, attr);
335*0bc05d58SJiri Pirko }
3363094333dSScott Feldman EXPORT_SYMBOL_GPL(switchdev_port_attr_set);
3373094333dSScott Feldman 
33822c1f67eSScott Feldman static int __switchdev_port_obj_add(struct net_device *dev,
339648b4a99SJiri Pirko 				    const struct switchdev_obj *obj,
3407ea6eb3fSJiri Pirko 				    struct switchdev_trans *trans)
341491d0f15SScott Feldman {
342491d0f15SScott Feldman 	const struct switchdev_ops *ops = dev->switchdev_ops;
343491d0f15SScott Feldman 	struct net_device *lower_dev;
344491d0f15SScott Feldman 	struct list_head *iter;
345491d0f15SScott Feldman 	int err = -EOPNOTSUPP;
346491d0f15SScott Feldman 
347491d0f15SScott Feldman 	if (ops && ops->switchdev_port_obj_add)
3489e8f4a54SJiri Pirko 		return ops->switchdev_port_obj_add(dev, obj, trans);
349491d0f15SScott Feldman 
350491d0f15SScott Feldman 	/* Switch device port(s) may be stacked under
351491d0f15SScott Feldman 	 * bond/team/vlan dev, so recurse down to add object on
352491d0f15SScott Feldman 	 * each port.
353491d0f15SScott Feldman 	 */
354491d0f15SScott Feldman 
355491d0f15SScott Feldman 	netdev_for_each_lower_dev(dev, lower_dev, iter) {
3569e8f4a54SJiri Pirko 		err = __switchdev_port_obj_add(lower_dev, obj, trans);
357491d0f15SScott Feldman 		if (err)
358491d0f15SScott Feldman 			break;
359491d0f15SScott Feldman 	}
360491d0f15SScott Feldman 
361491d0f15SScott Feldman 	return err;
362491d0f15SScott Feldman }
363491d0f15SScott Feldman 
364491d0f15SScott Feldman /**
365491d0f15SScott Feldman  *	switchdev_port_obj_add - Add port object
366491d0f15SScott Feldman  *
367491d0f15SScott Feldman  *	@dev: port device
368ab069002SVivien Didelot  *	@id: object ID
369491d0f15SScott Feldman  *	@obj: object to add
370491d0f15SScott Feldman  *
371491d0f15SScott Feldman  *	Use a 2-phase prepare-commit transaction model to ensure
372491d0f15SScott Feldman  *	system is not left in a partially updated state due to
373491d0f15SScott Feldman  *	failure from driver/device.
374491d0f15SScott Feldman  *
375491d0f15SScott Feldman  *	rtnl_lock must be held.
376491d0f15SScott Feldman  */
3779e8f4a54SJiri Pirko int switchdev_port_obj_add(struct net_device *dev,
378648b4a99SJiri Pirko 			   const struct switchdev_obj *obj)
379491d0f15SScott Feldman {
3807ea6eb3fSJiri Pirko 	struct switchdev_trans trans;
381491d0f15SScott Feldman 	int err;
382491d0f15SScott Feldman 
383491d0f15SScott Feldman 	ASSERT_RTNL();
384491d0f15SScott Feldman 
3857ea6eb3fSJiri Pirko 	switchdev_trans_init(&trans);
3867ea6eb3fSJiri Pirko 
387491d0f15SScott Feldman 	/* Phase I: prepare for obj add. Driver/device should fail
388491d0f15SScott Feldman 	 * here if there are going to be issues in the commit phase,
389491d0f15SScott Feldman 	 * such as lack of resources or support.  The driver/device
390491d0f15SScott Feldman 	 * should reserve resources needed for the commit phase here,
391491d0f15SScott Feldman 	 * but should not commit the obj.
392491d0f15SScott Feldman 	 */
393491d0f15SScott Feldman 
394f623ab7fSJiri Pirko 	trans.ph_prepare = true;
3959e8f4a54SJiri Pirko 	err = __switchdev_port_obj_add(dev, obj, &trans);
396491d0f15SScott Feldman 	if (err) {
397491d0f15SScott Feldman 		/* Prepare phase failed: abort the transaction.  Any
398491d0f15SScott Feldman 		 * resources reserved in the prepare phase are
399491d0f15SScott Feldman 		 * released.
400491d0f15SScott Feldman 		 */
401491d0f15SScott Feldman 
4029f6467cfSJiri Pirko 		if (err != -EOPNOTSUPP)
4037ea6eb3fSJiri Pirko 			switchdev_trans_items_destroy(&trans);
404491d0f15SScott Feldman 
405491d0f15SScott Feldman 		return err;
406491d0f15SScott Feldman 	}
407491d0f15SScott Feldman 
408491d0f15SScott Feldman 	/* Phase II: commit obj add.  This cannot fail as a fault
409491d0f15SScott Feldman 	 * of driver/device.  If it does, it's a bug in the driver/device
410491d0f15SScott Feldman 	 * because the driver said everythings was OK in phase I.
411491d0f15SScott Feldman 	 */
412491d0f15SScott Feldman 
413f623ab7fSJiri Pirko 	trans.ph_prepare = false;
4149e8f4a54SJiri Pirko 	err = __switchdev_port_obj_add(dev, obj, &trans);
4159e8f4a54SJiri Pirko 	WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, obj->id);
4167ea6eb3fSJiri Pirko 	switchdev_trans_items_warn_destroy(dev, &trans);
417491d0f15SScott Feldman 
418491d0f15SScott Feldman 	return err;
419491d0f15SScott Feldman }
420491d0f15SScott Feldman EXPORT_SYMBOL_GPL(switchdev_port_obj_add);
421491d0f15SScott Feldman 
422491d0f15SScott Feldman /**
423491d0f15SScott Feldman  *	switchdev_port_obj_del - Delete port object
424491d0f15SScott Feldman  *
425491d0f15SScott Feldman  *	@dev: port device
426ab069002SVivien Didelot  *	@id: object ID
427491d0f15SScott Feldman  *	@obj: object to delete
428491d0f15SScott Feldman  */
4299e8f4a54SJiri Pirko int switchdev_port_obj_del(struct net_device *dev,
430648b4a99SJiri Pirko 			   const struct switchdev_obj *obj)
431491d0f15SScott Feldman {
432491d0f15SScott Feldman 	const struct switchdev_ops *ops = dev->switchdev_ops;
433491d0f15SScott Feldman 	struct net_device *lower_dev;
434491d0f15SScott Feldman 	struct list_head *iter;
435491d0f15SScott Feldman 	int err = -EOPNOTSUPP;
436491d0f15SScott Feldman 
437491d0f15SScott Feldman 	if (ops && ops->switchdev_port_obj_del)
4389e8f4a54SJiri Pirko 		return ops->switchdev_port_obj_del(dev, obj);
439491d0f15SScott Feldman 
440491d0f15SScott Feldman 	/* Switch device port(s) may be stacked under
441491d0f15SScott Feldman 	 * bond/team/vlan dev, so recurse down to delete object on
442491d0f15SScott Feldman 	 * each port.
443491d0f15SScott Feldman 	 */
444491d0f15SScott Feldman 
445491d0f15SScott Feldman 	netdev_for_each_lower_dev(dev, lower_dev, iter) {
4469e8f4a54SJiri Pirko 		err = switchdev_port_obj_del(lower_dev, obj);
447491d0f15SScott Feldman 		if (err)
448491d0f15SScott Feldman 			break;
449491d0f15SScott Feldman 	}
450491d0f15SScott Feldman 
451491d0f15SScott Feldman 	return err;
452491d0f15SScott Feldman }
453491d0f15SScott Feldman EXPORT_SYMBOL_GPL(switchdev_port_obj_del);
454491d0f15SScott Feldman 
45545d4122cSSamudrala, Sridhar /**
45645d4122cSSamudrala, Sridhar  *	switchdev_port_obj_dump - Dump port objects
45745d4122cSSamudrala, Sridhar  *
45845d4122cSSamudrala, Sridhar  *	@dev: port device
45925f07adcSVivien Didelot  *	@id: object ID
46045d4122cSSamudrala, Sridhar  *	@obj: object to dump
46125f07adcSVivien Didelot  *	@cb: function to call with a filled object
46245d4122cSSamudrala, Sridhar  */
4639e8f4a54SJiri Pirko int switchdev_port_obj_dump(struct net_device *dev, struct switchdev_obj *obj,
464648b4a99SJiri Pirko 			    switchdev_obj_dump_cb_t *cb)
46545d4122cSSamudrala, Sridhar {
46645d4122cSSamudrala, Sridhar 	const struct switchdev_ops *ops = dev->switchdev_ops;
46745d4122cSSamudrala, Sridhar 	struct net_device *lower_dev;
46845d4122cSSamudrala, Sridhar 	struct list_head *iter;
46945d4122cSSamudrala, Sridhar 	int err = -EOPNOTSUPP;
47045d4122cSSamudrala, Sridhar 
47145d4122cSSamudrala, Sridhar 	if (ops && ops->switchdev_port_obj_dump)
4729e8f4a54SJiri Pirko 		return ops->switchdev_port_obj_dump(dev, obj, cb);
47345d4122cSSamudrala, Sridhar 
47445d4122cSSamudrala, Sridhar 	/* Switch device port(s) may be stacked under
47545d4122cSSamudrala, Sridhar 	 * bond/team/vlan dev, so recurse down to dump objects on
47645d4122cSSamudrala, Sridhar 	 * first port at bottom of stack.
47745d4122cSSamudrala, Sridhar 	 */
47845d4122cSSamudrala, Sridhar 
47945d4122cSSamudrala, Sridhar 	netdev_for_each_lower_dev(dev, lower_dev, iter) {
4809e8f4a54SJiri Pirko 		err = switchdev_port_obj_dump(lower_dev, obj, cb);
48145d4122cSSamudrala, Sridhar 		break;
48245d4122cSSamudrala, Sridhar 	}
48345d4122cSSamudrala, Sridhar 
48445d4122cSSamudrala, Sridhar 	return err;
48545d4122cSSamudrala, Sridhar }
48645d4122cSSamudrala, Sridhar EXPORT_SYMBOL_GPL(switchdev_port_obj_dump);
48745d4122cSSamudrala, Sridhar 
488ebb9a03aSJiri Pirko static DEFINE_MUTEX(switchdev_mutex);
489ebb9a03aSJiri Pirko static RAW_NOTIFIER_HEAD(switchdev_notif_chain);
49003bf0c28SJiri Pirko 
49103bf0c28SJiri Pirko /**
492ebb9a03aSJiri Pirko  *	register_switchdev_notifier - Register notifier
49303bf0c28SJiri Pirko  *	@nb: notifier_block
49403bf0c28SJiri Pirko  *
49503bf0c28SJiri Pirko  *	Register switch device notifier. This should be used by code
49603bf0c28SJiri Pirko  *	which needs to monitor events happening in particular device.
49703bf0c28SJiri Pirko  *	Return values are same as for atomic_notifier_chain_register().
49803bf0c28SJiri Pirko  */
499ebb9a03aSJiri Pirko int register_switchdev_notifier(struct notifier_block *nb)
50003bf0c28SJiri Pirko {
50103bf0c28SJiri Pirko 	int err;
50203bf0c28SJiri Pirko 
503ebb9a03aSJiri Pirko 	mutex_lock(&switchdev_mutex);
504ebb9a03aSJiri Pirko 	err = raw_notifier_chain_register(&switchdev_notif_chain, nb);
505ebb9a03aSJiri Pirko 	mutex_unlock(&switchdev_mutex);
50603bf0c28SJiri Pirko 	return err;
50703bf0c28SJiri Pirko }
508ebb9a03aSJiri Pirko EXPORT_SYMBOL_GPL(register_switchdev_notifier);
50903bf0c28SJiri Pirko 
51003bf0c28SJiri Pirko /**
511ebb9a03aSJiri Pirko  *	unregister_switchdev_notifier - Unregister notifier
51203bf0c28SJiri Pirko  *	@nb: notifier_block
51303bf0c28SJiri Pirko  *
51403bf0c28SJiri Pirko  *	Unregister switch device notifier.
51503bf0c28SJiri Pirko  *	Return values are same as for atomic_notifier_chain_unregister().
51603bf0c28SJiri Pirko  */
517ebb9a03aSJiri Pirko int unregister_switchdev_notifier(struct notifier_block *nb)
51803bf0c28SJiri Pirko {
51903bf0c28SJiri Pirko 	int err;
52003bf0c28SJiri Pirko 
521ebb9a03aSJiri Pirko 	mutex_lock(&switchdev_mutex);
522ebb9a03aSJiri Pirko 	err = raw_notifier_chain_unregister(&switchdev_notif_chain, nb);
523ebb9a03aSJiri Pirko 	mutex_unlock(&switchdev_mutex);
52403bf0c28SJiri Pirko 	return err;
52503bf0c28SJiri Pirko }
526ebb9a03aSJiri Pirko EXPORT_SYMBOL_GPL(unregister_switchdev_notifier);
52703bf0c28SJiri Pirko 
52803bf0c28SJiri Pirko /**
529ebb9a03aSJiri Pirko  *	call_switchdev_notifiers - Call notifiers
53003bf0c28SJiri Pirko  *	@val: value passed unmodified to notifier function
53103bf0c28SJiri Pirko  *	@dev: port device
53203bf0c28SJiri Pirko  *	@info: notifier information data
53303bf0c28SJiri Pirko  *
53403bf0c28SJiri Pirko  *	Call all network notifier blocks. This should be called by driver
53503bf0c28SJiri Pirko  *	when it needs to propagate hardware event.
53603bf0c28SJiri Pirko  *	Return values are same as for atomic_notifier_call_chain().
53703bf0c28SJiri Pirko  */
538ebb9a03aSJiri Pirko int call_switchdev_notifiers(unsigned long val, struct net_device *dev,
539ebb9a03aSJiri Pirko 			     struct switchdev_notifier_info *info)
54003bf0c28SJiri Pirko {
54103bf0c28SJiri Pirko 	int err;
54203bf0c28SJiri Pirko 
54303bf0c28SJiri Pirko 	info->dev = dev;
544ebb9a03aSJiri Pirko 	mutex_lock(&switchdev_mutex);
545ebb9a03aSJiri Pirko 	err = raw_notifier_call_chain(&switchdev_notif_chain, val, info);
546ebb9a03aSJiri Pirko 	mutex_unlock(&switchdev_mutex);
54703bf0c28SJiri Pirko 	return err;
54803bf0c28SJiri Pirko }
549ebb9a03aSJiri Pirko EXPORT_SYMBOL_GPL(call_switchdev_notifiers);
5508a44dbb2SRoopa Prabhu 
5517d4f8d87SScott Feldman struct switchdev_vlan_dump {
5528f24f309SJiri Pirko 	struct switchdev_obj_port_vlan vlan;
5537d4f8d87SScott Feldman 	struct sk_buff *skb;
5547d4f8d87SScott Feldman 	u32 filter_mask;
5557d4f8d87SScott Feldman 	u16 flags;
5567d4f8d87SScott Feldman 	u16 begin;
5577d4f8d87SScott Feldman 	u16 end;
5587d4f8d87SScott Feldman };
5597d4f8d87SScott Feldman 
560e23b002bSVivien Didelot static int switchdev_port_vlan_dump_put(struct switchdev_vlan_dump *dump)
5617d4f8d87SScott Feldman {
5627d4f8d87SScott Feldman 	struct bridge_vlan_info vinfo;
5637d4f8d87SScott Feldman 
5647d4f8d87SScott Feldman 	vinfo.flags = dump->flags;
5657d4f8d87SScott Feldman 
5667d4f8d87SScott Feldman 	if (dump->begin == 0 && dump->end == 0) {
5677d4f8d87SScott Feldman 		return 0;
5687d4f8d87SScott Feldman 	} else if (dump->begin == dump->end) {
5697d4f8d87SScott Feldman 		vinfo.vid = dump->begin;
5707d4f8d87SScott Feldman 		if (nla_put(dump->skb, IFLA_BRIDGE_VLAN_INFO,
5717d4f8d87SScott Feldman 			    sizeof(vinfo), &vinfo))
5727d4f8d87SScott Feldman 			return -EMSGSIZE;
5737d4f8d87SScott Feldman 	} else {
5747d4f8d87SScott Feldman 		vinfo.vid = dump->begin;
5757d4f8d87SScott Feldman 		vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
5767d4f8d87SScott Feldman 		if (nla_put(dump->skb, IFLA_BRIDGE_VLAN_INFO,
5777d4f8d87SScott Feldman 			    sizeof(vinfo), &vinfo))
5787d4f8d87SScott Feldman 			return -EMSGSIZE;
5797d4f8d87SScott Feldman 		vinfo.vid = dump->end;
5807d4f8d87SScott Feldman 		vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
5817d4f8d87SScott Feldman 		vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_END;
5827d4f8d87SScott Feldman 		if (nla_put(dump->skb, IFLA_BRIDGE_VLAN_INFO,
5837d4f8d87SScott Feldman 			    sizeof(vinfo), &vinfo))
5847d4f8d87SScott Feldman 			return -EMSGSIZE;
5857d4f8d87SScott Feldman 	}
5867d4f8d87SScott Feldman 
5877d4f8d87SScott Feldman 	return 0;
5887d4f8d87SScott Feldman }
5897d4f8d87SScott Feldman 
590648b4a99SJiri Pirko static int switchdev_port_vlan_dump_cb(struct switchdev_obj *obj)
5917d4f8d87SScott Feldman {
592648b4a99SJiri Pirko 	struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
5937d4f8d87SScott Feldman 	struct switchdev_vlan_dump *dump =
59425f07adcSVivien Didelot 		container_of(vlan, struct switchdev_vlan_dump, vlan);
5957d4f8d87SScott Feldman 	int err = 0;
5967d4f8d87SScott Feldman 
5977d4f8d87SScott Feldman 	if (vlan->vid_begin > vlan->vid_end)
5987d4f8d87SScott Feldman 		return -EINVAL;
5997d4f8d87SScott Feldman 
6007d4f8d87SScott Feldman 	if (dump->filter_mask & RTEXT_FILTER_BRVLAN) {
6017d4f8d87SScott Feldman 		dump->flags = vlan->flags;
6027d4f8d87SScott Feldman 		for (dump->begin = dump->end = vlan->vid_begin;
6037d4f8d87SScott Feldman 		     dump->begin <= vlan->vid_end;
6047d4f8d87SScott Feldman 		     dump->begin++, dump->end++) {
605e23b002bSVivien Didelot 			err = switchdev_port_vlan_dump_put(dump);
6067d4f8d87SScott Feldman 			if (err)
6077d4f8d87SScott Feldman 				return err;
6087d4f8d87SScott Feldman 		}
6097d4f8d87SScott Feldman 	} else if (dump->filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED) {
6107d4f8d87SScott Feldman 		if (dump->begin > vlan->vid_begin &&
6117d4f8d87SScott Feldman 		    dump->begin >= vlan->vid_end) {
6127d4f8d87SScott Feldman 			if ((dump->begin - 1) == vlan->vid_end &&
6137d4f8d87SScott Feldman 			    dump->flags == vlan->flags) {
6147d4f8d87SScott Feldman 				/* prepend */
6157d4f8d87SScott Feldman 				dump->begin = vlan->vid_begin;
6167d4f8d87SScott Feldman 			} else {
617e23b002bSVivien Didelot 				err = switchdev_port_vlan_dump_put(dump);
6187d4f8d87SScott Feldman 				dump->flags = vlan->flags;
6197d4f8d87SScott Feldman 				dump->begin = vlan->vid_begin;
6207d4f8d87SScott Feldman 				dump->end = vlan->vid_end;
6217d4f8d87SScott Feldman 			}
6227d4f8d87SScott Feldman 		} else if (dump->end <= vlan->vid_begin &&
6237d4f8d87SScott Feldman 		           dump->end < vlan->vid_end) {
6247d4f8d87SScott Feldman 			if ((dump->end  + 1) == vlan->vid_begin &&
6257d4f8d87SScott Feldman 			    dump->flags == vlan->flags) {
6267d4f8d87SScott Feldman 				/* append */
6277d4f8d87SScott Feldman 				dump->end = vlan->vid_end;
6287d4f8d87SScott Feldman 			} else {
629e23b002bSVivien Didelot 				err = switchdev_port_vlan_dump_put(dump);
6307d4f8d87SScott Feldman 				dump->flags = vlan->flags;
6317d4f8d87SScott Feldman 				dump->begin = vlan->vid_begin;
6327d4f8d87SScott Feldman 				dump->end = vlan->vid_end;
6337d4f8d87SScott Feldman 			}
6347d4f8d87SScott Feldman 		} else {
6357d4f8d87SScott Feldman 			err = -EINVAL;
6367d4f8d87SScott Feldman 		}
6377d4f8d87SScott Feldman 	}
6387d4f8d87SScott Feldman 
6397d4f8d87SScott Feldman 	return err;
6407d4f8d87SScott Feldman }
6417d4f8d87SScott Feldman 
6427d4f8d87SScott Feldman static int switchdev_port_vlan_fill(struct sk_buff *skb, struct net_device *dev,
6437d4f8d87SScott Feldman 				    u32 filter_mask)
6447d4f8d87SScott Feldman {
6457d4f8d87SScott Feldman 	struct switchdev_vlan_dump dump = {
6469e8f4a54SJiri Pirko 		.vlan.obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
6477d4f8d87SScott Feldman 		.skb = skb,
6487d4f8d87SScott Feldman 		.filter_mask = filter_mask,
6497d4f8d87SScott Feldman 	};
6507d4f8d87SScott Feldman 	int err = 0;
6517d4f8d87SScott Feldman 
6527d4f8d87SScott Feldman 	if ((filter_mask & RTEXT_FILTER_BRVLAN) ||
6537d4f8d87SScott Feldman 	    (filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED)) {
6549e8f4a54SJiri Pirko 		err = switchdev_port_obj_dump(dev, &dump.vlan.obj,
65525f07adcSVivien Didelot 					      switchdev_port_vlan_dump_cb);
6567d4f8d87SScott Feldman 		if (err)
6577d4f8d87SScott Feldman 			goto err_out;
6587d4f8d87SScott Feldman 		if (filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED)
6597d4f8d87SScott Feldman 			/* last one */
660e23b002bSVivien Didelot 			err = switchdev_port_vlan_dump_put(&dump);
6617d4f8d87SScott Feldman 	}
6627d4f8d87SScott Feldman 
6637d4f8d87SScott Feldman err_out:
6647d4f8d87SScott Feldman 	return err == -EOPNOTSUPP ? 0 : err;
6657d4f8d87SScott Feldman }
6667d4f8d87SScott Feldman 
6678793d0a6SScott Feldman /**
6688793d0a6SScott Feldman  *	switchdev_port_bridge_getlink - Get bridge port attributes
6698793d0a6SScott Feldman  *
6708793d0a6SScott Feldman  *	@dev: port device
6718793d0a6SScott Feldman  *
6728793d0a6SScott Feldman  *	Called for SELF on rtnl_bridge_getlink to get bridge port
6738793d0a6SScott Feldman  *	attributes.
6748793d0a6SScott Feldman  */
6758793d0a6SScott Feldman int switchdev_port_bridge_getlink(struct sk_buff *skb, u32 pid, u32 seq,
6768793d0a6SScott Feldman 				  struct net_device *dev, u32 filter_mask,
6778793d0a6SScott Feldman 				  int nlflags)
6788793d0a6SScott Feldman {
6798793d0a6SScott Feldman 	struct switchdev_attr attr = {
6801f868398SJiri Pirko 		.id = SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS,
6818793d0a6SScott Feldman 	};
6828793d0a6SScott Feldman 	u16 mode = BRIDGE_MODE_UNDEF;
6838793d0a6SScott Feldman 	u32 mask = BR_LEARNING | BR_LEARNING_SYNC;
6848793d0a6SScott Feldman 	int err;
6858793d0a6SScott Feldman 
6868793d0a6SScott Feldman 	err = switchdev_port_attr_get(dev, &attr);
6875c8079d0SVivien Didelot 	if (err && err != -EOPNOTSUPP)
6888793d0a6SScott Feldman 		return err;
6898793d0a6SScott Feldman 
6908793d0a6SScott Feldman 	return ndo_dflt_bridge_getlink(skb, pid, seq, dev, mode,
6917d4f8d87SScott Feldman 				       attr.u.brport_flags, mask, nlflags,
6927d4f8d87SScott Feldman 				       filter_mask, switchdev_port_vlan_fill);
6938793d0a6SScott Feldman }
6948793d0a6SScott Feldman EXPORT_SYMBOL_GPL(switchdev_port_bridge_getlink);
6958793d0a6SScott Feldman 
69647f8328bSScott Feldman static int switchdev_port_br_setflag(struct net_device *dev,
69747f8328bSScott Feldman 				     struct nlattr *nlattr,
69847f8328bSScott Feldman 				     unsigned long brport_flag)
69947f8328bSScott Feldman {
70047f8328bSScott Feldman 	struct switchdev_attr attr = {
7011f868398SJiri Pirko 		.id = SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS,
70247f8328bSScott Feldman 	};
70347f8328bSScott Feldman 	u8 flag = nla_get_u8(nlattr);
70447f8328bSScott Feldman 	int err;
70547f8328bSScott Feldman 
70647f8328bSScott Feldman 	err = switchdev_port_attr_get(dev, &attr);
70747f8328bSScott Feldman 	if (err)
70847f8328bSScott Feldman 		return err;
70947f8328bSScott Feldman 
71047f8328bSScott Feldman 	if (flag)
71142275bd8SScott Feldman 		attr.u.brport_flags |= brport_flag;
71247f8328bSScott Feldman 	else
71342275bd8SScott Feldman 		attr.u.brport_flags &= ~brport_flag;
71447f8328bSScott Feldman 
71547f8328bSScott Feldman 	return switchdev_port_attr_set(dev, &attr);
71647f8328bSScott Feldman }
71747f8328bSScott Feldman 
71847f8328bSScott Feldman static const struct nla_policy
71947f8328bSScott Feldman switchdev_port_bridge_policy[IFLA_BRPORT_MAX + 1] = {
72047f8328bSScott Feldman 	[IFLA_BRPORT_STATE]		= { .type = NLA_U8 },
72147f8328bSScott Feldman 	[IFLA_BRPORT_COST]		= { .type = NLA_U32 },
72247f8328bSScott Feldman 	[IFLA_BRPORT_PRIORITY]		= { .type = NLA_U16 },
72347f8328bSScott Feldman 	[IFLA_BRPORT_MODE]		= { .type = NLA_U8 },
72447f8328bSScott Feldman 	[IFLA_BRPORT_GUARD]		= { .type = NLA_U8 },
72547f8328bSScott Feldman 	[IFLA_BRPORT_PROTECT]		= { .type = NLA_U8 },
72647f8328bSScott Feldman 	[IFLA_BRPORT_FAST_LEAVE]	= { .type = NLA_U8 },
72747f8328bSScott Feldman 	[IFLA_BRPORT_LEARNING]		= { .type = NLA_U8 },
72847f8328bSScott Feldman 	[IFLA_BRPORT_LEARNING_SYNC]	= { .type = NLA_U8 },
72947f8328bSScott Feldman 	[IFLA_BRPORT_UNICAST_FLOOD]	= { .type = NLA_U8 },
73047f8328bSScott Feldman };
73147f8328bSScott Feldman 
73247f8328bSScott Feldman static int switchdev_port_br_setlink_protinfo(struct net_device *dev,
73347f8328bSScott Feldman 					      struct nlattr *protinfo)
73447f8328bSScott Feldman {
73547f8328bSScott Feldman 	struct nlattr *attr;
73647f8328bSScott Feldman 	int rem;
73747f8328bSScott Feldman 	int err;
73847f8328bSScott Feldman 
73947f8328bSScott Feldman 	err = nla_validate_nested(protinfo, IFLA_BRPORT_MAX,
74047f8328bSScott Feldman 				  switchdev_port_bridge_policy);
74147f8328bSScott Feldman 	if (err)
74247f8328bSScott Feldman 		return err;
74347f8328bSScott Feldman 
74447f8328bSScott Feldman 	nla_for_each_nested(attr, protinfo, rem) {
74547f8328bSScott Feldman 		switch (nla_type(attr)) {
74647f8328bSScott Feldman 		case IFLA_BRPORT_LEARNING:
74747f8328bSScott Feldman 			err = switchdev_port_br_setflag(dev, attr,
74847f8328bSScott Feldman 							BR_LEARNING);
74947f8328bSScott Feldman 			break;
75047f8328bSScott Feldman 		case IFLA_BRPORT_LEARNING_SYNC:
75147f8328bSScott Feldman 			err = switchdev_port_br_setflag(dev, attr,
75247f8328bSScott Feldman 							BR_LEARNING_SYNC);
75347f8328bSScott Feldman 			break;
75447f8328bSScott Feldman 		default:
75547f8328bSScott Feldman 			err = -EOPNOTSUPP;
75647f8328bSScott Feldman 			break;
75747f8328bSScott Feldman 		}
75847f8328bSScott Feldman 		if (err)
75947f8328bSScott Feldman 			return err;
76047f8328bSScott Feldman 	}
76147f8328bSScott Feldman 
76247f8328bSScott Feldman 	return 0;
76347f8328bSScott Feldman }
76447f8328bSScott Feldman 
76547f8328bSScott Feldman static int switchdev_port_br_afspec(struct net_device *dev,
76647f8328bSScott Feldman 				    struct nlattr *afspec,
76747f8328bSScott Feldman 				    int (*f)(struct net_device *dev,
768648b4a99SJiri Pirko 					     const struct switchdev_obj *obj))
76947f8328bSScott Feldman {
77047f8328bSScott Feldman 	struct nlattr *attr;
77147f8328bSScott Feldman 	struct bridge_vlan_info *vinfo;
7729e8f4a54SJiri Pirko 	struct switchdev_obj_port_vlan vlan = {
7739e8f4a54SJiri Pirko 		.obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
7749e8f4a54SJiri Pirko 	};
77547f8328bSScott Feldman 	int rem;
77647f8328bSScott Feldman 	int err;
77747f8328bSScott Feldman 
77847f8328bSScott Feldman 	nla_for_each_nested(attr, afspec, rem) {
77947f8328bSScott Feldman 		if (nla_type(attr) != IFLA_BRIDGE_VLAN_INFO)
78047f8328bSScott Feldman 			continue;
78147f8328bSScott Feldman 		if (nla_len(attr) != sizeof(struct bridge_vlan_info))
78247f8328bSScott Feldman 			return -EINVAL;
78347f8328bSScott Feldman 		vinfo = nla_data(attr);
784ab069002SVivien Didelot 		vlan.flags = vinfo->flags;
78547f8328bSScott Feldman 		if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
786ab069002SVivien Didelot 			if (vlan.vid_begin)
78747f8328bSScott Feldman 				return -EINVAL;
788ab069002SVivien Didelot 			vlan.vid_begin = vinfo->vid;
789cc02aa8eSNikolay Aleksandrov 			/* don't allow range of pvids */
790cc02aa8eSNikolay Aleksandrov 			if (vlan.flags & BRIDGE_VLAN_INFO_PVID)
791cc02aa8eSNikolay Aleksandrov 				return -EINVAL;
79247f8328bSScott Feldman 		} else if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END) {
793ab069002SVivien Didelot 			if (!vlan.vid_begin)
79447f8328bSScott Feldman 				return -EINVAL;
795ab069002SVivien Didelot 			vlan.vid_end = vinfo->vid;
796ab069002SVivien Didelot 			if (vlan.vid_end <= vlan.vid_begin)
79747f8328bSScott Feldman 				return -EINVAL;
7989e8f4a54SJiri Pirko 			err = f(dev, &vlan.obj);
79947f8328bSScott Feldman 			if (err)
80047f8328bSScott Feldman 				return err;
801ab069002SVivien Didelot 			memset(&vlan, 0, sizeof(vlan));
80247f8328bSScott Feldman 		} else {
803ab069002SVivien Didelot 			if (vlan.vid_begin)
80447f8328bSScott Feldman 				return -EINVAL;
805ab069002SVivien Didelot 			vlan.vid_begin = vinfo->vid;
806ab069002SVivien Didelot 			vlan.vid_end = vinfo->vid;
8079e8f4a54SJiri Pirko 			err = f(dev, &vlan.obj);
80847f8328bSScott Feldman 			if (err)
80947f8328bSScott Feldman 				return err;
810ab069002SVivien Didelot 			memset(&vlan, 0, sizeof(vlan));
81147f8328bSScott Feldman 		}
81247f8328bSScott Feldman 	}
81347f8328bSScott Feldman 
81447f8328bSScott Feldman 	return 0;
81547f8328bSScott Feldman }
81647f8328bSScott Feldman 
8178a44dbb2SRoopa Prabhu /**
81847f8328bSScott Feldman  *	switchdev_port_bridge_setlink - Set bridge port attributes
8198a44dbb2SRoopa Prabhu  *
8208a44dbb2SRoopa Prabhu  *	@dev: port device
82147f8328bSScott Feldman  *	@nlh: netlink header
82247f8328bSScott Feldman  *	@flags: netlink flags
8238a44dbb2SRoopa Prabhu  *
82447f8328bSScott Feldman  *	Called for SELF on rtnl_bridge_setlink to set bridge port
82547f8328bSScott Feldman  *	attributes.
8268a44dbb2SRoopa Prabhu  */
827ebb9a03aSJiri Pirko int switchdev_port_bridge_setlink(struct net_device *dev,
8288a44dbb2SRoopa Prabhu 				  struct nlmsghdr *nlh, u16 flags)
8298a44dbb2SRoopa Prabhu {
83047f8328bSScott Feldman 	struct nlattr *protinfo;
83147f8328bSScott Feldman 	struct nlattr *afspec;
83247f8328bSScott Feldman 	int err = 0;
8338a44dbb2SRoopa Prabhu 
83447f8328bSScott Feldman 	protinfo = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg),
83547f8328bSScott Feldman 				   IFLA_PROTINFO);
83647f8328bSScott Feldman 	if (protinfo) {
83747f8328bSScott Feldman 		err = switchdev_port_br_setlink_protinfo(dev, protinfo);
83847f8328bSScott Feldman 		if (err)
83947f8328bSScott Feldman 			return err;
84047f8328bSScott Feldman 	}
8418a44dbb2SRoopa Prabhu 
84247f8328bSScott Feldman 	afspec = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg),
84347f8328bSScott Feldman 				 IFLA_AF_SPEC);
84447f8328bSScott Feldman 	if (afspec)
84547f8328bSScott Feldman 		err = switchdev_port_br_afspec(dev, afspec,
84647f8328bSScott Feldman 					       switchdev_port_obj_add);
8478a44dbb2SRoopa Prabhu 
84847f8328bSScott Feldman 	return err;
8498a44dbb2SRoopa Prabhu }
850ebb9a03aSJiri Pirko EXPORT_SYMBOL_GPL(switchdev_port_bridge_setlink);
8518a44dbb2SRoopa Prabhu 
8528a44dbb2SRoopa Prabhu /**
8535c34e022SScott Feldman  *	switchdev_port_bridge_dellink - Set bridge port attributes
8548a44dbb2SRoopa Prabhu  *
8558a44dbb2SRoopa Prabhu  *	@dev: port device
8565c34e022SScott Feldman  *	@nlh: netlink header
8575c34e022SScott Feldman  *	@flags: netlink flags
8588a44dbb2SRoopa Prabhu  *
8595c34e022SScott Feldman  *	Called for SELF on rtnl_bridge_dellink to set bridge port
8605c34e022SScott Feldman  *	attributes.
8618a44dbb2SRoopa Prabhu  */
862ebb9a03aSJiri Pirko int switchdev_port_bridge_dellink(struct net_device *dev,
8638a44dbb2SRoopa Prabhu 				  struct nlmsghdr *nlh, u16 flags)
8648a44dbb2SRoopa Prabhu {
8655c34e022SScott Feldman 	struct nlattr *afspec;
8668a44dbb2SRoopa Prabhu 
8675c34e022SScott Feldman 	afspec = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg),
8685c34e022SScott Feldman 				 IFLA_AF_SPEC);
8695c34e022SScott Feldman 	if (afspec)
8705c34e022SScott Feldman 		return switchdev_port_br_afspec(dev, afspec,
8715c34e022SScott Feldman 						switchdev_port_obj_del);
8725c34e022SScott Feldman 
8738a44dbb2SRoopa Prabhu 	return 0;
8748a44dbb2SRoopa Prabhu }
875ebb9a03aSJiri Pirko EXPORT_SYMBOL_GPL(switchdev_port_bridge_dellink);
8768a44dbb2SRoopa Prabhu 
87745d4122cSSamudrala, Sridhar /**
87845d4122cSSamudrala, Sridhar  *	switchdev_port_fdb_add - Add FDB (MAC/VLAN) entry to port
87945d4122cSSamudrala, Sridhar  *
88045d4122cSSamudrala, Sridhar  *	@ndmsg: netlink hdr
88145d4122cSSamudrala, Sridhar  *	@nlattr: netlink attributes
88245d4122cSSamudrala, Sridhar  *	@dev: port device
88345d4122cSSamudrala, Sridhar  *	@addr: MAC address to add
88445d4122cSSamudrala, Sridhar  *	@vid: VLAN to add
88545d4122cSSamudrala, Sridhar  *
88645d4122cSSamudrala, Sridhar  *	Add FDB entry to switch device.
88745d4122cSSamudrala, Sridhar  */
88845d4122cSSamudrala, Sridhar int switchdev_port_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
88945d4122cSSamudrala, Sridhar 			   struct net_device *dev, const unsigned char *addr,
89045d4122cSSamudrala, Sridhar 			   u16 vid, u16 nlm_flags)
89145d4122cSSamudrala, Sridhar {
89252ba57cfSJiri Pirko 	struct switchdev_obj_port_fdb fdb = {
8939e8f4a54SJiri Pirko 		.obj.id = SWITCHDEV_OBJ_ID_PORT_FDB,
894cdf09697SDavid S. Miller 		.addr = addr,
89545d4122cSSamudrala, Sridhar 		.vid = vid,
89645d4122cSSamudrala, Sridhar 	};
89745d4122cSSamudrala, Sridhar 
8989e8f4a54SJiri Pirko 	return switchdev_port_obj_add(dev, &fdb.obj);
89945d4122cSSamudrala, Sridhar }
90045d4122cSSamudrala, Sridhar EXPORT_SYMBOL_GPL(switchdev_port_fdb_add);
90145d4122cSSamudrala, Sridhar 
90245d4122cSSamudrala, Sridhar /**
90345d4122cSSamudrala, Sridhar  *	switchdev_port_fdb_del - Delete FDB (MAC/VLAN) entry from port
90445d4122cSSamudrala, Sridhar  *
90545d4122cSSamudrala, Sridhar  *	@ndmsg: netlink hdr
90645d4122cSSamudrala, Sridhar  *	@nlattr: netlink attributes
90745d4122cSSamudrala, Sridhar  *	@dev: port device
90845d4122cSSamudrala, Sridhar  *	@addr: MAC address to delete
90945d4122cSSamudrala, Sridhar  *	@vid: VLAN to delete
91045d4122cSSamudrala, Sridhar  *
91145d4122cSSamudrala, Sridhar  *	Delete FDB entry from switch device.
91245d4122cSSamudrala, Sridhar  */
91345d4122cSSamudrala, Sridhar int switchdev_port_fdb_del(struct ndmsg *ndm, struct nlattr *tb[],
91445d4122cSSamudrala, Sridhar 			   struct net_device *dev, const unsigned char *addr,
91545d4122cSSamudrala, Sridhar 			   u16 vid)
91645d4122cSSamudrala, Sridhar {
91752ba57cfSJiri Pirko 	struct switchdev_obj_port_fdb fdb = {
9189e8f4a54SJiri Pirko 		.obj.id = SWITCHDEV_OBJ_ID_PORT_FDB,
919cdf09697SDavid S. Miller 		.addr = addr,
92045d4122cSSamudrala, Sridhar 		.vid = vid,
92145d4122cSSamudrala, Sridhar 	};
92245d4122cSSamudrala, Sridhar 
9239e8f4a54SJiri Pirko 	return switchdev_port_obj_del(dev, &fdb.obj);
92445d4122cSSamudrala, Sridhar }
92545d4122cSSamudrala, Sridhar EXPORT_SYMBOL_GPL(switchdev_port_fdb_del);
92645d4122cSSamudrala, Sridhar 
92745d4122cSSamudrala, Sridhar struct switchdev_fdb_dump {
92852ba57cfSJiri Pirko 	struct switchdev_obj_port_fdb fdb;
929e02a06b2SVivien Didelot 	struct net_device *dev;
93045d4122cSSamudrala, Sridhar 	struct sk_buff *skb;
93145d4122cSSamudrala, Sridhar 	struct netlink_callback *cb;
93245d4122cSSamudrala, Sridhar 	int idx;
93345d4122cSSamudrala, Sridhar };
93445d4122cSSamudrala, Sridhar 
935648b4a99SJiri Pirko static int switchdev_port_fdb_dump_cb(struct switchdev_obj *obj)
93645d4122cSSamudrala, Sridhar {
937648b4a99SJiri Pirko 	struct switchdev_obj_port_fdb *fdb = SWITCHDEV_OBJ_PORT_FDB(obj);
93845d4122cSSamudrala, Sridhar 	struct switchdev_fdb_dump *dump =
93925f07adcSVivien Didelot 		container_of(fdb, struct switchdev_fdb_dump, fdb);
94045d4122cSSamudrala, Sridhar 	u32 portid = NETLINK_CB(dump->cb->skb).portid;
94145d4122cSSamudrala, Sridhar 	u32 seq = dump->cb->nlh->nlmsg_seq;
94245d4122cSSamudrala, Sridhar 	struct nlmsghdr *nlh;
94345d4122cSSamudrala, Sridhar 	struct ndmsg *ndm;
94445d4122cSSamudrala, Sridhar 
94545d4122cSSamudrala, Sridhar 	if (dump->idx < dump->cb->args[0])
94645d4122cSSamudrala, Sridhar 		goto skip;
94745d4122cSSamudrala, Sridhar 
94845d4122cSSamudrala, Sridhar 	nlh = nlmsg_put(dump->skb, portid, seq, RTM_NEWNEIGH,
94945d4122cSSamudrala, Sridhar 			sizeof(*ndm), NLM_F_MULTI);
95045d4122cSSamudrala, Sridhar 	if (!nlh)
95145d4122cSSamudrala, Sridhar 		return -EMSGSIZE;
95245d4122cSSamudrala, Sridhar 
95345d4122cSSamudrala, Sridhar 	ndm = nlmsg_data(nlh);
95445d4122cSSamudrala, Sridhar 	ndm->ndm_family  = AF_BRIDGE;
95545d4122cSSamudrala, Sridhar 	ndm->ndm_pad1    = 0;
95645d4122cSSamudrala, Sridhar 	ndm->ndm_pad2    = 0;
95745d4122cSSamudrala, Sridhar 	ndm->ndm_flags   = NTF_SELF;
95845d4122cSSamudrala, Sridhar 	ndm->ndm_type    = 0;
959e02a06b2SVivien Didelot 	ndm->ndm_ifindex = dump->dev->ifindex;
96025f07adcSVivien Didelot 	ndm->ndm_state   = fdb->ndm_state;
96145d4122cSSamudrala, Sridhar 
96225f07adcSVivien Didelot 	if (nla_put(dump->skb, NDA_LLADDR, ETH_ALEN, fdb->addr))
96345d4122cSSamudrala, Sridhar 		goto nla_put_failure;
96445d4122cSSamudrala, Sridhar 
96525f07adcSVivien Didelot 	if (fdb->vid && nla_put_u16(dump->skb, NDA_VLAN, fdb->vid))
96645d4122cSSamudrala, Sridhar 		goto nla_put_failure;
96745d4122cSSamudrala, Sridhar 
96845d4122cSSamudrala, Sridhar 	nlmsg_end(dump->skb, nlh);
96945d4122cSSamudrala, Sridhar 
97045d4122cSSamudrala, Sridhar skip:
97145d4122cSSamudrala, Sridhar 	dump->idx++;
97245d4122cSSamudrala, Sridhar 	return 0;
97345d4122cSSamudrala, Sridhar 
97445d4122cSSamudrala, Sridhar nla_put_failure:
97545d4122cSSamudrala, Sridhar 	nlmsg_cancel(dump->skb, nlh);
97645d4122cSSamudrala, Sridhar 	return -EMSGSIZE;
97745d4122cSSamudrala, Sridhar }
97845d4122cSSamudrala, Sridhar 
97945d4122cSSamudrala, Sridhar /**
98045d4122cSSamudrala, Sridhar  *	switchdev_port_fdb_dump - Dump port FDB (MAC/VLAN) entries
98145d4122cSSamudrala, Sridhar  *
98245d4122cSSamudrala, Sridhar  *	@skb: netlink skb
98345d4122cSSamudrala, Sridhar  *	@cb: netlink callback
98445d4122cSSamudrala, Sridhar  *	@dev: port device
98545d4122cSSamudrala, Sridhar  *	@filter_dev: filter device
98645d4122cSSamudrala, Sridhar  *	@idx:
98745d4122cSSamudrala, Sridhar  *
98845d4122cSSamudrala, Sridhar  *	Delete FDB entry from switch device.
98945d4122cSSamudrala, Sridhar  */
99045d4122cSSamudrala, Sridhar int switchdev_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
99145d4122cSSamudrala, Sridhar 			    struct net_device *dev,
99245d4122cSSamudrala, Sridhar 			    struct net_device *filter_dev, int idx)
99345d4122cSSamudrala, Sridhar {
99445d4122cSSamudrala, Sridhar 	struct switchdev_fdb_dump dump = {
9959e8f4a54SJiri Pirko 		.fdb.obj.id = SWITCHDEV_OBJ_ID_PORT_FDB,
996e02a06b2SVivien Didelot 		.dev = dev,
99745d4122cSSamudrala, Sridhar 		.skb = skb,
99845d4122cSSamudrala, Sridhar 		.cb = cb,
99945d4122cSSamudrala, Sridhar 		.idx = idx,
100045d4122cSSamudrala, Sridhar 	};
100145d4122cSSamudrala, Sridhar 
10029e8f4a54SJiri Pirko 	switchdev_port_obj_dump(dev, &dump.fdb.obj, switchdev_port_fdb_dump_cb);
100345d4122cSSamudrala, Sridhar 	return dump.idx;
100445d4122cSSamudrala, Sridhar }
100545d4122cSSamudrala, Sridhar EXPORT_SYMBOL_GPL(switchdev_port_fdb_dump);
100645d4122cSSamudrala, Sridhar 
1007ebb9a03aSJiri Pirko static struct net_device *switchdev_get_lowest_dev(struct net_device *dev)
1008b5d6fbdeSScott Feldman {
10099d47c0a2SJiri Pirko 	const struct switchdev_ops *ops = dev->switchdev_ops;
1010b5d6fbdeSScott Feldman 	struct net_device *lower_dev;
1011b5d6fbdeSScott Feldman 	struct net_device *port_dev;
1012b5d6fbdeSScott Feldman 	struct list_head *iter;
1013b5d6fbdeSScott Feldman 
1014b5d6fbdeSScott Feldman 	/* Recusively search down until we find a sw port dev.
1015f8e20a9fSScott Feldman 	 * (A sw port dev supports switchdev_port_attr_get).
1016b5d6fbdeSScott Feldman 	 */
1017b5d6fbdeSScott Feldman 
1018f8e20a9fSScott Feldman 	if (ops && ops->switchdev_port_attr_get)
1019b5d6fbdeSScott Feldman 		return dev;
1020b5d6fbdeSScott Feldman 
1021b5d6fbdeSScott Feldman 	netdev_for_each_lower_dev(dev, lower_dev, iter) {
1022ebb9a03aSJiri Pirko 		port_dev = switchdev_get_lowest_dev(lower_dev);
1023b5d6fbdeSScott Feldman 		if (port_dev)
1024b5d6fbdeSScott Feldman 			return port_dev;
1025b5d6fbdeSScott Feldman 	}
1026b5d6fbdeSScott Feldman 
1027b5d6fbdeSScott Feldman 	return NULL;
1028b5d6fbdeSScott Feldman }
1029b5d6fbdeSScott Feldman 
1030ebb9a03aSJiri Pirko static struct net_device *switchdev_get_dev_by_nhs(struct fib_info *fi)
1031b5d6fbdeSScott Feldman {
1032f8e20a9fSScott Feldman 	struct switchdev_attr attr = {
10331f868398SJiri Pirko 		.id = SWITCHDEV_ATTR_ID_PORT_PARENT_ID,
1034f8e20a9fSScott Feldman 	};
1035f8e20a9fSScott Feldman 	struct switchdev_attr prev_attr;
1036b5d6fbdeSScott Feldman 	struct net_device *dev = NULL;
1037b5d6fbdeSScott Feldman 	int nhsel;
1038b5d6fbdeSScott Feldman 
1039b5d6fbdeSScott Feldman 	/* For this route, all nexthop devs must be on the same switch. */
1040b5d6fbdeSScott Feldman 
1041b5d6fbdeSScott Feldman 	for (nhsel = 0; nhsel < fi->fib_nhs; nhsel++) {
1042b5d6fbdeSScott Feldman 		const struct fib_nh *nh = &fi->fib_nh[nhsel];
1043b5d6fbdeSScott Feldman 
1044b5d6fbdeSScott Feldman 		if (!nh->nh_dev)
1045b5d6fbdeSScott Feldman 			return NULL;
1046b5d6fbdeSScott Feldman 
1047ebb9a03aSJiri Pirko 		dev = switchdev_get_lowest_dev(nh->nh_dev);
1048b5d6fbdeSScott Feldman 		if (!dev)
1049b5d6fbdeSScott Feldman 			return NULL;
1050b5d6fbdeSScott Feldman 
1051f8e20a9fSScott Feldman 		if (switchdev_port_attr_get(dev, &attr))
1052b5d6fbdeSScott Feldman 			return NULL;
1053b5d6fbdeSScott Feldman 
1054d754f98bSScott Feldman 		if (nhsel > 0 &&
1055d754f98bSScott Feldman 		    !netdev_phys_item_id_same(&prev_attr.u.ppid, &attr.u.ppid))
1056b5d6fbdeSScott Feldman 				return NULL;
1057b5d6fbdeSScott Feldman 
1058f8e20a9fSScott Feldman 		prev_attr = attr;
1059b5d6fbdeSScott Feldman 	}
1060b5d6fbdeSScott Feldman 
1061b5d6fbdeSScott Feldman 	return dev;
1062b5d6fbdeSScott Feldman }
1063b5d6fbdeSScott Feldman 
10645e8d9049SScott Feldman /**
10657616dcbbSScott Feldman  *	switchdev_fib_ipv4_add - Add/modify switch IPv4 route entry
10665e8d9049SScott Feldman  *
10675e8d9049SScott Feldman  *	@dst: route's IPv4 destination address
10685e8d9049SScott Feldman  *	@dst_len: destination address length (prefix length)
10695e8d9049SScott Feldman  *	@fi: route FIB info structure
10705e8d9049SScott Feldman  *	@tos: route TOS
10715e8d9049SScott Feldman  *	@type: route type
1072f8f21471SScott Feldman  *	@nlflags: netlink flags passed in (NLM_F_*)
10735e8d9049SScott Feldman  *	@tb_id: route table ID
10745e8d9049SScott Feldman  *
10757616dcbbSScott Feldman  *	Add/modify switch IPv4 route entry.
10765e8d9049SScott Feldman  */
1077ebb9a03aSJiri Pirko int switchdev_fib_ipv4_add(u32 dst, int dst_len, struct fib_info *fi,
1078f8f21471SScott Feldman 			   u8 tos, u8 type, u32 nlflags, u32 tb_id)
10795e8d9049SScott Feldman {
1080ab069002SVivien Didelot 	struct switchdev_obj_ipv4_fib ipv4_fib = {
10819e8f4a54SJiri Pirko 		.obj.id = SWITCHDEV_OBJ_ID_IPV4_FIB,
10827a7ee531SScott Feldman 		.dst = dst,
108358c2cb16SScott Feldman 		.dst_len = dst_len,
108458c2cb16SScott Feldman 		.fi = fi,
108558c2cb16SScott Feldman 		.tos = tos,
108658c2cb16SScott Feldman 		.type = type,
108758c2cb16SScott Feldman 		.nlflags = nlflags,
108858c2cb16SScott Feldman 		.tb_id = tb_id,
108958c2cb16SScott Feldman 	};
1090b5d6fbdeSScott Feldman 	struct net_device *dev;
1091b5d6fbdeSScott Feldman 	int err = 0;
1092b5d6fbdeSScott Feldman 
10938e05fd71SScott Feldman 	/* Don't offload route if using custom ip rules or if
10948e05fd71SScott Feldman 	 * IPv4 FIB offloading has been disabled completely.
10958e05fd71SScott Feldman 	 */
10968e05fd71SScott Feldman 
1097e1315db1SScott Feldman #ifdef CONFIG_IP_MULTIPLE_TABLES
1098e1315db1SScott Feldman 	if (fi->fib_net->ipv4.fib_has_custom_rules)
1099e1315db1SScott Feldman 		return 0;
1100e1315db1SScott Feldman #endif
1101e1315db1SScott Feldman 
1102e1315db1SScott Feldman 	if (fi->fib_net->ipv4.fib_offload_disabled)
1103104616e7SScott Feldman 		return 0;
1104104616e7SScott Feldman 
1105ebb9a03aSJiri Pirko 	dev = switchdev_get_dev_by_nhs(fi);
1106b5d6fbdeSScott Feldman 	if (!dev)
11075e8d9049SScott Feldman 		return 0;
1108b5d6fbdeSScott Feldman 
11099e8f4a54SJiri Pirko 	err = switchdev_port_obj_add(dev, &ipv4_fib.obj);
1110b5d6fbdeSScott Feldman 	if (!err)
1111eea39946SRoopa Prabhu 		fi->fib_flags |= RTNH_F_OFFLOAD;
1112b5d6fbdeSScott Feldman 
1113af201f72SScott Feldman 	return err == -EOPNOTSUPP ? 0 : err;
11145e8d9049SScott Feldman }
1115ebb9a03aSJiri Pirko EXPORT_SYMBOL_GPL(switchdev_fib_ipv4_add);
11165e8d9049SScott Feldman 
11175e8d9049SScott Feldman /**
1118ebb9a03aSJiri Pirko  *	switchdev_fib_ipv4_del - Delete IPv4 route entry from switch
11195e8d9049SScott Feldman  *
11205e8d9049SScott Feldman  *	@dst: route's IPv4 destination address
11215e8d9049SScott Feldman  *	@dst_len: destination address length (prefix length)
11225e8d9049SScott Feldman  *	@fi: route FIB info structure
11235e8d9049SScott Feldman  *	@tos: route TOS
11245e8d9049SScott Feldman  *	@type: route type
11255e8d9049SScott Feldman  *	@tb_id: route table ID
11265e8d9049SScott Feldman  *
11275e8d9049SScott Feldman  *	Delete IPv4 route entry from switch device.
11285e8d9049SScott Feldman  */
1129ebb9a03aSJiri Pirko int switchdev_fib_ipv4_del(u32 dst, int dst_len, struct fib_info *fi,
11305e8d9049SScott Feldman 			   u8 tos, u8 type, u32 tb_id)
11315e8d9049SScott Feldman {
1132ab069002SVivien Didelot 	struct switchdev_obj_ipv4_fib ipv4_fib = {
11339e8f4a54SJiri Pirko 		.obj.id = SWITCHDEV_OBJ_ID_IPV4_FIB,
11347a7ee531SScott Feldman 		.dst = dst,
113558c2cb16SScott Feldman 		.dst_len = dst_len,
113658c2cb16SScott Feldman 		.fi = fi,
113758c2cb16SScott Feldman 		.tos = tos,
113858c2cb16SScott Feldman 		.type = type,
113958c2cb16SScott Feldman 		.nlflags = 0,
114058c2cb16SScott Feldman 		.tb_id = tb_id,
114158c2cb16SScott Feldman 	};
1142b5d6fbdeSScott Feldman 	struct net_device *dev;
1143b5d6fbdeSScott Feldman 	int err = 0;
1144b5d6fbdeSScott Feldman 
1145eea39946SRoopa Prabhu 	if (!(fi->fib_flags & RTNH_F_OFFLOAD))
11465e8d9049SScott Feldman 		return 0;
1147b5d6fbdeSScott Feldman 
1148ebb9a03aSJiri Pirko 	dev = switchdev_get_dev_by_nhs(fi);
1149b5d6fbdeSScott Feldman 	if (!dev)
1150b5d6fbdeSScott Feldman 		return 0;
1151b5d6fbdeSScott Feldman 
11529e8f4a54SJiri Pirko 	err = switchdev_port_obj_del(dev, &ipv4_fib.obj);
1153b5d6fbdeSScott Feldman 	if (!err)
1154eea39946SRoopa Prabhu 		fi->fib_flags &= ~RTNH_F_OFFLOAD;
1155b5d6fbdeSScott Feldman 
1156af201f72SScott Feldman 	return err == -EOPNOTSUPP ? 0 : err;
11575e8d9049SScott Feldman }
1158ebb9a03aSJiri Pirko EXPORT_SYMBOL_GPL(switchdev_fib_ipv4_del);
11598e05fd71SScott Feldman 
11608e05fd71SScott Feldman /**
1161ebb9a03aSJiri Pirko  *	switchdev_fib_ipv4_abort - Abort an IPv4 FIB operation
11628e05fd71SScott Feldman  *
11638e05fd71SScott Feldman  *	@fi: route FIB info structure
11648e05fd71SScott Feldman  */
1165ebb9a03aSJiri Pirko void switchdev_fib_ipv4_abort(struct fib_info *fi)
11668e05fd71SScott Feldman {
11678e05fd71SScott Feldman 	/* There was a problem installing this route to the offload
11688e05fd71SScott Feldman 	 * device.  For now, until we come up with more refined
11698e05fd71SScott Feldman 	 * policy handling, abruptly end IPv4 fib offloading for
11708e05fd71SScott Feldman 	 * for entire net by flushing offload device(s) of all
11718e05fd71SScott Feldman 	 * IPv4 routes, and mark IPv4 fib offloading broken from
11728e05fd71SScott Feldman 	 * this point forward.
11738e05fd71SScott Feldman 	 */
11748e05fd71SScott Feldman 
11758e05fd71SScott Feldman 	fib_flush_external(fi->fib_net);
11768e05fd71SScott Feldman 	fi->fib_net->ipv4.fib_offload_disabled = true;
11778e05fd71SScott Feldman }
1178ebb9a03aSJiri Pirko EXPORT_SYMBOL_GPL(switchdev_fib_ipv4_abort);
11791a3b2ec9SScott Feldman 
11801a3b2ec9SScott Feldman static bool switchdev_port_same_parent_id(struct net_device *a,
11811a3b2ec9SScott Feldman 					  struct net_device *b)
11821a3b2ec9SScott Feldman {
11831a3b2ec9SScott Feldman 	struct switchdev_attr a_attr = {
11841f868398SJiri Pirko 		.id = SWITCHDEV_ATTR_ID_PORT_PARENT_ID,
11851a3b2ec9SScott Feldman 		.flags = SWITCHDEV_F_NO_RECURSE,
11861a3b2ec9SScott Feldman 	};
11871a3b2ec9SScott Feldman 	struct switchdev_attr b_attr = {
11881f868398SJiri Pirko 		.id = SWITCHDEV_ATTR_ID_PORT_PARENT_ID,
11891a3b2ec9SScott Feldman 		.flags = SWITCHDEV_F_NO_RECURSE,
11901a3b2ec9SScott Feldman 	};
11911a3b2ec9SScott Feldman 
11921a3b2ec9SScott Feldman 	if (switchdev_port_attr_get(a, &a_attr) ||
11931a3b2ec9SScott Feldman 	    switchdev_port_attr_get(b, &b_attr))
11941a3b2ec9SScott Feldman 		return false;
11951a3b2ec9SScott Feldman 
11961a3b2ec9SScott Feldman 	return netdev_phys_item_id_same(&a_attr.u.ppid, &b_attr.u.ppid);
11971a3b2ec9SScott Feldman }
11981a3b2ec9SScott Feldman 
11991a3b2ec9SScott Feldman static u32 switchdev_port_fwd_mark_get(struct net_device *dev,
12001a3b2ec9SScott Feldman 				       struct net_device *group_dev)
12011a3b2ec9SScott Feldman {
12021a3b2ec9SScott Feldman 	struct net_device *lower_dev;
12031a3b2ec9SScott Feldman 	struct list_head *iter;
12041a3b2ec9SScott Feldman 
12051a3b2ec9SScott Feldman 	netdev_for_each_lower_dev(group_dev, lower_dev, iter) {
12061a3b2ec9SScott Feldman 		if (lower_dev == dev)
12071a3b2ec9SScott Feldman 			continue;
12081a3b2ec9SScott Feldman 		if (switchdev_port_same_parent_id(dev, lower_dev))
12091a3b2ec9SScott Feldman 			return lower_dev->offload_fwd_mark;
12101a3b2ec9SScott Feldman 		return switchdev_port_fwd_mark_get(dev, lower_dev);
12111a3b2ec9SScott Feldman 	}
12121a3b2ec9SScott Feldman 
12131a3b2ec9SScott Feldman 	return dev->ifindex;
12141a3b2ec9SScott Feldman }
12151a3b2ec9SScott Feldman 
12161a3b2ec9SScott Feldman static void switchdev_port_fwd_mark_reset(struct net_device *group_dev,
12171a3b2ec9SScott Feldman 					  u32 old_mark, u32 *reset_mark)
12181a3b2ec9SScott Feldman {
12191a3b2ec9SScott Feldman 	struct net_device *lower_dev;
12201a3b2ec9SScott Feldman 	struct list_head *iter;
12211a3b2ec9SScott Feldman 
12221a3b2ec9SScott Feldman 	netdev_for_each_lower_dev(group_dev, lower_dev, iter) {
12231a3b2ec9SScott Feldman 		if (lower_dev->offload_fwd_mark == old_mark) {
12241a3b2ec9SScott Feldman 			if (!*reset_mark)
12251a3b2ec9SScott Feldman 				*reset_mark = lower_dev->ifindex;
12261a3b2ec9SScott Feldman 			lower_dev->offload_fwd_mark = *reset_mark;
12271a3b2ec9SScott Feldman 		}
12281a3b2ec9SScott Feldman 		switchdev_port_fwd_mark_reset(lower_dev, old_mark, reset_mark);
12291a3b2ec9SScott Feldman 	}
12301a3b2ec9SScott Feldman }
12311a3b2ec9SScott Feldman 
12321a3b2ec9SScott Feldman /**
12331a3b2ec9SScott Feldman  *	switchdev_port_fwd_mark_set - Set port offload forwarding mark
12341a3b2ec9SScott Feldman  *
12351a3b2ec9SScott Feldman  *	@dev: port device
12361a3b2ec9SScott Feldman  *	@group_dev: containing device
12371a3b2ec9SScott Feldman  *	@joining: true if dev is joining group; false if leaving group
12381a3b2ec9SScott Feldman  *
12391a3b2ec9SScott Feldman  *	An ungrouped port's offload mark is just its ifindex.  A grouped
12401a3b2ec9SScott Feldman  *	port's (member of a bridge, for example) offload mark is the ifindex
12411a3b2ec9SScott Feldman  *	of one of the ports in the group with the same parent (switch) ID.
12421a3b2ec9SScott Feldman  *	Ports on the same device in the same group will have the same mark.
12431a3b2ec9SScott Feldman  *
12441a3b2ec9SScott Feldman  *	Example:
12451a3b2ec9SScott Feldman  *
12461a3b2ec9SScott Feldman  *		br0		ifindex=9
12471a3b2ec9SScott Feldman  *		  sw1p1		ifindex=2	mark=2
12481a3b2ec9SScott Feldman  *		  sw1p2		ifindex=3	mark=2
12491a3b2ec9SScott Feldman  *		  sw2p1		ifindex=4	mark=5
12501a3b2ec9SScott Feldman  *		  sw2p2		ifindex=5	mark=5
12511a3b2ec9SScott Feldman  *
12521a3b2ec9SScott Feldman  *	If sw2p2 leaves the bridge, we'll have:
12531a3b2ec9SScott Feldman  *
12541a3b2ec9SScott Feldman  *		br0		ifindex=9
12551a3b2ec9SScott Feldman  *		  sw1p1		ifindex=2	mark=2
12561a3b2ec9SScott Feldman  *		  sw1p2		ifindex=3	mark=2
12571a3b2ec9SScott Feldman  *		  sw2p1		ifindex=4	mark=4
12581a3b2ec9SScott Feldman  *		sw2p2		ifindex=5	mark=5
12591a3b2ec9SScott Feldman  */
12601a3b2ec9SScott Feldman void switchdev_port_fwd_mark_set(struct net_device *dev,
12611a3b2ec9SScott Feldman 				 struct net_device *group_dev,
12621a3b2ec9SScott Feldman 				 bool joining)
12631a3b2ec9SScott Feldman {
12641a3b2ec9SScott Feldman 	u32 mark = dev->ifindex;
12651a3b2ec9SScott Feldman 	u32 reset_mark = 0;
12661a3b2ec9SScott Feldman 
12671a3b2ec9SScott Feldman 	if (group_dev && joining) {
12681a3b2ec9SScott Feldman 		mark = switchdev_port_fwd_mark_get(dev, group_dev);
12691a3b2ec9SScott Feldman 	} else if (group_dev && !joining) {
12701a3b2ec9SScott Feldman 		if (dev->offload_fwd_mark == mark)
12711a3b2ec9SScott Feldman 			/* Ohoh, this port was the mark reference port,
12721a3b2ec9SScott Feldman 			 * but it's leaving the group, so reset the
12731a3b2ec9SScott Feldman 			 * mark for the remaining ports in the group.
12741a3b2ec9SScott Feldman 			 */
12751a3b2ec9SScott Feldman 			switchdev_port_fwd_mark_reset(group_dev, mark,
12761a3b2ec9SScott Feldman 						      &reset_mark);
12771a3b2ec9SScott Feldman 	}
12781a3b2ec9SScott Feldman 
12791a3b2ec9SScott Feldman 	dev->offload_fwd_mark = mark;
12801a3b2ec9SScott Feldman }
12811a3b2ec9SScott Feldman EXPORT_SYMBOL_GPL(switchdev_port_fwd_mark_set);
1282