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 <vivien.didelot@savoirfairelinux.com> | |
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 | { | |
d0004a02 | 20 | struct dsa_port *dp; |
1faabf74 | 21 | |
d0004a02 | 22 | dsa_switch_for_each_port(dp, ds) |
1faabf74 VD |
23 | if (dp->ageing_time && dp->ageing_time < ageing_time) |
24 | ageing_time = dp->ageing_time; | |
1faabf74 VD |
25 | |
26 | return ageing_time; | |
27 | } | |
28 | ||
29 | static int dsa_switch_ageing_time(struct dsa_switch *ds, | |
30 | struct dsa_notifier_ageing_time_info *info) | |
31 | { | |
32 | unsigned int ageing_time = info->ageing_time; | |
77b61365 VO |
33 | |
34 | if (ds->ageing_time_min && ageing_time < ds->ageing_time_min) | |
35 | return -ERANGE; | |
36 | ||
37 | if (ds->ageing_time_max && ageing_time > ds->ageing_time_max) | |
38 | return -ERANGE; | |
1faabf74 VD |
39 | |
40 | /* Program the fastest ageing time in case of multiple bridges */ | |
41 | ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time); | |
42 | ||
43 | if (ds->ops->set_ageing_time) | |
44 | return ds->ops->set_ageing_time(ds, ageing_time); | |
45 | ||
46 | return 0; | |
47 | } | |
48 | ||
fac6abd5 VO |
49 | static bool dsa_port_mtu_match(struct dsa_port *dp, |
50 | struct dsa_notifier_mtu_info *info) | |
bfcb8132 | 51 | { |
fac6abd5 | 52 | if (dp->ds->index == info->sw_index && dp->index == info->port) |
88faba20 | 53 | return true; |
bfcb8132 | 54 | |
88faba20 VO |
55 | /* Do not propagate to other switches in the tree if the notifier was |
56 | * targeted for a single switch. | |
57 | */ | |
58 | if (info->targeted_match) | |
bfcb8132 VO |
59 | return false; |
60 | ||
fac6abd5 | 61 | if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp)) |
bfcb8132 VO |
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 | { | |
fac6abd5 VO |
70 | struct dsa_port *dp; |
71 | int ret; | |
bfcb8132 VO |
72 | |
73 | if (!ds->ops->port_change_mtu) | |
74 | return -EOPNOTSUPP; | |
75 | ||
fac6abd5 VO |
76 | dsa_switch_for_each_port(dp, ds) { |
77 | if (dsa_port_mtu_match(dp, info)) { | |
78 | ret = ds->ops->port_change_mtu(ds, dp->index, | |
79 | info->mtu); | |
bfcb8132 VO |
80 | if (ret) |
81 | return ret; | |
82 | } | |
83 | } | |
84 | ||
85 | return 0; | |
86 | } | |
87 | ||
04d3a4c6 VD |
88 | static int dsa_switch_bridge_join(struct dsa_switch *ds, |
89 | struct dsa_notifier_bridge_info *info) | |
90 | { | |
f66a6a69 | 91 | struct dsa_switch_tree *dst = ds->dst; |
e19cc13c | 92 | int err; |
f66a6a69 | 93 | |
67b5fb5d VO |
94 | if (dst->index == info->tree_index && ds->index == info->sw_index) { |
95 | if (!ds->ops->port_bridge_join) | |
96 | return -EOPNOTSUPP; | |
97 | ||
e19cc13c VO |
98 | err = ds->ops->port_bridge_join(ds, info->port, info->br); |
99 | if (err) | |
100 | return err; | |
101 | } | |
04d3a4c6 | 102 | |
f66a6a69 | 103 | if ((dst->index != info->tree_index || ds->index != info->sw_index) && |
e19cc13c VO |
104 | ds->ops->crosschip_bridge_join) { |
105 | err = ds->ops->crosschip_bridge_join(ds, info->tree_index, | |
106 | info->sw_index, | |
107 | info->port, info->br); | |
108 | if (err) | |
109 | return err; | |
110 | } | |
04d3a4c6 | 111 | |
e19cc13c | 112 | return dsa_tag_8021q_bridge_join(ds, info); |
04d3a4c6 VD |
113 | } |
114 | ||
115 | static int dsa_switch_bridge_leave(struct dsa_switch *ds, | |
116 | struct dsa_notifier_bridge_info *info) | |
117 | { | |
f66a6a69 | 118 | struct dsa_switch_tree *dst = ds->dst; |
89153ed6 | 119 | struct netlink_ext_ack extack = {0}; |
58adf9dc VO |
120 | bool change_vlan_filtering = false; |
121 | bool vlan_filtering; | |
d0004a02 VO |
122 | struct dsa_port *dp; |
123 | int err; | |
d371b7c9 | 124 | |
f66a6a69 | 125 | if (dst->index == info->tree_index && ds->index == info->sw_index && |
bcb9928a | 126 | ds->ops->port_bridge_leave) |
04d3a4c6 VD |
127 | ds->ops->port_bridge_leave(ds, info->port, info->br); |
128 | ||
f66a6a69 | 129 | if ((dst->index != info->tree_index || ds->index != info->sw_index) && |
bcb9928a | 130 | ds->ops->crosschip_bridge_leave) |
f66a6a69 VO |
131 | ds->ops->crosschip_bridge_leave(ds, info->tree_index, |
132 | info->sw_index, info->port, | |
40ef2c93 | 133 | info->br); |
04d3a4c6 | 134 | |
58adf9dc VO |
135 | if (ds->needs_standalone_vlan_filtering && !br_vlan_enabled(info->br)) { |
136 | change_vlan_filtering = true; | |
137 | vlan_filtering = true; | |
138 | } else if (!ds->needs_standalone_vlan_filtering && | |
139 | br_vlan_enabled(info->br)) { | |
140 | change_vlan_filtering = true; | |
141 | vlan_filtering = false; | |
142 | } | |
143 | ||
d371b7c9 VO |
144 | /* If the bridge was vlan_filtering, the bridge core doesn't trigger an |
145 | * event for changing vlan_filtering setting upon slave ports leaving | |
146 | * it. That is a good thing, because that lets us handle it and also | |
147 | * handle the case where the switch's vlan_filtering setting is global | |
148 | * (not per port). When that happens, the correct moment to trigger the | |
479dc497 VO |
149 | * vlan_filtering callback is only when the last port leaves the last |
150 | * VLAN-aware bridge. | |
d371b7c9 | 151 | */ |
58adf9dc | 152 | if (change_vlan_filtering && ds->vlan_filtering_is_global) { |
d0004a02 | 153 | dsa_switch_for_each_port(dp, ds) { |
479dc497 VO |
154 | struct net_device *bridge_dev; |
155 | ||
d0004a02 | 156 | bridge_dev = dp->bridge_dev; |
479dc497 VO |
157 | |
158 | if (bridge_dev && br_vlan_enabled(bridge_dev)) { | |
58adf9dc | 159 | change_vlan_filtering = false; |
d371b7c9 VO |
160 | break; |
161 | } | |
162 | } | |
163 | } | |
58adf9dc VO |
164 | |
165 | if (change_vlan_filtering) { | |
68bb8ea8 | 166 | err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port), |
58adf9dc | 167 | vlan_filtering, &extack); |
89153ed6 VO |
168 | if (extack._msg) |
169 | dev_err(ds->dev, "port %d: %s\n", info->port, | |
170 | extack._msg); | |
43a4b4db | 171 | if (err && err != -EOPNOTSUPP) |
d371b7c9 VO |
172 | return err; |
173 | } | |
e19cc13c VO |
174 | |
175 | return dsa_tag_8021q_bridge_leave(ds, info); | |
04d3a4c6 VD |
176 | } |
177 | ||
b8e997c4 VO |
178 | /* Matches for all upstream-facing ports (the CPU port and all upstream-facing |
179 | * DSA links) that sit between the targeted port on which the notifier was | |
180 | * emitted and its dedicated CPU port. | |
181 | */ | |
fac6abd5 VO |
182 | static bool dsa_port_host_address_match(struct dsa_port *dp, |
183 | int info_sw_index, int info_port) | |
b8e997c4 VO |
184 | { |
185 | struct dsa_port *targeted_dp, *cpu_dp; | |
186 | struct dsa_switch *targeted_ds; | |
187 | ||
fac6abd5 | 188 | targeted_ds = dsa_switch_find(dp->ds->dst->index, info_sw_index); |
b8e997c4 VO |
189 | targeted_dp = dsa_to_port(targeted_ds, info_port); |
190 | cpu_dp = targeted_dp->cpu_dp; | |
191 | ||
fac6abd5 VO |
192 | if (dsa_switch_is_upstream_of(dp->ds, targeted_ds)) |
193 | return dp->index == dsa_towards_port(dp->ds, cpu_dp->ds->index, | |
194 | cpu_dp->index); | |
b8e997c4 VO |
195 | |
196 | return false; | |
197 | } | |
198 | ||
161ca59d VO |
199 | static struct dsa_mac_addr *dsa_mac_addr_find(struct list_head *addr_list, |
200 | const unsigned char *addr, | |
201 | u16 vid) | |
202 | { | |
203 | struct dsa_mac_addr *a; | |
204 | ||
205 | list_for_each_entry(a, addr_list, list) | |
206 | if (ether_addr_equal(a->addr, addr) && a->vid == vid) | |
207 | return a; | |
208 | ||
209 | return NULL; | |
210 | } | |
211 | ||
fac6abd5 VO |
212 | static int dsa_port_do_mdb_add(struct dsa_port *dp, |
213 | const struct switchdev_obj_port_mdb *mdb) | |
161ca59d | 214 | { |
fac6abd5 | 215 | struct dsa_switch *ds = dp->ds; |
161ca59d | 216 | struct dsa_mac_addr *a; |
fac6abd5 | 217 | int port = dp->index; |
2d7e73f0 | 218 | int err; |
161ca59d VO |
219 | |
220 | /* No need to bother with refcounting for user ports */ | |
221 | if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) | |
222 | return ds->ops->port_mdb_add(ds, port, mdb); | |
223 | ||
224 | a = dsa_mac_addr_find(&dp->mdbs, mdb->addr, mdb->vid); | |
225 | if (a) { | |
226 | refcount_inc(&a->refcount); | |
2d7e73f0 | 227 | return 0; |
161ca59d VO |
228 | } |
229 | ||
230 | a = kzalloc(sizeof(*a), GFP_KERNEL); | |
2d7e73f0 DM |
231 | if (!a) |
232 | return -ENOMEM; | |
161ca59d VO |
233 | |
234 | err = ds->ops->port_mdb_add(ds, port, mdb); | |
235 | if (err) { | |
236 | kfree(a); | |
2d7e73f0 | 237 | return err; |
161ca59d VO |
238 | } |
239 | ||
240 | ether_addr_copy(a->addr, mdb->addr); | |
241 | a->vid = mdb->vid; | |
242 | refcount_set(&a->refcount, 1); | |
243 | list_add_tail(&a->list, &dp->mdbs); | |
244 | ||
2d7e73f0 | 245 | return 0; |
161ca59d VO |
246 | } |
247 | ||
fac6abd5 VO |
248 | static int dsa_port_do_mdb_del(struct dsa_port *dp, |
249 | const struct switchdev_obj_port_mdb *mdb) | |
161ca59d | 250 | { |
fac6abd5 | 251 | struct dsa_switch *ds = dp->ds; |
161ca59d | 252 | struct dsa_mac_addr *a; |
fac6abd5 | 253 | int port = dp->index; |
2d7e73f0 | 254 | int err; |
161ca59d VO |
255 | |
256 | /* No need to bother with refcounting for user ports */ | |
257 | if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) | |
258 | return ds->ops->port_mdb_del(ds, port, mdb); | |
259 | ||
260 | a = dsa_mac_addr_find(&dp->mdbs, mdb->addr, mdb->vid); | |
2d7e73f0 DM |
261 | if (!a) |
262 | return -ENOENT; | |
161ca59d VO |
263 | |
264 | if (!refcount_dec_and_test(&a->refcount)) | |
2d7e73f0 | 265 | return 0; |
161ca59d VO |
266 | |
267 | err = ds->ops->port_mdb_del(ds, port, mdb); | |
268 | if (err) { | |
269 | refcount_inc(&a->refcount); | |
2d7e73f0 | 270 | return err; |
161ca59d VO |
271 | } |
272 | ||
273 | list_del(&a->list); | |
274 | kfree(a); | |
275 | ||
2d7e73f0 | 276 | return 0; |
161ca59d VO |
277 | } |
278 | ||
fac6abd5 VO |
279 | static int dsa_port_do_fdb_add(struct dsa_port *dp, const unsigned char *addr, |
280 | u16 vid) | |
3f6e32f9 | 281 | { |
fac6abd5 | 282 | struct dsa_switch *ds = dp->ds; |
3f6e32f9 | 283 | struct dsa_mac_addr *a; |
fac6abd5 | 284 | int port = dp->index; |
2d7e73f0 | 285 | int err; |
3f6e32f9 VO |
286 | |
287 | /* No need to bother with refcounting for user ports */ | |
288 | if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) | |
289 | return ds->ops->port_fdb_add(ds, port, addr, vid); | |
290 | ||
291 | a = dsa_mac_addr_find(&dp->fdbs, addr, vid); | |
292 | if (a) { | |
293 | refcount_inc(&a->refcount); | |
2d7e73f0 | 294 | return 0; |
3f6e32f9 VO |
295 | } |
296 | ||
297 | a = kzalloc(sizeof(*a), GFP_KERNEL); | |
2d7e73f0 DM |
298 | if (!a) |
299 | return -ENOMEM; | |
3f6e32f9 VO |
300 | |
301 | err = ds->ops->port_fdb_add(ds, port, addr, vid); | |
302 | if (err) { | |
303 | kfree(a); | |
2d7e73f0 | 304 | return err; |
3f6e32f9 VO |
305 | } |
306 | ||
307 | ether_addr_copy(a->addr, addr); | |
308 | a->vid = vid; | |
309 | refcount_set(&a->refcount, 1); | |
310 | list_add_tail(&a->list, &dp->fdbs); | |
311 | ||
2d7e73f0 | 312 | return 0; |
3f6e32f9 VO |
313 | } |
314 | ||
fac6abd5 VO |
315 | static int dsa_port_do_fdb_del(struct dsa_port *dp, const unsigned char *addr, |
316 | u16 vid) | |
3f6e32f9 | 317 | { |
fac6abd5 | 318 | struct dsa_switch *ds = dp->ds; |
3f6e32f9 | 319 | struct dsa_mac_addr *a; |
fac6abd5 | 320 | int port = dp->index; |
2d7e73f0 | 321 | int err; |
3f6e32f9 VO |
322 | |
323 | /* No need to bother with refcounting for user ports */ | |
324 | if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) | |
325 | return ds->ops->port_fdb_del(ds, port, addr, vid); | |
326 | ||
327 | a = dsa_mac_addr_find(&dp->fdbs, addr, vid); | |
2d7e73f0 DM |
328 | if (!a) |
329 | return -ENOENT; | |
3f6e32f9 VO |
330 | |
331 | if (!refcount_dec_and_test(&a->refcount)) | |
2d7e73f0 | 332 | return 0; |
3f6e32f9 VO |
333 | |
334 | err = ds->ops->port_fdb_del(ds, port, addr, vid); | |
335 | if (err) { | |
336 | refcount_inc(&a->refcount); | |
2d7e73f0 | 337 | return err; |
3f6e32f9 VO |
338 | } |
339 | ||
340 | list_del(&a->list); | |
341 | kfree(a); | |
342 | ||
2d7e73f0 | 343 | return 0; |
3f6e32f9 VO |
344 | } |
345 | ||
3dc80afc VO |
346 | static int dsa_switch_host_fdb_add(struct dsa_switch *ds, |
347 | struct dsa_notifier_fdb_info *info) | |
348 | { | |
fac6abd5 | 349 | struct dsa_port *dp; |
3dc80afc | 350 | int err = 0; |
3dc80afc VO |
351 | |
352 | if (!ds->ops->port_fdb_add) | |
353 | return -EOPNOTSUPP; | |
354 | ||
fac6abd5 VO |
355 | dsa_switch_for_each_port(dp, ds) { |
356 | if (dsa_port_host_address_match(dp, info->sw_index, | |
357 | info->port)) { | |
358 | err = dsa_port_do_fdb_add(dp, info->addr, info->vid); | |
3dc80afc VO |
359 | if (err) |
360 | break; | |
361 | } | |
362 | } | |
363 | ||
364 | return err; | |
365 | } | |
366 | ||
367 | static int dsa_switch_host_fdb_del(struct dsa_switch *ds, | |
368 | struct dsa_notifier_fdb_info *info) | |
369 | { | |
fac6abd5 | 370 | struct dsa_port *dp; |
3f6e32f9 | 371 | int err = 0; |
3f6e32f9 | 372 | |
3dc80afc VO |
373 | if (!ds->ops->port_fdb_del) |
374 | return -EOPNOTSUPP; | |
375 | ||
fac6abd5 VO |
376 | dsa_switch_for_each_port(dp, ds) { |
377 | if (dsa_port_host_address_match(dp, info->sw_index, | |
378 | info->port)) { | |
379 | err = dsa_port_do_fdb_del(dp, info->addr, info->vid); | |
3f6e32f9 VO |
380 | if (err) |
381 | break; | |
382 | } | |
383 | } | |
3dc80afc | 384 | |
3f6e32f9 | 385 | return err; |
3dc80afc VO |
386 | } |
387 | ||
685fb6a4 VD |
388 | static int dsa_switch_fdb_add(struct dsa_switch *ds, |
389 | struct dsa_notifier_fdb_info *info) | |
390 | { | |
3169241f | 391 | int port = dsa_towards_port(ds, info->sw_index, info->port); |
fac6abd5 | 392 | struct dsa_port *dp = dsa_to_port(ds, port); |
685fb6a4 | 393 | |
1b6dd556 AS |
394 | if (!ds->ops->port_fdb_add) |
395 | return -EOPNOTSUPP; | |
685fb6a4 | 396 | |
fac6abd5 | 397 | return dsa_port_do_fdb_add(dp, info->addr, info->vid); |
685fb6a4 VD |
398 | } |
399 | ||
400 | static int dsa_switch_fdb_del(struct dsa_switch *ds, | |
401 | struct dsa_notifier_fdb_info *info) | |
402 | { | |
3169241f | 403 | int port = dsa_towards_port(ds, info->sw_index, info->port); |
fac6abd5 | 404 | struct dsa_port *dp = dsa_to_port(ds, port); |
685fb6a4 VD |
405 | |
406 | if (!ds->ops->port_fdb_del) | |
407 | return -EOPNOTSUPP; | |
408 | ||
fac6abd5 | 409 | return dsa_port_do_fdb_del(dp, info->addr, info->vid); |
685fb6a4 VD |
410 | } |
411 | ||
18596f50 GM |
412 | static int dsa_switch_hsr_join(struct dsa_switch *ds, |
413 | struct dsa_notifier_hsr_info *info) | |
414 | { | |
415 | if (ds->index == info->sw_index && ds->ops->port_hsr_join) | |
416 | return ds->ops->port_hsr_join(ds, info->port, info->hsr); | |
417 | ||
418 | return -EOPNOTSUPP; | |
419 | } | |
420 | ||
421 | static int dsa_switch_hsr_leave(struct dsa_switch *ds, | |
422 | struct dsa_notifier_hsr_info *info) | |
423 | { | |
424 | if (ds->index == info->sw_index && ds->ops->port_hsr_leave) | |
425 | return ds->ops->port_hsr_leave(ds, info->port, info->hsr); | |
426 | ||
427 | return -EOPNOTSUPP; | |
428 | } | |
429 | ||
058102a6 TW |
430 | static int dsa_switch_lag_change(struct dsa_switch *ds, |
431 | struct dsa_notifier_lag_info *info) | |
432 | { | |
433 | if (ds->index == info->sw_index && ds->ops->port_lag_change) | |
434 | return ds->ops->port_lag_change(ds, info->port); | |
435 | ||
436 | if (ds->index != info->sw_index && ds->ops->crosschip_lag_change) | |
437 | return ds->ops->crosschip_lag_change(ds, info->sw_index, | |
438 | info->port); | |
439 | ||
440 | return 0; | |
441 | } | |
442 | ||
443 | static int dsa_switch_lag_join(struct dsa_switch *ds, | |
444 | struct dsa_notifier_lag_info *info) | |
445 | { | |
446 | if (ds->index == info->sw_index && ds->ops->port_lag_join) | |
447 | return ds->ops->port_lag_join(ds, info->port, info->lag, | |
448 | info->info); | |
449 | ||
450 | if (ds->index != info->sw_index && ds->ops->crosschip_lag_join) | |
451 | return ds->ops->crosschip_lag_join(ds, info->sw_index, | |
452 | info->port, info->lag, | |
453 | info->info); | |
454 | ||
b71d0987 | 455 | return -EOPNOTSUPP; |
058102a6 TW |
456 | } |
457 | ||
458 | static int dsa_switch_lag_leave(struct dsa_switch *ds, | |
459 | struct dsa_notifier_lag_info *info) | |
460 | { | |
461 | if (ds->index == info->sw_index && ds->ops->port_lag_leave) | |
462 | return ds->ops->port_lag_leave(ds, info->port, info->lag); | |
463 | ||
464 | if (ds->index != info->sw_index && ds->ops->crosschip_lag_leave) | |
465 | return ds->ops->crosschip_lag_leave(ds, info->sw_index, | |
466 | info->port, info->lag); | |
467 | ||
b71d0987 | 468 | return -EOPNOTSUPP; |
058102a6 TW |
469 | } |
470 | ||
ffb68fc5 VO |
471 | static int dsa_switch_mdb_add(struct dsa_switch *ds, |
472 | struct dsa_notifier_mdb_info *info) | |
e6db98db | 473 | { |
abd49535 | 474 | int port = dsa_towards_port(ds, info->sw_index, info->port); |
fac6abd5 | 475 | struct dsa_port *dp = dsa_to_port(ds, port); |
e6db98db | 476 | |
a52b2da7 | 477 | if (!ds->ops->port_mdb_add) |
e6db98db VD |
478 | return -EOPNOTSUPP; |
479 | ||
fac6abd5 | 480 | return dsa_port_do_mdb_add(dp, info->mdb); |
8ae5bcdc VD |
481 | } |
482 | ||
483 | static int dsa_switch_mdb_del(struct dsa_switch *ds, | |
484 | struct dsa_notifier_mdb_info *info) | |
485 | { | |
161ca59d | 486 | int port = dsa_towards_port(ds, info->sw_index, info->port); |
fac6abd5 | 487 | struct dsa_port *dp = dsa_to_port(ds, port); |
161ca59d | 488 | |
8ae5bcdc VD |
489 | if (!ds->ops->port_mdb_del) |
490 | return -EOPNOTSUPP; | |
491 | ||
fac6abd5 | 492 | return dsa_port_do_mdb_del(dp, info->mdb); |
8ae5bcdc VD |
493 | } |
494 | ||
b8e997c4 VO |
495 | static int dsa_switch_host_mdb_add(struct dsa_switch *ds, |
496 | struct dsa_notifier_mdb_info *info) | |
497 | { | |
fac6abd5 | 498 | struct dsa_port *dp; |
b8e997c4 | 499 | int err = 0; |
b8e997c4 VO |
500 | |
501 | if (!ds->ops->port_mdb_add) | |
502 | return -EOPNOTSUPP; | |
503 | ||
fac6abd5 VO |
504 | dsa_switch_for_each_port(dp, ds) { |
505 | if (dsa_port_host_address_match(dp, info->sw_index, | |
506 | info->port)) { | |
507 | err = dsa_port_do_mdb_add(dp, info->mdb); | |
b8e997c4 VO |
508 | if (err) |
509 | break; | |
510 | } | |
511 | } | |
512 | ||
513 | return err; | |
514 | } | |
515 | ||
516 | static int dsa_switch_host_mdb_del(struct dsa_switch *ds, | |
517 | struct dsa_notifier_mdb_info *info) | |
518 | { | |
fac6abd5 | 519 | struct dsa_port *dp; |
161ca59d | 520 | int err = 0; |
161ca59d | 521 | |
b8e997c4 VO |
522 | if (!ds->ops->port_mdb_del) |
523 | return -EOPNOTSUPP; | |
524 | ||
fac6abd5 VO |
525 | dsa_switch_for_each_port(dp, ds) { |
526 | if (dsa_port_host_address_match(dp, info->sw_index, | |
527 | info->port)) { | |
528 | err = dsa_port_do_mdb_del(dp, info->mdb); | |
161ca59d VO |
529 | if (err) |
530 | break; | |
531 | } | |
532 | } | |
b8e997c4 | 533 | |
161ca59d | 534 | return err; |
b8e997c4 VO |
535 | } |
536 | ||
fac6abd5 VO |
537 | static bool dsa_port_vlan_match(struct dsa_port *dp, |
538 | struct dsa_notifier_vlan_info *info) | |
e65d45cc | 539 | { |
fac6abd5 | 540 | if (dp->ds->index == info->sw_index && dp->index == info->port) |
e65d45cc VD |
541 | return true; |
542 | ||
fac6abd5 | 543 | if (dsa_port_is_dsa(dp)) |
e65d45cc VD |
544 | return true; |
545 | ||
546 | return false; | |
547 | } | |
548 | ||
ffb68fc5 VO |
549 | static int dsa_switch_vlan_add(struct dsa_switch *ds, |
550 | struct dsa_notifier_vlan_info *info) | |
9c428c59 | 551 | { |
fac6abd5 VO |
552 | struct dsa_port *dp; |
553 | int err; | |
9c428c59 | 554 | |
1958d581 | 555 | if (!ds->ops->port_vlan_add) |
9c428c59 VD |
556 | return -EOPNOTSUPP; |
557 | ||
fac6abd5 VO |
558 | dsa_switch_for_each_port(dp, ds) { |
559 | if (dsa_port_vlan_match(dp, info)) { | |
560 | err = ds->ops->port_vlan_add(ds, dp->index, info->vlan, | |
31046a5f | 561 | info->extack); |
e65d45cc VD |
562 | if (err) |
563 | return err; | |
564 | } | |
9c428c59 VD |
565 | } |
566 | ||
d0c627b8 VD |
567 | return 0; |
568 | } | |
569 | ||
570 | static int dsa_switch_vlan_del(struct dsa_switch *ds, | |
571 | struct dsa_notifier_vlan_info *info) | |
572 | { | |
d0c627b8 VD |
573 | if (!ds->ops->port_vlan_del) |
574 | return -EOPNOTSUPP; | |
575 | ||
1ca4aa9c | 576 | if (ds->index == info->sw_index) |
e65d45cc | 577 | return ds->ops->port_vlan_del(ds, info->port, info->vlan); |
1ca4aa9c | 578 | |
7e1741b4 VD |
579 | /* Do not deprogram the DSA links as they may be used as conduit |
580 | * for other VLAN members in the fabric. | |
581 | */ | |
1ca4aa9c | 582 | return 0; |
d0c627b8 VD |
583 | } |
584 | ||
53da0eba VO |
585 | static int dsa_switch_change_tag_proto(struct dsa_switch *ds, |
586 | struct dsa_notifier_tag_proto_info *info) | |
587 | { | |
588 | const struct dsa_device_ops *tag_ops = info->tag_ops; | |
d0004a02 VO |
589 | struct dsa_port *dp, *cpu_dp; |
590 | int err; | |
53da0eba VO |
591 | |
592 | if (!ds->ops->change_tag_protocol) | |
593 | return -EOPNOTSUPP; | |
594 | ||
595 | ASSERT_RTNL(); | |
596 | ||
d0004a02 VO |
597 | dsa_switch_for_each_cpu_port(cpu_dp, ds) { |
598 | err = ds->ops->change_tag_protocol(ds, cpu_dp->index, | |
599 | tag_ops->proto); | |
21e0b508 TW |
600 | if (err) |
601 | return err; | |
602 | ||
d0004a02 | 603 | dsa_port_set_tag_protocol(cpu_dp, tag_ops); |
53da0eba VO |
604 | } |
605 | ||
606 | /* Now that changing the tag protocol can no longer fail, let's update | |
607 | * the remaining bits which are "duplicated for faster access", and the | |
608 | * bits that depend on the tagger, such as the MTU. | |
609 | */ | |
d0004a02 VO |
610 | dsa_switch_for_each_user_port(dp, ds) { |
611 | struct net_device *slave = dp->slave; | |
53da0eba | 612 | |
d0004a02 | 613 | dsa_slave_setup_tagger(slave); |
53da0eba | 614 | |
d0004a02 VO |
615 | /* rtnl_mutex is held in dsa_tree_change_tag_proto */ |
616 | dsa_slave_change_mtu(slave, slave->mtu); | |
53da0eba VO |
617 | } |
618 | ||
619 | return 0; | |
620 | } | |
621 | ||
c595c433 HV |
622 | static int dsa_switch_mrp_add(struct dsa_switch *ds, |
623 | struct dsa_notifier_mrp_info *info) | |
624 | { | |
c595c433 HV |
625 | if (!ds->ops->port_mrp_add) |
626 | return -EOPNOTSUPP; | |
627 | ||
f9bcdc36 VO |
628 | if (ds->index == info->sw_index) |
629 | return ds->ops->port_mrp_add(ds, info->port, info->mrp); | |
c595c433 | 630 | |
f9bcdc36 | 631 | return 0; |
c595c433 HV |
632 | } |
633 | ||
634 | static int dsa_switch_mrp_del(struct dsa_switch *ds, | |
635 | struct dsa_notifier_mrp_info *info) | |
636 | { | |
637 | if (!ds->ops->port_mrp_del) | |
638 | return -EOPNOTSUPP; | |
639 | ||
640 | if (ds->index == info->sw_index) | |
641 | return ds->ops->port_mrp_del(ds, info->port, info->mrp); | |
642 | ||
643 | return 0; | |
644 | } | |
645 | ||
c595c433 HV |
646 | static int |
647 | dsa_switch_mrp_add_ring_role(struct dsa_switch *ds, | |
648 | struct dsa_notifier_mrp_ring_role_info *info) | |
649 | { | |
c595c433 HV |
650 | if (!ds->ops->port_mrp_add) |
651 | return -EOPNOTSUPP; | |
652 | ||
f9bcdc36 VO |
653 | if (ds->index == info->sw_index) |
654 | return ds->ops->port_mrp_add_ring_role(ds, info->port, | |
655 | info->mrp); | |
c595c433 | 656 | |
f9bcdc36 | 657 | return 0; |
c595c433 HV |
658 | } |
659 | ||
660 | static int | |
661 | dsa_switch_mrp_del_ring_role(struct dsa_switch *ds, | |
662 | struct dsa_notifier_mrp_ring_role_info *info) | |
663 | { | |
664 | if (!ds->ops->port_mrp_del) | |
665 | return -EOPNOTSUPP; | |
666 | ||
667 | if (ds->index == info->sw_index) | |
668 | return ds->ops->port_mrp_del_ring_role(ds, info->port, | |
669 | info->mrp); | |
670 | ||
671 | return 0; | |
672 | } | |
673 | ||
f515f192 VD |
674 | static int dsa_switch_event(struct notifier_block *nb, |
675 | unsigned long event, void *info) | |
676 | { | |
677 | struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb); | |
678 | int err; | |
679 | ||
680 | switch (event) { | |
1faabf74 VD |
681 | case DSA_NOTIFIER_AGEING_TIME: |
682 | err = dsa_switch_ageing_time(ds, info); | |
683 | break; | |
04d3a4c6 VD |
684 | case DSA_NOTIFIER_BRIDGE_JOIN: |
685 | err = dsa_switch_bridge_join(ds, info); | |
686 | break; | |
687 | case DSA_NOTIFIER_BRIDGE_LEAVE: | |
688 | err = dsa_switch_bridge_leave(ds, info); | |
689 | break; | |
685fb6a4 VD |
690 | case DSA_NOTIFIER_FDB_ADD: |
691 | err = dsa_switch_fdb_add(ds, info); | |
692 | break; | |
693 | case DSA_NOTIFIER_FDB_DEL: | |
694 | err = dsa_switch_fdb_del(ds, info); | |
695 | break; | |
3dc80afc VO |
696 | case DSA_NOTIFIER_HOST_FDB_ADD: |
697 | err = dsa_switch_host_fdb_add(ds, info); | |
698 | break; | |
699 | case DSA_NOTIFIER_HOST_FDB_DEL: | |
700 | err = dsa_switch_host_fdb_del(ds, info); | |
701 | break; | |
18596f50 GM |
702 | case DSA_NOTIFIER_HSR_JOIN: |
703 | err = dsa_switch_hsr_join(ds, info); | |
704 | break; | |
705 | case DSA_NOTIFIER_HSR_LEAVE: | |
706 | err = dsa_switch_hsr_leave(ds, info); | |
707 | break; | |
058102a6 TW |
708 | case DSA_NOTIFIER_LAG_CHANGE: |
709 | err = dsa_switch_lag_change(ds, info); | |
710 | break; | |
711 | case DSA_NOTIFIER_LAG_JOIN: | |
712 | err = dsa_switch_lag_join(ds, info); | |
713 | break; | |
714 | case DSA_NOTIFIER_LAG_LEAVE: | |
715 | err = dsa_switch_lag_leave(ds, info); | |
716 | break; | |
8ae5bcdc VD |
717 | case DSA_NOTIFIER_MDB_ADD: |
718 | err = dsa_switch_mdb_add(ds, info); | |
719 | break; | |
720 | case DSA_NOTIFIER_MDB_DEL: | |
721 | err = dsa_switch_mdb_del(ds, info); | |
722 | break; | |
b8e997c4 VO |
723 | case DSA_NOTIFIER_HOST_MDB_ADD: |
724 | err = dsa_switch_host_mdb_add(ds, info); | |
725 | break; | |
726 | case DSA_NOTIFIER_HOST_MDB_DEL: | |
727 | err = dsa_switch_host_mdb_del(ds, info); | |
728 | break; | |
d0c627b8 VD |
729 | case DSA_NOTIFIER_VLAN_ADD: |
730 | err = dsa_switch_vlan_add(ds, info); | |
731 | break; | |
732 | case DSA_NOTIFIER_VLAN_DEL: | |
733 | err = dsa_switch_vlan_del(ds, info); | |
734 | break; | |
bfcb8132 VO |
735 | case DSA_NOTIFIER_MTU: |
736 | err = dsa_switch_mtu(ds, info); | |
737 | break; | |
53da0eba VO |
738 | case DSA_NOTIFIER_TAG_PROTO: |
739 | err = dsa_switch_change_tag_proto(ds, info); | |
740 | break; | |
c595c433 HV |
741 | case DSA_NOTIFIER_MRP_ADD: |
742 | err = dsa_switch_mrp_add(ds, info); | |
743 | break; | |
744 | case DSA_NOTIFIER_MRP_DEL: | |
745 | err = dsa_switch_mrp_del(ds, info); | |
746 | break; | |
747 | case DSA_NOTIFIER_MRP_ADD_RING_ROLE: | |
748 | err = dsa_switch_mrp_add_ring_role(ds, info); | |
749 | break; | |
750 | case DSA_NOTIFIER_MRP_DEL_RING_ROLE: | |
751 | err = dsa_switch_mrp_del_ring_role(ds, info); | |
752 | break; | |
c64b9c05 VO |
753 | case DSA_NOTIFIER_TAG_8021Q_VLAN_ADD: |
754 | err = dsa_switch_tag_8021q_vlan_add(ds, info); | |
755 | break; | |
756 | case DSA_NOTIFIER_TAG_8021Q_VLAN_DEL: | |
757 | err = dsa_switch_tag_8021q_vlan_del(ds, info); | |
758 | break; | |
f515f192 VD |
759 | default: |
760 | err = -EOPNOTSUPP; | |
761 | break; | |
762 | } | |
763 | ||
f515f192 VD |
764 | if (err) |
765 | dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n", | |
766 | event, err); | |
767 | ||
768 | return notifier_from_errno(err); | |
769 | } | |
770 | ||
771 | int dsa_switch_register_notifier(struct dsa_switch *ds) | |
772 | { | |
773 | ds->nb.notifier_call = dsa_switch_event; | |
774 | ||
775 | return raw_notifier_chain_register(&ds->dst->nh, &ds->nb); | |
776 | } | |
777 | ||
778 | void dsa_switch_unregister_notifier(struct dsa_switch *ds) | |
779 | { | |
780 | int err; | |
781 | ||
782 | err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb); | |
783 | if (err) | |
784 | dev_err(ds->dev, "failed to unregister notifier (%d)\n", err); | |
785 | } |