]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
f515f192 VD |
2 | /* |
3 | * Handling of a single switch chip, part of a switch fabric | |
4 | * | |
4333d619 VD |
5 | * Copyright (c) 2017 Savoir-faire Linux Inc. |
6 | * Vivien Didelot <[email protected]> | |
f515f192 VD |
7 | */ |
8 | ||
d371b7c9 | 9 | #include <linux/if_bridge.h> |
f515f192 VD |
10 | #include <linux/netdevice.h> |
11 | #include <linux/notifier.h> | |
061f6a50 | 12 | #include <linux/if_vlan.h> |
1faabf74 | 13 | #include <net/switchdev.h> |
ea5dd34b VD |
14 | |
15 | #include "dsa_priv.h" | |
f515f192 | 16 | |
1faabf74 VD |
17 | static unsigned int dsa_switch_fastest_ageing_time(struct dsa_switch *ds, |
18 | unsigned int ageing_time) | |
19 | { | |
20 | int i; | |
21 | ||
22 | for (i = 0; i < ds->num_ports; ++i) { | |
68bb8ea8 | 23 | struct dsa_port *dp = dsa_to_port(ds, i); |
1faabf74 VD |
24 | |
25 | if (dp->ageing_time && dp->ageing_time < ageing_time) | |
26 | ageing_time = dp->ageing_time; | |
27 | } | |
28 | ||
29 | return ageing_time; | |
30 | } | |
31 | ||
32 | static int dsa_switch_ageing_time(struct dsa_switch *ds, | |
33 | struct dsa_notifier_ageing_time_info *info) | |
34 | { | |
35 | unsigned int ageing_time = info->ageing_time; | |
77b61365 VO |
36 | |
37 | if (ds->ageing_time_min && ageing_time < ds->ageing_time_min) | |
38 | return -ERANGE; | |
39 | ||
40 | if (ds->ageing_time_max && ageing_time > ds->ageing_time_max) | |
41 | return -ERANGE; | |
1faabf74 VD |
42 | |
43 | /* Program the fastest ageing time in case of multiple bridges */ | |
44 | ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time); | |
45 | ||
46 | if (ds->ops->set_ageing_time) | |
47 | return ds->ops->set_ageing_time(ds, ageing_time); | |
48 | ||
49 | return 0; | |
50 | } | |
51 | ||
bfcb8132 VO |
52 | static bool dsa_switch_mtu_match(struct dsa_switch *ds, int port, |
53 | struct dsa_notifier_mtu_info *info) | |
54 | { | |
55 | if (ds->index == info->sw_index) | |
56 | return (port == info->port) || dsa_is_dsa_port(ds, port); | |
57 | ||
58 | if (!info->propagate_upstream) | |
59 | return false; | |
60 | ||
61 | if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port)) | |
62 | return true; | |
63 | ||
64 | return false; | |
65 | } | |
66 | ||
67 | static int dsa_switch_mtu(struct dsa_switch *ds, | |
68 | struct dsa_notifier_mtu_info *info) | |
69 | { | |
70 | int port, ret; | |
71 | ||
72 | if (!ds->ops->port_change_mtu) | |
73 | return -EOPNOTSUPP; | |
74 | ||
75 | for (port = 0; port < ds->num_ports; port++) { | |
76 | if (dsa_switch_mtu_match(ds, port, info)) { | |
77 | ret = ds->ops->port_change_mtu(ds, port, info->mtu); | |
78 | if (ret) | |
79 | return ret; | |
80 | } | |
81 | } | |
82 | ||
83 | return 0; | |
84 | } | |
85 | ||
04d3a4c6 VD |
86 | static int dsa_switch_bridge_join(struct dsa_switch *ds, |
87 | struct dsa_notifier_bridge_info *info) | |
88 | { | |
f66a6a69 VO |
89 | struct dsa_switch_tree *dst = ds->dst; |
90 | ||
91 | if (dst->index == info->tree_index && ds->index == info->sw_index && | |
92 | ds->ops->port_bridge_join) | |
04d3a4c6 VD |
93 | return ds->ops->port_bridge_join(ds, info->port, info->br); |
94 | ||
f66a6a69 VO |
95 | if ((dst->index != info->tree_index || ds->index != info->sw_index) && |
96 | ds->ops->crosschip_bridge_join) | |
97 | return ds->ops->crosschip_bridge_join(ds, info->tree_index, | |
98 | info->sw_index, | |
40ef2c93 | 99 | info->port, info->br); |
04d3a4c6 VD |
100 | |
101 | return 0; | |
102 | } | |
103 | ||
104 | static int dsa_switch_bridge_leave(struct dsa_switch *ds, | |
105 | struct dsa_notifier_bridge_info *info) | |
106 | { | |
d371b7c9 | 107 | bool unset_vlan_filtering = br_vlan_enabled(info->br); |
f66a6a69 | 108 | struct dsa_switch_tree *dst = ds->dst; |
89153ed6 | 109 | struct netlink_ext_ack extack = {0}; |
d371b7c9 VO |
110 | int err, i; |
111 | ||
f66a6a69 VO |
112 | if (dst->index == info->tree_index && ds->index == info->sw_index && |
113 | ds->ops->port_bridge_join) | |
04d3a4c6 VD |
114 | ds->ops->port_bridge_leave(ds, info->port, info->br); |
115 | ||
f66a6a69 VO |
116 | if ((dst->index != info->tree_index || ds->index != info->sw_index) && |
117 | ds->ops->crosschip_bridge_join) | |
118 | ds->ops->crosschip_bridge_leave(ds, info->tree_index, | |
119 | info->sw_index, info->port, | |
40ef2c93 | 120 | info->br); |
04d3a4c6 | 121 | |
d371b7c9 VO |
122 | /* If the bridge was vlan_filtering, the bridge core doesn't trigger an |
123 | * event for changing vlan_filtering setting upon slave ports leaving | |
124 | * it. That is a good thing, because that lets us handle it and also | |
125 | * handle the case where the switch's vlan_filtering setting is global | |
126 | * (not per port). When that happens, the correct moment to trigger the | |
127 | * vlan_filtering callback is only when the last port left this bridge. | |
128 | */ | |
129 | if (unset_vlan_filtering && ds->vlan_filtering_is_global) { | |
130 | for (i = 0; i < ds->num_ports; i++) { | |
131 | if (i == info->port) | |
132 | continue; | |
133 | if (dsa_to_port(ds, i)->bridge_dev == info->br) { | |
134 | unset_vlan_filtering = false; | |
135 | break; | |
136 | } | |
137 | } | |
138 | } | |
139 | if (unset_vlan_filtering) { | |
68bb8ea8 | 140 | err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port), |
89153ed6 VO |
141 | false, &extack); |
142 | if (extack._msg) | |
143 | dev_err(ds->dev, "port %d: %s\n", info->port, | |
144 | extack._msg); | |
d371b7c9 VO |
145 | if (err && err != EOPNOTSUPP) |
146 | return err; | |
147 | } | |
04d3a4c6 VD |
148 | return 0; |
149 | } | |
150 | ||
685fb6a4 VD |
151 | static int dsa_switch_fdb_add(struct dsa_switch *ds, |
152 | struct dsa_notifier_fdb_info *info) | |
153 | { | |
3169241f | 154 | int port = dsa_towards_port(ds, info->sw_index, info->port); |
685fb6a4 | 155 | |
1b6dd556 AS |
156 | if (!ds->ops->port_fdb_add) |
157 | return -EOPNOTSUPP; | |
685fb6a4 | 158 | |
3169241f | 159 | return ds->ops->port_fdb_add(ds, port, info->addr, info->vid); |
685fb6a4 VD |
160 | } |
161 | ||
162 | static int dsa_switch_fdb_del(struct dsa_switch *ds, | |
163 | struct dsa_notifier_fdb_info *info) | |
164 | { | |
3169241f | 165 | int port = dsa_towards_port(ds, info->sw_index, info->port); |
685fb6a4 VD |
166 | |
167 | if (!ds->ops->port_fdb_del) | |
168 | return -EOPNOTSUPP; | |
169 | ||
3169241f | 170 | return ds->ops->port_fdb_del(ds, port, info->addr, info->vid); |
685fb6a4 VD |
171 | } |
172 | ||
18596f50 GM |
173 | static int dsa_switch_hsr_join(struct dsa_switch *ds, |
174 | struct dsa_notifier_hsr_info *info) | |
175 | { | |
176 | if (ds->index == info->sw_index && ds->ops->port_hsr_join) | |
177 | return ds->ops->port_hsr_join(ds, info->port, info->hsr); | |
178 | ||
179 | return -EOPNOTSUPP; | |
180 | } | |
181 | ||
182 | static int dsa_switch_hsr_leave(struct dsa_switch *ds, | |
183 | struct dsa_notifier_hsr_info *info) | |
184 | { | |
185 | if (ds->index == info->sw_index && ds->ops->port_hsr_leave) | |
186 | return ds->ops->port_hsr_leave(ds, info->port, info->hsr); | |
187 | ||
188 | return -EOPNOTSUPP; | |
189 | } | |
190 | ||
058102a6 TW |
191 | static int dsa_switch_lag_change(struct dsa_switch *ds, |
192 | struct dsa_notifier_lag_info *info) | |
193 | { | |
194 | if (ds->index == info->sw_index && ds->ops->port_lag_change) | |
195 | return ds->ops->port_lag_change(ds, info->port); | |
196 | ||
197 | if (ds->index != info->sw_index && ds->ops->crosschip_lag_change) | |
198 | return ds->ops->crosschip_lag_change(ds, info->sw_index, | |
199 | info->port); | |
200 | ||
201 | return 0; | |
202 | } | |
203 | ||
204 | static int dsa_switch_lag_join(struct dsa_switch *ds, | |
205 | struct dsa_notifier_lag_info *info) | |
206 | { | |
207 | if (ds->index == info->sw_index && ds->ops->port_lag_join) | |
208 | return ds->ops->port_lag_join(ds, info->port, info->lag, | |
209 | info->info); | |
210 | ||
211 | if (ds->index != info->sw_index && ds->ops->crosschip_lag_join) | |
212 | return ds->ops->crosschip_lag_join(ds, info->sw_index, | |
213 | info->port, info->lag, | |
214 | info->info); | |
215 | ||
216 | return 0; | |
217 | } | |
218 | ||
219 | static int dsa_switch_lag_leave(struct dsa_switch *ds, | |
220 | struct dsa_notifier_lag_info *info) | |
221 | { | |
222 | if (ds->index == info->sw_index && ds->ops->port_lag_leave) | |
223 | return ds->ops->port_lag_leave(ds, info->port, info->lag); | |
224 | ||
225 | if (ds->index != info->sw_index && ds->ops->crosschip_lag_leave) | |
226 | return ds->ops->crosschip_lag_leave(ds, info->sw_index, | |
227 | info->port, info->lag); | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
e65d45cc VD |
232 | static bool dsa_switch_mdb_match(struct dsa_switch *ds, int port, |
233 | struct dsa_notifier_mdb_info *info) | |
234 | { | |
235 | if (ds->index == info->sw_index && port == info->port) | |
236 | return true; | |
237 | ||
238 | if (dsa_is_dsa_port(ds, port)) | |
239 | return true; | |
240 | ||
241 | return false; | |
242 | } | |
243 | ||
ffb68fc5 VO |
244 | static int dsa_switch_mdb_add(struct dsa_switch *ds, |
245 | struct dsa_notifier_mdb_info *info) | |
e6db98db | 246 | { |
a52b2da7 VO |
247 | int err = 0; |
248 | int port; | |
e6db98db | 249 | |
a52b2da7 | 250 | if (!ds->ops->port_mdb_add) |
e6db98db VD |
251 | return -EOPNOTSUPP; |
252 | ||
e65d45cc VD |
253 | for (port = 0; port < ds->num_ports; port++) { |
254 | if (dsa_switch_mdb_match(ds, port, info)) { | |
a52b2da7 | 255 | err = ds->ops->port_mdb_add(ds, port, info->mdb); |
e65d45cc | 256 | if (err) |
a52b2da7 | 257 | break; |
e65d45cc | 258 | } |
e6db98db VD |
259 | } |
260 | ||
a52b2da7 | 261 | return err; |
8ae5bcdc VD |
262 | } |
263 | ||
264 | static int dsa_switch_mdb_del(struct dsa_switch *ds, | |
265 | struct dsa_notifier_mdb_info *info) | |
266 | { | |
8ae5bcdc VD |
267 | if (!ds->ops->port_mdb_del) |
268 | return -EOPNOTSUPP; | |
269 | ||
a1a6b7ea | 270 | if (ds->index == info->sw_index) |
e65d45cc | 271 | return ds->ops->port_mdb_del(ds, info->port, info->mdb); |
a1a6b7ea VD |
272 | |
273 | return 0; | |
8ae5bcdc VD |
274 | } |
275 | ||
e65d45cc VD |
276 | static bool dsa_switch_vlan_match(struct dsa_switch *ds, int port, |
277 | struct dsa_notifier_vlan_info *info) | |
278 | { | |
279 | if (ds->index == info->sw_index && port == info->port) | |
280 | return true; | |
281 | ||
7e1741b4 | 282 | if (dsa_is_dsa_port(ds, port)) |
e65d45cc VD |
283 | return true; |
284 | ||
285 | return false; | |
286 | } | |
287 | ||
ffb68fc5 VO |
288 | static int dsa_switch_vlan_add(struct dsa_switch *ds, |
289 | struct dsa_notifier_vlan_info *info) | |
9c428c59 VD |
290 | { |
291 | int port, err; | |
292 | ||
1958d581 | 293 | if (!ds->ops->port_vlan_add) |
9c428c59 VD |
294 | return -EOPNOTSUPP; |
295 | ||
e65d45cc VD |
296 | for (port = 0; port < ds->num_ports; port++) { |
297 | if (dsa_switch_vlan_match(ds, port, info)) { | |
31046a5f VO |
298 | err = ds->ops->port_vlan_add(ds, port, info->vlan, |
299 | info->extack); | |
e65d45cc VD |
300 | if (err) |
301 | return err; | |
302 | } | |
9c428c59 VD |
303 | } |
304 | ||
d0c627b8 VD |
305 | return 0; |
306 | } | |
307 | ||
308 | static int dsa_switch_vlan_del(struct dsa_switch *ds, | |
309 | struct dsa_notifier_vlan_info *info) | |
310 | { | |
d0c627b8 VD |
311 | if (!ds->ops->port_vlan_del) |
312 | return -EOPNOTSUPP; | |
313 | ||
1ca4aa9c | 314 | if (ds->index == info->sw_index) |
e65d45cc | 315 | return ds->ops->port_vlan_del(ds, info->port, info->vlan); |
1ca4aa9c | 316 | |
7e1741b4 VD |
317 | /* Do not deprogram the DSA links as they may be used as conduit |
318 | * for other VLAN members in the fabric. | |
319 | */ | |
1ca4aa9c | 320 | return 0; |
d0c627b8 VD |
321 | } |
322 | ||
53da0eba VO |
323 | static bool dsa_switch_tag_proto_match(struct dsa_switch *ds, int port, |
324 | struct dsa_notifier_tag_proto_info *info) | |
325 | { | |
326 | if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)) | |
327 | return true; | |
328 | ||
329 | return false; | |
330 | } | |
331 | ||
332 | static int dsa_switch_change_tag_proto(struct dsa_switch *ds, | |
333 | struct dsa_notifier_tag_proto_info *info) | |
334 | { | |
335 | const struct dsa_device_ops *tag_ops = info->tag_ops; | |
336 | int port, err; | |
337 | ||
338 | if (!ds->ops->change_tag_protocol) | |
339 | return -EOPNOTSUPP; | |
340 | ||
341 | ASSERT_RTNL(); | |
342 | ||
343 | for (port = 0; port < ds->num_ports; port++) { | |
344 | if (dsa_switch_tag_proto_match(ds, port, info)) { | |
345 | err = ds->ops->change_tag_protocol(ds, port, | |
346 | tag_ops->proto); | |
347 | if (err) | |
348 | return err; | |
349 | ||
350 | if (dsa_is_cpu_port(ds, port)) | |
351 | dsa_port_set_tag_protocol(dsa_to_port(ds, port), | |
352 | tag_ops); | |
353 | } | |
354 | } | |
355 | ||
356 | /* Now that changing the tag protocol can no longer fail, let's update | |
357 | * the remaining bits which are "duplicated for faster access", and the | |
358 | * bits that depend on the tagger, such as the MTU. | |
359 | */ | |
360 | for (port = 0; port < ds->num_ports; port++) { | |
361 | if (dsa_is_user_port(ds, port)) { | |
362 | struct net_device *slave; | |
363 | ||
364 | slave = dsa_to_port(ds, port)->slave; | |
365 | dsa_slave_setup_tagger(slave); | |
366 | ||
367 | /* rtnl_mutex is held in dsa_tree_change_tag_proto */ | |
368 | dsa_slave_change_mtu(slave, slave->mtu); | |
369 | } | |
370 | } | |
371 | ||
372 | return 0; | |
373 | } | |
374 | ||
c595c433 HV |
375 | static bool dsa_switch_mrp_match(struct dsa_switch *ds, int port, |
376 | struct dsa_notifier_mrp_info *info) | |
377 | { | |
378 | if (ds->index == info->sw_index && port == info->port) | |
379 | return true; | |
380 | ||
381 | if (dsa_is_dsa_port(ds, port)) | |
382 | return true; | |
383 | ||
384 | return false; | |
385 | } | |
386 | ||
387 | static int dsa_switch_mrp_add(struct dsa_switch *ds, | |
388 | struct dsa_notifier_mrp_info *info) | |
389 | { | |
390 | int err = 0; | |
391 | int port; | |
392 | ||
393 | if (!ds->ops->port_mrp_add) | |
394 | return -EOPNOTSUPP; | |
395 | ||
396 | for (port = 0; port < ds->num_ports; port++) { | |
397 | if (dsa_switch_mrp_match(ds, port, info)) { | |
398 | err = ds->ops->port_mrp_add(ds, port, info->mrp); | |
399 | if (err) | |
400 | break; | |
401 | } | |
402 | } | |
403 | ||
404 | return err; | |
405 | } | |
406 | ||
407 | static int dsa_switch_mrp_del(struct dsa_switch *ds, | |
408 | struct dsa_notifier_mrp_info *info) | |
409 | { | |
410 | if (!ds->ops->port_mrp_del) | |
411 | return -EOPNOTSUPP; | |
412 | ||
413 | if (ds->index == info->sw_index) | |
414 | return ds->ops->port_mrp_del(ds, info->port, info->mrp); | |
415 | ||
416 | return 0; | |
417 | } | |
418 | ||
419 | static bool | |
420 | dsa_switch_mrp_ring_role_match(struct dsa_switch *ds, int port, | |
421 | struct dsa_notifier_mrp_ring_role_info *info) | |
422 | { | |
423 | if (ds->index == info->sw_index && port == info->port) | |
424 | return true; | |
425 | ||
426 | if (dsa_is_dsa_port(ds, port)) | |
427 | return true; | |
428 | ||
429 | return false; | |
430 | } | |
431 | ||
432 | static int | |
433 | dsa_switch_mrp_add_ring_role(struct dsa_switch *ds, | |
434 | struct dsa_notifier_mrp_ring_role_info *info) | |
435 | { | |
436 | int err = 0; | |
437 | int port; | |
438 | ||
439 | if (!ds->ops->port_mrp_add) | |
440 | return -EOPNOTSUPP; | |
441 | ||
442 | for (port = 0; port < ds->num_ports; port++) { | |
443 | if (dsa_switch_mrp_ring_role_match(ds, port, info)) { | |
444 | err = ds->ops->port_mrp_add_ring_role(ds, port, | |
445 | info->mrp); | |
446 | if (err) | |
447 | break; | |
448 | } | |
449 | } | |
450 | ||
451 | return err; | |
452 | } | |
453 | ||
454 | static int | |
455 | dsa_switch_mrp_del_ring_role(struct dsa_switch *ds, | |
456 | struct dsa_notifier_mrp_ring_role_info *info) | |
457 | { | |
458 | if (!ds->ops->port_mrp_del) | |
459 | return -EOPNOTSUPP; | |
460 | ||
461 | if (ds->index == info->sw_index) | |
462 | return ds->ops->port_mrp_del_ring_role(ds, info->port, | |
463 | info->mrp); | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
f515f192 VD |
468 | static int dsa_switch_event(struct notifier_block *nb, |
469 | unsigned long event, void *info) | |
470 | { | |
471 | struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb); | |
472 | int err; | |
473 | ||
474 | switch (event) { | |
1faabf74 VD |
475 | case DSA_NOTIFIER_AGEING_TIME: |
476 | err = dsa_switch_ageing_time(ds, info); | |
477 | break; | |
04d3a4c6 VD |
478 | case DSA_NOTIFIER_BRIDGE_JOIN: |
479 | err = dsa_switch_bridge_join(ds, info); | |
480 | break; | |
481 | case DSA_NOTIFIER_BRIDGE_LEAVE: | |
482 | err = dsa_switch_bridge_leave(ds, info); | |
483 | break; | |
685fb6a4 VD |
484 | case DSA_NOTIFIER_FDB_ADD: |
485 | err = dsa_switch_fdb_add(ds, info); | |
486 | break; | |
487 | case DSA_NOTIFIER_FDB_DEL: | |
488 | err = dsa_switch_fdb_del(ds, info); | |
489 | break; | |
18596f50 GM |
490 | case DSA_NOTIFIER_HSR_JOIN: |
491 | err = dsa_switch_hsr_join(ds, info); | |
492 | break; | |
493 | case DSA_NOTIFIER_HSR_LEAVE: | |
494 | err = dsa_switch_hsr_leave(ds, info); | |
495 | break; | |
058102a6 TW |
496 | case DSA_NOTIFIER_LAG_CHANGE: |
497 | err = dsa_switch_lag_change(ds, info); | |
498 | break; | |
499 | case DSA_NOTIFIER_LAG_JOIN: | |
500 | err = dsa_switch_lag_join(ds, info); | |
501 | break; | |
502 | case DSA_NOTIFIER_LAG_LEAVE: | |
503 | err = dsa_switch_lag_leave(ds, info); | |
504 | break; | |
8ae5bcdc VD |
505 | case DSA_NOTIFIER_MDB_ADD: |
506 | err = dsa_switch_mdb_add(ds, info); | |
507 | break; | |
508 | case DSA_NOTIFIER_MDB_DEL: | |
509 | err = dsa_switch_mdb_del(ds, info); | |
510 | break; | |
d0c627b8 VD |
511 | case DSA_NOTIFIER_VLAN_ADD: |
512 | err = dsa_switch_vlan_add(ds, info); | |
513 | break; | |
514 | case DSA_NOTIFIER_VLAN_DEL: | |
515 | err = dsa_switch_vlan_del(ds, info); | |
516 | break; | |
bfcb8132 VO |
517 | case DSA_NOTIFIER_MTU: |
518 | err = dsa_switch_mtu(ds, info); | |
519 | break; | |
53da0eba VO |
520 | case DSA_NOTIFIER_TAG_PROTO: |
521 | err = dsa_switch_change_tag_proto(ds, info); | |
522 | break; | |
c595c433 HV |
523 | case DSA_NOTIFIER_MRP_ADD: |
524 | err = dsa_switch_mrp_add(ds, info); | |
525 | break; | |
526 | case DSA_NOTIFIER_MRP_DEL: | |
527 | err = dsa_switch_mrp_del(ds, info); | |
528 | break; | |
529 | case DSA_NOTIFIER_MRP_ADD_RING_ROLE: | |
530 | err = dsa_switch_mrp_add_ring_role(ds, info); | |
531 | break; | |
532 | case DSA_NOTIFIER_MRP_DEL_RING_ROLE: | |
533 | err = dsa_switch_mrp_del_ring_role(ds, info); | |
534 | break; | |
f515f192 VD |
535 | default: |
536 | err = -EOPNOTSUPP; | |
537 | break; | |
538 | } | |
539 | ||
f515f192 VD |
540 | if (err) |
541 | dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n", | |
542 | event, err); | |
543 | ||
544 | return notifier_from_errno(err); | |
545 | } | |
546 | ||
547 | int dsa_switch_register_notifier(struct dsa_switch *ds) | |
548 | { | |
549 | ds->nb.notifier_call = dsa_switch_event; | |
550 | ||
551 | return raw_notifier_chain_register(&ds->dst->nh, &ds->nb); | |
552 | } | |
553 | ||
554 | void dsa_switch_unregister_notifier(struct dsa_switch *ds) | |
555 | { | |
556 | int err; | |
557 | ||
558 | err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb); | |
559 | if (err) | |
560 | dev_err(ds->dev, "failed to unregister notifier (%d)\n", err); | |
561 | } |