1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | /* Copyright (C) 2017-2018 Netronome Systems, Inc. */ |
3 | |
4 | #include <linux/rtnetlink.h> |
5 | #include <net/devlink.h> |
6 | |
7 | #include "nfpcore/nfp.h" |
8 | #include "nfpcore/nfp_nsp.h" |
9 | #include "nfp_app.h" |
10 | #include "nfp_main.h" |
11 | #include "nfp_port.h" |
12 | |
13 | static int |
14 | nfp_devlink_fill_eth_port(struct nfp_port *port, |
15 | struct nfp_eth_table_port *copy) |
16 | { |
17 | struct nfp_eth_table_port *eth_port; |
18 | |
19 | eth_port = __nfp_port_get_eth_port(port); |
20 | if (!eth_port) |
21 | return -EINVAL; |
22 | |
23 | memcpy(copy, eth_port, sizeof(*eth_port)); |
24 | |
25 | return 0; |
26 | } |
27 | |
28 | static int |
29 | nfp_devlink_fill_eth_port_from_id(struct nfp_pf *pf, |
30 | struct devlink_port *dl_port, |
31 | struct nfp_eth_table_port *copy) |
32 | { |
33 | struct nfp_port *port = container_of(dl_port, struct nfp_port, dl_port); |
34 | |
35 | return nfp_devlink_fill_eth_port(port, copy); |
36 | } |
37 | |
38 | static int |
39 | nfp_devlink_set_lanes(struct nfp_pf *pf, unsigned int idx, unsigned int lanes) |
40 | { |
41 | struct nfp_nsp *nsp; |
42 | int ret; |
43 | |
44 | nsp = nfp_eth_config_start(cpp: pf->cpp, idx); |
45 | if (IS_ERR(ptr: nsp)) |
46 | return PTR_ERR(ptr: nsp); |
47 | |
48 | ret = __nfp_eth_set_split(nsp, lanes); |
49 | if (ret) { |
50 | nfp_eth_config_cleanup_end(nsp); |
51 | return ret; |
52 | } |
53 | |
54 | ret = nfp_eth_config_commit_end(nsp); |
55 | if (ret < 0) |
56 | return ret; |
57 | if (ret) /* no change */ |
58 | return 0; |
59 | |
60 | return nfp_net_refresh_port_table_sync(pf); |
61 | } |
62 | |
63 | static int |
64 | nfp_devlink_port_split(struct devlink *devlink, struct devlink_port *port, |
65 | unsigned int count, struct netlink_ext_ack *extack) |
66 | { |
67 | struct nfp_pf *pf = devlink_priv(devlink); |
68 | struct nfp_eth_table_port eth_port; |
69 | unsigned int lanes; |
70 | int ret; |
71 | |
72 | rtnl_lock(); |
73 | ret = nfp_devlink_fill_eth_port_from_id(pf, dl_port: port, copy: ð_port); |
74 | rtnl_unlock(); |
75 | if (ret) |
76 | return ret; |
77 | |
78 | if (eth_port.port_lanes % count) { |
79 | NL_SET_ERR_MSG_MOD(extack, "invalid count" ); |
80 | return -EINVAL; |
81 | } |
82 | |
83 | /* Special case the 100G CXP -> 2x40G split */ |
84 | lanes = eth_port.port_lanes / count; |
85 | if (eth_port.lanes == 10 && count == 2) |
86 | lanes = 8 / count; |
87 | |
88 | return nfp_devlink_set_lanes(pf, idx: eth_port.index, lanes); |
89 | } |
90 | |
91 | static int |
92 | nfp_devlink_port_unsplit(struct devlink *devlink, struct devlink_port *port, |
93 | struct netlink_ext_ack *extack) |
94 | { |
95 | struct nfp_pf *pf = devlink_priv(devlink); |
96 | struct nfp_eth_table_port eth_port; |
97 | unsigned int lanes; |
98 | int ret; |
99 | |
100 | rtnl_lock(); |
101 | ret = nfp_devlink_fill_eth_port_from_id(pf, dl_port: port, copy: ð_port); |
102 | rtnl_unlock(); |
103 | if (ret) |
104 | return ret; |
105 | |
106 | if (!eth_port.is_split) { |
107 | NL_SET_ERR_MSG_MOD(extack, "port is not split" ); |
108 | return -EINVAL; |
109 | } |
110 | |
111 | /* Special case the 100G CXP -> 2x40G unsplit */ |
112 | lanes = eth_port.port_lanes; |
113 | if (eth_port.port_lanes == 8) |
114 | lanes = 10; |
115 | |
116 | return nfp_devlink_set_lanes(pf, idx: eth_port.index, lanes); |
117 | } |
118 | |
119 | static int |
120 | nfp_devlink_sb_pool_get(struct devlink *devlink, unsigned int sb_index, |
121 | u16 pool_index, struct devlink_sb_pool_info *pool_info) |
122 | { |
123 | struct nfp_pf *pf = devlink_priv(devlink); |
124 | |
125 | return nfp_shared_buf_pool_get(pf, sb: sb_index, pool_index, pool_info); |
126 | } |
127 | |
128 | static int |
129 | nfp_devlink_sb_pool_set(struct devlink *devlink, unsigned int sb_index, |
130 | u16 pool_index, |
131 | u32 size, enum devlink_sb_threshold_type threshold_type, |
132 | struct netlink_ext_ack *extack) |
133 | { |
134 | struct nfp_pf *pf = devlink_priv(devlink); |
135 | |
136 | return nfp_shared_buf_pool_set(pf, sb: sb_index, pool_index, |
137 | size, threshold_type); |
138 | } |
139 | |
140 | static int nfp_devlink_eswitch_mode_get(struct devlink *devlink, u16 *mode) |
141 | { |
142 | struct nfp_pf *pf = devlink_priv(devlink); |
143 | |
144 | return nfp_app_eswitch_mode_get(app: pf->app, mode); |
145 | } |
146 | |
147 | static int nfp_devlink_eswitch_mode_set(struct devlink *devlink, u16 mode, |
148 | struct netlink_ext_ack *extack) |
149 | { |
150 | struct nfp_pf *pf = devlink_priv(devlink); |
151 | |
152 | return nfp_app_eswitch_mode_set(app: pf->app, mode); |
153 | } |
154 | |
155 | static const struct nfp_devlink_versions_simple { |
156 | const char *key; |
157 | const char *hwinfo; |
158 | } nfp_devlink_versions_hwinfo[] = { |
159 | { DEVLINK_INFO_VERSION_GENERIC_BOARD_ID, "assembly.partno" , }, |
160 | { DEVLINK_INFO_VERSION_GENERIC_BOARD_REV, "assembly.revision" , }, |
161 | { DEVLINK_INFO_VERSION_GENERIC_BOARD_MANUFACTURE, "assembly.vendor" , }, |
162 | { "board.model" , /* code name */ "assembly.model" , }, |
163 | }; |
164 | |
165 | static int |
166 | nfp_devlink_versions_get_hwinfo(struct nfp_pf *pf, struct devlink_info_req *req) |
167 | { |
168 | unsigned int i; |
169 | int err; |
170 | |
171 | for (i = 0; i < ARRAY_SIZE(nfp_devlink_versions_hwinfo); i++) { |
172 | const struct nfp_devlink_versions_simple *info; |
173 | const char *val; |
174 | |
175 | info = &nfp_devlink_versions_hwinfo[i]; |
176 | |
177 | val = nfp_hwinfo_lookup(hwinfo: pf->hwinfo, lookup: info->hwinfo); |
178 | if (!val) |
179 | continue; |
180 | |
181 | err = devlink_info_version_fixed_put(req, version_name: info->key, version_value: val); |
182 | if (err) |
183 | return err; |
184 | } |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static const struct nfp_devlink_versions { |
190 | enum nfp_nsp_versions id; |
191 | const char *key; |
192 | } nfp_devlink_versions_nsp[] = { |
193 | { NFP_VERSIONS_BUNDLE, DEVLINK_INFO_VERSION_GENERIC_FW_BUNDLE_ID, }, |
194 | { NFP_VERSIONS_BSP, DEVLINK_INFO_VERSION_GENERIC_FW_MGMT, }, |
195 | { NFP_VERSIONS_CPLD, "fw.cpld" , }, |
196 | { NFP_VERSIONS_APP, DEVLINK_INFO_VERSION_GENERIC_FW_APP, }, |
197 | { NFP_VERSIONS_UNDI, DEVLINK_INFO_VERSION_GENERIC_FW_UNDI, }, |
198 | { NFP_VERSIONS_NCSI, DEVLINK_INFO_VERSION_GENERIC_FW_NCSI, }, |
199 | { NFP_VERSIONS_CFGR, "chip.init" , }, |
200 | }; |
201 | |
202 | static int |
203 | nfp_devlink_versions_get_nsp(struct devlink_info_req *req, bool flash, |
204 | const u8 *buf, unsigned int size) |
205 | { |
206 | unsigned int i; |
207 | int err; |
208 | |
209 | for (i = 0; i < ARRAY_SIZE(nfp_devlink_versions_nsp); i++) { |
210 | const struct nfp_devlink_versions *info; |
211 | const char *version; |
212 | |
213 | info = &nfp_devlink_versions_nsp[i]; |
214 | |
215 | version = nfp_nsp_versions_get(id: info->id, flash, buf, size); |
216 | if (IS_ERR(ptr: version)) { |
217 | if (PTR_ERR(ptr: version) == -ENOENT) |
218 | continue; |
219 | else |
220 | return PTR_ERR(ptr: version); |
221 | } |
222 | |
223 | if (flash) |
224 | err = devlink_info_version_stored_put(req, version_name: info->key, |
225 | version_value: version); |
226 | else |
227 | err = devlink_info_version_running_put(req, version_name: info->key, |
228 | version_value: version); |
229 | if (err) |
230 | return err; |
231 | } |
232 | |
233 | return 0; |
234 | } |
235 | |
236 | static int |
237 | nfp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req, |
238 | struct netlink_ext_ack *extack) |
239 | { |
240 | struct nfp_pf *pf = devlink_priv(devlink); |
241 | const char *sn, *vendor, *part; |
242 | struct nfp_nsp *nsp; |
243 | char *buf = NULL; |
244 | int err; |
245 | |
246 | vendor = nfp_hwinfo_lookup(hwinfo: pf->hwinfo, lookup: "assembly.vendor" ); |
247 | part = nfp_hwinfo_lookup(hwinfo: pf->hwinfo, lookup: "assembly.partno" ); |
248 | sn = nfp_hwinfo_lookup(hwinfo: pf->hwinfo, lookup: "assembly.serial" ); |
249 | if (vendor && part && sn) { |
250 | char *buf; |
251 | |
252 | buf = kmalloc(strlen(vendor) + strlen(part) + strlen(sn) + 1, |
253 | GFP_KERNEL); |
254 | if (!buf) |
255 | return -ENOMEM; |
256 | |
257 | buf[0] = '\0'; |
258 | strcat(p: buf, q: vendor); |
259 | strcat(p: buf, q: part); |
260 | strcat(p: buf, q: sn); |
261 | |
262 | err = devlink_info_serial_number_put(req, sn: buf); |
263 | kfree(objp: buf); |
264 | if (err) |
265 | return err; |
266 | } |
267 | |
268 | nsp = nfp_nsp_open(cpp: pf->cpp); |
269 | if (IS_ERR(ptr: nsp)) { |
270 | NL_SET_ERR_MSG_MOD(extack, "can't access NSP" ); |
271 | return PTR_ERR(ptr: nsp); |
272 | } |
273 | |
274 | if (nfp_nsp_has_versions(state: nsp)) { |
275 | buf = kzalloc(NFP_NSP_VERSION_BUFSZ, GFP_KERNEL); |
276 | if (!buf) { |
277 | err = -ENOMEM; |
278 | goto err_close_nsp; |
279 | } |
280 | |
281 | err = nfp_nsp_versions(state: nsp, buf, NFP_NSP_VERSION_BUFSZ); |
282 | if (err) |
283 | goto err_free_buf; |
284 | |
285 | err = nfp_devlink_versions_get_nsp(req, flash: false, |
286 | buf, NFP_NSP_VERSION_BUFSZ); |
287 | if (err) |
288 | goto err_free_buf; |
289 | |
290 | err = nfp_devlink_versions_get_nsp(req, flash: true, |
291 | buf, NFP_NSP_VERSION_BUFSZ); |
292 | if (err) |
293 | goto err_free_buf; |
294 | |
295 | kfree(objp: buf); |
296 | } |
297 | |
298 | nfp_nsp_close(state: nsp); |
299 | |
300 | return nfp_devlink_versions_get_hwinfo(pf, req); |
301 | |
302 | err_free_buf: |
303 | kfree(objp: buf); |
304 | err_close_nsp: |
305 | nfp_nsp_close(state: nsp); |
306 | return err; |
307 | } |
308 | |
309 | static int |
310 | nfp_devlink_flash_update(struct devlink *devlink, |
311 | struct devlink_flash_update_params *params, |
312 | struct netlink_ext_ack *extack) |
313 | { |
314 | return nfp_flash_update_common(pf: devlink_priv(devlink), fw: params->fw, extack); |
315 | } |
316 | |
317 | const struct devlink_ops nfp_devlink_ops = { |
318 | .sb_pool_get = nfp_devlink_sb_pool_get, |
319 | .sb_pool_set = nfp_devlink_sb_pool_set, |
320 | .eswitch_mode_get = nfp_devlink_eswitch_mode_get, |
321 | .eswitch_mode_set = nfp_devlink_eswitch_mode_set, |
322 | .info_get = nfp_devlink_info_get, |
323 | .flash_update = nfp_devlink_flash_update, |
324 | }; |
325 | |
326 | static const struct devlink_port_ops nfp_devlink_port_ops = { |
327 | .port_split = nfp_devlink_port_split, |
328 | .port_unsplit = nfp_devlink_port_unsplit, |
329 | }; |
330 | |
331 | int nfp_devlink_port_register(struct nfp_app *app, struct nfp_port *port) |
332 | { |
333 | struct devlink_port_attrs attrs = {}; |
334 | struct nfp_eth_table_port eth_port; |
335 | struct devlink *devlink; |
336 | const u8 *serial; |
337 | int serial_len; |
338 | int ret; |
339 | |
340 | SET_NETDEV_DEVLINK_PORT(port->netdev, &port->dl_port); |
341 | |
342 | rtnl_lock(); |
343 | ret = nfp_devlink_fill_eth_port(port, copy: ð_port); |
344 | rtnl_unlock(); |
345 | if (ret) |
346 | return ret; |
347 | |
348 | attrs.split = eth_port.is_split; |
349 | attrs.splittable = eth_port.port_lanes > 1 && !attrs.split; |
350 | attrs.lanes = eth_port.port_lanes; |
351 | attrs.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL; |
352 | attrs.phys.port_number = eth_port.label_port; |
353 | attrs.phys.split_subport_number = eth_port.label_subport; |
354 | serial_len = nfp_cpp_serial(cpp: port->app->cpp, serial: &serial); |
355 | memcpy(attrs.switch_id.id, serial, serial_len); |
356 | attrs.switch_id.id_len = serial_len; |
357 | devlink_port_attrs_set(devlink_port: &port->dl_port, devlink_port_attrs: &attrs); |
358 | |
359 | devlink = priv_to_devlink(priv: app->pf); |
360 | |
361 | return devl_port_register_with_ops(devlink, devlink_port: &port->dl_port, |
362 | port_index: port->eth_id, ops: &nfp_devlink_port_ops); |
363 | } |
364 | |
365 | void nfp_devlink_port_unregister(struct nfp_port *port) |
366 | { |
367 | devl_port_unregister(devlink_port: &port->dl_port); |
368 | } |
369 | |