Commit | Line | Data |
---|---|---|
c4c8f39a JK |
1 | // SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) |
2 | /* | |
3 | * Copyright (C) 2018 Netronome Systems, Inc. | |
4 | * | |
5 | * This software is dual licensed under the GNU General License Version 2, | |
6 | * June 1991 as shown in the file COPYING in the top-level directory of this | |
7 | * source tree or the BSD 2-Clause License provided below. You have the | |
8 | * option to license this software under the complete terms of either license. | |
9 | * | |
10 | * The BSD 2-Clause License: | |
11 | * | |
12 | * Redistribution and use in source and binary forms, with or | |
13 | * without modification, are permitted provided that the following | |
14 | * conditions are met: | |
15 | * | |
16 | * 1. Redistributions of source code must retain the above | |
17 | * copyright notice, this list of conditions and the following | |
18 | * disclaimer. | |
19 | * | |
20 | * 2. Redistributions in binary form must reproduce the above | |
21 | * copyright notice, this list of conditions and the following | |
22 | * disclaimer in the documentation and/or other materials | |
23 | * provided with the distribution. | |
24 | * | |
25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
29 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
30 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
31 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
32 | * SOFTWARE. | |
33 | */ | |
34 | ||
d05d902e | 35 | #include <linux/bitfield.h> |
cc54dc28 | 36 | #include <linux/etherdevice.h> |
d05d902e JK |
37 | #include <linux/lockdep.h> |
38 | #include <linux/netdevice.h> | |
39 | #include <linux/rcupdate.h> | |
40 | #include <linux/slab.h> | |
8c8e6406 JK |
41 | #include <net/pkt_cls.h> |
42 | #include <net/pkt_sched.h> | |
cc54dc28 JK |
43 | |
44 | #include "../nfpcore/nfp.h" | |
c4c8f39a JK |
45 | #include "../nfpcore/nfp_cpp.h" |
46 | #include "../nfpcore/nfp_nsp.h" | |
47 | #include "../nfp_app.h" | |
48 | #include "../nfp_main.h" | |
cc54dc28 | 49 | #include "../nfp_net.h" |
d05d902e | 50 | #include "../nfp_net_repr.h" |
cc54dc28 | 51 | #include "../nfp_port.h" |
c4c8f39a JK |
52 | #include "main.h" |
53 | ||
d05d902e JK |
54 | static u32 nfp_abm_portid(enum nfp_repr_type rtype, unsigned int id) |
55 | { | |
56 | return FIELD_PREP(NFP_ABM_PORTID_TYPE, rtype) | | |
57 | FIELD_PREP(NFP_ABM_PORTID_ID, id); | |
58 | } | |
59 | ||
8c8e6406 JK |
60 | static void |
61 | nfp_abm_red_destroy(struct net_device *netdev, struct nfp_abm_link *alink, | |
62 | u32 handle) | |
63 | { | |
64 | struct nfp_port *port = nfp_port_from_netdev(netdev); | |
65 | ||
66 | if (handle != alink->qdiscs[0].handle) | |
67 | return; | |
68 | ||
69 | alink->qdiscs[0].handle = TC_H_UNSPEC; | |
70 | port->tc_offload_cnt = 0; | |
71 | nfp_abm_ctrl_set_all_q_lvls(alink, ~0); | |
72 | } | |
73 | ||
74 | static int | |
75 | nfp_abm_red_replace(struct net_device *netdev, struct nfp_abm_link *alink, | |
76 | struct tc_red_qopt_offload *opt) | |
77 | { | |
78 | struct nfp_port *port = nfp_port_from_netdev(netdev); | |
79 | int err; | |
80 | ||
81 | if (opt->set.min != opt->set.max || !opt->set.is_ecn) { | |
82 | nfp_warn(alink->abm->app->cpp, | |
83 | "RED offload failed - unsupported parameters\n"); | |
84 | err = -EINVAL; | |
85 | goto err_destroy; | |
86 | } | |
87 | err = nfp_abm_ctrl_set_all_q_lvls(alink, opt->set.min); | |
88 | if (err) | |
89 | goto err_destroy; | |
90 | ||
91 | alink->qdiscs[0].handle = opt->handle; | |
92 | port->tc_offload_cnt = 1; | |
93 | ||
94 | return 0; | |
95 | err_destroy: | |
96 | if (alink->qdiscs[0].handle != TC_H_UNSPEC) | |
97 | nfp_abm_red_destroy(netdev, alink, alink->qdiscs[0].handle); | |
98 | return err; | |
99 | } | |
100 | ||
101 | static int | |
102 | nfp_abm_setup_tc_red(struct net_device *netdev, struct nfp_abm_link *alink, | |
103 | struct tc_red_qopt_offload *opt) | |
104 | { | |
105 | if (opt->parent != TC_H_ROOT) | |
106 | return -EOPNOTSUPP; | |
107 | ||
108 | switch (opt->command) { | |
109 | case TC_RED_REPLACE: | |
110 | return nfp_abm_red_replace(netdev, alink, opt); | |
111 | case TC_RED_DESTROY: | |
112 | nfp_abm_red_destroy(netdev, alink, opt->handle); | |
113 | return 0; | |
114 | default: | |
115 | return -EOPNOTSUPP; | |
116 | } | |
117 | } | |
118 | ||
119 | static int | |
120 | nfp_abm_setup_tc(struct nfp_app *app, struct net_device *netdev, | |
121 | enum tc_setup_type type, void *type_data) | |
122 | { | |
123 | struct nfp_repr *repr = netdev_priv(netdev); | |
124 | struct nfp_port *port; | |
125 | ||
126 | port = nfp_port_from_netdev(netdev); | |
127 | if (!port || port->type != NFP_PORT_PF_PORT) | |
128 | return -EOPNOTSUPP; | |
129 | ||
130 | switch (type) { | |
131 | case TC_SETUP_QDISC_RED: | |
132 | return nfp_abm_setup_tc_red(netdev, repr->app_priv, type_data); | |
133 | default: | |
134 | return -EOPNOTSUPP; | |
135 | } | |
136 | } | |
137 | ||
d05d902e JK |
138 | static struct net_device *nfp_abm_repr_get(struct nfp_app *app, u32 port_id) |
139 | { | |
140 | enum nfp_repr_type rtype; | |
141 | struct nfp_reprs *reprs; | |
142 | u8 port; | |
143 | ||
144 | rtype = FIELD_GET(NFP_ABM_PORTID_TYPE, port_id); | |
145 | port = FIELD_GET(NFP_ABM_PORTID_ID, port_id); | |
146 | ||
147 | reprs = rcu_dereference(app->reprs[rtype]); | |
148 | if (!reprs) | |
149 | return NULL; | |
150 | ||
151 | if (port >= reprs->num_reprs) | |
152 | return NULL; | |
153 | ||
154 | return rcu_dereference(reprs->reprs[port]); | |
155 | } | |
156 | ||
157 | static int | |
158 | nfp_abm_spawn_repr(struct nfp_app *app, struct nfp_abm_link *alink, | |
159 | enum nfp_port_type ptype) | |
160 | { | |
161 | struct net_device *netdev; | |
162 | enum nfp_repr_type rtype; | |
163 | struct nfp_reprs *reprs; | |
164 | struct nfp_repr *repr; | |
165 | struct nfp_port *port; | |
166 | int err; | |
167 | ||
168 | if (ptype == NFP_PORT_PHYS_PORT) | |
169 | rtype = NFP_REPR_TYPE_PHYS_PORT; | |
170 | else | |
171 | rtype = NFP_REPR_TYPE_PF; | |
172 | ||
173 | netdev = nfp_repr_alloc(app); | |
174 | if (!netdev) | |
175 | return -ENOMEM; | |
176 | repr = netdev_priv(netdev); | |
177 | repr->app_priv = alink; | |
178 | ||
179 | port = nfp_port_alloc(app, ptype, netdev); | |
180 | if (IS_ERR(port)) { | |
181 | err = PTR_ERR(port); | |
182 | goto err_free_repr; | |
183 | } | |
184 | ||
185 | if (ptype == NFP_PORT_PHYS_PORT) { | |
1f700367 | 186 | port->eth_forced = true; |
d05d902e JK |
187 | err = nfp_port_init_phy_port(app->pf, app, port, alink->id); |
188 | if (err) | |
189 | goto err_free_port; | |
190 | } else { | |
191 | port->pf_id = alink->abm->pf_id; | |
290f54db JK |
192 | port->pf_split = app->pf->max_data_vnics > 1; |
193 | port->pf_split_id = alink->id; | |
d05d902e JK |
194 | port->vnic = alink->vnic->dp.ctrl_bar; |
195 | } | |
196 | ||
197 | SET_NETDEV_DEV(netdev, &alink->vnic->pdev->dev); | |
198 | eth_hw_addr_random(netdev); | |
199 | ||
200 | err = nfp_repr_init(app, netdev, nfp_abm_portid(rtype, alink->id), | |
201 | port, alink->vnic->dp.netdev); | |
202 | if (err) | |
203 | goto err_free_port; | |
204 | ||
205 | reprs = nfp_reprs_get_locked(app, rtype); | |
206 | WARN(nfp_repr_get_locked(app, reprs, alink->id), "duplicate repr"); | |
207 | rcu_assign_pointer(reprs->reprs[alink->id], netdev); | |
208 | ||
209 | nfp_info(app->cpp, "%s Port %d Representor(%s) created\n", | |
210 | ptype == NFP_PORT_PF_PORT ? "PCIe" : "Phys", | |
211 | alink->id, netdev->name); | |
212 | ||
213 | return 0; | |
214 | ||
215 | err_free_port: | |
216 | nfp_port_free(port); | |
217 | err_free_repr: | |
218 | nfp_repr_free(netdev); | |
219 | return err; | |
220 | } | |
221 | ||
222 | static void | |
223 | nfp_abm_kill_repr(struct nfp_app *app, struct nfp_abm_link *alink, | |
224 | enum nfp_repr_type rtype) | |
225 | { | |
226 | struct net_device *netdev; | |
227 | struct nfp_reprs *reprs; | |
228 | ||
229 | reprs = nfp_reprs_get_locked(app, rtype); | |
230 | netdev = nfp_repr_get_locked(app, reprs, alink->id); | |
231 | if (!netdev) | |
232 | return; | |
233 | rcu_assign_pointer(reprs->reprs[alink->id], NULL); | |
234 | synchronize_rcu(); | |
235 | /* Cast to make sure nfp_repr_clean_and_free() takes a nfp_repr */ | |
236 | nfp_repr_clean_and_free((struct nfp_repr *)netdev_priv(netdev)); | |
237 | } | |
238 | ||
239 | static void | |
240 | nfp_abm_kill_reprs(struct nfp_abm *abm, struct nfp_abm_link *alink) | |
241 | { | |
242 | nfp_abm_kill_repr(abm->app, alink, NFP_REPR_TYPE_PF); | |
243 | nfp_abm_kill_repr(abm->app, alink, NFP_REPR_TYPE_PHYS_PORT); | |
244 | } | |
245 | ||
246 | static void nfp_abm_kill_reprs_all(struct nfp_abm *abm) | |
247 | { | |
248 | struct nfp_pf *pf = abm->app->pf; | |
249 | struct nfp_net *nn; | |
250 | ||
251 | list_for_each_entry(nn, &pf->vnics, vnic_list) | |
252 | nfp_abm_kill_reprs(abm, (struct nfp_abm_link *)nn->app_priv); | |
253 | } | |
254 | ||
255 | static enum devlink_eswitch_mode nfp_abm_eswitch_mode_get(struct nfp_app *app) | |
256 | { | |
257 | struct nfp_abm *abm = app->priv; | |
258 | ||
259 | return abm->eswitch_mode; | |
260 | } | |
261 | ||
262 | static int nfp_abm_eswitch_set_legacy(struct nfp_abm *abm) | |
263 | { | |
264 | nfp_abm_kill_reprs_all(abm); | |
055ee0d6 | 265 | nfp_abm_ctrl_qm_disable(abm); |
d05d902e JK |
266 | |
267 | abm->eswitch_mode = DEVLINK_ESWITCH_MODE_LEGACY; | |
268 | return 0; | |
269 | } | |
270 | ||
271 | static void nfp_abm_eswitch_clean_up(struct nfp_abm *abm) | |
272 | { | |
273 | if (abm->eswitch_mode != DEVLINK_ESWITCH_MODE_LEGACY) | |
274 | WARN_ON(nfp_abm_eswitch_set_legacy(abm)); | |
275 | } | |
276 | ||
277 | static int nfp_abm_eswitch_set_switchdev(struct nfp_abm *abm) | |
278 | { | |
279 | struct nfp_app *app = abm->app; | |
280 | struct nfp_pf *pf = app->pf; | |
281 | struct nfp_net *nn; | |
282 | int err; | |
283 | ||
055ee0d6 JK |
284 | err = nfp_abm_ctrl_qm_enable(abm); |
285 | if (err) | |
286 | return err; | |
287 | ||
d05d902e JK |
288 | list_for_each_entry(nn, &pf->vnics, vnic_list) { |
289 | struct nfp_abm_link *alink = nn->app_priv; | |
290 | ||
291 | err = nfp_abm_spawn_repr(app, alink, NFP_PORT_PHYS_PORT); | |
292 | if (err) | |
293 | goto err_kill_all_reprs; | |
294 | ||
295 | err = nfp_abm_spawn_repr(app, alink, NFP_PORT_PF_PORT); | |
296 | if (err) | |
297 | goto err_kill_all_reprs; | |
298 | } | |
299 | ||
300 | abm->eswitch_mode = DEVLINK_ESWITCH_MODE_SWITCHDEV; | |
301 | return 0; | |
302 | ||
303 | err_kill_all_reprs: | |
304 | nfp_abm_kill_reprs_all(abm); | |
055ee0d6 | 305 | nfp_abm_ctrl_qm_disable(abm); |
d05d902e JK |
306 | return err; |
307 | } | |
308 | ||
309 | static int nfp_abm_eswitch_mode_set(struct nfp_app *app, u16 mode) | |
310 | { | |
311 | struct nfp_abm *abm = app->priv; | |
312 | ||
313 | if (abm->eswitch_mode == mode) | |
314 | return 0; | |
315 | ||
316 | switch (mode) { | |
317 | case DEVLINK_ESWITCH_MODE_LEGACY: | |
318 | return nfp_abm_eswitch_set_legacy(abm); | |
319 | case DEVLINK_ESWITCH_MODE_SWITCHDEV: | |
320 | return nfp_abm_eswitch_set_switchdev(abm); | |
321 | default: | |
322 | return -EINVAL; | |
323 | } | |
324 | } | |
325 | ||
cc54dc28 JK |
326 | static void |
327 | nfp_abm_vnic_set_mac(struct nfp_pf *pf, struct nfp_abm *abm, struct nfp_net *nn, | |
328 | unsigned int id) | |
329 | { | |
330 | struct nfp_eth_table_port *eth_port = &pf->eth_tbl->ports[id]; | |
331 | u8 mac_addr[ETH_ALEN]; | |
332 | const char *mac_str; | |
333 | char name[32]; | |
334 | ||
335 | if (id > pf->eth_tbl->count) { | |
336 | nfp_warn(pf->cpp, "No entry for persistent MAC address\n"); | |
337 | eth_hw_addr_random(nn->dp.netdev); | |
338 | return; | |
339 | } | |
340 | ||
341 | snprintf(name, sizeof(name), "eth%u.mac.pf%u", | |
342 | eth_port->eth_index, abm->pf_id); | |
343 | ||
344 | mac_str = nfp_hwinfo_lookup(pf->hwinfo, name); | |
345 | if (!mac_str) { | |
346 | nfp_warn(pf->cpp, "Can't lookup persistent MAC address (%s)\n", | |
347 | name); | |
348 | eth_hw_addr_random(nn->dp.netdev); | |
349 | return; | |
350 | } | |
351 | ||
352 | if (sscanf(mac_str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", | |
353 | &mac_addr[0], &mac_addr[1], &mac_addr[2], | |
354 | &mac_addr[3], &mac_addr[4], &mac_addr[5]) != 6) { | |
355 | nfp_warn(pf->cpp, "Can't parse persistent MAC address (%s)\n", | |
356 | mac_str); | |
357 | eth_hw_addr_random(nn->dp.netdev); | |
358 | return; | |
359 | } | |
360 | ||
361 | ether_addr_copy(nn->dp.netdev->dev_addr, mac_addr); | |
362 | ether_addr_copy(nn->dp.netdev->perm_addr, mac_addr); | |
363 | } | |
364 | ||
365 | static int | |
366 | nfp_abm_vnic_alloc(struct nfp_app *app, struct nfp_net *nn, unsigned int id) | |
367 | { | |
1f700367 | 368 | struct nfp_eth_table_port *eth_port = &app->pf->eth_tbl->ports[id]; |
cc54dc28 JK |
369 | struct nfp_abm *abm = app->priv; |
370 | struct nfp_abm_link *alink; | |
1f700367 | 371 | int err; |
cc54dc28 JK |
372 | |
373 | alink = kzalloc(sizeof(*alink), GFP_KERNEL); | |
374 | if (!alink) | |
375 | return -ENOMEM; | |
376 | nn->app_priv = alink; | |
377 | alink->abm = abm; | |
378 | alink->vnic = nn; | |
379 | alink->id = id; | |
380 | ||
1f700367 JK |
381 | /* This is a multi-host app, make sure MAC/PHY is up, but don't |
382 | * make the MAC/PHY state follow the state of any of the ports. | |
383 | */ | |
384 | err = nfp_eth_set_configured(app->cpp, eth_port->index, true); | |
385 | if (err < 0) | |
386 | goto err_free_alink; | |
387 | ||
d05d902e | 388 | netif_keep_dst(nn->dp.netdev); |
cc54dc28 JK |
389 | |
390 | nfp_abm_vnic_set_mac(app->pf, abm, nn, id); | |
391 | nfp_abm_ctrl_read_params(alink); | |
392 | ||
393 | return 0; | |
1f700367 JK |
394 | |
395 | err_free_alink: | |
396 | kfree(alink); | |
397 | return err; | |
cc54dc28 JK |
398 | } |
399 | ||
400 | static void nfp_abm_vnic_free(struct nfp_app *app, struct nfp_net *nn) | |
401 | { | |
402 | struct nfp_abm_link *alink = nn->app_priv; | |
403 | ||
d05d902e | 404 | nfp_abm_kill_reprs(alink->abm, alink); |
cc54dc28 JK |
405 | kfree(alink); |
406 | } | |
407 | ||
c4c8f39a JK |
408 | static int nfp_abm_init(struct nfp_app *app) |
409 | { | |
410 | struct nfp_pf *pf = app->pf; | |
d05d902e | 411 | struct nfp_reprs *reprs; |
c4c8f39a | 412 | struct nfp_abm *abm; |
cc54dc28 | 413 | int err; |
c4c8f39a JK |
414 | |
415 | if (!pf->eth_tbl) { | |
416 | nfp_err(pf->cpp, "ABM NIC requires ETH table\n"); | |
417 | return -EINVAL; | |
418 | } | |
419 | if (pf->max_data_vnics != pf->eth_tbl->count) { | |
420 | nfp_err(pf->cpp, "ETH entries don't match vNICs (%d vs %d)\n", | |
421 | pf->max_data_vnics, pf->eth_tbl->count); | |
422 | return -EINVAL; | |
423 | } | |
424 | if (!pf->mac_stats_bar) { | |
425 | nfp_warn(app->cpp, "ABM NIC requires mac_stats symbol\n"); | |
426 | return -EINVAL; | |
427 | } | |
428 | ||
429 | abm = kzalloc(sizeof(*abm), GFP_KERNEL); | |
430 | if (!abm) | |
431 | return -ENOMEM; | |
432 | app->priv = abm; | |
433 | abm->app = app; | |
434 | ||
cc54dc28 JK |
435 | err = nfp_abm_ctrl_find_addrs(abm); |
436 | if (err) | |
437 | goto err_free_abm; | |
438 | ||
055ee0d6 JK |
439 | /* We start in legacy mode, make sure advanced queuing is disabled */ |
440 | err = nfp_abm_ctrl_qm_disable(abm); | |
441 | if (err) | |
442 | goto err_free_abm; | |
443 | ||
d05d902e JK |
444 | err = -ENOMEM; |
445 | reprs = nfp_reprs_alloc(pf->max_data_vnics); | |
446 | if (!reprs) | |
447 | goto err_free_abm; | |
448 | RCU_INIT_POINTER(app->reprs[NFP_REPR_TYPE_PHYS_PORT], reprs); | |
449 | ||
450 | reprs = nfp_reprs_alloc(pf->max_data_vnics); | |
451 | if (!reprs) | |
452 | goto err_free_phys; | |
453 | RCU_INIT_POINTER(app->reprs[NFP_REPR_TYPE_PF], reprs); | |
454 | ||
c4c8f39a | 455 | return 0; |
cc54dc28 | 456 | |
d05d902e JK |
457 | err_free_phys: |
458 | nfp_reprs_clean_and_free_by_type(app, NFP_REPR_TYPE_PHYS_PORT); | |
cc54dc28 JK |
459 | err_free_abm: |
460 | kfree(abm); | |
461 | app->priv = NULL; | |
462 | return err; | |
c4c8f39a JK |
463 | } |
464 | ||
465 | static void nfp_abm_clean(struct nfp_app *app) | |
466 | { | |
467 | struct nfp_abm *abm = app->priv; | |
468 | ||
d05d902e JK |
469 | nfp_abm_eswitch_clean_up(abm); |
470 | nfp_reprs_clean_and_free_by_type(app, NFP_REPR_TYPE_PF); | |
471 | nfp_reprs_clean_and_free_by_type(app, NFP_REPR_TYPE_PHYS_PORT); | |
c4c8f39a JK |
472 | kfree(abm); |
473 | app->priv = NULL; | |
474 | } | |
475 | ||
476 | const struct nfp_app_type app_abm = { | |
477 | .id = NFP_APP_ACTIVE_BUFFER_MGMT_NIC, | |
478 | .name = "abm", | |
479 | ||
480 | .init = nfp_abm_init, | |
481 | .clean = nfp_abm_clean, | |
482 | ||
cc54dc28 JK |
483 | .vnic_alloc = nfp_abm_vnic_alloc, |
484 | .vnic_free = nfp_abm_vnic_free, | |
d05d902e | 485 | |
8c8e6406 JK |
486 | .setup_tc = nfp_abm_setup_tc, |
487 | ||
d05d902e JK |
488 | .eswitch_mode_get = nfp_abm_eswitch_mode_get, |
489 | .eswitch_mode_set = nfp_abm_eswitch_mode_set, | |
490 | ||
491 | .repr_get = nfp_abm_repr_get, | |
c4c8f39a | 492 | }; |