diff options
Diffstat (limited to 'net/switchdev/switchdev.c')
-rw-r--r-- | net/switchdev/switchdev.c | 217 |
1 files changed, 197 insertions, 20 deletions
diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c index 8c1e558db118..46568b85c333 100644 --- a/net/switchdev/switchdev.c +++ b/net/switchdev/switchdev.c | |||
@@ -1,6 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * net/switchdev/switchdev.c - Switch device API | 2 | * net/switchdev/switchdev.c - Switch device API |
3 | * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us> | 3 | * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us> |
4 | * Copyright (c) 2014-2015 Scott Feldman <sfeldma@gmail.com> | ||
4 | * | 5 | * |
5 | * This program is free software; you can redistribute it and/or modify | 6 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by | 7 | * it under the terms of the GNU General Public License as published by |
@@ -14,6 +15,7 @@ | |||
14 | #include <linux/mutex.h> | 15 | #include <linux/mutex.h> |
15 | #include <linux/notifier.h> | 16 | #include <linux/notifier.h> |
16 | #include <linux/netdevice.h> | 17 | #include <linux/netdevice.h> |
18 | #include <net/ip_fib.h> | ||
17 | #include <net/switchdev.h> | 19 | #include <net/switchdev.h> |
18 | 20 | ||
19 | /** | 21 | /** |
@@ -26,13 +28,13 @@ | |||
26 | int netdev_switch_parent_id_get(struct net_device *dev, | 28 | int netdev_switch_parent_id_get(struct net_device *dev, |
27 | struct netdev_phys_item_id *psid) | 29 | struct netdev_phys_item_id *psid) |
28 | { | 30 | { |
29 | const struct net_device_ops *ops = dev->netdev_ops; | 31 | const struct swdev_ops *ops = dev->swdev_ops; |
30 | 32 | ||
31 | if (!ops->ndo_switch_parent_id_get) | 33 | if (!ops || !ops->swdev_parent_id_get) |
32 | return -EOPNOTSUPP; | 34 | return -EOPNOTSUPP; |
33 | return ops->ndo_switch_parent_id_get(dev, psid); | 35 | return ops->swdev_parent_id_get(dev, psid); |
34 | } | 36 | } |
35 | EXPORT_SYMBOL(netdev_switch_parent_id_get); | 37 | EXPORT_SYMBOL_GPL(netdev_switch_parent_id_get); |
36 | 38 | ||
37 | /** | 39 | /** |
38 | * netdev_switch_port_stp_update - Notify switch device port of STP | 40 | * netdev_switch_port_stp_update - Notify switch device port of STP |
@@ -44,20 +46,29 @@ EXPORT_SYMBOL(netdev_switch_parent_id_get); | |||
44 | */ | 46 | */ |
45 | int netdev_switch_port_stp_update(struct net_device *dev, u8 state) | 47 | int netdev_switch_port_stp_update(struct net_device *dev, u8 state) |
46 | { | 48 | { |
47 | const struct net_device_ops *ops = dev->netdev_ops; | 49 | const struct swdev_ops *ops = dev->swdev_ops; |
50 | struct net_device *lower_dev; | ||
51 | struct list_head *iter; | ||
52 | int err = -EOPNOTSUPP; | ||
48 | 53 | ||
49 | if (!ops->ndo_switch_port_stp_update) | 54 | if (ops && ops->swdev_port_stp_update) |
50 | return -EOPNOTSUPP; | 55 | return ops->swdev_port_stp_update(dev, state); |
51 | WARN_ON(!ops->ndo_switch_parent_id_get); | 56 | |
52 | return ops->ndo_switch_port_stp_update(dev, state); | 57 | netdev_for_each_lower_dev(dev, lower_dev, iter) { |
58 | err = netdev_switch_port_stp_update(lower_dev, state); | ||
59 | if (err && err != -EOPNOTSUPP) | ||
60 | return err; | ||
61 | } | ||
62 | |||
63 | return err; | ||
53 | } | 64 | } |
54 | EXPORT_SYMBOL(netdev_switch_port_stp_update); | 65 | EXPORT_SYMBOL_GPL(netdev_switch_port_stp_update); |
55 | 66 | ||
56 | static DEFINE_MUTEX(netdev_switch_mutex); | 67 | static DEFINE_MUTEX(netdev_switch_mutex); |
57 | static RAW_NOTIFIER_HEAD(netdev_switch_notif_chain); | 68 | static RAW_NOTIFIER_HEAD(netdev_switch_notif_chain); |
58 | 69 | ||
59 | /** | 70 | /** |
60 | * register_netdev_switch_notifier - Register nofifier | 71 | * register_netdev_switch_notifier - Register notifier |
61 | * @nb: notifier_block | 72 | * @nb: notifier_block |
62 | * | 73 | * |
63 | * Register switch device notifier. This should be used by code | 74 | * Register switch device notifier. This should be used by code |
@@ -73,10 +84,10 @@ int register_netdev_switch_notifier(struct notifier_block *nb) | |||
73 | mutex_unlock(&netdev_switch_mutex); | 84 | mutex_unlock(&netdev_switch_mutex); |
74 | return err; | 85 | return err; |
75 | } | 86 | } |
76 | EXPORT_SYMBOL(register_netdev_switch_notifier); | 87 | EXPORT_SYMBOL_GPL(register_netdev_switch_notifier); |
77 | 88 | ||
78 | /** | 89 | /** |
79 | * unregister_netdev_switch_notifier - Unregister nofifier | 90 | * unregister_netdev_switch_notifier - Unregister notifier |
80 | * @nb: notifier_block | 91 | * @nb: notifier_block |
81 | * | 92 | * |
82 | * Unregister switch device notifier. | 93 | * Unregister switch device notifier. |
@@ -91,10 +102,10 @@ int unregister_netdev_switch_notifier(struct notifier_block *nb) | |||
91 | mutex_unlock(&netdev_switch_mutex); | 102 | mutex_unlock(&netdev_switch_mutex); |
92 | return err; | 103 | return err; |
93 | } | 104 | } |
94 | EXPORT_SYMBOL(unregister_netdev_switch_notifier); | 105 | EXPORT_SYMBOL_GPL(unregister_netdev_switch_notifier); |
95 | 106 | ||
96 | /** | 107 | /** |
97 | * call_netdev_switch_notifiers - Call nofifiers | 108 | * call_netdev_switch_notifiers - Call notifiers |
98 | * @val: value passed unmodified to notifier function | 109 | * @val: value passed unmodified to notifier function |
99 | * @dev: port device | 110 | * @dev: port device |
100 | * @info: notifier information data | 111 | * @info: notifier information data |
@@ -114,7 +125,7 @@ int call_netdev_switch_notifiers(unsigned long val, struct net_device *dev, | |||
114 | mutex_unlock(&netdev_switch_mutex); | 125 | mutex_unlock(&netdev_switch_mutex); |
115 | return err; | 126 | return err; |
116 | } | 127 | } |
117 | EXPORT_SYMBOL(call_netdev_switch_notifiers); | 128 | EXPORT_SYMBOL_GPL(call_netdev_switch_notifiers); |
118 | 129 | ||
119 | /** | 130 | /** |
120 | * netdev_switch_port_bridge_setlink - Notify switch device port of bridge | 131 | * netdev_switch_port_bridge_setlink - Notify switch device port of bridge |
@@ -139,7 +150,7 @@ int netdev_switch_port_bridge_setlink(struct net_device *dev, | |||
139 | 150 | ||
140 | return ops->ndo_bridge_setlink(dev, nlh, flags); | 151 | return ops->ndo_bridge_setlink(dev, nlh, flags); |
141 | } | 152 | } |
142 | EXPORT_SYMBOL(netdev_switch_port_bridge_setlink); | 153 | EXPORT_SYMBOL_GPL(netdev_switch_port_bridge_setlink); |
143 | 154 | ||
144 | /** | 155 | /** |
145 | * netdev_switch_port_bridge_dellink - Notify switch device port of bridge | 156 | * netdev_switch_port_bridge_dellink - Notify switch device port of bridge |
@@ -164,7 +175,7 @@ int netdev_switch_port_bridge_dellink(struct net_device *dev, | |||
164 | 175 | ||
165 | return ops->ndo_bridge_dellink(dev, nlh, flags); | 176 | return ops->ndo_bridge_dellink(dev, nlh, flags); |
166 | } | 177 | } |
167 | EXPORT_SYMBOL(netdev_switch_port_bridge_dellink); | 178 | EXPORT_SYMBOL_GPL(netdev_switch_port_bridge_dellink); |
168 | 179 | ||
169 | /** | 180 | /** |
170 | * ndo_dflt_netdev_switch_port_bridge_setlink - default ndo bridge setlink | 181 | * ndo_dflt_netdev_switch_port_bridge_setlink - default ndo bridge setlink |
@@ -194,7 +205,7 @@ int ndo_dflt_netdev_switch_port_bridge_setlink(struct net_device *dev, | |||
194 | 205 | ||
195 | return ret; | 206 | return ret; |
196 | } | 207 | } |
197 | EXPORT_SYMBOL(ndo_dflt_netdev_switch_port_bridge_setlink); | 208 | EXPORT_SYMBOL_GPL(ndo_dflt_netdev_switch_port_bridge_setlink); |
198 | 209 | ||
199 | /** | 210 | /** |
200 | * ndo_dflt_netdev_switch_port_bridge_dellink - default ndo bridge dellink | 211 | * ndo_dflt_netdev_switch_port_bridge_dellink - default ndo bridge dellink |
@@ -224,4 +235,170 @@ int ndo_dflt_netdev_switch_port_bridge_dellink(struct net_device *dev, | |||
224 | 235 | ||
225 | return ret; | 236 | return ret; |
226 | } | 237 | } |
227 | EXPORT_SYMBOL(ndo_dflt_netdev_switch_port_bridge_dellink); | 238 | EXPORT_SYMBOL_GPL(ndo_dflt_netdev_switch_port_bridge_dellink); |
239 | |||
240 | static struct net_device *netdev_switch_get_lowest_dev(struct net_device *dev) | ||
241 | { | ||
242 | const struct swdev_ops *ops = dev->swdev_ops; | ||
243 | struct net_device *lower_dev; | ||
244 | struct net_device *port_dev; | ||
245 | struct list_head *iter; | ||
246 | |||
247 | /* Recusively search down until we find a sw port dev. | ||
248 | * (A sw port dev supports swdev_parent_id_get). | ||
249 | */ | ||
250 | |||
251 | if (dev->features & NETIF_F_HW_SWITCH_OFFLOAD && | ||
252 | ops && ops->swdev_parent_id_get) | ||
253 | return dev; | ||
254 | |||
255 | netdev_for_each_lower_dev(dev, lower_dev, iter) { | ||
256 | port_dev = netdev_switch_get_lowest_dev(lower_dev); | ||
257 | if (port_dev) | ||
258 | return port_dev; | ||
259 | } | ||
260 | |||
261 | return NULL; | ||
262 | } | ||
263 | |||
264 | static struct net_device *netdev_switch_get_dev_by_nhs(struct fib_info *fi) | ||
265 | { | ||
266 | struct netdev_phys_item_id psid; | ||
267 | struct netdev_phys_item_id prev_psid; | ||
268 | struct net_device *dev = NULL; | ||
269 | int nhsel; | ||
270 | |||
271 | /* For this route, all nexthop devs must be on the same switch. */ | ||
272 | |||
273 | for (nhsel = 0; nhsel < fi->fib_nhs; nhsel++) { | ||
274 | const struct fib_nh *nh = &fi->fib_nh[nhsel]; | ||
275 | |||
276 | if (!nh->nh_dev) | ||
277 | return NULL; | ||
278 | |||
279 | dev = netdev_switch_get_lowest_dev(nh->nh_dev); | ||
280 | if (!dev) | ||
281 | return NULL; | ||
282 | |||
283 | if (netdev_switch_parent_id_get(dev, &psid)) | ||
284 | return NULL; | ||
285 | |||
286 | if (nhsel > 0) { | ||
287 | if (prev_psid.id_len != psid.id_len) | ||
288 | return NULL; | ||
289 | if (memcmp(prev_psid.id, psid.id, psid.id_len)) | ||
290 | return NULL; | ||
291 | } | ||
292 | |||
293 | prev_psid = psid; | ||
294 | } | ||
295 | |||
296 | return dev; | ||
297 | } | ||
298 | |||
299 | /** | ||
300 | * netdev_switch_fib_ipv4_add - Add IPv4 route entry to switch | ||
301 | * | ||
302 | * @dst: route's IPv4 destination address | ||
303 | * @dst_len: destination address length (prefix length) | ||
304 | * @fi: route FIB info structure | ||
305 | * @tos: route TOS | ||
306 | * @type: route type | ||
307 | * @nlflags: netlink flags passed in (NLM_F_*) | ||
308 | * @tb_id: route table ID | ||
309 | * | ||
310 | * Add IPv4 route entry to switch device. | ||
311 | */ | ||
312 | int netdev_switch_fib_ipv4_add(u32 dst, int dst_len, struct fib_info *fi, | ||
313 | u8 tos, u8 type, u32 nlflags, u32 tb_id) | ||
314 | { | ||
315 | struct net_device *dev; | ||
316 | const struct swdev_ops *ops; | ||
317 | int err = 0; | ||
318 | |||
319 | /* Don't offload route if using custom ip rules or if | ||
320 | * IPv4 FIB offloading has been disabled completely. | ||
321 | */ | ||
322 | |||
323 | #ifdef CONFIG_IP_MULTIPLE_TABLES | ||
324 | if (fi->fib_net->ipv4.fib_has_custom_rules) | ||
325 | return 0; | ||
326 | #endif | ||
327 | |||
328 | if (fi->fib_net->ipv4.fib_offload_disabled) | ||
329 | return 0; | ||
330 | |||
331 | dev = netdev_switch_get_dev_by_nhs(fi); | ||
332 | if (!dev) | ||
333 | return 0; | ||
334 | ops = dev->swdev_ops; | ||
335 | |||
336 | if (ops->swdev_fib_ipv4_add) { | ||
337 | err = ops->swdev_fib_ipv4_add(dev, htonl(dst), dst_len, | ||
338 | fi, tos, type, nlflags, | ||
339 | tb_id); | ||
340 | if (!err) | ||
341 | fi->fib_flags |= RTNH_F_EXTERNAL; | ||
342 | } | ||
343 | |||
344 | return err; | ||
345 | } | ||
346 | EXPORT_SYMBOL_GPL(netdev_switch_fib_ipv4_add); | ||
347 | |||
348 | /** | ||
349 | * netdev_switch_fib_ipv4_del - Delete IPv4 route entry from switch | ||
350 | * | ||
351 | * @dst: route's IPv4 destination address | ||
352 | * @dst_len: destination address length (prefix length) | ||
353 | * @fi: route FIB info structure | ||
354 | * @tos: route TOS | ||
355 | * @type: route type | ||
356 | * @tb_id: route table ID | ||
357 | * | ||
358 | * Delete IPv4 route entry from switch device. | ||
359 | */ | ||
360 | int netdev_switch_fib_ipv4_del(u32 dst, int dst_len, struct fib_info *fi, | ||
361 | u8 tos, u8 type, u32 tb_id) | ||
362 | { | ||
363 | struct net_device *dev; | ||
364 | const struct swdev_ops *ops; | ||
365 | int err = 0; | ||
366 | |||
367 | if (!(fi->fib_flags & RTNH_F_EXTERNAL)) | ||
368 | return 0; | ||
369 | |||
370 | dev = netdev_switch_get_dev_by_nhs(fi); | ||
371 | if (!dev) | ||
372 | return 0; | ||
373 | ops = dev->swdev_ops; | ||
374 | |||
375 | if (ops->swdev_fib_ipv4_del) { | ||
376 | err = ops->swdev_fib_ipv4_del(dev, htonl(dst), dst_len, | ||
377 | fi, tos, type, tb_id); | ||
378 | if (!err) | ||
379 | fi->fib_flags &= ~RTNH_F_EXTERNAL; | ||
380 | } | ||
381 | |||
382 | return err; | ||
383 | } | ||
384 | EXPORT_SYMBOL_GPL(netdev_switch_fib_ipv4_del); | ||
385 | |||
386 | /** | ||
387 | * netdev_switch_fib_ipv4_abort - Abort an IPv4 FIB operation | ||
388 | * | ||
389 | * @fi: route FIB info structure | ||
390 | */ | ||
391 | void netdev_switch_fib_ipv4_abort(struct fib_info *fi) | ||
392 | { | ||
393 | /* There was a problem installing this route to the offload | ||
394 | * device. For now, until we come up with more refined | ||
395 | * policy handling, abruptly end IPv4 fib offloading for | ||
396 | * for entire net by flushing offload device(s) of all | ||
397 | * IPv4 routes, and mark IPv4 fib offloading broken from | ||
398 | * this point forward. | ||
399 | */ | ||
400 | |||
401 | fib_flush_external(fi->fib_net); | ||
402 | fi->fib_net->ipv4.fib_offload_disabled = true; | ||
403 | } | ||
404 | EXPORT_SYMBOL_GPL(netdev_switch_fib_ipv4_abort); | ||