Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
6bc506b4 IS |
2 | #include <linux/kernel.h> |
3 | #include <linux/list.h> | |
4 | #include <linux/netdevice.h> | |
5 | #include <linux/rtnetlink.h> | |
6 | #include <linux/skbuff.h> | |
7 | #include <net/switchdev.h> | |
8 | ||
9 | #include "br_private.h" | |
10 | ||
47211192 TW |
11 | static struct static_key_false br_switchdev_tx_fwd_offload; |
12 | ||
13 | static bool nbp_switchdev_can_offload_tx_fwd(const struct net_bridge_port *p, | |
14 | const struct sk_buff *skb) | |
15 | { | |
16 | if (!static_branch_unlikely(&br_switchdev_tx_fwd_offload)) | |
17 | return false; | |
18 | ||
19 | return (p->flags & BR_TX_FWD_OFFLOAD) && | |
20 | (p->hwdom != BR_INPUT_SKB_CB(skb)->src_hwdom); | |
21 | } | |
22 | ||
23 | bool br_switchdev_frame_uses_tx_fwd_offload(struct sk_buff *skb) | |
24 | { | |
25 | if (!static_branch_unlikely(&br_switchdev_tx_fwd_offload)) | |
26 | return false; | |
27 | ||
28 | return BR_INPUT_SKB_CB(skb)->tx_fwd_offload; | |
29 | } | |
30 | ||
c5381154 VO |
31 | void br_switchdev_frame_set_offload_fwd_mark(struct sk_buff *skb) |
32 | { | |
33 | skb->offload_fwd_mark = br_switchdev_frame_uses_tx_fwd_offload(skb); | |
34 | } | |
35 | ||
47211192 TW |
36 | /* Mark the frame for TX forwarding offload if this egress port supports it */ |
37 | void nbp_switchdev_frame_mark_tx_fwd_offload(const struct net_bridge_port *p, | |
38 | struct sk_buff *skb) | |
39 | { | |
40 | if (nbp_switchdev_can_offload_tx_fwd(p, skb)) | |
41 | BR_INPUT_SKB_CB(skb)->tx_fwd_offload = true; | |
42 | } | |
43 | ||
44 | /* Lazily adds the hwdom of the egress bridge port to the bit mask of hwdoms | |
45 | * that the skb has been already forwarded to, to avoid further cloning to | |
46 | * other ports in the same hwdom by making nbp_switchdev_allowed_egress() | |
47 | * return false. | |
48 | */ | |
49 | void nbp_switchdev_frame_mark_tx_fwd_to_hwdom(const struct net_bridge_port *p, | |
50 | struct sk_buff *skb) | |
51 | { | |
52 | if (nbp_switchdev_can_offload_tx_fwd(p, skb)) | |
53 | set_bit(p->hwdom, &BR_INPUT_SKB_CB(skb)->fwd_hwdoms); | |
54 | } | |
55 | ||
6bc506b4 IS |
56 | void nbp_switchdev_frame_mark(const struct net_bridge_port *p, |
57 | struct sk_buff *skb) | |
58 | { | |
f7cf972f TW |
59 | if (p->hwdom) |
60 | BR_INPUT_SKB_CB(skb)->src_hwdom = p->hwdom; | |
6bc506b4 IS |
61 | } |
62 | ||
63 | bool nbp_switchdev_allowed_egress(const struct net_bridge_port *p, | |
64 | const struct sk_buff *skb) | |
65 | { | |
47211192 TW |
66 | struct br_input_skb_cb *cb = BR_INPUT_SKB_CB(skb); |
67 | ||
68 | return !test_bit(p->hwdom, &cb->fwd_hwdoms) && | |
69 | (!skb->offload_fwd_mark || cb->src_hwdom != p->hwdom); | |
6bc506b4 | 70 | } |
3922285d AS |
71 | |
72 | /* Flags that can be offloaded to hardware */ | |
73 | #define BR_PORT_FLAGS_HW_OFFLOAD (BR_LEARNING | BR_FLOOD | \ | |
74 | BR_MCAST_FLOOD | BR_BCAST_FLOOD) | |
75 | ||
76 | int br_switchdev_set_port_flag(struct net_bridge_port *p, | |
77 | unsigned long flags, | |
078bbb85 VO |
78 | unsigned long mask, |
79 | struct netlink_ext_ack *extack) | |
3922285d AS |
80 | { |
81 | struct switchdev_attr attr = { | |
82 | .orig_dev = p->dev, | |
3922285d | 83 | }; |
d45224d6 FF |
84 | struct switchdev_notifier_port_attr_info info = { |
85 | .attr = &attr, | |
86 | }; | |
3922285d AS |
87 | int err; |
88 | ||
304ae3bf VO |
89 | mask &= BR_PORT_FLAGS_HW_OFFLOAD; |
90 | if (!mask) | |
3922285d AS |
91 | return 0; |
92 | ||
e18f4c18 VO |
93 | attr.id = SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS; |
94 | attr.u.brport_flags.val = flags; | |
95 | attr.u.brport_flags.mask = mask; | |
304ae3bf | 96 | |
d45224d6 FF |
97 | /* We run from atomic context here */ |
98 | err = call_switchdev_notifiers(SWITCHDEV_PORT_ATTR_SET, p->dev, | |
078bbb85 | 99 | &info.info, extack); |
d45224d6 | 100 | err = notifier_to_errno(err); |
3922285d AS |
101 | if (err == -EOPNOTSUPP) |
102 | return 0; | |
3922285d | 103 | |
1ef07644 | 104 | if (err) { |
078bbb85 VO |
105 | if (extack && !extack->_msg) |
106 | NL_SET_ERR_MSG_MOD(extack, | |
107 | "bridge flag offload is not supported"); | |
3922285d AS |
108 | return -EOPNOTSUPP; |
109 | } | |
110 | ||
111 | attr.id = SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS; | |
112 | attr.flags = SWITCHDEV_F_DEFER; | |
1ef07644 | 113 | |
dcbdf135 | 114 | err = switchdev_port_attr_set(p->dev, &attr, extack); |
3922285d | 115 | if (err) { |
dcbdf135 VO |
116 | if (extack && !extack->_msg) |
117 | NL_SET_ERR_MSG_MOD(extack, | |
118 | "error setting offload flag on port"); | |
3922285d AS |
119 | return err; |
120 | } | |
121 | ||
122 | return 0; | |
123 | } | |
6b26b51b | 124 | |
6b26b51b | 125 | void |
6eb38bf8 TW |
126 | br_switchdev_fdb_notify(struct net_bridge *br, |
127 | const struct net_bridge_fdb_entry *fdb, int type) | |
6b26b51b | 128 | { |
3e19ae7c | 129 | const struct net_bridge_port *dst = READ_ONCE(fdb->dst); |
e5b4b898 TW |
130 | struct switchdev_notifier_fdb_info info = { |
131 | .addr = fdb->key.addr.addr, | |
132 | .vid = fdb->key.vlan_id, | |
133 | .added_by_user = test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags), | |
2c4eca3e | 134 | .is_local = test_bit(BR_FDB_LOCAL, &fdb->flags), |
e5b4b898 TW |
135 | .offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags), |
136 | }; | |
2e19bb35 | 137 | struct net_device *dev = (!dst || info.is_local) ? br->dev : dst->dev; |
e5b4b898 | 138 | |
6b26b51b AS |
139 | switch (type) { |
140 | case RTM_DELNEIGH: | |
e5b4b898 | 141 | call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE, |
6eb38bf8 | 142 | dev, &info.info, NULL); |
6b26b51b AS |
143 | break; |
144 | case RTM_NEWNEIGH: | |
e5b4b898 | 145 | call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE, |
6eb38bf8 | 146 | dev, &info.info, NULL); |
6b26b51b AS |
147 | break; |
148 | } | |
149 | } | |
d66e4348 | 150 | |
169327d5 PM |
151 | int br_switchdev_port_vlan_add(struct net_device *dev, u16 vid, u16 flags, |
152 | struct netlink_ext_ack *extack) | |
d66e4348 PM |
153 | { |
154 | struct switchdev_obj_port_vlan v = { | |
155 | .obj.orig_dev = dev, | |
156 | .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN, | |
157 | .flags = flags, | |
b7a9e0da | 158 | .vid = vid, |
d66e4348 PM |
159 | }; |
160 | ||
69b7320e | 161 | return switchdev_port_obj_add(dev, &v.obj, extack); |
d66e4348 PM |
162 | } |
163 | ||
164 | int br_switchdev_port_vlan_del(struct net_device *dev, u16 vid) | |
165 | { | |
166 | struct switchdev_obj_port_vlan v = { | |
167 | .obj.orig_dev = dev, | |
168 | .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN, | |
b7a9e0da | 169 | .vid = vid, |
d66e4348 PM |
170 | }; |
171 | ||
172 | return switchdev_port_obj_del(dev, &v.obj); | |
173 | } | |
85826610 TW |
174 | |
175 | static int nbp_switchdev_hwdom_set(struct net_bridge_port *joining) | |
176 | { | |
177 | struct net_bridge *br = joining->br; | |
178 | struct net_bridge_port *p; | |
179 | int hwdom; | |
180 | ||
181 | /* joining is yet to be added to the port list. */ | |
182 | list_for_each_entry(p, &br->port_list, list) { | |
2f5dc00f | 183 | if (netdev_phys_item_id_same(&joining->ppid, &p->ppid)) { |
85826610 TW |
184 | joining->hwdom = p->hwdom; |
185 | return 0; | |
186 | } | |
187 | } | |
188 | ||
189 | hwdom = find_next_zero_bit(&br->busy_hwdoms, BR_HWDOM_MAX, 1); | |
190 | if (hwdom >= BR_HWDOM_MAX) | |
191 | return -EBUSY; | |
192 | ||
193 | set_bit(hwdom, &br->busy_hwdoms); | |
194 | joining->hwdom = hwdom; | |
195 | return 0; | |
196 | } | |
197 | ||
198 | static void nbp_switchdev_hwdom_put(struct net_bridge_port *leaving) | |
199 | { | |
200 | struct net_bridge *br = leaving->br; | |
201 | struct net_bridge_port *p; | |
202 | ||
203 | /* leaving is no longer in the port list. */ | |
204 | list_for_each_entry(p, &br->port_list, list) { | |
205 | if (p->hwdom == leaving->hwdom) | |
206 | return; | |
207 | } | |
208 | ||
209 | clear_bit(leaving->hwdom, &br->busy_hwdoms); | |
210 | } | |
211 | ||
2f5dc00f VO |
212 | static int nbp_switchdev_add(struct net_bridge_port *p, |
213 | struct netdev_phys_item_id ppid, | |
47211192 | 214 | bool tx_fwd_offload, |
2f5dc00f | 215 | struct netlink_ext_ack *extack) |
85826610 | 216 | { |
47211192 TW |
217 | int err; |
218 | ||
2f5dc00f VO |
219 | if (p->offload_count) { |
220 | /* Prevent unsupported configurations such as a bridge port | |
221 | * which is a bonding interface, and the member ports are from | |
222 | * different hardware switches. | |
223 | */ | |
224 | if (!netdev_phys_item_id_same(&p->ppid, &ppid)) { | |
225 | NL_SET_ERR_MSG_MOD(extack, | |
226 | "Same bridge port cannot be offloaded by two physical switches"); | |
227 | return -EBUSY; | |
228 | } | |
85826610 | 229 | |
2f5dc00f VO |
230 | /* Tolerate drivers that call switchdev_bridge_port_offload() |
231 | * more than once for the same bridge port, such as when the | |
232 | * bridge port is an offloaded bonding/team interface. | |
233 | */ | |
234 | p->offload_count++; | |
85826610 | 235 | |
2f5dc00f | 236 | return 0; |
85826610 TW |
237 | } |
238 | ||
2f5dc00f VO |
239 | p->ppid = ppid; |
240 | p->offload_count = 1; | |
241 | ||
47211192 TW |
242 | err = nbp_switchdev_hwdom_set(p); |
243 | if (err) | |
244 | return err; | |
245 | ||
246 | if (tx_fwd_offload) { | |
247 | p->flags |= BR_TX_FWD_OFFLOAD; | |
248 | static_branch_inc(&br_switchdev_tx_fwd_offload); | |
249 | } | |
250 | ||
251 | return 0; | |
85826610 TW |
252 | } |
253 | ||
2f5dc00f | 254 | static void nbp_switchdev_del(struct net_bridge_port *p) |
85826610 | 255 | { |
2f5dc00f VO |
256 | if (WARN_ON(!p->offload_count)) |
257 | return; | |
258 | ||
259 | p->offload_count--; | |
260 | ||
261 | if (p->offload_count) | |
262 | return; | |
85826610 TW |
263 | |
264 | if (p->hwdom) | |
265 | nbp_switchdev_hwdom_put(p); | |
47211192 TW |
266 | |
267 | if (p->flags & BR_TX_FWD_OFFLOAD) { | |
268 | p->flags &= ~BR_TX_FWD_OFFLOAD; | |
269 | static_branch_dec(&br_switchdev_tx_fwd_offload); | |
270 | } | |
85826610 | 271 | } |
2f5dc00f | 272 | |
5cda5272 VO |
273 | static int br_fdb_replay_one(struct net_bridge *br, struct notifier_block *nb, |
274 | const struct net_bridge_fdb_entry *fdb, | |
275 | unsigned long action, const void *ctx) | |
276 | { | |
277 | const struct net_bridge_port *p = READ_ONCE(fdb->dst); | |
278 | struct switchdev_notifier_fdb_info item; | |
279 | int err; | |
280 | ||
281 | item.addr = fdb->key.addr.addr; | |
282 | item.vid = fdb->key.vlan_id; | |
283 | item.added_by_user = test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags); | |
284 | item.offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags); | |
285 | item.is_local = test_bit(BR_FDB_LOCAL, &fdb->flags); | |
286 | item.info.dev = (!p || item.is_local) ? br->dev : p->dev; | |
287 | item.info.ctx = ctx; | |
288 | ||
289 | err = nb->notifier_call(nb, action, &item); | |
290 | return notifier_to_errno(err); | |
291 | } | |
292 | ||
293 | static int br_fdb_replay(const struct net_device *br_dev, const void *ctx, | |
294 | bool adding, struct notifier_block *nb) | |
295 | { | |
296 | struct net_bridge_fdb_entry *fdb; | |
297 | struct net_bridge *br; | |
298 | unsigned long action; | |
299 | int err = 0; | |
300 | ||
301 | if (!nb) | |
302 | return 0; | |
303 | ||
304 | if (!netif_is_bridge_master(br_dev)) | |
305 | return -EINVAL; | |
306 | ||
307 | br = netdev_priv(br_dev); | |
308 | ||
309 | if (adding) | |
310 | action = SWITCHDEV_FDB_ADD_TO_DEVICE; | |
311 | else | |
312 | action = SWITCHDEV_FDB_DEL_TO_DEVICE; | |
313 | ||
314 | rcu_read_lock(); | |
315 | ||
316 | hlist_for_each_entry_rcu(fdb, &br->fdb_list, fdb_node) { | |
317 | err = br_fdb_replay_one(br, nb, fdb, action, ctx); | |
318 | if (err) | |
319 | break; | |
320 | } | |
321 | ||
322 | rcu_read_unlock(); | |
323 | ||
324 | return err; | |
325 | } | |
326 | ||
4e51bf44 VO |
327 | static int nbp_switchdev_sync_objs(struct net_bridge_port *p, const void *ctx, |
328 | struct notifier_block *atomic_nb, | |
329 | struct notifier_block *blocking_nb, | |
330 | struct netlink_ext_ack *extack) | |
331 | { | |
332 | struct net_device *br_dev = p->br->dev; | |
333 | struct net_device *dev = p->dev; | |
334 | int err; | |
335 | ||
336 | err = br_vlan_replay(br_dev, dev, ctx, true, blocking_nb, extack); | |
337 | if (err && err != -EOPNOTSUPP) | |
338 | return err; | |
339 | ||
340 | err = br_mdb_replay(br_dev, dev, ctx, true, blocking_nb, extack); | |
341 | if (err && err != -EOPNOTSUPP) | |
342 | return err; | |
343 | ||
b4454bc6 | 344 | err = br_fdb_replay(br_dev, ctx, true, atomic_nb); |
4e51bf44 VO |
345 | if (err && err != -EOPNOTSUPP) |
346 | return err; | |
347 | ||
348 | return 0; | |
349 | } | |
350 | ||
351 | static void nbp_switchdev_unsync_objs(struct net_bridge_port *p, | |
352 | const void *ctx, | |
353 | struct notifier_block *atomic_nb, | |
354 | struct notifier_block *blocking_nb) | |
355 | { | |
356 | struct net_device *br_dev = p->br->dev; | |
357 | struct net_device *dev = p->dev; | |
358 | ||
359 | br_vlan_replay(br_dev, dev, ctx, false, blocking_nb, NULL); | |
360 | ||
361 | br_mdb_replay(br_dev, dev, ctx, false, blocking_nb, NULL); | |
362 | ||
b4454bc6 | 363 | br_fdb_replay(br_dev, ctx, false, atomic_nb); |
4e51bf44 VO |
364 | } |
365 | ||
2f5dc00f VO |
366 | /* Let the bridge know that this port is offloaded, so that it can assign a |
367 | * switchdev hardware domain to it. | |
368 | */ | |
957e2235 VO |
369 | int br_switchdev_port_offload(struct net_bridge_port *p, |
370 | struct net_device *dev, const void *ctx, | |
371 | struct notifier_block *atomic_nb, | |
372 | struct notifier_block *blocking_nb, | |
373 | bool tx_fwd_offload, | |
374 | struct netlink_ext_ack *extack) | |
2f5dc00f VO |
375 | { |
376 | struct netdev_phys_item_id ppid; | |
2f5dc00f VO |
377 | int err; |
378 | ||
2f5dc00f VO |
379 | err = dev_get_port_parent_id(dev, &ppid, false); |
380 | if (err) | |
381 | return err; | |
382 | ||
47211192 | 383 | err = nbp_switchdev_add(p, ppid, tx_fwd_offload, extack); |
4e51bf44 VO |
384 | if (err) |
385 | return err; | |
386 | ||
387 | err = nbp_switchdev_sync_objs(p, ctx, atomic_nb, blocking_nb, extack); | |
388 | if (err) | |
389 | goto out_switchdev_del; | |
390 | ||
391 | return 0; | |
392 | ||
393 | out_switchdev_del: | |
394 | nbp_switchdev_del(p); | |
395 | ||
396 | return err; | |
2f5dc00f | 397 | } |
2f5dc00f | 398 | |
957e2235 VO |
399 | void br_switchdev_port_unoffload(struct net_bridge_port *p, const void *ctx, |
400 | struct notifier_block *atomic_nb, | |
401 | struct notifier_block *blocking_nb) | |
2f5dc00f | 402 | { |
4e51bf44 VO |
403 | nbp_switchdev_unsync_objs(p, ctx, atomic_nb, blocking_nb); |
404 | ||
2f5dc00f VO |
405 | nbp_switchdev_del(p); |
406 | } |