xref: /linux/net/core/netdev_work.c (revision fab183d632628381b466a41479489541ac0e29a0)
112c765beSJakub Kicinski // SPDX-License-Identifier: GPL-2.0-or-later
212c765beSJakub Kicinski 
3129cdce9SJakub Kicinski #include <linux/export.h>
412c765beSJakub Kicinski #include <linux/list.h>
512c765beSJakub Kicinski #include <linux/netdevice.h>
612c765beSJakub Kicinski #include <linux/rtnetlink.h>
712c765beSJakub Kicinski #include <linux/spinlock.h>
812c765beSJakub Kicinski #include <linux/workqueue.h>
912c765beSJakub Kicinski #include <net/netdev_lock.h>
1012c765beSJakub Kicinski 
1112c765beSJakub Kicinski #include "dev.h"
1212c765beSJakub Kicinski 
1312c765beSJakub Kicinski static void netdev_work_proc(struct work_struct *work);
1412c765beSJakub Kicinski 
1512c765beSJakub Kicinski /* @netdev_work_lock protects:
1612c765beSJakub Kicinski  *  - @netdev_work_list
1712c765beSJakub Kicinski  *  - within the list entries (struct net_device fields):
1812c765beSJakub Kicinski  *	- work_node
1912c765beSJakub Kicinski  *	- work_tracker
20129cdce9SJakub Kicinski  *	- work_pending
2112c765beSJakub Kicinski  *	- work_core_pending
2212c765beSJakub Kicinski  */
2312c765beSJakub Kicinski static LIST_HEAD(netdev_work_list);
2412c765beSJakub Kicinski static DEFINE_SPINLOCK(netdev_work_lock);
2512c765beSJakub Kicinski static DECLARE_WORK(netdev_work, netdev_work_proc);
2612c765beSJakub Kicinski 
netdev_work_enqueue(struct net_device * dev,unsigned long events,unsigned long core)27129cdce9SJakub Kicinski static void netdev_work_enqueue(struct net_device *dev, unsigned long events,
28129cdce9SJakub Kicinski 				unsigned long core)
2912c765beSJakub Kicinski {
30129cdce9SJakub Kicinski 	if (!events && !core)
31129cdce9SJakub Kicinski 		return;
32129cdce9SJakub Kicinski 
3312c765beSJakub Kicinski 	spin_lock_bh(&netdev_work_lock);
34*d31c14e5SJakub Kicinski 	if (!dev_isalive(dev)) {
35*d31c14e5SJakub Kicinski 		spin_unlock_bh(&netdev_work_lock);
36*d31c14e5SJakub Kicinski 		return;
37*d31c14e5SJakub Kicinski 	}
3812c765beSJakub Kicinski 	if (list_empty(&dev->work_node)) {
3912c765beSJakub Kicinski 		list_add_tail(&dev->work_node, &netdev_work_list);
4012c765beSJakub Kicinski 		netdev_hold(dev, &dev->work_tracker, GFP_ATOMIC);
4112c765beSJakub Kicinski 	}
42129cdce9SJakub Kicinski 	dev->work_pending |= events;
43129cdce9SJakub Kicinski 	dev->work_core_pending |= core;
4412c765beSJakub Kicinski 	spin_unlock_bh(&netdev_work_lock);
4512c765beSJakub Kicinski 
4612c765beSJakub Kicinski 	schedule_work(&netdev_work);
4712c765beSJakub Kicinski }
4812c765beSJakub Kicinski 
49129cdce9SJakub Kicinski static unsigned long
netdev_work_dequeue(struct net_device * dev,unsigned long * pending,unsigned long mask)50129cdce9SJakub Kicinski netdev_work_dequeue(struct net_device *dev, unsigned long *pending,
51129cdce9SJakub Kicinski 		    unsigned long mask)
52129cdce9SJakub Kicinski {
53129cdce9SJakub Kicinski 	unsigned long events;
54129cdce9SJakub Kicinski 
55129cdce9SJakub Kicinski 	spin_lock_bh(&netdev_work_lock);
56129cdce9SJakub Kicinski 	events = *pending & mask;
57129cdce9SJakub Kicinski 	*pending &= ~events;
58129cdce9SJakub Kicinski 	if (!list_empty(&dev->work_node) &&
59129cdce9SJakub Kicinski 	    !dev->work_pending && !dev->work_core_pending) {
60129cdce9SJakub Kicinski 		list_del_init(&dev->work_node);
61129cdce9SJakub Kicinski 		netdev_put(dev, &dev->work_tracker);
62129cdce9SJakub Kicinski 	}
63129cdce9SJakub Kicinski 	spin_unlock_bh(&netdev_work_lock);
64129cdce9SJakub Kicinski 
65129cdce9SJakub Kicinski 	return events;
66129cdce9SJakub Kicinski }
67129cdce9SJakub Kicinski 
netdev_work_cancel_all(struct net_device * dev)68*d31c14e5SJakub Kicinski void netdev_work_cancel_all(struct net_device *dev)
69*d31c14e5SJakub Kicinski {
70*d31c14e5SJakub Kicinski 	spin_lock_bh(&netdev_work_lock);
71*d31c14e5SJakub Kicinski 	dev->work_pending = 0;
72*d31c14e5SJakub Kicinski 	dev->work_core_pending = 0;
73*d31c14e5SJakub Kicinski 	if (!list_empty(&dev->work_node)) {
74*d31c14e5SJakub Kicinski 		list_del_init(&dev->work_node);
75*d31c14e5SJakub Kicinski 		netdev_put(dev, &dev->work_tracker);
76*d31c14e5SJakub Kicinski 	}
77*d31c14e5SJakub Kicinski 	spin_unlock_bh(&netdev_work_lock);
78*d31c14e5SJakub Kicinski }
79*d31c14e5SJakub Kicinski 
netdev_work_sched(struct net_device * dev,unsigned long events)80129cdce9SJakub Kicinski void netdev_work_sched(struct net_device *dev, unsigned long events)
81129cdce9SJakub Kicinski {
82129cdce9SJakub Kicinski 	netdev_work_enqueue(dev, events, 0);
83129cdce9SJakub Kicinski }
84129cdce9SJakub Kicinski EXPORT_SYMBOL(netdev_work_sched);
85129cdce9SJakub Kicinski 
8612c765beSJakub Kicinski /**
87129cdce9SJakub Kicinski  * netdev_work_cancel() - cancel selected work for a netdev
8812c765beSJakub Kicinski  * @dev: net_device
8912c765beSJakub Kicinski  * @mask: events to cancel
9012c765beSJakub Kicinski  *
9112c765beSJakub Kicinski  * Clear @mask from the device's work pending mask. If no work is left pending
92129cdce9SJakub Kicinski  * the device is dequeued and its ndo_work won't be called.
9312c765beSJakub Kicinski  *
9412c765beSJakub Kicinski  * No expectations on locking, but also no guarantees provided. If the caller
9512c765beSJakub Kicinski  * wants to touch @dev afterwards (e.g. call the work that got canceled)
9612c765beSJakub Kicinski  * they have to ensure @dev does not get freed.
9712c765beSJakub Kicinski  *
9812c765beSJakub Kicinski  * Returns: the subset of @mask that was actually pending, so the caller can run
9912c765beSJakub Kicinski  * those events inline.
10012c765beSJakub Kicinski  */
netdev_work_cancel(struct net_device * dev,unsigned long mask)101129cdce9SJakub Kicinski unsigned long netdev_work_cancel(struct net_device *dev, unsigned long mask)
102129cdce9SJakub Kicinski {
103129cdce9SJakub Kicinski 	return netdev_work_dequeue(dev, &dev->work_pending, mask);
104129cdce9SJakub Kicinski }
105129cdce9SJakub Kicinski EXPORT_SYMBOL(netdev_work_cancel);
106129cdce9SJakub Kicinski 
__netdev_work_core_sched(struct net_device * dev,unsigned long events)107129cdce9SJakub Kicinski void __netdev_work_core_sched(struct net_device *dev, unsigned long events)
108129cdce9SJakub Kicinski {
109129cdce9SJakub Kicinski 	netdev_work_enqueue(dev, 0, events);
110129cdce9SJakub Kicinski }
111129cdce9SJakub Kicinski 
11212c765beSJakub Kicinski unsigned long
__netdev_work_core_cancel(struct net_device * dev,unsigned long mask)11312c765beSJakub Kicinski __netdev_work_core_cancel(struct net_device *dev, unsigned long mask)
11412c765beSJakub Kicinski {
115129cdce9SJakub Kicinski 	return netdev_work_dequeue(dev, &dev->work_core_pending, mask);
11612c765beSJakub Kicinski }
11712c765beSJakub Kicinski 
netdev_work_run(struct net_device * dev,unsigned long events,unsigned long core)118129cdce9SJakub Kicinski static void netdev_work_run(struct net_device *dev, unsigned long events,
119129cdce9SJakub Kicinski 			    unsigned long core)
120129cdce9SJakub Kicinski {
121129cdce9SJakub Kicinski 	if (!netif_device_present(dev))
122129cdce9SJakub Kicinski 		return;
123129cdce9SJakub Kicinski 
124129cdce9SJakub Kicinski 	if (core & NETDEV_WORK_RX_MODE)
125129cdce9SJakub Kicinski 		netif_rx_mode_run(dev);
126129cdce9SJakub Kicinski 	if (events && dev->netdev_ops->ndo_work)
127129cdce9SJakub Kicinski 		dev->netdev_ops->ndo_work(dev, events);
12812c765beSJakub Kicinski }
12912c765beSJakub Kicinski 
netdev_work_proc(struct work_struct * work)13012c765beSJakub Kicinski static void netdev_work_proc(struct work_struct *work)
13112c765beSJakub Kicinski {
13212c765beSJakub Kicinski 	rtnl_lock();
13312c765beSJakub Kicinski 
13412c765beSJakub Kicinski 	while (true) {
135129cdce9SJakub Kicinski 		unsigned long events = 0, core = 0;
13612c765beSJakub Kicinski 		netdevice_tracker tracker;
13712c765beSJakub Kicinski 		struct net_device *dev;
13812c765beSJakub Kicinski 
13912c765beSJakub Kicinski 		spin_lock_bh(&netdev_work_lock);
14012c765beSJakub Kicinski 		if (list_empty(&netdev_work_list)) {
14112c765beSJakub Kicinski 			spin_unlock_bh(&netdev_work_lock);
14212c765beSJakub Kicinski 			break;
14312c765beSJakub Kicinski 		}
14412c765beSJakub Kicinski 		dev = list_first_entry(&netdev_work_list, struct net_device,
14512c765beSJakub Kicinski 				       work_node);
14612c765beSJakub Kicinski 		/* Take a temporary reference so @dev can't be freed while we
14712c765beSJakub Kicinski 		 * drop the lock to grab its ops lock; the work reference is
14812c765beSJakub Kicinski 		 * only released once we claim the work below.
14912c765beSJakub Kicinski 		 * The re-locking dance is to ensure that ops lock is enough
15012c765beSJakub Kicinski 		 * to ensure canceling work is not racy with dequeue.
15112c765beSJakub Kicinski 		 */
15212c765beSJakub Kicinski 		netdev_hold(dev, &tracker, GFP_ATOMIC);
15312c765beSJakub Kicinski 		spin_unlock_bh(&netdev_work_lock);
15412c765beSJakub Kicinski 
15512c765beSJakub Kicinski 		netdev_lock_ops(dev);
15612c765beSJakub Kicinski 		spin_lock_bh(&netdev_work_lock);
15712c765beSJakub Kicinski 		if (!list_empty(&dev->work_node)) {
15812c765beSJakub Kicinski 			list_del_init(&dev->work_node);
15912c765beSJakub Kicinski 			core = dev->work_core_pending;
16012c765beSJakub Kicinski 			dev->work_core_pending = 0;
161129cdce9SJakub Kicinski 			events = dev->work_pending;
162129cdce9SJakub Kicinski 			dev->work_pending = 0;
16312c765beSJakub Kicinski 			/* We took another ref above */
16412c765beSJakub Kicinski 			netdev_put(dev, &dev->work_tracker);
16512c765beSJakub Kicinski 
16612c765beSJakub Kicinski 			if (!dev_isalive(dev))
167129cdce9SJakub Kicinski 				core = events = 0;
16812c765beSJakub Kicinski 		}
16912c765beSJakub Kicinski 		spin_unlock_bh(&netdev_work_lock);
17012c765beSJakub Kicinski 
171129cdce9SJakub Kicinski 		netdev_work_run(dev, events, core);
17212c765beSJakub Kicinski 		netdev_unlock_ops(dev);
17312c765beSJakub Kicinski 
17412c765beSJakub Kicinski 		netdev_put(dev, &tracker);
17512c765beSJakub Kicinski 	}
17612c765beSJakub Kicinski 
17712c765beSJakub Kicinski 	rtnl_unlock();
17812c765beSJakub Kicinski }
179