1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Microchip Sparx5 Switch driver |
3 | * |
4 | * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. |
5 | */ |
6 | |
7 | #include <linux/if_bridge.h> |
8 | #include <net/switchdev.h> |
9 | |
10 | #include "sparx5_main_regs.h" |
11 | #include "sparx5_main.h" |
12 | |
13 | static struct workqueue_struct *sparx5_owq; |
14 | |
15 | struct sparx5_switchdev_event_work { |
16 | struct work_struct work; |
17 | struct switchdev_notifier_fdb_info fdb_info; |
18 | struct net_device *dev; |
19 | struct sparx5 *sparx5; |
20 | unsigned long event; |
21 | }; |
22 | |
23 | static int sparx5_port_attr_pre_bridge_flags(struct sparx5_port *port, |
24 | struct switchdev_brport_flags flags) |
25 | { |
26 | if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD)) |
27 | return -EINVAL; |
28 | |
29 | return 0; |
30 | } |
31 | |
32 | static void sparx5_port_update_mcast_ip_flood(struct sparx5_port *port, bool flood_flag) |
33 | { |
34 | bool should_flood = flood_flag || port->is_mrouter; |
35 | int pgid; |
36 | |
37 | for (pgid = PGID_IPV4_MC_DATA; pgid <= PGID_IPV6_MC_CTRL; pgid++) |
38 | sparx5_pgid_update_mask(port, pgid, enable: should_flood); |
39 | } |
40 | |
41 | static void sparx5_port_attr_bridge_flags(struct sparx5_port *port, |
42 | struct switchdev_brport_flags flags) |
43 | { |
44 | if (flags.mask & BR_MCAST_FLOOD) { |
45 | sparx5_pgid_update_mask(port, PGID_MC_FLOOD, enable: !!(flags.val & BR_MCAST_FLOOD)); |
46 | sparx5_port_update_mcast_ip_flood(port, flood_flag: !!(flags.val & BR_MCAST_FLOOD)); |
47 | } |
48 | |
49 | if (flags.mask & BR_FLOOD) |
50 | sparx5_pgid_update_mask(port, PGID_UC_FLOOD, enable: !!(flags.val & BR_FLOOD)); |
51 | if (flags.mask & BR_BCAST_FLOOD) |
52 | sparx5_pgid_update_mask(port, PGID_BCAST, enable: !!(flags.val & BR_BCAST_FLOOD)); |
53 | } |
54 | |
55 | static void sparx5_attr_stp_state_set(struct sparx5_port *port, |
56 | u8 state) |
57 | { |
58 | struct sparx5 *sparx5 = port->sparx5; |
59 | |
60 | if (!test_bit(port->portno, sparx5->bridge_mask)) { |
61 | netdev_err(dev: port->ndev, |
62 | format: "Controlling non-bridged port %d?\n" , port->portno); |
63 | return; |
64 | } |
65 | |
66 | switch (state) { |
67 | case BR_STATE_FORWARDING: |
68 | set_bit(nr: port->portno, addr: sparx5->bridge_fwd_mask); |
69 | fallthrough; |
70 | case BR_STATE_LEARNING: |
71 | set_bit(nr: port->portno, addr: sparx5->bridge_lrn_mask); |
72 | break; |
73 | |
74 | default: |
75 | /* All other states treated as blocking */ |
76 | clear_bit(nr: port->portno, addr: sparx5->bridge_fwd_mask); |
77 | clear_bit(nr: port->portno, addr: sparx5->bridge_lrn_mask); |
78 | break; |
79 | } |
80 | |
81 | /* apply the bridge_fwd_mask to all the ports */ |
82 | sparx5_update_fwd(sparx5); |
83 | } |
84 | |
85 | static void sparx5_port_attr_ageing_set(struct sparx5_port *port, |
86 | unsigned long ageing_clock_t) |
87 | { |
88 | unsigned long ageing_jiffies = clock_t_to_jiffies(x: ageing_clock_t); |
89 | u32 ageing_time = jiffies_to_msecs(j: ageing_jiffies); |
90 | |
91 | sparx5_set_ageing(sparx5: port->sparx5, msecs: ageing_time); |
92 | } |
93 | |
94 | static void sparx5_port_attr_mrouter_set(struct sparx5_port *port, |
95 | struct net_device *orig_dev, |
96 | bool enable) |
97 | { |
98 | struct sparx5 *sparx5 = port->sparx5; |
99 | struct sparx5_mdb_entry *e; |
100 | bool flood_flag; |
101 | |
102 | if ((enable && port->is_mrouter) || (!enable && !port->is_mrouter)) |
103 | return; |
104 | |
105 | /* Add/del mrouter port on all active mdb entries in HW. |
106 | * Don't change entry port mask, since that represents |
107 | * ports that actually joined that group. |
108 | */ |
109 | mutex_lock(&sparx5->mdb_lock); |
110 | list_for_each_entry(e, &sparx5->mdb_entries, list) { |
111 | if (!test_bit(port->portno, e->port_mask) && |
112 | ether_addr_is_ip_mcast(addr: e->addr)) |
113 | sparx5_pgid_update_mask(port, pgid: e->pgid_idx, enable); |
114 | } |
115 | mutex_unlock(lock: &sparx5->mdb_lock); |
116 | |
117 | /* Enable/disable flooding depending on if port is mrouter port |
118 | * or if mcast flood is enabled. |
119 | */ |
120 | port->is_mrouter = enable; |
121 | flood_flag = br_port_flag_is_set(dev: port->ndev, BR_MCAST_FLOOD); |
122 | sparx5_port_update_mcast_ip_flood(port, flood_flag); |
123 | } |
124 | |
125 | static int sparx5_port_attr_set(struct net_device *dev, const void *ctx, |
126 | const struct switchdev_attr *attr, |
127 | struct netlink_ext_ack *extack) |
128 | { |
129 | struct sparx5_port *port = netdev_priv(dev); |
130 | |
131 | switch (attr->id) { |
132 | case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS: |
133 | return sparx5_port_attr_pre_bridge_flags(port, |
134 | flags: attr->u.brport_flags); |
135 | case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS: |
136 | sparx5_port_attr_bridge_flags(port, flags: attr->u.brport_flags); |
137 | break; |
138 | case SWITCHDEV_ATTR_ID_PORT_STP_STATE: |
139 | sparx5_attr_stp_state_set(port, state: attr->u.stp_state); |
140 | break; |
141 | case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME: |
142 | sparx5_port_attr_ageing_set(port, ageing_clock_t: attr->u.ageing_time); |
143 | break; |
144 | case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING: |
145 | /* Used PVID 1 when default_pvid is 0, to avoid |
146 | * collision with non-bridged ports. |
147 | */ |
148 | if (port->pvid == 0) |
149 | port->pvid = 1; |
150 | port->vlan_aware = attr->u.vlan_filtering; |
151 | sparx5_vlan_port_apply(sparx5: port->sparx5, port); |
152 | break; |
153 | case SWITCHDEV_ATTR_ID_PORT_MROUTER: |
154 | sparx5_port_attr_mrouter_set(port, |
155 | orig_dev: attr->orig_dev, |
156 | enable: attr->u.mrouter); |
157 | break; |
158 | default: |
159 | return -EOPNOTSUPP; |
160 | } |
161 | |
162 | return 0; |
163 | } |
164 | |
165 | static int sparx5_port_bridge_join(struct sparx5_port *port, |
166 | struct net_device *bridge, |
167 | struct netlink_ext_ack *extack) |
168 | { |
169 | struct sparx5 *sparx5 = port->sparx5; |
170 | struct net_device *ndev = port->ndev; |
171 | int err; |
172 | |
173 | if (bitmap_empty(src: sparx5->bridge_mask, SPX5_PORTS)) |
174 | /* First bridged port */ |
175 | sparx5->hw_bridge_dev = bridge; |
176 | else |
177 | if (sparx5->hw_bridge_dev != bridge) |
178 | /* This is adding the port to a second bridge, this is |
179 | * unsupported |
180 | */ |
181 | return -ENODEV; |
182 | |
183 | set_bit(nr: port->portno, addr: sparx5->bridge_mask); |
184 | |
185 | err = switchdev_bridge_port_offload(brport_dev: ndev, dev: ndev, NULL, NULL, NULL, |
186 | tx_fwd_offload: false, extack); |
187 | if (err) |
188 | goto err_switchdev_offload; |
189 | |
190 | /* Remove standalone port entry */ |
191 | sparx5_mact_forget(sparx5, mac: ndev->dev_addr, vid: 0); |
192 | |
193 | /* Port enters in bridge mode therefor don't need to copy to CPU |
194 | * frames for multicast in case the bridge is not requesting them |
195 | */ |
196 | __dev_mc_unsync(dev: ndev, unsync: sparx5_mc_unsync); |
197 | |
198 | return 0; |
199 | |
200 | err_switchdev_offload: |
201 | clear_bit(nr: port->portno, addr: sparx5->bridge_mask); |
202 | return err; |
203 | } |
204 | |
205 | static void sparx5_port_bridge_leave(struct sparx5_port *port, |
206 | struct net_device *bridge) |
207 | { |
208 | struct sparx5 *sparx5 = port->sparx5; |
209 | |
210 | switchdev_bridge_port_unoffload(brport_dev: port->ndev, NULL, NULL, NULL); |
211 | |
212 | clear_bit(nr: port->portno, addr: sparx5->bridge_mask); |
213 | if (bitmap_empty(src: sparx5->bridge_mask, SPX5_PORTS)) |
214 | sparx5->hw_bridge_dev = NULL; |
215 | |
216 | /* Clear bridge vlan settings before updating the port settings */ |
217 | port->vlan_aware = 0; |
218 | port->pvid = NULL_VID; |
219 | port->vid = NULL_VID; |
220 | |
221 | /* Forward frames to CPU */ |
222 | sparx5_mact_learn(sparx5, PGID_CPU, mac: port->ndev->dev_addr, vid: 0); |
223 | |
224 | /* Port enters in host more therefore restore mc list */ |
225 | __dev_mc_sync(dev: port->ndev, sync: sparx5_mc_sync, unsync: sparx5_mc_unsync); |
226 | } |
227 | |
228 | static int sparx5_port_changeupper(struct net_device *dev, |
229 | struct netdev_notifier_changeupper_info *info) |
230 | { |
231 | struct sparx5_port *port = netdev_priv(dev); |
232 | struct netlink_ext_ack *extack; |
233 | int err = 0; |
234 | |
235 | extack = netdev_notifier_info_to_extack(info: &info->info); |
236 | |
237 | if (netif_is_bridge_master(dev: info->upper_dev)) { |
238 | if (info->linking) |
239 | err = sparx5_port_bridge_join(port, bridge: info->upper_dev, |
240 | extack); |
241 | else |
242 | sparx5_port_bridge_leave(port, bridge: info->upper_dev); |
243 | |
244 | sparx5_vlan_port_apply(sparx5: port->sparx5, port); |
245 | } |
246 | |
247 | return err; |
248 | } |
249 | |
250 | static int sparx5_port_add_addr(struct net_device *dev, bool up) |
251 | { |
252 | struct sparx5_port *port = netdev_priv(dev); |
253 | struct sparx5 *sparx5 = port->sparx5; |
254 | u16 vid = port->pvid; |
255 | |
256 | if (up) |
257 | sparx5_mact_learn(sparx5, PGID_CPU, mac: port->ndev->dev_addr, vid); |
258 | else |
259 | sparx5_mact_forget(sparx5, mac: port->ndev->dev_addr, vid); |
260 | |
261 | return 0; |
262 | } |
263 | |
264 | static int sparx5_netdevice_port_event(struct net_device *dev, |
265 | struct notifier_block *nb, |
266 | unsigned long event, void *ptr) |
267 | { |
268 | int err = 0; |
269 | |
270 | if (!sparx5_netdevice_check(dev)) |
271 | return 0; |
272 | |
273 | switch (event) { |
274 | case NETDEV_CHANGEUPPER: |
275 | err = sparx5_port_changeupper(dev, info: ptr); |
276 | break; |
277 | case NETDEV_PRE_UP: |
278 | err = sparx5_port_add_addr(dev, up: true); |
279 | break; |
280 | case NETDEV_DOWN: |
281 | err = sparx5_port_add_addr(dev, up: false); |
282 | break; |
283 | } |
284 | |
285 | return err; |
286 | } |
287 | |
288 | static int sparx5_netdevice_event(struct notifier_block *nb, |
289 | unsigned long event, void *ptr) |
290 | { |
291 | struct net_device *dev = netdev_notifier_info_to_dev(info: ptr); |
292 | int ret = 0; |
293 | |
294 | ret = sparx5_netdevice_port_event(dev, nb, event, ptr); |
295 | |
296 | return notifier_from_errno(err: ret); |
297 | } |
298 | |
299 | static void sparx5_switchdev_bridge_fdb_event_work(struct work_struct *work) |
300 | { |
301 | struct sparx5_switchdev_event_work *switchdev_work = |
302 | container_of(work, struct sparx5_switchdev_event_work, work); |
303 | struct net_device *dev = switchdev_work->dev; |
304 | struct switchdev_notifier_fdb_info *fdb_info; |
305 | struct sparx5_port *port; |
306 | struct sparx5 *sparx5; |
307 | bool host_addr; |
308 | u16 vid; |
309 | |
310 | rtnl_lock(); |
311 | if (!sparx5_netdevice_check(dev)) { |
312 | host_addr = true; |
313 | sparx5 = switchdev_work->sparx5; |
314 | } else { |
315 | host_addr = false; |
316 | sparx5 = switchdev_work->sparx5; |
317 | port = netdev_priv(dev); |
318 | } |
319 | |
320 | fdb_info = &switchdev_work->fdb_info; |
321 | |
322 | /* Used PVID 1 when default_pvid is 0, to avoid |
323 | * collision with non-bridged ports. |
324 | */ |
325 | if (fdb_info->vid == 0) |
326 | vid = 1; |
327 | else |
328 | vid = fdb_info->vid; |
329 | |
330 | switch (switchdev_work->event) { |
331 | case SWITCHDEV_FDB_ADD_TO_DEVICE: |
332 | if (host_addr) |
333 | sparx5_add_mact_entry(sparx5, dev, PGID_CPU, |
334 | addr: fdb_info->addr, vid); |
335 | else |
336 | sparx5_add_mact_entry(sparx5, dev: port->ndev, portno: port->portno, |
337 | addr: fdb_info->addr, vid); |
338 | break; |
339 | case SWITCHDEV_FDB_DEL_TO_DEVICE: |
340 | sparx5_del_mact_entry(sparx5, addr: fdb_info->addr, vid); |
341 | break; |
342 | } |
343 | |
344 | rtnl_unlock(); |
345 | kfree(objp: switchdev_work->fdb_info.addr); |
346 | kfree(objp: switchdev_work); |
347 | dev_put(dev); |
348 | } |
349 | |
350 | static void sparx5_schedule_work(struct work_struct *work) |
351 | { |
352 | queue_work(wq: sparx5_owq, work); |
353 | } |
354 | |
355 | static int sparx5_switchdev_event(struct notifier_block *nb, |
356 | unsigned long event, void *ptr) |
357 | { |
358 | struct net_device *dev = switchdev_notifier_info_to_dev(info: ptr); |
359 | struct sparx5_switchdev_event_work *switchdev_work; |
360 | struct switchdev_notifier_fdb_info *fdb_info; |
361 | struct switchdev_notifier_info *info = ptr; |
362 | struct sparx5 *spx5; |
363 | int err; |
364 | |
365 | spx5 = container_of(nb, struct sparx5, switchdev_nb); |
366 | |
367 | switch (event) { |
368 | case SWITCHDEV_PORT_ATTR_SET: |
369 | err = switchdev_handle_port_attr_set(dev, port_attr_info: ptr, |
370 | check_cb: sparx5_netdevice_check, |
371 | set_cb: sparx5_port_attr_set); |
372 | return notifier_from_errno(err); |
373 | case SWITCHDEV_FDB_ADD_TO_DEVICE: |
374 | fallthrough; |
375 | case SWITCHDEV_FDB_DEL_TO_DEVICE: |
376 | switchdev_work = kzalloc(size: sizeof(*switchdev_work), GFP_ATOMIC); |
377 | if (!switchdev_work) |
378 | return NOTIFY_BAD; |
379 | |
380 | switchdev_work->dev = dev; |
381 | switchdev_work->event = event; |
382 | switchdev_work->sparx5 = spx5; |
383 | |
384 | fdb_info = container_of(info, |
385 | struct switchdev_notifier_fdb_info, |
386 | info); |
387 | INIT_WORK(&switchdev_work->work, |
388 | sparx5_switchdev_bridge_fdb_event_work); |
389 | memcpy(&switchdev_work->fdb_info, ptr, |
390 | sizeof(switchdev_work->fdb_info)); |
391 | switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); |
392 | if (!switchdev_work->fdb_info.addr) |
393 | goto err_addr_alloc; |
394 | |
395 | ether_addr_copy(dst: (u8 *)switchdev_work->fdb_info.addr, |
396 | src: fdb_info->addr); |
397 | dev_hold(dev); |
398 | |
399 | sparx5_schedule_work(work: &switchdev_work->work); |
400 | break; |
401 | } |
402 | |
403 | return NOTIFY_DONE; |
404 | err_addr_alloc: |
405 | kfree(objp: switchdev_work); |
406 | return NOTIFY_BAD; |
407 | } |
408 | |
409 | static int sparx5_handle_port_vlan_add(struct net_device *dev, |
410 | struct notifier_block *nb, |
411 | const struct switchdev_obj_port_vlan *v) |
412 | { |
413 | struct sparx5_port *port = netdev_priv(dev); |
414 | |
415 | if (netif_is_bridge_master(dev)) { |
416 | struct sparx5 *sparx5 = |
417 | container_of(nb, struct sparx5, |
418 | switchdev_blocking_nb); |
419 | |
420 | /* Flood broadcast to CPU */ |
421 | sparx5_mact_learn(sparx5, PGID_BCAST, mac: dev->broadcast, |
422 | vid: v->vid); |
423 | return 0; |
424 | } |
425 | |
426 | if (!sparx5_netdevice_check(dev)) |
427 | return -EOPNOTSUPP; |
428 | |
429 | return sparx5_vlan_vid_add(port, vid: v->vid, |
430 | pvid: v->flags & BRIDGE_VLAN_INFO_PVID, |
431 | untagged: v->flags & BRIDGE_VLAN_INFO_UNTAGGED); |
432 | } |
433 | |
434 | static int sparx5_alloc_mdb_entry(struct sparx5 *sparx5, |
435 | const unsigned char *addr, |
436 | u16 vid, |
437 | struct sparx5_mdb_entry **entry_out) |
438 | { |
439 | struct sparx5_mdb_entry *entry; |
440 | u16 pgid_idx; |
441 | int err; |
442 | |
443 | entry = kzalloc(size: sizeof(*entry), GFP_KERNEL); |
444 | if (!entry) |
445 | return -ENOMEM; |
446 | |
447 | err = sparx5_pgid_alloc_mcast(spx5: sparx5, idx: &pgid_idx); |
448 | if (err) { |
449 | kfree(objp: entry); |
450 | return err; |
451 | } |
452 | |
453 | memcpy(entry->addr, addr, ETH_ALEN); |
454 | entry->vid = vid; |
455 | entry->pgid_idx = pgid_idx; |
456 | |
457 | mutex_lock(&sparx5->mdb_lock); |
458 | list_add_tail(new: &entry->list, head: &sparx5->mdb_entries); |
459 | mutex_unlock(lock: &sparx5->mdb_lock); |
460 | |
461 | *entry_out = entry; |
462 | return 0; |
463 | } |
464 | |
465 | static void sparx5_free_mdb_entry(struct sparx5 *sparx5, |
466 | const unsigned char *addr, |
467 | u16 vid) |
468 | { |
469 | struct sparx5_mdb_entry *entry, *tmp; |
470 | |
471 | mutex_lock(&sparx5->mdb_lock); |
472 | list_for_each_entry_safe(entry, tmp, &sparx5->mdb_entries, list) { |
473 | if ((vid == 0 || entry->vid == vid) && |
474 | ether_addr_equal(addr1: addr, addr2: entry->addr)) { |
475 | list_del(entry: &entry->list); |
476 | |
477 | sparx5_pgid_free(spx5: sparx5, idx: entry->pgid_idx); |
478 | kfree(objp: entry); |
479 | goto out; |
480 | } |
481 | } |
482 | |
483 | out: |
484 | mutex_unlock(lock: &sparx5->mdb_lock); |
485 | } |
486 | |
487 | static struct sparx5_mdb_entry *sparx5_mdb_get_entry(struct sparx5 *sparx5, |
488 | const unsigned char *addr, |
489 | u16 vid) |
490 | { |
491 | struct sparx5_mdb_entry *e, *found = NULL; |
492 | |
493 | mutex_lock(&sparx5->mdb_lock); |
494 | list_for_each_entry(e, &sparx5->mdb_entries, list) { |
495 | if (ether_addr_equal(addr1: e->addr, addr2: addr) && e->vid == vid) { |
496 | found = e; |
497 | goto out; |
498 | } |
499 | } |
500 | |
501 | out: |
502 | mutex_unlock(lock: &sparx5->mdb_lock); |
503 | return found; |
504 | } |
505 | |
506 | static void sparx5_cpu_copy_ena(struct sparx5 *spx5, u16 pgid, bool enable) |
507 | { |
508 | spx5_rmw(ANA_AC_PGID_MISC_CFG_PGID_CPU_COPY_ENA_SET(enable), |
509 | ANA_AC_PGID_MISC_CFG_PGID_CPU_COPY_ENA, sparx5: spx5, |
510 | ANA_AC_PGID_MISC_CFG(pgid)); |
511 | } |
512 | |
513 | static int sparx5_handle_port_mdb_add(struct net_device *dev, |
514 | struct notifier_block *nb, |
515 | const struct switchdev_obj_port_mdb *v) |
516 | { |
517 | struct sparx5_port *port = netdev_priv(dev); |
518 | struct sparx5 *spx5 = port->sparx5; |
519 | struct sparx5_mdb_entry *entry; |
520 | bool is_host, is_new; |
521 | int err, i; |
522 | u16 vid; |
523 | |
524 | if (!sparx5_netdevice_check(dev)) |
525 | return -EOPNOTSUPP; |
526 | |
527 | is_host = netif_is_bridge_master(dev: v->obj.orig_dev); |
528 | |
529 | /* When VLAN unaware the vlan value is not parsed and we receive vid 0. |
530 | * Fall back to bridge vid 1. |
531 | */ |
532 | if (!br_vlan_enabled(dev: spx5->hw_bridge_dev)) |
533 | vid = 1; |
534 | else |
535 | vid = v->vid; |
536 | |
537 | is_new = false; |
538 | entry = sparx5_mdb_get_entry(sparx5: spx5, addr: v->addr, vid); |
539 | if (!entry) { |
540 | err = sparx5_alloc_mdb_entry(sparx5: spx5, addr: v->addr, vid, entry_out: &entry); |
541 | is_new = true; |
542 | if (err) |
543 | return err; |
544 | } |
545 | |
546 | mutex_lock(&spx5->mdb_lock); |
547 | |
548 | /* Add any mrouter ports to the new entry */ |
549 | if (is_new && ether_addr_is_ip_mcast(addr: v->addr)) |
550 | for (i = 0; i < SPX5_PORTS; i++) |
551 | if (spx5->ports[i] && spx5->ports[i]->is_mrouter) |
552 | sparx5_pgid_update_mask(port: spx5->ports[i], |
553 | pgid: entry->pgid_idx, |
554 | enable: true); |
555 | |
556 | if (is_host && !entry->cpu_copy) { |
557 | sparx5_cpu_copy_ena(spx5, pgid: entry->pgid_idx, enable: true); |
558 | entry->cpu_copy = true; |
559 | } else if (!is_host) { |
560 | sparx5_pgid_update_mask(port, pgid: entry->pgid_idx, enable: true); |
561 | set_bit(nr: port->portno, addr: entry->port_mask); |
562 | } |
563 | mutex_unlock(lock: &spx5->mdb_lock); |
564 | |
565 | sparx5_mact_learn(sparx5: spx5, port: entry->pgid_idx, mac: entry->addr, vid: entry->vid); |
566 | |
567 | return 0; |
568 | } |
569 | |
570 | static int sparx5_handle_port_mdb_del(struct net_device *dev, |
571 | struct notifier_block *nb, |
572 | const struct switchdev_obj_port_mdb *v) |
573 | { |
574 | struct sparx5_port *port = netdev_priv(dev); |
575 | struct sparx5 *spx5 = port->sparx5; |
576 | struct sparx5_mdb_entry *entry; |
577 | bool is_host; |
578 | u16 vid; |
579 | |
580 | if (!sparx5_netdevice_check(dev)) |
581 | return -EOPNOTSUPP; |
582 | |
583 | is_host = netif_is_bridge_master(dev: v->obj.orig_dev); |
584 | |
585 | if (!br_vlan_enabled(dev: spx5->hw_bridge_dev)) |
586 | vid = 1; |
587 | else |
588 | vid = v->vid; |
589 | |
590 | entry = sparx5_mdb_get_entry(sparx5: spx5, addr: v->addr, vid); |
591 | if (!entry) |
592 | return 0; |
593 | |
594 | mutex_lock(&spx5->mdb_lock); |
595 | if (is_host && entry->cpu_copy) { |
596 | sparx5_cpu_copy_ena(spx5, pgid: entry->pgid_idx, enable: false); |
597 | entry->cpu_copy = false; |
598 | } else if (!is_host) { |
599 | clear_bit(nr: port->portno, addr: entry->port_mask); |
600 | |
601 | /* Port not mrouter port or addr is L2 mcast, remove port from mask. */ |
602 | if (!port->is_mrouter || !ether_addr_is_ip_mcast(addr: v->addr)) |
603 | sparx5_pgid_update_mask(port, pgid: entry->pgid_idx, enable: false); |
604 | } |
605 | mutex_unlock(lock: &spx5->mdb_lock); |
606 | |
607 | if (bitmap_empty(src: entry->port_mask, SPX5_PORTS) && !entry->cpu_copy) { |
608 | /* Clear pgid in case mrouter ports exists |
609 | * that are not part of the group. |
610 | */ |
611 | sparx5_pgid_clear(spx5, pgid: entry->pgid_idx); |
612 | sparx5_mact_forget(sparx5: spx5, mac: entry->addr, vid: entry->vid); |
613 | sparx5_free_mdb_entry(sparx5: spx5, addr: entry->addr, vid: entry->vid); |
614 | } |
615 | return 0; |
616 | } |
617 | |
618 | static int sparx5_handle_port_obj_add(struct net_device *dev, |
619 | struct notifier_block *nb, |
620 | struct switchdev_notifier_port_obj_info *info) |
621 | { |
622 | const struct switchdev_obj *obj = info->obj; |
623 | int err; |
624 | |
625 | switch (obj->id) { |
626 | case SWITCHDEV_OBJ_ID_PORT_VLAN: |
627 | err = sparx5_handle_port_vlan_add(dev, nb, |
628 | SWITCHDEV_OBJ_PORT_VLAN(obj)); |
629 | break; |
630 | case SWITCHDEV_OBJ_ID_PORT_MDB: |
631 | case SWITCHDEV_OBJ_ID_HOST_MDB: |
632 | err = sparx5_handle_port_mdb_add(dev, nb, |
633 | SWITCHDEV_OBJ_PORT_MDB(obj)); |
634 | break; |
635 | default: |
636 | err = -EOPNOTSUPP; |
637 | break; |
638 | } |
639 | |
640 | info->handled = true; |
641 | return err; |
642 | } |
643 | |
644 | static int sparx5_handle_port_vlan_del(struct net_device *dev, |
645 | struct notifier_block *nb, |
646 | u16 vid) |
647 | { |
648 | struct sparx5_port *port = netdev_priv(dev); |
649 | int ret; |
650 | |
651 | /* Master bridge? */ |
652 | if (netif_is_bridge_master(dev)) { |
653 | struct sparx5 *sparx5 = |
654 | container_of(nb, struct sparx5, |
655 | switchdev_blocking_nb); |
656 | |
657 | sparx5_mact_forget(sparx5, mac: dev->broadcast, vid); |
658 | return 0; |
659 | } |
660 | |
661 | if (!sparx5_netdevice_check(dev)) |
662 | return -EOPNOTSUPP; |
663 | |
664 | ret = sparx5_vlan_vid_del(port, vid); |
665 | if (ret) |
666 | return ret; |
667 | |
668 | return 0; |
669 | } |
670 | |
671 | static int sparx5_handle_port_obj_del(struct net_device *dev, |
672 | struct notifier_block *nb, |
673 | struct switchdev_notifier_port_obj_info *info) |
674 | { |
675 | const struct switchdev_obj *obj = info->obj; |
676 | int err; |
677 | |
678 | switch (obj->id) { |
679 | case SWITCHDEV_OBJ_ID_PORT_VLAN: |
680 | err = sparx5_handle_port_vlan_del(dev, nb, |
681 | SWITCHDEV_OBJ_PORT_VLAN(obj)->vid); |
682 | break; |
683 | case SWITCHDEV_OBJ_ID_PORT_MDB: |
684 | case SWITCHDEV_OBJ_ID_HOST_MDB: |
685 | err = sparx5_handle_port_mdb_del(dev, nb, |
686 | SWITCHDEV_OBJ_PORT_MDB(obj)); |
687 | break; |
688 | default: |
689 | err = -EOPNOTSUPP; |
690 | break; |
691 | } |
692 | |
693 | info->handled = true; |
694 | return err; |
695 | } |
696 | |
697 | static int sparx5_switchdev_blocking_event(struct notifier_block *nb, |
698 | unsigned long event, |
699 | void *ptr) |
700 | { |
701 | struct net_device *dev = switchdev_notifier_info_to_dev(info: ptr); |
702 | int err; |
703 | |
704 | switch (event) { |
705 | case SWITCHDEV_PORT_OBJ_ADD: |
706 | err = sparx5_handle_port_obj_add(dev, nb, info: ptr); |
707 | return notifier_from_errno(err); |
708 | case SWITCHDEV_PORT_OBJ_DEL: |
709 | err = sparx5_handle_port_obj_del(dev, nb, info: ptr); |
710 | return notifier_from_errno(err); |
711 | case SWITCHDEV_PORT_ATTR_SET: |
712 | err = switchdev_handle_port_attr_set(dev, port_attr_info: ptr, |
713 | check_cb: sparx5_netdevice_check, |
714 | set_cb: sparx5_port_attr_set); |
715 | return notifier_from_errno(err); |
716 | } |
717 | |
718 | return NOTIFY_DONE; |
719 | } |
720 | |
721 | int sparx5_register_notifier_blocks(struct sparx5 *s5) |
722 | { |
723 | int err; |
724 | |
725 | s5->netdevice_nb.notifier_call = sparx5_netdevice_event; |
726 | err = register_netdevice_notifier(nb: &s5->netdevice_nb); |
727 | if (err) |
728 | return err; |
729 | |
730 | s5->switchdev_nb.notifier_call = sparx5_switchdev_event; |
731 | err = register_switchdev_notifier(nb: &s5->switchdev_nb); |
732 | if (err) |
733 | goto err_switchdev_nb; |
734 | |
735 | s5->switchdev_blocking_nb.notifier_call = sparx5_switchdev_blocking_event; |
736 | err = register_switchdev_blocking_notifier(nb: &s5->switchdev_blocking_nb); |
737 | if (err) |
738 | goto err_switchdev_blocking_nb; |
739 | |
740 | sparx5_owq = alloc_ordered_workqueue("sparx5_order" , 0); |
741 | if (!sparx5_owq) { |
742 | err = -ENOMEM; |
743 | goto err_switchdev_blocking_nb; |
744 | } |
745 | |
746 | return 0; |
747 | |
748 | err_switchdev_blocking_nb: |
749 | unregister_switchdev_notifier(nb: &s5->switchdev_nb); |
750 | err_switchdev_nb: |
751 | unregister_netdevice_notifier(nb: &s5->netdevice_nb); |
752 | |
753 | return err; |
754 | } |
755 | |
756 | void sparx5_unregister_notifier_blocks(struct sparx5 *s5) |
757 | { |
758 | destroy_workqueue(wq: sparx5_owq); |
759 | |
760 | unregister_switchdev_blocking_notifier(nb: &s5->switchdev_blocking_nb); |
761 | unregister_switchdev_notifier(nb: &s5->switchdev_nb); |
762 | unregister_netdevice_notifier(nb: &s5->netdevice_nb); |
763 | } |
764 | |