diff options
Diffstat (limited to 'net/switchdev/switchdev.c')
-rw-r--r-- | net/switchdev/switchdev.c | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c index b8aaf820ef65..5e64b591aff7 100644 --- a/net/switchdev/switchdev.c +++ b/net/switchdev/switchdev.c | |||
@@ -17,6 +17,7 @@ | |||
17 | #include <linux/netdevice.h> | 17 | #include <linux/netdevice.h> |
18 | #include <linux/if_bridge.h> | 18 | #include <linux/if_bridge.h> |
19 | #include <linux/list.h> | 19 | #include <linux/list.h> |
20 | #include <linux/workqueue.h> | ||
20 | #include <net/ip_fib.h> | 21 | #include <net/ip_fib.h> |
21 | #include <net/switchdev.h> | 22 | #include <net/switchdev.h> |
22 | 23 | ||
@@ -92,6 +93,85 @@ static void switchdev_trans_items_warn_destroy(struct net_device *dev, | |||
92 | switchdev_trans_items_destroy(trans); | 93 | switchdev_trans_items_destroy(trans); |
93 | } | 94 | } |
94 | 95 | ||
96 | static LIST_HEAD(deferred); | ||
97 | static DEFINE_SPINLOCK(deferred_lock); | ||
98 | |||
99 | typedef void switchdev_deferred_func_t(struct net_device *dev, | ||
100 | const void *data); | ||
101 | |||
102 | struct switchdev_deferred_item { | ||
103 | struct list_head list; | ||
104 | struct net_device *dev; | ||
105 | switchdev_deferred_func_t *func; | ||
106 | unsigned long data[0]; | ||
107 | }; | ||
108 | |||
109 | static struct switchdev_deferred_item *switchdev_deferred_dequeue(void) | ||
110 | { | ||
111 | struct switchdev_deferred_item *dfitem; | ||
112 | |||
113 | spin_lock_bh(&deferred_lock); | ||
114 | if (list_empty(&deferred)) { | ||
115 | dfitem = NULL; | ||
116 | goto unlock; | ||
117 | } | ||
118 | dfitem = list_first_entry(&deferred, | ||
119 | struct switchdev_deferred_item, list); | ||
120 | list_del(&dfitem->list); | ||
121 | unlock: | ||
122 | spin_unlock_bh(&deferred_lock); | ||
123 | return dfitem; | ||
124 | } | ||
125 | |||
126 | /** | ||
127 | * switchdev_deferred_process - Process ops in deferred queue | ||
128 | * | ||
129 | * Called to flush the ops currently queued in deferred ops queue. | ||
130 | * rtnl_lock must be held. | ||
131 | */ | ||
132 | void switchdev_deferred_process(void) | ||
133 | { | ||
134 | struct switchdev_deferred_item *dfitem; | ||
135 | |||
136 | ASSERT_RTNL(); | ||
137 | |||
138 | while ((dfitem = switchdev_deferred_dequeue())) { | ||
139 | dfitem->func(dfitem->dev, dfitem->data); | ||
140 | dev_put(dfitem->dev); | ||
141 | kfree(dfitem); | ||
142 | } | ||
143 | } | ||
144 | EXPORT_SYMBOL_GPL(switchdev_deferred_process); | ||
145 | |||
146 | static void switchdev_deferred_process_work(struct work_struct *work) | ||
147 | { | ||
148 | rtnl_lock(); | ||
149 | switchdev_deferred_process(); | ||
150 | rtnl_unlock(); | ||
151 | } | ||
152 | |||
153 | static DECLARE_WORK(deferred_process_work, switchdev_deferred_process_work); | ||
154 | |||
155 | static int switchdev_deferred_enqueue(struct net_device *dev, | ||
156 | const void *data, size_t data_len, | ||
157 | switchdev_deferred_func_t *func) | ||
158 | { | ||
159 | struct switchdev_deferred_item *dfitem; | ||
160 | |||
161 | dfitem = kmalloc(sizeof(*dfitem) + data_len, GFP_ATOMIC); | ||
162 | if (!dfitem) | ||
163 | return -ENOMEM; | ||
164 | dfitem->dev = dev; | ||
165 | dfitem->func = func; | ||
166 | memcpy(dfitem->data, data, data_len); | ||
167 | dev_hold(dev); | ||
168 | spin_lock_bh(&deferred_lock); | ||
169 | list_add_tail(&dfitem->list, &deferred); | ||
170 | spin_unlock_bh(&deferred_lock); | ||
171 | schedule_work(&deferred_process_work); | ||
172 | return 0; | ||
173 | } | ||
174 | |||
95 | /** | 175 | /** |
96 | * switchdev_port_attr_get - Get port attribute | 176 | * switchdev_port_attr_get - Get port attribute |
97 | * | 177 | * |