1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | /* Copyright (C) 2017-2018 Netronome Systems, Inc. */ |
3 | |
4 | #include <linux/ethtool.h> |
5 | #include <linux/vmalloc.h> |
6 | |
7 | #include "nfp_asm.h" |
8 | #include "nfp_main.h" |
9 | #include "nfpcore/nfp.h" |
10 | #include "nfpcore/nfp_nffw.h" |
11 | #include "nfpcore/nfp6000/nfp6000.h" |
12 | |
13 | #define NFP_DUMP_SPEC_RTSYM "_abi_dump_spec" |
14 | |
15 | #define ALIGN8(x) ALIGN(x, 8) |
16 | |
17 | enum nfp_dumpspec_type { |
18 | NFP_DUMPSPEC_TYPE_CPP_CSR = 0, |
19 | NFP_DUMPSPEC_TYPE_XPB_CSR = 1, |
20 | NFP_DUMPSPEC_TYPE_ME_CSR = 2, |
21 | NFP_DUMPSPEC_TYPE_INDIRECT_ME_CSR = 3, |
22 | NFP_DUMPSPEC_TYPE_RTSYM = 4, |
23 | NFP_DUMPSPEC_TYPE_HWINFO = 5, |
24 | NFP_DUMPSPEC_TYPE_FWNAME = 6, |
25 | NFP_DUMPSPEC_TYPE_HWINFO_FIELD = 7, |
26 | NFP_DUMPSPEC_TYPE_PROLOG = 10000, |
27 | NFP_DUMPSPEC_TYPE_ERROR = 10001, |
28 | }; |
29 | |
30 | /* The following structs must be carefully aligned so that they can be used to |
31 | * interpret the binary dumpspec and populate the dump data in a deterministic |
32 | * way. |
33 | */ |
34 | |
35 | /* generic type plus length */ |
36 | struct nfp_dump_tl { |
37 | __be32 type; |
38 | __be32 length; /* chunk length to follow, aligned to 8 bytes */ |
39 | char data[]; |
40 | }; |
41 | |
42 | /* NFP CPP parameters */ |
43 | struct nfp_dumpspec_cpp_isl_id { |
44 | u8 target; |
45 | u8 action; |
46 | u8 token; |
47 | u8 island; |
48 | }; |
49 | |
50 | struct nfp_dump_common_cpp { |
51 | struct nfp_dumpspec_cpp_isl_id cpp_id; |
52 | __be32 offset; /* address to start dump */ |
53 | __be32 dump_length; /* total bytes to dump, aligned to reg size */ |
54 | }; |
55 | |
56 | /* CSR dumpables */ |
57 | struct nfp_dumpspec_csr { |
58 | struct nfp_dump_tl tl; |
59 | struct nfp_dump_common_cpp cpp; |
60 | __be32 register_width; /* in bits */ |
61 | }; |
62 | |
63 | struct nfp_dumpspec_rtsym { |
64 | struct nfp_dump_tl tl; |
65 | char rtsym[]; |
66 | }; |
67 | |
68 | /* header for register dumpable */ |
69 | struct nfp_dump_csr { |
70 | struct nfp_dump_tl tl; |
71 | struct nfp_dump_common_cpp cpp; |
72 | __be32 register_width; /* in bits */ |
73 | __be32 error; /* error code encountered while reading */ |
74 | __be32 error_offset; /* offset being read when error occurred */ |
75 | }; |
76 | |
77 | struct nfp_dump_rtsym { |
78 | struct nfp_dump_tl tl; |
79 | struct nfp_dump_common_cpp cpp; |
80 | __be32 error; /* error code encountered while reading */ |
81 | u8 padded_name_length; /* pad so data starts at 8 byte boundary */ |
82 | char rtsym[]; |
83 | /* after padded_name_length, there is dump_length data */ |
84 | }; |
85 | |
86 | struct nfp_dump_prolog { |
87 | struct nfp_dump_tl tl; |
88 | __be32 dump_level; |
89 | }; |
90 | |
91 | struct nfp_dump_error { |
92 | struct nfp_dump_tl tl; |
93 | __be32 error; |
94 | char padding[4]; |
95 | char spec[]; |
96 | }; |
97 | |
98 | /* to track state through debug size calculation TLV traversal */ |
99 | struct nfp_level_size { |
100 | __be32 requested_level; /* input */ |
101 | u32 total_size; /* output */ |
102 | }; |
103 | |
104 | /* to track state during debug dump creation TLV traversal */ |
105 | struct nfp_dump_state { |
106 | __be32 requested_level; /* input param */ |
107 | u32 dumped_size; /* adds up to size of dumped data */ |
108 | u32 buf_size; /* size of buffer pointer to by p */ |
109 | void *p; /* current point in dump buffer */ |
110 | }; |
111 | |
112 | typedef int (*nfp_tlv_visit)(struct nfp_pf *pf, struct nfp_dump_tl *tl, |
113 | void *param); |
114 | |
115 | static int |
116 | nfp_traverse_tlvs(struct nfp_pf *pf, void *data, u32 data_length, void *param, |
117 | nfp_tlv_visit tlv_visit) |
118 | { |
119 | long long remaining = data_length; |
120 | struct nfp_dump_tl *tl; |
121 | u32 total_tlv_size; |
122 | void *p = data; |
123 | int err; |
124 | |
125 | while (remaining >= sizeof(*tl)) { |
126 | tl = p; |
127 | if (!tl->type && !tl->length) |
128 | break; |
129 | |
130 | if (be32_to_cpu(tl->length) > remaining - sizeof(*tl)) |
131 | return -EINVAL; |
132 | |
133 | total_tlv_size = sizeof(*tl) + be32_to_cpu(tl->length); |
134 | |
135 | /* Spec TLVs should be aligned to 4 bytes. */ |
136 | if (total_tlv_size % 4 != 0) |
137 | return -EINVAL; |
138 | |
139 | p += total_tlv_size; |
140 | remaining -= total_tlv_size; |
141 | err = tlv_visit(pf, tl, param); |
142 | if (err) |
143 | return err; |
144 | } |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | static u32 nfp_get_numeric_cpp_id(struct nfp_dumpspec_cpp_isl_id *cpp_id) |
150 | { |
151 | return NFP_CPP_ISLAND_ID(cpp_id->target, cpp_id->action, cpp_id->token, |
152 | cpp_id->island); |
153 | } |
154 | |
155 | struct nfp_dumpspec * |
156 | nfp_net_dump_load_dumpspec(struct nfp_cpp *cpp, struct nfp_rtsym_table *rtbl) |
157 | { |
158 | const struct nfp_rtsym *specsym; |
159 | struct nfp_dumpspec *dumpspec; |
160 | int bytes_read; |
161 | u64 sym_size; |
162 | |
163 | specsym = nfp_rtsym_lookup(rtbl, NFP_DUMP_SPEC_RTSYM); |
164 | if (!specsym) |
165 | return NULL; |
166 | sym_size = nfp_rtsym_size(rtsym: specsym); |
167 | |
168 | /* expected size of this buffer is in the order of tens of kilobytes */ |
169 | dumpspec = vmalloc(size: sizeof(*dumpspec) + sym_size); |
170 | if (!dumpspec) |
171 | return NULL; |
172 | dumpspec->size = sym_size; |
173 | |
174 | bytes_read = nfp_rtsym_read(cpp, sym: specsym, off: 0, buf: dumpspec->data, len: sym_size); |
175 | if (bytes_read != sym_size) { |
176 | vfree(addr: dumpspec); |
177 | nfp_warn(cpp, "Debug dump specification read failed.\n" ); |
178 | return NULL; |
179 | } |
180 | |
181 | return dumpspec; |
182 | } |
183 | |
184 | static int nfp_dump_error_tlv_size(struct nfp_dump_tl *spec) |
185 | { |
186 | return ALIGN8(sizeof(struct nfp_dump_error) + sizeof(*spec) + |
187 | be32_to_cpu(spec->length)); |
188 | } |
189 | |
190 | static int nfp_calc_fwname_tlv_size(struct nfp_pf *pf) |
191 | { |
192 | u32 fwname_len = strlen(nfp_mip_name(pf->mip)); |
193 | |
194 | return sizeof(struct nfp_dump_tl) + ALIGN8(fwname_len + 1); |
195 | } |
196 | |
197 | static int nfp_calc_hwinfo_field_sz(struct nfp_pf *pf, struct nfp_dump_tl *spec) |
198 | { |
199 | u32 tl_len, key_len; |
200 | const char *value; |
201 | |
202 | tl_len = be32_to_cpu(spec->length); |
203 | key_len = strnlen(p: spec->data, maxlen: tl_len); |
204 | if (key_len == tl_len) |
205 | return nfp_dump_error_tlv_size(spec); |
206 | |
207 | value = nfp_hwinfo_lookup(hwinfo: pf->hwinfo, lookup: spec->data); |
208 | if (!value) |
209 | return nfp_dump_error_tlv_size(spec); |
210 | |
211 | return sizeof(struct nfp_dump_tl) + ALIGN8(key_len + strlen(value) + 2); |
212 | } |
213 | |
214 | static bool nfp_csr_spec_valid(struct nfp_dumpspec_csr *spec_csr) |
215 | { |
216 | u32 required_read_sz = sizeof(*spec_csr) - sizeof(spec_csr->tl); |
217 | u32 available_sz = be32_to_cpu(spec_csr->tl.length); |
218 | u32 reg_width; |
219 | |
220 | if (available_sz < required_read_sz) |
221 | return false; |
222 | |
223 | reg_width = be32_to_cpu(spec_csr->register_width); |
224 | |
225 | return reg_width == 32 || reg_width == 64; |
226 | } |
227 | |
228 | static int |
229 | nfp_calc_rtsym_dump_sz(struct nfp_pf *pf, struct nfp_dump_tl *spec) |
230 | { |
231 | struct nfp_rtsym_table *rtbl = pf->rtbl; |
232 | struct nfp_dumpspec_rtsym *spec_rtsym; |
233 | const struct nfp_rtsym *sym; |
234 | u32 tl_len, key_len; |
235 | |
236 | spec_rtsym = (struct nfp_dumpspec_rtsym *)spec; |
237 | tl_len = be32_to_cpu(spec->length); |
238 | key_len = strnlen(p: spec_rtsym->rtsym, maxlen: tl_len); |
239 | if (key_len == tl_len) |
240 | return nfp_dump_error_tlv_size(spec); |
241 | |
242 | sym = nfp_rtsym_lookup(rtbl, name: spec_rtsym->rtsym); |
243 | if (!sym) |
244 | return nfp_dump_error_tlv_size(spec); |
245 | |
246 | return ALIGN8(offsetof(struct nfp_dump_rtsym, rtsym) + key_len + 1) + |
247 | ALIGN8(nfp_rtsym_size(sym)); |
248 | } |
249 | |
250 | static int |
251 | nfp_add_tlv_size(struct nfp_pf *pf, struct nfp_dump_tl *tl, void *param) |
252 | { |
253 | struct nfp_dumpspec_csr *spec_csr; |
254 | u32 *size = param; |
255 | u32 hwinfo_size; |
256 | |
257 | switch (be32_to_cpu(tl->type)) { |
258 | case NFP_DUMPSPEC_TYPE_FWNAME: |
259 | *size += nfp_calc_fwname_tlv_size(pf); |
260 | break; |
261 | case NFP_DUMPSPEC_TYPE_CPP_CSR: |
262 | case NFP_DUMPSPEC_TYPE_XPB_CSR: |
263 | case NFP_DUMPSPEC_TYPE_ME_CSR: |
264 | spec_csr = (struct nfp_dumpspec_csr *)tl; |
265 | if (!nfp_csr_spec_valid(spec_csr)) |
266 | *size += nfp_dump_error_tlv_size(spec: tl); |
267 | else |
268 | *size += ALIGN8(sizeof(struct nfp_dump_csr)) + |
269 | ALIGN8(be32_to_cpu(spec_csr->cpp.dump_length)); |
270 | break; |
271 | case NFP_DUMPSPEC_TYPE_INDIRECT_ME_CSR: |
272 | spec_csr = (struct nfp_dumpspec_csr *)tl; |
273 | if (!nfp_csr_spec_valid(spec_csr)) |
274 | *size += nfp_dump_error_tlv_size(spec: tl); |
275 | else |
276 | *size += ALIGN8(sizeof(struct nfp_dump_csr)) + |
277 | ALIGN8(be32_to_cpu(spec_csr->cpp.dump_length) * |
278 | NFP_IND_NUM_CONTEXTS); |
279 | break; |
280 | case NFP_DUMPSPEC_TYPE_RTSYM: |
281 | *size += nfp_calc_rtsym_dump_sz(pf, spec: tl); |
282 | break; |
283 | case NFP_DUMPSPEC_TYPE_HWINFO: |
284 | hwinfo_size = nfp_hwinfo_get_packed_str_size(hwinfo: pf->hwinfo); |
285 | *size += sizeof(struct nfp_dump_tl) + ALIGN8(hwinfo_size); |
286 | break; |
287 | case NFP_DUMPSPEC_TYPE_HWINFO_FIELD: |
288 | *size += nfp_calc_hwinfo_field_sz(pf, spec: tl); |
289 | break; |
290 | default: |
291 | *size += nfp_dump_error_tlv_size(spec: tl); |
292 | break; |
293 | } |
294 | |
295 | return 0; |
296 | } |
297 | |
298 | static int |
299 | nfp_calc_specific_level_size(struct nfp_pf *pf, struct nfp_dump_tl *dump_level, |
300 | void *param) |
301 | { |
302 | struct nfp_level_size *lev_sz = param; |
303 | |
304 | if (dump_level->type != lev_sz->requested_level) |
305 | return 0; |
306 | |
307 | return nfp_traverse_tlvs(pf, data: dump_level->data, |
308 | be32_to_cpu(dump_level->length), |
309 | param: &lev_sz->total_size, tlv_visit: nfp_add_tlv_size); |
310 | } |
311 | |
312 | s64 nfp_net_dump_calculate_size(struct nfp_pf *pf, struct nfp_dumpspec *spec, |
313 | u32 flag) |
314 | { |
315 | struct nfp_level_size lev_sz; |
316 | int err; |
317 | |
318 | lev_sz.requested_level = cpu_to_be32(flag); |
319 | lev_sz.total_size = ALIGN8(sizeof(struct nfp_dump_prolog)); |
320 | |
321 | err = nfp_traverse_tlvs(pf, data: spec->data, data_length: spec->size, param: &lev_sz, |
322 | tlv_visit: nfp_calc_specific_level_size); |
323 | if (err) |
324 | return err; |
325 | |
326 | return lev_sz.total_size; |
327 | } |
328 | |
329 | static int nfp_add_tlv(u32 type, u32 total_tlv_sz, struct nfp_dump_state *dump) |
330 | { |
331 | struct nfp_dump_tl *tl = dump->p; |
332 | |
333 | if (total_tlv_sz > dump->buf_size) |
334 | return -ENOSPC; |
335 | |
336 | if (dump->buf_size - total_tlv_sz < dump->dumped_size) |
337 | return -ENOSPC; |
338 | |
339 | tl->type = cpu_to_be32(type); |
340 | tl->length = cpu_to_be32(total_tlv_sz - sizeof(*tl)); |
341 | |
342 | dump->dumped_size += total_tlv_sz; |
343 | dump->p += total_tlv_sz; |
344 | |
345 | return 0; |
346 | } |
347 | |
348 | static int |
349 | nfp_dump_error_tlv(struct nfp_dump_tl *spec, int error, |
350 | struct nfp_dump_state *dump) |
351 | { |
352 | struct nfp_dump_error * = dump->p; |
353 | u32 total_spec_size, total_size; |
354 | int err; |
355 | |
356 | total_spec_size = sizeof(*spec) + be32_to_cpu(spec->length); |
357 | total_size = ALIGN8(sizeof(*dump_header) + total_spec_size); |
358 | |
359 | err = nfp_add_tlv(type: NFP_DUMPSPEC_TYPE_ERROR, total_tlv_sz: total_size, dump); |
360 | if (err) |
361 | return err; |
362 | |
363 | dump_header->error = cpu_to_be32(error); |
364 | memcpy(dump_header->spec, spec, total_spec_size); |
365 | |
366 | return 0; |
367 | } |
368 | |
369 | static int nfp_dump_fwname(struct nfp_pf *pf, struct nfp_dump_state *dump) |
370 | { |
371 | struct nfp_dump_tl * = dump->p; |
372 | u32 fwname_len, total_size; |
373 | const char *fwname; |
374 | int err; |
375 | |
376 | fwname = nfp_mip_name(mip: pf->mip); |
377 | fwname_len = strlen(fwname); |
378 | total_size = sizeof(*dump_header) + ALIGN8(fwname_len + 1); |
379 | |
380 | err = nfp_add_tlv(type: NFP_DUMPSPEC_TYPE_FWNAME, total_tlv_sz: total_size, dump); |
381 | if (err) |
382 | return err; |
383 | |
384 | memcpy(dump_header->data, fwname, fwname_len); |
385 | |
386 | return 0; |
387 | } |
388 | |
389 | static int |
390 | nfp_dump_hwinfo(struct nfp_pf *pf, struct nfp_dump_tl *spec, |
391 | struct nfp_dump_state *dump) |
392 | { |
393 | struct nfp_dump_tl * = dump->p; |
394 | u32 hwinfo_size, total_size; |
395 | char *hwinfo; |
396 | int err; |
397 | |
398 | hwinfo = nfp_hwinfo_get_packed_strings(hwinfo: pf->hwinfo); |
399 | hwinfo_size = nfp_hwinfo_get_packed_str_size(hwinfo: pf->hwinfo); |
400 | total_size = sizeof(*dump_header) + ALIGN8(hwinfo_size); |
401 | |
402 | err = nfp_add_tlv(type: NFP_DUMPSPEC_TYPE_HWINFO, total_tlv_sz: total_size, dump); |
403 | if (err) |
404 | return err; |
405 | |
406 | memcpy(dump_header->data, hwinfo, hwinfo_size); |
407 | |
408 | return 0; |
409 | } |
410 | |
411 | static int nfp_dump_hwinfo_field(struct nfp_pf *pf, struct nfp_dump_tl *spec, |
412 | struct nfp_dump_state *dump) |
413 | { |
414 | struct nfp_dump_tl * = dump->p; |
415 | u32 tl_len, key_len, val_len; |
416 | const char *key, *value; |
417 | u32 total_size; |
418 | int err; |
419 | |
420 | tl_len = be32_to_cpu(spec->length); |
421 | key_len = strnlen(p: spec->data, maxlen: tl_len); |
422 | if (key_len == tl_len) |
423 | return nfp_dump_error_tlv(spec, error: -EINVAL, dump); |
424 | |
425 | key = spec->data; |
426 | value = nfp_hwinfo_lookup(hwinfo: pf->hwinfo, lookup: key); |
427 | if (!value) |
428 | return nfp_dump_error_tlv(spec, error: -ENOENT, dump); |
429 | |
430 | val_len = strlen(value); |
431 | total_size = sizeof(*dump_header) + ALIGN8(key_len + val_len + 2); |
432 | err = nfp_add_tlv(type: NFP_DUMPSPEC_TYPE_HWINFO_FIELD, total_tlv_sz: total_size, dump); |
433 | if (err) |
434 | return err; |
435 | |
436 | memcpy(dump_header->data, key, key_len + 1); |
437 | memcpy(dump_header->data + key_len + 1, value, val_len + 1); |
438 | |
439 | return 0; |
440 | } |
441 | |
442 | static bool is_xpb_read(struct nfp_dumpspec_cpp_isl_id *cpp_id) |
443 | { |
444 | return cpp_id->target == NFP_CPP_TARGET_ISLAND_XPB && |
445 | cpp_id->action == 0 && cpp_id->token == 0; |
446 | } |
447 | |
448 | static int |
449 | nfp_dump_csr_range(struct nfp_pf *pf, struct nfp_dumpspec_csr *spec_csr, |
450 | struct nfp_dump_state *dump) |
451 | { |
452 | struct nfp_dump_csr * = dump->p; |
453 | u32 reg_sz, , total_size; |
454 | u32 cpp_rd_addr, max_rd_addr; |
455 | int bytes_read; |
456 | void *dest; |
457 | u32 cpp_id; |
458 | int err; |
459 | |
460 | if (!nfp_csr_spec_valid(spec_csr)) |
461 | return nfp_dump_error_tlv(spec: &spec_csr->tl, error: -EINVAL, dump); |
462 | |
463 | reg_sz = be32_to_cpu(spec_csr->register_width) / BITS_PER_BYTE; |
464 | header_size = ALIGN8(sizeof(*dump_header)); |
465 | total_size = header_size + |
466 | ALIGN8(be32_to_cpu(spec_csr->cpp.dump_length)); |
467 | dest = dump->p + header_size; |
468 | |
469 | err = nfp_add_tlv(be32_to_cpu(spec_csr->tl.type), total_tlv_sz: total_size, dump); |
470 | if (err) |
471 | return err; |
472 | |
473 | dump_header->cpp = spec_csr->cpp; |
474 | dump_header->register_width = spec_csr->register_width; |
475 | |
476 | cpp_id = nfp_get_numeric_cpp_id(cpp_id: &spec_csr->cpp.cpp_id); |
477 | cpp_rd_addr = be32_to_cpu(spec_csr->cpp.offset); |
478 | max_rd_addr = cpp_rd_addr + be32_to_cpu(spec_csr->cpp.dump_length); |
479 | |
480 | while (cpp_rd_addr < max_rd_addr) { |
481 | if (is_xpb_read(cpp_id: &spec_csr->cpp.cpp_id)) { |
482 | err = nfp_xpb_readl(cpp: pf->cpp, xpb_tgt: cpp_rd_addr, value: (u32 *)dest); |
483 | } else { |
484 | bytes_read = nfp_cpp_read(cpp: pf->cpp, cpp_id, address: cpp_rd_addr, |
485 | kernel_vaddr: dest, length: reg_sz); |
486 | err = bytes_read == reg_sz ? 0 : -EIO; |
487 | } |
488 | if (err) { |
489 | dump_header->error = cpu_to_be32(err); |
490 | dump_header->error_offset = cpu_to_be32(cpp_rd_addr); |
491 | break; |
492 | } |
493 | cpp_rd_addr += reg_sz; |
494 | dest += reg_sz; |
495 | } |
496 | |
497 | return 0; |
498 | } |
499 | |
500 | /* Write context to CSRCtxPtr, then read from it. Then the value can be read |
501 | * from IndCtxStatus. |
502 | */ |
503 | static int |
504 | nfp_read_indirect_csr(struct nfp_cpp *cpp, |
505 | struct nfp_dumpspec_cpp_isl_id cpp_params, u32 offset, |
506 | u32 reg_sz, u32 context, void *dest) |
507 | { |
508 | u32 csr_ctx_ptr_offs; |
509 | u32 cpp_id; |
510 | int result; |
511 | |
512 | csr_ctx_ptr_offs = nfp_get_ind_csr_ctx_ptr_offs(read_offset: offset); |
513 | cpp_id = NFP_CPP_ISLAND_ID(cpp_params.target, |
514 | NFP_IND_ME_REFL_WR_SIG_INIT, |
515 | cpp_params.token, cpp_params.island); |
516 | result = nfp_cpp_writel(cpp, cpp_id, address: csr_ctx_ptr_offs, value: context); |
517 | if (result) |
518 | return result; |
519 | |
520 | cpp_id = nfp_get_numeric_cpp_id(cpp_id: &cpp_params); |
521 | result = nfp_cpp_read(cpp, cpp_id, address: csr_ctx_ptr_offs, kernel_vaddr: dest, length: reg_sz); |
522 | if (result != reg_sz) |
523 | return result < 0 ? result : -EIO; |
524 | |
525 | result = nfp_cpp_read(cpp, cpp_id, address: offset, kernel_vaddr: dest, length: reg_sz); |
526 | if (result != reg_sz) |
527 | return result < 0 ? result : -EIO; |
528 | |
529 | return 0; |
530 | } |
531 | |
532 | static int |
533 | nfp_read_all_indirect_csr_ctx(struct nfp_cpp *cpp, |
534 | struct nfp_dumpspec_csr *spec_csr, u32 address, |
535 | u32 reg_sz, void *dest) |
536 | { |
537 | u32 ctx; |
538 | int err; |
539 | |
540 | for (ctx = 0; ctx < NFP_IND_NUM_CONTEXTS; ctx++) { |
541 | err = nfp_read_indirect_csr(cpp, cpp_params: spec_csr->cpp.cpp_id, offset: address, |
542 | reg_sz, context: ctx, dest: dest + ctx * reg_sz); |
543 | if (err) |
544 | return err; |
545 | } |
546 | |
547 | return 0; |
548 | } |
549 | |
550 | static int |
551 | nfp_dump_indirect_csr_range(struct nfp_pf *pf, |
552 | struct nfp_dumpspec_csr *spec_csr, |
553 | struct nfp_dump_state *dump) |
554 | { |
555 | struct nfp_dump_csr * = dump->p; |
556 | u32 reg_sz, , total_size; |
557 | u32 cpp_rd_addr, max_rd_addr; |
558 | u32 reg_data_length; |
559 | void *dest; |
560 | int err; |
561 | |
562 | if (!nfp_csr_spec_valid(spec_csr)) |
563 | return nfp_dump_error_tlv(spec: &spec_csr->tl, error: -EINVAL, dump); |
564 | |
565 | reg_sz = be32_to_cpu(spec_csr->register_width) / BITS_PER_BYTE; |
566 | header_size = ALIGN8(sizeof(*dump_header)); |
567 | reg_data_length = be32_to_cpu(spec_csr->cpp.dump_length) * |
568 | NFP_IND_NUM_CONTEXTS; |
569 | total_size = header_size + ALIGN8(reg_data_length); |
570 | dest = dump->p + header_size; |
571 | |
572 | err = nfp_add_tlv(be32_to_cpu(spec_csr->tl.type), total_tlv_sz: total_size, dump); |
573 | if (err) |
574 | return err; |
575 | |
576 | dump_header->cpp = spec_csr->cpp; |
577 | dump_header->register_width = spec_csr->register_width; |
578 | |
579 | cpp_rd_addr = be32_to_cpu(spec_csr->cpp.offset); |
580 | max_rd_addr = cpp_rd_addr + be32_to_cpu(spec_csr->cpp.dump_length); |
581 | while (cpp_rd_addr < max_rd_addr) { |
582 | err = nfp_read_all_indirect_csr_ctx(cpp: pf->cpp, spec_csr, |
583 | address: cpp_rd_addr, reg_sz, dest); |
584 | if (err) { |
585 | dump_header->error = cpu_to_be32(err); |
586 | dump_header->error_offset = cpu_to_be32(cpp_rd_addr); |
587 | break; |
588 | } |
589 | cpp_rd_addr += reg_sz; |
590 | dest += reg_sz * NFP_IND_NUM_CONTEXTS; |
591 | } |
592 | |
593 | return 0; |
594 | } |
595 | |
596 | static int |
597 | nfp_dump_single_rtsym(struct nfp_pf *pf, struct nfp_dumpspec_rtsym *spec, |
598 | struct nfp_dump_state *dump) |
599 | { |
600 | struct nfp_dump_rtsym * = dump->p; |
601 | struct nfp_dumpspec_cpp_isl_id cpp_params; |
602 | struct nfp_rtsym_table *rtbl = pf->rtbl; |
603 | u32 , total_size, sym_size; |
604 | const struct nfp_rtsym *sym; |
605 | u32 tl_len, key_len; |
606 | int bytes_read; |
607 | void *dest; |
608 | int err; |
609 | |
610 | tl_len = be32_to_cpu(spec->tl.length); |
611 | key_len = strnlen(p: spec->rtsym, maxlen: tl_len); |
612 | if (key_len == tl_len) |
613 | return nfp_dump_error_tlv(spec: &spec->tl, error: -EINVAL, dump); |
614 | |
615 | sym = nfp_rtsym_lookup(rtbl, name: spec->rtsym); |
616 | if (!sym) |
617 | return nfp_dump_error_tlv(spec: &spec->tl, error: -ENOENT, dump); |
618 | |
619 | sym_size = nfp_rtsym_size(rtsym: sym); |
620 | header_size = |
621 | ALIGN8(offsetof(struct nfp_dump_rtsym, rtsym) + key_len + 1); |
622 | total_size = header_size + ALIGN8(sym_size); |
623 | dest = dump->p + header_size; |
624 | |
625 | err = nfp_add_tlv(be32_to_cpu(spec->tl.type), total_tlv_sz: total_size, dump); |
626 | if (err) |
627 | return err; |
628 | |
629 | dump_header->padded_name_length = |
630 | header_size - offsetof(struct nfp_dump_rtsym, rtsym); |
631 | memcpy(dump_header->rtsym, spec->rtsym, key_len + 1); |
632 | dump_header->cpp.dump_length = cpu_to_be32(sym_size); |
633 | |
634 | if (sym->type != NFP_RTSYM_TYPE_ABS) { |
635 | cpp_params.target = sym->target; |
636 | cpp_params.action = NFP_CPP_ACTION_RW; |
637 | cpp_params.token = 0; |
638 | cpp_params.island = sym->domain; |
639 | dump_header->cpp.cpp_id = cpp_params; |
640 | dump_header->cpp.offset = cpu_to_be32(sym->addr); |
641 | } |
642 | |
643 | bytes_read = nfp_rtsym_read(cpp: pf->cpp, sym, off: 0, buf: dest, len: sym_size); |
644 | if (bytes_read != sym_size) { |
645 | if (bytes_read >= 0) |
646 | bytes_read = -EIO; |
647 | dump_header->error = cpu_to_be32(bytes_read); |
648 | } |
649 | |
650 | return 0; |
651 | } |
652 | |
653 | static int |
654 | nfp_dump_for_tlv(struct nfp_pf *pf, struct nfp_dump_tl *tl, void *param) |
655 | { |
656 | struct nfp_dumpspec_rtsym *spec_rtsym; |
657 | struct nfp_dump_state *dump = param; |
658 | struct nfp_dumpspec_csr *spec_csr; |
659 | int err; |
660 | |
661 | switch (be32_to_cpu(tl->type)) { |
662 | case NFP_DUMPSPEC_TYPE_FWNAME: |
663 | err = nfp_dump_fwname(pf, dump); |
664 | if (err) |
665 | return err; |
666 | break; |
667 | case NFP_DUMPSPEC_TYPE_CPP_CSR: |
668 | case NFP_DUMPSPEC_TYPE_XPB_CSR: |
669 | case NFP_DUMPSPEC_TYPE_ME_CSR: |
670 | spec_csr = (struct nfp_dumpspec_csr *)tl; |
671 | err = nfp_dump_csr_range(pf, spec_csr, dump); |
672 | if (err) |
673 | return err; |
674 | break; |
675 | case NFP_DUMPSPEC_TYPE_INDIRECT_ME_CSR: |
676 | spec_csr = (struct nfp_dumpspec_csr *)tl; |
677 | err = nfp_dump_indirect_csr_range(pf, spec_csr, dump); |
678 | if (err) |
679 | return err; |
680 | break; |
681 | case NFP_DUMPSPEC_TYPE_RTSYM: |
682 | spec_rtsym = (struct nfp_dumpspec_rtsym *)tl; |
683 | err = nfp_dump_single_rtsym(pf, spec: spec_rtsym, dump); |
684 | if (err) |
685 | return err; |
686 | break; |
687 | case NFP_DUMPSPEC_TYPE_HWINFO: |
688 | err = nfp_dump_hwinfo(pf, spec: tl, dump); |
689 | if (err) |
690 | return err; |
691 | break; |
692 | case NFP_DUMPSPEC_TYPE_HWINFO_FIELD: |
693 | err = nfp_dump_hwinfo_field(pf, spec: tl, dump); |
694 | if (err) |
695 | return err; |
696 | break; |
697 | default: |
698 | err = nfp_dump_error_tlv(spec: tl, error: -EOPNOTSUPP, dump); |
699 | if (err) |
700 | return err; |
701 | } |
702 | |
703 | return 0; |
704 | } |
705 | |
706 | static int |
707 | nfp_dump_specific_level(struct nfp_pf *pf, struct nfp_dump_tl *dump_level, |
708 | void *param) |
709 | { |
710 | struct nfp_dump_state *dump = param; |
711 | |
712 | if (dump_level->type != dump->requested_level) |
713 | return 0; |
714 | |
715 | return nfp_traverse_tlvs(pf, data: dump_level->data, |
716 | be32_to_cpu(dump_level->length), param: dump, |
717 | tlv_visit: nfp_dump_for_tlv); |
718 | } |
719 | |
720 | static int nfp_dump_populate_prolog(struct nfp_dump_state *dump) |
721 | { |
722 | struct nfp_dump_prolog *prolog = dump->p; |
723 | u32 total_size; |
724 | int err; |
725 | |
726 | total_size = ALIGN8(sizeof(*prolog)); |
727 | |
728 | err = nfp_add_tlv(type: NFP_DUMPSPEC_TYPE_PROLOG, total_tlv_sz: total_size, dump); |
729 | if (err) |
730 | return err; |
731 | |
732 | prolog->dump_level = dump->requested_level; |
733 | |
734 | return 0; |
735 | } |
736 | |
737 | int nfp_net_dump_populate_buffer(struct nfp_pf *pf, struct nfp_dumpspec *spec, |
738 | struct ethtool_dump *dump_param, void *dest) |
739 | { |
740 | struct nfp_dump_state dump; |
741 | int err; |
742 | |
743 | dump.requested_level = cpu_to_be32(dump_param->flag); |
744 | dump.dumped_size = 0; |
745 | dump.p = dest; |
746 | dump.buf_size = dump_param->len; |
747 | |
748 | err = nfp_dump_populate_prolog(dump: &dump); |
749 | if (err) |
750 | return err; |
751 | |
752 | err = nfp_traverse_tlvs(pf, data: spec->data, data_length: spec->size, param: &dump, |
753 | tlv_visit: nfp_dump_specific_level); |
754 | if (err) |
755 | return err; |
756 | |
757 | /* Set size of actual dump, to trigger warning if different from |
758 | * calculated size. |
759 | */ |
760 | dump_param->len = dump.dumped_size; |
761 | |
762 | return 0; |
763 | } |
764 | |