Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
007f790c JP |
2 | /* |
3 | * net/switchdev/switchdev.c - Switch device API | |
7ea6eb3f | 4 | * Copyright (c) 2014-2015 Jiri Pirko <jiri@resnulli.us> |
f8f21471 | 5 | * Copyright (c) 2014-2015 Scott Feldman <sfeldma@gmail.com> |
007f790c JP |
6 | */ |
7 | ||
8 | #include <linux/kernel.h> | |
9 | #include <linux/types.h> | |
10 | #include <linux/init.h> | |
03bf0c28 JP |
11 | #include <linux/mutex.h> |
12 | #include <linux/notifier.h> | |
007f790c | 13 | #include <linux/netdevice.h> |
850d0cbc | 14 | #include <linux/etherdevice.h> |
47f8328b | 15 | #include <linux/if_bridge.h> |
7ea6eb3f | 16 | #include <linux/list.h> |
793f4014 | 17 | #include <linux/workqueue.h> |
87aaf2ca | 18 | #include <linux/if_vlan.h> |
4f2c6ae5 | 19 | #include <linux/rtnetlink.h> |
007f790c JP |
20 | #include <net/switchdev.h> |
21 | ||
793f4014 JP |
22 | static LIST_HEAD(deferred); |
23 | static DEFINE_SPINLOCK(deferred_lock); | |
24 | ||
25 | typedef void switchdev_deferred_func_t(struct net_device *dev, | |
26 | const void *data); | |
27 | ||
28 | struct switchdev_deferred_item { | |
29 | struct list_head list; | |
30 | struct net_device *dev; | |
31 | switchdev_deferred_func_t *func; | |
32 | unsigned long data[0]; | |
33 | }; | |
34 | ||
35 | static struct switchdev_deferred_item *switchdev_deferred_dequeue(void) | |
36 | { | |
37 | struct switchdev_deferred_item *dfitem; | |
38 | ||
39 | spin_lock_bh(&deferred_lock); | |
40 | if (list_empty(&deferred)) { | |
41 | dfitem = NULL; | |
42 | goto unlock; | |
43 | } | |
44 | dfitem = list_first_entry(&deferred, | |
45 | struct switchdev_deferred_item, list); | |
46 | list_del(&dfitem->list); | |
47 | unlock: | |
48 | spin_unlock_bh(&deferred_lock); | |
49 | return dfitem; | |
50 | } | |
51 | ||
52 | /** | |
53 | * switchdev_deferred_process - Process ops in deferred queue | |
54 | * | |
55 | * Called to flush the ops currently queued in deferred ops queue. | |
56 | * rtnl_lock must be held. | |
57 | */ | |
58 | void switchdev_deferred_process(void) | |
59 | { | |
60 | struct switchdev_deferred_item *dfitem; | |
61 | ||
62 | ASSERT_RTNL(); | |
63 | ||
64 | while ((dfitem = switchdev_deferred_dequeue())) { | |
65 | dfitem->func(dfitem->dev, dfitem->data); | |
66 | dev_put(dfitem->dev); | |
67 | kfree(dfitem); | |
68 | } | |
69 | } | |
70 | EXPORT_SYMBOL_GPL(switchdev_deferred_process); | |
71 | ||
72 | static void switchdev_deferred_process_work(struct work_struct *work) | |
73 | { | |
74 | rtnl_lock(); | |
75 | switchdev_deferred_process(); | |
76 | rtnl_unlock(); | |
77 | } | |
78 | ||
79 | static DECLARE_WORK(deferred_process_work, switchdev_deferred_process_work); | |
80 | ||
81 | static int switchdev_deferred_enqueue(struct net_device *dev, | |
82 | const void *data, size_t data_len, | |
83 | switchdev_deferred_func_t *func) | |
84 | { | |
85 | struct switchdev_deferred_item *dfitem; | |
86 | ||
87 | dfitem = kmalloc(sizeof(*dfitem) + data_len, GFP_ATOMIC); | |
88 | if (!dfitem) | |
89 | return -ENOMEM; | |
90 | dfitem->dev = dev; | |
91 | dfitem->func = func; | |
92 | memcpy(dfitem->data, data, data_len); | |
93 | dev_hold(dev); | |
94 | spin_lock_bh(&deferred_lock); | |
95 | list_add_tail(&dfitem->list, &deferred); | |
96 | spin_unlock_bh(&deferred_lock); | |
97 | schedule_work(&deferred_process_work); | |
98 | return 0; | |
99 | } | |
100 | ||
d45224d6 FF |
101 | static int switchdev_port_attr_notify(enum switchdev_notifier_type nt, |
102 | struct net_device *dev, | |
103 | const struct switchdev_attr *attr, | |
104 | struct switchdev_trans *trans) | |
3094333d | 105 | { |
d45224d6 FF |
106 | int err; |
107 | int rc; | |
3094333d | 108 | |
d45224d6 FF |
109 | struct switchdev_notifier_port_attr_info attr_info = { |
110 | .attr = attr, | |
111 | .trans = trans, | |
112 | .handled = false, | |
113 | }; | |
3094333d | 114 | |
d45224d6 FF |
115 | rc = call_switchdev_blocking_notifiers(nt, dev, |
116 | &attr_info.info, NULL); | |
117 | err = notifier_to_errno(rc); | |
118 | if (err) { | |
119 | WARN_ON(!attr_info.handled); | |
120 | return err; | |
3094333d SF |
121 | } |
122 | ||
d45224d6 FF |
123 | if (!attr_info.handled) |
124 | return -EOPNOTSUPP; | |
464314ea | 125 | |
d45224d6 | 126 | return 0; |
3094333d SF |
127 | } |
128 | ||
0bc05d58 JP |
129 | static int switchdev_port_attr_set_now(struct net_device *dev, |
130 | const struct switchdev_attr *attr) | |
3094333d | 131 | { |
7ea6eb3f | 132 | struct switchdev_trans trans; |
3094333d SF |
133 | int err; |
134 | ||
3094333d SF |
135 | /* Phase I: prepare for attr set. Driver/device should fail |
136 | * here if there are going to be issues in the commit phase, | |
137 | * such as lack of resources or support. The driver/device | |
138 | * should reserve resources needed for the commit phase here, | |
139 | * but should not commit the attr. | |
140 | */ | |
141 | ||
f623ab7f | 142 | trans.ph_prepare = true; |
d45224d6 FF |
143 | err = switchdev_port_attr_notify(SWITCHDEV_PORT_ATTR_SET, dev, attr, |
144 | &trans); | |
91cf8ece | 145 | if (err) |
3094333d | 146 | return err; |
3094333d SF |
147 | |
148 | /* Phase II: commit attr set. This cannot fail as a fault | |
149 | * of driver/device. If it does, it's a bug in the driver/device | |
150 | * because the driver said everythings was OK in phase I. | |
151 | */ | |
152 | ||
f623ab7f | 153 | trans.ph_prepare = false; |
d45224d6 FF |
154 | err = switchdev_port_attr_notify(SWITCHDEV_PORT_ATTR_SET, dev, attr, |
155 | &trans); | |
e9fdaec0 SF |
156 | WARN(err, "%s: Commit of attribute (id=%d) failed.\n", |
157 | dev->name, attr->id); | |
3094333d SF |
158 | |
159 | return err; | |
160 | } | |
0bc05d58 JP |
161 | |
162 | static void switchdev_port_attr_set_deferred(struct net_device *dev, | |
163 | const void *data) | |
164 | { | |
165 | const struct switchdev_attr *attr = data; | |
166 | int err; | |
167 | ||
168 | err = switchdev_port_attr_set_now(dev, attr); | |
169 | if (err && err != -EOPNOTSUPP) | |
170 | netdev_err(dev, "failed (err=%d) to set attribute (id=%d)\n", | |
171 | err, attr->id); | |
7ceb2afb ER |
172 | if (attr->complete) |
173 | attr->complete(dev, err, attr->complete_priv); | |
0bc05d58 JP |
174 | } |
175 | ||
176 | static int switchdev_port_attr_set_defer(struct net_device *dev, | |
177 | const struct switchdev_attr *attr) | |
178 | { | |
179 | return switchdev_deferred_enqueue(dev, attr, sizeof(*attr), | |
180 | switchdev_port_attr_set_deferred); | |
181 | } | |
182 | ||
183 | /** | |
184 | * switchdev_port_attr_set - Set port attribute | |
185 | * | |
186 | * @dev: port device | |
187 | * @attr: attribute to set | |
188 | * | |
189 | * Use a 2-phase prepare-commit transaction model to ensure | |
190 | * system is not left in a partially updated state due to | |
191 | * failure from driver/device. | |
192 | * | |
193 | * rtnl_lock must be held and must not be in atomic section, | |
194 | * in case SWITCHDEV_F_DEFER flag is not set. | |
195 | */ | |
196 | int switchdev_port_attr_set(struct net_device *dev, | |
197 | const struct switchdev_attr *attr) | |
198 | { | |
199 | if (attr->flags & SWITCHDEV_F_DEFER) | |
200 | return switchdev_port_attr_set_defer(dev, attr); | |
201 | ASSERT_RTNL(); | |
202 | return switchdev_port_attr_set_now(dev, attr); | |
203 | } | |
3094333d SF |
204 | EXPORT_SYMBOL_GPL(switchdev_port_attr_set); |
205 | ||
e258d919 SF |
206 | static size_t switchdev_obj_size(const struct switchdev_obj *obj) |
207 | { | |
208 | switch (obj->id) { | |
209 | case SWITCHDEV_OBJ_ID_PORT_VLAN: | |
210 | return sizeof(struct switchdev_obj_port_vlan); | |
4d41e125 ER |
211 | case SWITCHDEV_OBJ_ID_PORT_MDB: |
212 | return sizeof(struct switchdev_obj_port_mdb); | |
47d5b6db AL |
213 | case SWITCHDEV_OBJ_ID_HOST_MDB: |
214 | return sizeof(struct switchdev_obj_port_mdb); | |
e258d919 SF |
215 | default: |
216 | BUG(); | |
217 | } | |
218 | return 0; | |
219 | } | |
220 | ||
d17d9f5e PM |
221 | static int switchdev_port_obj_notify(enum switchdev_notifier_type nt, |
222 | struct net_device *dev, | |
223 | const struct switchdev_obj *obj, | |
69b7320e PM |
224 | struct switchdev_trans *trans, |
225 | struct netlink_ext_ack *extack) | |
491d0f15 | 226 | { |
d17d9f5e PM |
227 | int rc; |
228 | int err; | |
491d0f15 | 229 | |
d17d9f5e PM |
230 | struct switchdev_notifier_port_obj_info obj_info = { |
231 | .obj = obj, | |
232 | .trans = trans, | |
233 | .handled = false, | |
234 | }; | |
491d0f15 | 235 | |
479c86dc | 236 | rc = call_switchdev_blocking_notifiers(nt, dev, &obj_info.info, extack); |
d17d9f5e PM |
237 | err = notifier_to_errno(rc); |
238 | if (err) { | |
239 | WARN_ON(!obj_info.handled); | |
240 | return err; | |
491d0f15 | 241 | } |
d17d9f5e PM |
242 | if (!obj_info.handled) |
243 | return -EOPNOTSUPP; | |
244 | return 0; | |
491d0f15 SF |
245 | } |
246 | ||
4d429c5d | 247 | static int switchdev_port_obj_add_now(struct net_device *dev, |
69b7320e PM |
248 | const struct switchdev_obj *obj, |
249 | struct netlink_ext_ack *extack) | |
491d0f15 | 250 | { |
7ea6eb3f | 251 | struct switchdev_trans trans; |
491d0f15 SF |
252 | int err; |
253 | ||
254 | ASSERT_RTNL(); | |
255 | ||
256 | /* Phase I: prepare for obj add. Driver/device should fail | |
257 | * here if there are going to be issues in the commit phase, | |
258 | * such as lack of resources or support. The driver/device | |
259 | * should reserve resources needed for the commit phase here, | |
260 | * but should not commit the obj. | |
261 | */ | |
262 | ||
f623ab7f | 263 | trans.ph_prepare = true; |
d17d9f5e | 264 | err = switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD, |
69b7320e | 265 | dev, obj, &trans, extack); |
91cf8ece | 266 | if (err) |
491d0f15 | 267 | return err; |
491d0f15 SF |
268 | |
269 | /* Phase II: commit obj add. This cannot fail as a fault | |
270 | * of driver/device. If it does, it's a bug in the driver/device | |
271 | * because the driver said everythings was OK in phase I. | |
272 | */ | |
273 | ||
f623ab7f | 274 | trans.ph_prepare = false; |
d17d9f5e | 275 | err = switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD, |
69b7320e | 276 | dev, obj, &trans, extack); |
9e8f4a54 | 277 | WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, obj->id); |
491d0f15 SF |
278 | |
279 | return err; | |
280 | } | |
4d429c5d JP |
281 | |
282 | static void switchdev_port_obj_add_deferred(struct net_device *dev, | |
283 | const void *data) | |
284 | { | |
285 | const struct switchdev_obj *obj = data; | |
286 | int err; | |
287 | ||
69b7320e | 288 | err = switchdev_port_obj_add_now(dev, obj, NULL); |
4d429c5d JP |
289 | if (err && err != -EOPNOTSUPP) |
290 | netdev_err(dev, "failed (err=%d) to add object (id=%d)\n", | |
291 | err, obj->id); | |
7ceb2afb ER |
292 | if (obj->complete) |
293 | obj->complete(dev, err, obj->complete_priv); | |
4d429c5d JP |
294 | } |
295 | ||
296 | static int switchdev_port_obj_add_defer(struct net_device *dev, | |
297 | const struct switchdev_obj *obj) | |
298 | { | |
e258d919 | 299 | return switchdev_deferred_enqueue(dev, obj, switchdev_obj_size(obj), |
4d429c5d JP |
300 | switchdev_port_obj_add_deferred); |
301 | } | |
491d0f15 SF |
302 | |
303 | /** | |
4d429c5d | 304 | * switchdev_port_obj_add - Add port object |
491d0f15 SF |
305 | * |
306 | * @dev: port device | |
ab069002 | 307 | * @id: object ID |
4d429c5d JP |
308 | * @obj: object to add |
309 | * | |
310 | * Use a 2-phase prepare-commit transaction model to ensure | |
311 | * system is not left in a partially updated state due to | |
312 | * failure from driver/device. | |
313 | * | |
314 | * rtnl_lock must be held and must not be in atomic section, | |
315 | * in case SWITCHDEV_F_DEFER flag is not set. | |
491d0f15 | 316 | */ |
4d429c5d | 317 | int switchdev_port_obj_add(struct net_device *dev, |
69b7320e PM |
318 | const struct switchdev_obj *obj, |
319 | struct netlink_ext_ack *extack) | |
4d429c5d JP |
320 | { |
321 | if (obj->flags & SWITCHDEV_F_DEFER) | |
322 | return switchdev_port_obj_add_defer(dev, obj); | |
323 | ASSERT_RTNL(); | |
69b7320e | 324 | return switchdev_port_obj_add_now(dev, obj, extack); |
4d429c5d JP |
325 | } |
326 | EXPORT_SYMBOL_GPL(switchdev_port_obj_add); | |
327 | ||
328 | static int switchdev_port_obj_del_now(struct net_device *dev, | |
329 | const struct switchdev_obj *obj) | |
491d0f15 | 330 | { |
d17d9f5e | 331 | return switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_DEL, |
69b7320e | 332 | dev, obj, NULL, NULL); |
491d0f15 | 333 | } |
4d429c5d JP |
334 | |
335 | static void switchdev_port_obj_del_deferred(struct net_device *dev, | |
336 | const void *data) | |
337 | { | |
338 | const struct switchdev_obj *obj = data; | |
339 | int err; | |
340 | ||
341 | err = switchdev_port_obj_del_now(dev, obj); | |
342 | if (err && err != -EOPNOTSUPP) | |
343 | netdev_err(dev, "failed (err=%d) to del object (id=%d)\n", | |
344 | err, obj->id); | |
7ceb2afb ER |
345 | if (obj->complete) |
346 | obj->complete(dev, err, obj->complete_priv); | |
4d429c5d JP |
347 | } |
348 | ||
349 | static int switchdev_port_obj_del_defer(struct net_device *dev, | |
350 | const struct switchdev_obj *obj) | |
351 | { | |
e258d919 | 352 | return switchdev_deferred_enqueue(dev, obj, switchdev_obj_size(obj), |
4d429c5d JP |
353 | switchdev_port_obj_del_deferred); |
354 | } | |
355 | ||
356 | /** | |
357 | * switchdev_port_obj_del - Delete port object | |
358 | * | |
359 | * @dev: port device | |
360 | * @id: object ID | |
361 | * @obj: object to delete | |
362 | * | |
363 | * rtnl_lock must be held and must not be in atomic section, | |
364 | * in case SWITCHDEV_F_DEFER flag is not set. | |
365 | */ | |
366 | int switchdev_port_obj_del(struct net_device *dev, | |
367 | const struct switchdev_obj *obj) | |
368 | { | |
369 | if (obj->flags & SWITCHDEV_F_DEFER) | |
370 | return switchdev_port_obj_del_defer(dev, obj); | |
371 | ASSERT_RTNL(); | |
372 | return switchdev_port_obj_del_now(dev, obj); | |
373 | } | |
491d0f15 SF |
374 | EXPORT_SYMBOL_GPL(switchdev_port_obj_del); |
375 | ||
ff5cf100 | 376 | static ATOMIC_NOTIFIER_HEAD(switchdev_notif_chain); |
a93e3b17 | 377 | static BLOCKING_NOTIFIER_HEAD(switchdev_blocking_notif_chain); |
03bf0c28 JP |
378 | |
379 | /** | |
ebb9a03a | 380 | * register_switchdev_notifier - Register notifier |
03bf0c28 JP |
381 | * @nb: notifier_block |
382 | * | |
ff5cf100 | 383 | * Register switch device notifier. |
03bf0c28 | 384 | */ |
ebb9a03a | 385 | int register_switchdev_notifier(struct notifier_block *nb) |
03bf0c28 | 386 | { |
ff5cf100 | 387 | return atomic_notifier_chain_register(&switchdev_notif_chain, nb); |
03bf0c28 | 388 | } |
ebb9a03a | 389 | EXPORT_SYMBOL_GPL(register_switchdev_notifier); |
03bf0c28 JP |
390 | |
391 | /** | |
ebb9a03a | 392 | * unregister_switchdev_notifier - Unregister notifier |
03bf0c28 JP |
393 | * @nb: notifier_block |
394 | * | |
395 | * Unregister switch device notifier. | |
03bf0c28 | 396 | */ |
ebb9a03a | 397 | int unregister_switchdev_notifier(struct notifier_block *nb) |
03bf0c28 | 398 | { |
ff5cf100 | 399 | return atomic_notifier_chain_unregister(&switchdev_notif_chain, nb); |
03bf0c28 | 400 | } |
ebb9a03a | 401 | EXPORT_SYMBOL_GPL(unregister_switchdev_notifier); |
03bf0c28 JP |
402 | |
403 | /** | |
ebb9a03a | 404 | * call_switchdev_notifiers - Call notifiers |
03bf0c28 JP |
405 | * @val: value passed unmodified to notifier function |
406 | * @dev: port device | |
407 | * @info: notifier information data | |
408 | * | |
ff5cf100 | 409 | * Call all network notifier blocks. |
03bf0c28 | 410 | */ |
ebb9a03a | 411 | int call_switchdev_notifiers(unsigned long val, struct net_device *dev, |
6685987c PM |
412 | struct switchdev_notifier_info *info, |
413 | struct netlink_ext_ack *extack) | |
03bf0c28 | 414 | { |
03bf0c28 | 415 | info->dev = dev; |
6685987c | 416 | info->extack = extack; |
ff5cf100 | 417 | return atomic_notifier_call_chain(&switchdev_notif_chain, val, info); |
03bf0c28 | 418 | } |
ebb9a03a | 419 | EXPORT_SYMBOL_GPL(call_switchdev_notifiers); |
8a44dbb2 | 420 | |
a93e3b17 PM |
421 | int register_switchdev_blocking_notifier(struct notifier_block *nb) |
422 | { | |
423 | struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain; | |
424 | ||
425 | return blocking_notifier_chain_register(chain, nb); | |
426 | } | |
427 | EXPORT_SYMBOL_GPL(register_switchdev_blocking_notifier); | |
428 | ||
429 | int unregister_switchdev_blocking_notifier(struct notifier_block *nb) | |
430 | { | |
431 | struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain; | |
432 | ||
433 | return blocking_notifier_chain_unregister(chain, nb); | |
434 | } | |
435 | EXPORT_SYMBOL_GPL(unregister_switchdev_blocking_notifier); | |
436 | ||
437 | int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev, | |
479c86dc PM |
438 | struct switchdev_notifier_info *info, |
439 | struct netlink_ext_ack *extack) | |
a93e3b17 PM |
440 | { |
441 | info->dev = dev; | |
479c86dc | 442 | info->extack = extack; |
a93e3b17 PM |
443 | return blocking_notifier_call_chain(&switchdev_blocking_notif_chain, |
444 | val, info); | |
445 | } | |
446 | EXPORT_SYMBOL_GPL(call_switchdev_blocking_notifiers); | |
447 | ||
f30f0601 PM |
448 | static int __switchdev_handle_port_obj_add(struct net_device *dev, |
449 | struct switchdev_notifier_port_obj_info *port_obj_info, | |
450 | bool (*check_cb)(const struct net_device *dev), | |
451 | int (*add_cb)(struct net_device *dev, | |
452 | const struct switchdev_obj *obj, | |
69213513 PM |
453 | struct switchdev_trans *trans, |
454 | struct netlink_ext_ack *extack)) | |
f30f0601 | 455 | { |
69213513 | 456 | struct netlink_ext_ack *extack; |
f30f0601 PM |
457 | struct net_device *lower_dev; |
458 | struct list_head *iter; | |
459 | int err = -EOPNOTSUPP; | |
460 | ||
69213513 PM |
461 | extack = switchdev_notifier_info_to_extack(&port_obj_info->info); |
462 | ||
f30f0601 PM |
463 | if (check_cb(dev)) { |
464 | /* This flag is only checked if the return value is success. */ | |
465 | port_obj_info->handled = true; | |
69213513 PM |
466 | return add_cb(dev, port_obj_info->obj, port_obj_info->trans, |
467 | extack); | |
f30f0601 PM |
468 | } |
469 | ||
470 | /* Switch ports might be stacked under e.g. a LAG. Ignore the | |
471 | * unsupported devices, another driver might be able to handle them. But | |
472 | * propagate to the callers any hard errors. | |
473 | * | |
474 | * If the driver does its own bookkeeping of stacked ports, it's not | |
475 | * necessary to go through this helper. | |
476 | */ | |
477 | netdev_for_each_lower_dev(dev, lower_dev, iter) { | |
478 | err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info, | |
479 | check_cb, add_cb); | |
480 | if (err && err != -EOPNOTSUPP) | |
481 | return err; | |
482 | } | |
483 | ||
484 | return err; | |
485 | } | |
486 | ||
487 | int switchdev_handle_port_obj_add(struct net_device *dev, | |
488 | struct switchdev_notifier_port_obj_info *port_obj_info, | |
489 | bool (*check_cb)(const struct net_device *dev), | |
490 | int (*add_cb)(struct net_device *dev, | |
491 | const struct switchdev_obj *obj, | |
69213513 PM |
492 | struct switchdev_trans *trans, |
493 | struct netlink_ext_ack *extack)) | |
f30f0601 PM |
494 | { |
495 | int err; | |
496 | ||
497 | err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb, | |
498 | add_cb); | |
499 | if (err == -EOPNOTSUPP) | |
500 | err = 0; | |
501 | return err; | |
502 | } | |
503 | EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add); | |
504 | ||
505 | static int __switchdev_handle_port_obj_del(struct net_device *dev, | |
506 | struct switchdev_notifier_port_obj_info *port_obj_info, | |
507 | bool (*check_cb)(const struct net_device *dev), | |
508 | int (*del_cb)(struct net_device *dev, | |
509 | const struct switchdev_obj *obj)) | |
510 | { | |
511 | struct net_device *lower_dev; | |
512 | struct list_head *iter; | |
513 | int err = -EOPNOTSUPP; | |
514 | ||
515 | if (check_cb(dev)) { | |
516 | /* This flag is only checked if the return value is success. */ | |
517 | port_obj_info->handled = true; | |
518 | return del_cb(dev, port_obj_info->obj); | |
519 | } | |
520 | ||
521 | /* Switch ports might be stacked under e.g. a LAG. Ignore the | |
522 | * unsupported devices, another driver might be able to handle them. But | |
523 | * propagate to the callers any hard errors. | |
524 | * | |
525 | * If the driver does its own bookkeeping of stacked ports, it's not | |
526 | * necessary to go through this helper. | |
527 | */ | |
528 | netdev_for_each_lower_dev(dev, lower_dev, iter) { | |
529 | err = __switchdev_handle_port_obj_del(lower_dev, port_obj_info, | |
530 | check_cb, del_cb); | |
531 | if (err && err != -EOPNOTSUPP) | |
532 | return err; | |
533 | } | |
534 | ||
535 | return err; | |
536 | } | |
537 | ||
538 | int switchdev_handle_port_obj_del(struct net_device *dev, | |
539 | struct switchdev_notifier_port_obj_info *port_obj_info, | |
540 | bool (*check_cb)(const struct net_device *dev), | |
541 | int (*del_cb)(struct net_device *dev, | |
542 | const struct switchdev_obj *obj)) | |
543 | { | |
544 | int err; | |
545 | ||
546 | err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb, | |
547 | del_cb); | |
548 | if (err == -EOPNOTSUPP) | |
549 | err = 0; | |
550 | return err; | |
551 | } | |
552 | EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del); | |
1cb33af1 FF |
553 | |
554 | static int __switchdev_handle_port_attr_set(struct net_device *dev, | |
555 | struct switchdev_notifier_port_attr_info *port_attr_info, | |
556 | bool (*check_cb)(const struct net_device *dev), | |
557 | int (*set_cb)(struct net_device *dev, | |
558 | const struct switchdev_attr *attr, | |
559 | struct switchdev_trans *trans)) | |
560 | { | |
561 | struct net_device *lower_dev; | |
562 | struct list_head *iter; | |
563 | int err = -EOPNOTSUPP; | |
564 | ||
565 | if (check_cb(dev)) { | |
566 | port_attr_info->handled = true; | |
567 | return set_cb(dev, port_attr_info->attr, | |
568 | port_attr_info->trans); | |
569 | } | |
570 | ||
571 | /* Switch ports might be stacked under e.g. a LAG. Ignore the | |
572 | * unsupported devices, another driver might be able to handle them. But | |
573 | * propagate to the callers any hard errors. | |
574 | * | |
575 | * If the driver does its own bookkeeping of stacked ports, it's not | |
576 | * necessary to go through this helper. | |
577 | */ | |
578 | netdev_for_each_lower_dev(dev, lower_dev, iter) { | |
579 | err = __switchdev_handle_port_attr_set(lower_dev, port_attr_info, | |
580 | check_cb, set_cb); | |
581 | if (err && err != -EOPNOTSUPP) | |
582 | return err; | |
583 | } | |
584 | ||
585 | return err; | |
586 | } | |
587 | ||
588 | int switchdev_handle_port_attr_set(struct net_device *dev, | |
589 | struct switchdev_notifier_port_attr_info *port_attr_info, | |
590 | bool (*check_cb)(const struct net_device *dev), | |
591 | int (*set_cb)(struct net_device *dev, | |
592 | const struct switchdev_attr *attr, | |
593 | struct switchdev_trans *trans)) | |
594 | { | |
595 | int err; | |
596 | ||
597 | err = __switchdev_handle_port_attr_set(dev, port_attr_info, check_cb, | |
598 | set_cb); | |
599 | if (err == -EOPNOTSUPP) | |
600 | err = 0; | |
601 | return err; | |
602 | } | |
603 | EXPORT_SYMBOL_GPL(switchdev_handle_port_attr_set); |