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