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; | |
fbfc8502 | 32 | unsigned long data[]; |
793f4014 JP |
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 | 224 | struct netlink_ext_ack *extack) |
491d0f15 | 225 | { |
d17d9f5e PM |
226 | int rc; |
227 | int err; | |
491d0f15 | 228 | |
d17d9f5e PM |
229 | struct switchdev_notifier_port_obj_info obj_info = { |
230 | .obj = obj, | |
d17d9f5e PM |
231 | .handled = false, |
232 | }; | |
491d0f15 | 233 | |
479c86dc | 234 | rc = call_switchdev_blocking_notifiers(nt, dev, &obj_info.info, extack); |
d17d9f5e PM |
235 | err = notifier_to_errno(rc); |
236 | if (err) { | |
237 | WARN_ON(!obj_info.handled); | |
238 | return err; | |
491d0f15 | 239 | } |
d17d9f5e PM |
240 | if (!obj_info.handled) |
241 | return -EOPNOTSUPP; | |
242 | return 0; | |
491d0f15 SF |
243 | } |
244 | ||
4d429c5d | 245 | static int switchdev_port_obj_add_now(struct net_device *dev, |
69b7320e PM |
246 | const struct switchdev_obj *obj, |
247 | struct netlink_ext_ack *extack) | |
491d0f15 | 248 | { |
491d0f15 SF |
249 | ASSERT_RTNL(); |
250 | ||
ffb68fc5 VO |
251 | return switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD, |
252 | dev, obj, extack); | |
491d0f15 | 253 | } |
4d429c5d JP |
254 | |
255 | static void switchdev_port_obj_add_deferred(struct net_device *dev, | |
256 | const void *data) | |
257 | { | |
258 | const struct switchdev_obj *obj = data; | |
259 | int err; | |
260 | ||
69b7320e | 261 | err = switchdev_port_obj_add_now(dev, obj, NULL); |
4d429c5d JP |
262 | if (err && err != -EOPNOTSUPP) |
263 | netdev_err(dev, "failed (err=%d) to add object (id=%d)\n", | |
264 | err, obj->id); | |
7ceb2afb ER |
265 | if (obj->complete) |
266 | obj->complete(dev, err, obj->complete_priv); | |
4d429c5d JP |
267 | } |
268 | ||
269 | static int switchdev_port_obj_add_defer(struct net_device *dev, | |
270 | const struct switchdev_obj *obj) | |
271 | { | |
e258d919 | 272 | return switchdev_deferred_enqueue(dev, obj, switchdev_obj_size(obj), |
4d429c5d JP |
273 | switchdev_port_obj_add_deferred); |
274 | } | |
491d0f15 SF |
275 | |
276 | /** | |
4d429c5d | 277 | * switchdev_port_obj_add - Add port object |
491d0f15 SF |
278 | * |
279 | * @dev: port device | |
4d429c5d | 280 | * @obj: object to add |
c8af73f0 | 281 | * @extack: netlink extended ack |
4d429c5d | 282 | * |
4d429c5d JP |
283 | * rtnl_lock must be held and must not be in atomic section, |
284 | * in case SWITCHDEV_F_DEFER flag is not set. | |
491d0f15 | 285 | */ |
4d429c5d | 286 | int switchdev_port_obj_add(struct net_device *dev, |
69b7320e PM |
287 | const struct switchdev_obj *obj, |
288 | struct netlink_ext_ack *extack) | |
4d429c5d JP |
289 | { |
290 | if (obj->flags & SWITCHDEV_F_DEFER) | |
291 | return switchdev_port_obj_add_defer(dev, obj); | |
292 | ASSERT_RTNL(); | |
69b7320e | 293 | return switchdev_port_obj_add_now(dev, obj, extack); |
4d429c5d JP |
294 | } |
295 | EXPORT_SYMBOL_GPL(switchdev_port_obj_add); | |
296 | ||
297 | static int switchdev_port_obj_del_now(struct net_device *dev, | |
298 | const struct switchdev_obj *obj) | |
491d0f15 | 299 | { |
d17d9f5e | 300 | return switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_DEL, |
ffb68fc5 | 301 | dev, obj, NULL); |
491d0f15 | 302 | } |
4d429c5d JP |
303 | |
304 | static void switchdev_port_obj_del_deferred(struct net_device *dev, | |
305 | const void *data) | |
306 | { | |
307 | const struct switchdev_obj *obj = data; | |
308 | int err; | |
309 | ||
310 | err = switchdev_port_obj_del_now(dev, obj); | |
311 | if (err && err != -EOPNOTSUPP) | |
312 | netdev_err(dev, "failed (err=%d) to del object (id=%d)\n", | |
313 | err, obj->id); | |
7ceb2afb ER |
314 | if (obj->complete) |
315 | obj->complete(dev, err, obj->complete_priv); | |
4d429c5d JP |
316 | } |
317 | ||
318 | static int switchdev_port_obj_del_defer(struct net_device *dev, | |
319 | const struct switchdev_obj *obj) | |
320 | { | |
e258d919 | 321 | return switchdev_deferred_enqueue(dev, obj, switchdev_obj_size(obj), |
4d429c5d JP |
322 | switchdev_port_obj_del_deferred); |
323 | } | |
324 | ||
325 | /** | |
326 | * switchdev_port_obj_del - Delete port object | |
327 | * | |
328 | * @dev: port device | |
4d429c5d JP |
329 | * @obj: object to delete |
330 | * | |
331 | * rtnl_lock must be held and must not be in atomic section, | |
332 | * in case SWITCHDEV_F_DEFER flag is not set. | |
333 | */ | |
334 | int switchdev_port_obj_del(struct net_device *dev, | |
335 | const struct switchdev_obj *obj) | |
336 | { | |
337 | if (obj->flags & SWITCHDEV_F_DEFER) | |
338 | return switchdev_port_obj_del_defer(dev, obj); | |
339 | ASSERT_RTNL(); | |
340 | return switchdev_port_obj_del_now(dev, obj); | |
341 | } | |
491d0f15 SF |
342 | EXPORT_SYMBOL_GPL(switchdev_port_obj_del); |
343 | ||
ff5cf100 | 344 | static ATOMIC_NOTIFIER_HEAD(switchdev_notif_chain); |
a93e3b17 | 345 | static BLOCKING_NOTIFIER_HEAD(switchdev_blocking_notif_chain); |
03bf0c28 JP |
346 | |
347 | /** | |
ebb9a03a | 348 | * register_switchdev_notifier - Register notifier |
03bf0c28 JP |
349 | * @nb: notifier_block |
350 | * | |
ff5cf100 | 351 | * Register switch device notifier. |
03bf0c28 | 352 | */ |
ebb9a03a | 353 | int register_switchdev_notifier(struct notifier_block *nb) |
03bf0c28 | 354 | { |
ff5cf100 | 355 | return atomic_notifier_chain_register(&switchdev_notif_chain, nb); |
03bf0c28 | 356 | } |
ebb9a03a | 357 | EXPORT_SYMBOL_GPL(register_switchdev_notifier); |
03bf0c28 JP |
358 | |
359 | /** | |
ebb9a03a | 360 | * unregister_switchdev_notifier - Unregister notifier |
03bf0c28 JP |
361 | * @nb: notifier_block |
362 | * | |
363 | * Unregister switch device notifier. | |
03bf0c28 | 364 | */ |
ebb9a03a | 365 | int unregister_switchdev_notifier(struct notifier_block *nb) |
03bf0c28 | 366 | { |
ff5cf100 | 367 | return atomic_notifier_chain_unregister(&switchdev_notif_chain, nb); |
03bf0c28 | 368 | } |
ebb9a03a | 369 | EXPORT_SYMBOL_GPL(unregister_switchdev_notifier); |
03bf0c28 JP |
370 | |
371 | /** | |
ebb9a03a | 372 | * call_switchdev_notifiers - Call notifiers |
03bf0c28 JP |
373 | * @val: value passed unmodified to notifier function |
374 | * @dev: port device | |
375 | * @info: notifier information data | |
ea6754ae | 376 | * @extack: netlink extended ack |
ff5cf100 | 377 | * Call all network notifier blocks. |
03bf0c28 | 378 | */ |
ebb9a03a | 379 | int call_switchdev_notifiers(unsigned long val, struct net_device *dev, |
6685987c PM |
380 | struct switchdev_notifier_info *info, |
381 | struct netlink_ext_ack *extack) | |
03bf0c28 | 382 | { |
03bf0c28 | 383 | info->dev = dev; |
6685987c | 384 | info->extack = extack; |
ff5cf100 | 385 | return atomic_notifier_call_chain(&switchdev_notif_chain, val, info); |
03bf0c28 | 386 | } |
ebb9a03a | 387 | EXPORT_SYMBOL_GPL(call_switchdev_notifiers); |
8a44dbb2 | 388 | |
a93e3b17 PM |
389 | int register_switchdev_blocking_notifier(struct notifier_block *nb) |
390 | { | |
391 | struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain; | |
392 | ||
393 | return blocking_notifier_chain_register(chain, nb); | |
394 | } | |
395 | EXPORT_SYMBOL_GPL(register_switchdev_blocking_notifier); | |
396 | ||
397 | int unregister_switchdev_blocking_notifier(struct notifier_block *nb) | |
398 | { | |
399 | struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain; | |
400 | ||
401 | return blocking_notifier_chain_unregister(chain, nb); | |
402 | } | |
403 | EXPORT_SYMBOL_GPL(unregister_switchdev_blocking_notifier); | |
404 | ||
405 | int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev, | |
479c86dc PM |
406 | struct switchdev_notifier_info *info, |
407 | struct netlink_ext_ack *extack) | |
a93e3b17 PM |
408 | { |
409 | info->dev = dev; | |
479c86dc | 410 | info->extack = extack; |
a93e3b17 PM |
411 | return blocking_notifier_call_chain(&switchdev_blocking_notif_chain, |
412 | val, info); | |
413 | } | |
414 | EXPORT_SYMBOL_GPL(call_switchdev_blocking_notifiers); | |
415 | ||
f30f0601 PM |
416 | static int __switchdev_handle_port_obj_add(struct net_device *dev, |
417 | struct switchdev_notifier_port_obj_info *port_obj_info, | |
418 | bool (*check_cb)(const struct net_device *dev), | |
419 | int (*add_cb)(struct net_device *dev, | |
420 | const struct switchdev_obj *obj, | |
69213513 | 421 | struct netlink_ext_ack *extack)) |
f30f0601 | 422 | { |
69213513 | 423 | struct netlink_ext_ack *extack; |
f30f0601 PM |
424 | struct net_device *lower_dev; |
425 | struct list_head *iter; | |
426 | int err = -EOPNOTSUPP; | |
427 | ||
69213513 PM |
428 | extack = switchdev_notifier_info_to_extack(&port_obj_info->info); |
429 | ||
f30f0601 PM |
430 | if (check_cb(dev)) { |
431 | /* This flag is only checked if the return value is success. */ | |
432 | port_obj_info->handled = true; | |
ffb68fc5 | 433 | return add_cb(dev, port_obj_info->obj, extack); |
f30f0601 PM |
434 | } |
435 | ||
436 | /* Switch ports might be stacked under e.g. a LAG. Ignore the | |
437 | * unsupported devices, another driver might be able to handle them. But | |
438 | * propagate to the callers any hard errors. | |
439 | * | |
440 | * If the driver does its own bookkeeping of stacked ports, it's not | |
441 | * necessary to go through this helper. | |
442 | */ | |
443 | netdev_for_each_lower_dev(dev, lower_dev, iter) { | |
07c6f980 RK |
444 | if (netif_is_bridge_master(lower_dev)) |
445 | continue; | |
446 | ||
f30f0601 PM |
447 | err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info, |
448 | check_cb, add_cb); | |
449 | if (err && err != -EOPNOTSUPP) | |
450 | return err; | |
451 | } | |
452 | ||
453 | return err; | |
454 | } | |
455 | ||
456 | int switchdev_handle_port_obj_add(struct net_device *dev, | |
457 | struct switchdev_notifier_port_obj_info *port_obj_info, | |
458 | bool (*check_cb)(const struct net_device *dev), | |
459 | int (*add_cb)(struct net_device *dev, | |
460 | const struct switchdev_obj *obj, | |
69213513 | 461 | struct netlink_ext_ack *extack)) |
f30f0601 PM |
462 | { |
463 | int err; | |
464 | ||
465 | err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb, | |
466 | add_cb); | |
467 | if (err == -EOPNOTSUPP) | |
468 | err = 0; | |
469 | return err; | |
470 | } | |
471 | EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add); | |
472 | ||
473 | static int __switchdev_handle_port_obj_del(struct net_device *dev, | |
474 | struct switchdev_notifier_port_obj_info *port_obj_info, | |
475 | bool (*check_cb)(const struct net_device *dev), | |
476 | int (*del_cb)(struct net_device *dev, | |
477 | const struct switchdev_obj *obj)) | |
478 | { | |
479 | struct net_device *lower_dev; | |
480 | struct list_head *iter; | |
481 | int err = -EOPNOTSUPP; | |
482 | ||
483 | if (check_cb(dev)) { | |
484 | /* This flag is only checked if the return value is success. */ | |
485 | port_obj_info->handled = true; | |
486 | return del_cb(dev, port_obj_info->obj); | |
487 | } | |
488 | ||
489 | /* Switch ports might be stacked under e.g. a LAG. Ignore the | |
490 | * unsupported devices, another driver might be able to handle them. But | |
491 | * propagate to the callers any hard errors. | |
492 | * | |
493 | * If the driver does its own bookkeeping of stacked ports, it's not | |
494 | * necessary to go through this helper. | |
495 | */ | |
496 | netdev_for_each_lower_dev(dev, lower_dev, iter) { | |
07c6f980 RK |
497 | if (netif_is_bridge_master(lower_dev)) |
498 | continue; | |
499 | ||
f30f0601 PM |
500 | err = __switchdev_handle_port_obj_del(lower_dev, port_obj_info, |
501 | check_cb, del_cb); | |
502 | if (err && err != -EOPNOTSUPP) | |
503 | return err; | |
504 | } | |
505 | ||
506 | return err; | |
507 | } | |
508 | ||
509 | int switchdev_handle_port_obj_del(struct net_device *dev, | |
510 | struct switchdev_notifier_port_obj_info *port_obj_info, | |
511 | bool (*check_cb)(const struct net_device *dev), | |
512 | int (*del_cb)(struct net_device *dev, | |
513 | const struct switchdev_obj *obj)) | |
514 | { | |
515 | int err; | |
516 | ||
517 | err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb, | |
518 | del_cb); | |
519 | if (err == -EOPNOTSUPP) | |
520 | err = 0; | |
521 | return err; | |
522 | } | |
523 | EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del); | |
1cb33af1 FF |
524 | |
525 | static int __switchdev_handle_port_attr_set(struct net_device *dev, | |
526 | struct switchdev_notifier_port_attr_info *port_attr_info, | |
527 | bool (*check_cb)(const struct net_device *dev), | |
528 | int (*set_cb)(struct net_device *dev, | |
529 | const struct switchdev_attr *attr, | |
530 | struct switchdev_trans *trans)) | |
531 | { | |
532 | struct net_device *lower_dev; | |
533 | struct list_head *iter; | |
534 | int err = -EOPNOTSUPP; | |
535 | ||
536 | if (check_cb(dev)) { | |
537 | port_attr_info->handled = true; | |
538 | return set_cb(dev, port_attr_info->attr, | |
539 | port_attr_info->trans); | |
540 | } | |
541 | ||
542 | /* Switch ports might be stacked under e.g. a LAG. Ignore the | |
543 | * unsupported devices, another driver might be able to handle them. But | |
544 | * propagate to the callers any hard errors. | |
545 | * | |
546 | * If the driver does its own bookkeeping of stacked ports, it's not | |
547 | * necessary to go through this helper. | |
548 | */ | |
549 | netdev_for_each_lower_dev(dev, lower_dev, iter) { | |
07c6f980 RK |
550 | if (netif_is_bridge_master(lower_dev)) |
551 | continue; | |
552 | ||
1cb33af1 FF |
553 | err = __switchdev_handle_port_attr_set(lower_dev, port_attr_info, |
554 | check_cb, set_cb); | |
555 | if (err && err != -EOPNOTSUPP) | |
556 | return err; | |
557 | } | |
558 | ||
559 | return err; | |
560 | } | |
561 | ||
562 | int switchdev_handle_port_attr_set(struct net_device *dev, | |
563 | struct switchdev_notifier_port_attr_info *port_attr_info, | |
564 | bool (*check_cb)(const struct net_device *dev), | |
565 | int (*set_cb)(struct net_device *dev, | |
566 | const struct switchdev_attr *attr, | |
567 | struct switchdev_trans *trans)) | |
568 | { | |
569 | int err; | |
570 | ||
571 | err = __switchdev_handle_port_attr_set(dev, port_attr_info, check_cb, | |
572 | set_cb); | |
573 | if (err == -EOPNOTSUPP) | |
574 | err = 0; | |
575 | return err; | |
576 | } | |
577 | EXPORT_SYMBOL_GPL(switchdev_handle_port_attr_set); |