| // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
| /* Copyright (C) 2017-2018 Netronome Systems, Inc. */ |
| |
| #include <linux/ethtool.h> |
| #include <linux/vmalloc.h> |
| |
| #include "nfp_asm.h" |
| #include "nfp_main.h" |
| #include "nfpcore/nfp.h" |
| #include "nfpcore/nfp_nffw.h" |
| #include "nfpcore/nfp6000/nfp6000.h" |
| |
| #define NFP_DUMP_SPEC_RTSYM "_abi_dump_spec" |
| |
| #define ALIGN8(x) ALIGN(x, 8) |
| |
| enum nfp_dumpspec_type { |
| NFP_DUMPSPEC_TYPE_CPP_CSR = 0, |
| NFP_DUMPSPEC_TYPE_XPB_CSR = 1, |
| NFP_DUMPSPEC_TYPE_ME_CSR = 2, |
| NFP_DUMPSPEC_TYPE_INDIRECT_ME_CSR = 3, |
| NFP_DUMPSPEC_TYPE_RTSYM = 4, |
| NFP_DUMPSPEC_TYPE_HWINFO = 5, |
| NFP_DUMPSPEC_TYPE_FWNAME = 6, |
| NFP_DUMPSPEC_TYPE_HWINFO_FIELD = 7, |
| NFP_DUMPSPEC_TYPE_PROLOG = 10000, |
| NFP_DUMPSPEC_TYPE_ERROR = 10001, |
| }; |
| |
| /* The following structs must be carefully aligned so that they can be used to |
| * interpret the binary dumpspec and populate the dump data in a deterministic |
| * way. |
| */ |
| |
| /* generic type plus length */ |
| struct nfp_dump_tl { |
| /* New members must be added within the struct_group() macro below. */ |
| struct_group_tagged(nfp_dump_tl_hdr, hdr, |
| __be32 type; |
| __be32 length; /* chunk length to follow, aligned to 8 bytes */ |
| ); |
| char data[]; |
| }; |
| static_assert(offsetof(struct nfp_dump_tl, data) == sizeof(struct nfp_dump_tl_hdr), |
| "struct member likely outside of struct_group_tagged()"); |
| |
| /* NFP CPP parameters */ |
| struct nfp_dumpspec_cpp_isl_id { |
| u8 target; |
| u8 action; |
| u8 token; |
| u8 island; |
| }; |
| |
| struct nfp_dump_common_cpp { |
| struct nfp_dumpspec_cpp_isl_id cpp_id; |
| __be32 offset; /* address to start dump */ |
| __be32 dump_length; /* total bytes to dump, aligned to reg size */ |
| }; |
| |
| /* CSR dumpables */ |
| struct nfp_dumpspec_csr { |
| struct nfp_dump_tl_hdr tl; |
| struct nfp_dump_common_cpp cpp; |
| __be32 register_width; /* in bits */ |
| }; |
| |
| struct nfp_dumpspec_rtsym { |
| struct nfp_dump_tl_hdr tl; |
| char rtsym[]; |
| }; |
| |
| /* header for register dumpable */ |
| struct nfp_dump_csr { |
| struct nfp_dump_tl_hdr tl; |
| struct nfp_dump_common_cpp cpp; |
| __be32 register_width; /* in bits */ |
| __be32 error; /* error code encountered while reading */ |
| __be32 error_offset; /* offset being read when error occurred */ |
| }; |
| |
| struct nfp_dump_rtsym { |
| struct nfp_dump_tl_hdr tl; |
| struct nfp_dump_common_cpp cpp; |
| __be32 error; /* error code encountered while reading */ |
| u8 padded_name_length; /* pad so data starts at 8 byte boundary */ |
| char rtsym[]; |
| /* after padded_name_length, there is dump_length data */ |
| }; |
| |
| struct nfp_dump_prolog { |
| struct nfp_dump_tl_hdr tl; |
| __be32 dump_level; |
| }; |
| |
| struct nfp_dump_error { |
| struct nfp_dump_tl_hdr tl; |
| __be32 error; |
| char padding[4]; |
| char spec[]; |
| }; |
| |
| /* to track state through debug size calculation TLV traversal */ |
| struct nfp_level_size { |
| __be32 requested_level; /* input */ |
| u32 total_size; /* output */ |
| }; |
| |
| /* to track state during debug dump creation TLV traversal */ |
| struct nfp_dump_state { |
| __be32 requested_level; /* input param */ |
| u32 dumped_size; /* adds up to size of dumped data */ |
| u32 buf_size; /* size of buffer pointer to by p */ |
| void *p; /* current point in dump buffer */ |
| }; |
| |
| typedef int (*nfp_tlv_visit)(struct nfp_pf *pf, struct nfp_dump_tl *tl, |
| void *param); |
| |
| static int |
| nfp_traverse_tlvs(struct nfp_pf *pf, void *data, u32 data_length, void *param, |
| nfp_tlv_visit tlv_visit) |
| { |
| long long remaining = data_length; |
| struct nfp_dump_tl *tl; |
| u32 total_tlv_size; |
| void *p = data; |
| int err; |
| |
| while (remaining >= sizeof(*tl)) { |
| tl = p; |
| if (!tl->type && !tl->length) |
| break; |
| |
| if (be32_to_cpu(tl->length) > remaining - sizeof(*tl)) |
| return -EINVAL; |
| |
| total_tlv_size = sizeof(*tl) + be32_to_cpu(tl->length); |
| |
| /* Spec TLVs should be aligned to 4 bytes. */ |
| if (total_tlv_size % 4 != 0) |
| return -EINVAL; |
| |
| p += total_tlv_size; |
| remaining -= total_tlv_size; |
| err = tlv_visit(pf, tl, param); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static u32 nfp_get_numeric_cpp_id(struct nfp_dumpspec_cpp_isl_id *cpp_id) |
| { |
| return NFP_CPP_ISLAND_ID(cpp_id->target, cpp_id->action, cpp_id->token, |
| cpp_id->island); |
| } |
| |
| struct nfp_dumpspec * |
| nfp_net_dump_load_dumpspec(struct nfp_cpp *cpp, struct nfp_rtsym_table *rtbl) |
| { |
| const struct nfp_rtsym *specsym; |
| struct nfp_dumpspec *dumpspec; |
| int bytes_read; |
| u64 sym_size; |
| |
| specsym = nfp_rtsym_lookup(rtbl, NFP_DUMP_SPEC_RTSYM); |
| if (!specsym) |
| return NULL; |
| sym_size = nfp_rtsym_size(specsym); |
| |
| /* expected size of this buffer is in the order of tens of kilobytes */ |
| dumpspec = vmalloc(sizeof(*dumpspec) + sym_size); |
| if (!dumpspec) |
| return NULL; |
| dumpspec->size = sym_size; |
| |
| bytes_read = nfp_rtsym_read(cpp, specsym, 0, dumpspec->data, sym_size); |
| if (bytes_read != sym_size) { |
| vfree(dumpspec); |
| nfp_warn(cpp, "Debug dump specification read failed.\n"); |
| return NULL; |
| } |
| |
| return dumpspec; |
| } |
| |
| static int nfp_dump_error_tlv_size(struct nfp_dump_tl *spec) |
| { |
| return ALIGN8(sizeof(struct nfp_dump_error) + sizeof(*spec) + |
| be32_to_cpu(spec->length)); |
| } |
| |
| static int nfp_calc_fwname_tlv_size(struct nfp_pf *pf) |
| { |
| u32 fwname_len = strlen(nfp_mip_name(pf->mip)); |
| |
| return sizeof(struct nfp_dump_tl) + ALIGN8(fwname_len + 1); |
| } |
| |
| static int nfp_calc_hwinfo_field_sz(struct nfp_pf *pf, struct nfp_dump_tl *spec) |
| { |
| u32 tl_len, key_len; |
| const char *value; |
| |
| tl_len = be32_to_cpu(spec->length); |
| key_len = strnlen(spec->data, tl_len); |
| if (key_len == tl_len) |
| return nfp_dump_error_tlv_size(spec); |
| |
| value = nfp_hwinfo_lookup(pf->hwinfo, spec->data); |
| if (!value) |
| return nfp_dump_error_tlv_size(spec); |
| |
| return sizeof(struct nfp_dump_tl) + ALIGN8(key_len + strlen(value) + 2); |
| } |
| |
| static bool nfp_csr_spec_valid(struct nfp_dumpspec_csr *spec_csr) |
| { |
| u32 required_read_sz = sizeof(*spec_csr) - sizeof(spec_csr->tl); |
| u32 available_sz = be32_to_cpu(spec_csr->tl.length); |
| u32 reg_width; |
| |
| if (available_sz < required_read_sz) |
| return false; |
| |
| reg_width = be32_to_cpu(spec_csr->register_width); |
| |
| return reg_width == 32 || reg_width == 64; |
| } |
| |
| static int |
| nfp_calc_rtsym_dump_sz(struct nfp_pf *pf, struct nfp_dump_tl *spec) |
| { |
| struct nfp_rtsym_table *rtbl = pf->rtbl; |
| struct nfp_dumpspec_rtsym *spec_rtsym; |
| const struct nfp_rtsym *sym; |
| u32 tl_len, key_len; |
| |
| spec_rtsym = (struct nfp_dumpspec_rtsym *)spec; |
| tl_len = be32_to_cpu(spec->length); |
| key_len = strnlen(spec_rtsym->rtsym, tl_len); |
| if (key_len == tl_len) |
| return nfp_dump_error_tlv_size(spec); |
| |
| sym = nfp_rtsym_lookup(rtbl, spec_rtsym->rtsym); |
| if (!sym) |
| return nfp_dump_error_tlv_size(spec); |
| |
| return ALIGN8(offsetof(struct nfp_dump_rtsym, rtsym) + key_len + 1) + |
| ALIGN8(nfp_rtsym_size(sym)); |
| } |
| |
| static int |
| nfp_add_tlv_size(struct nfp_pf *pf, struct nfp_dump_tl *tl, void *param) |
| { |
| struct nfp_dumpspec_csr *spec_csr; |
| u32 *size = param; |
| u32 hwinfo_size; |
| |
| switch (be32_to_cpu(tl->type)) { |
| case NFP_DUMPSPEC_TYPE_FWNAME: |
| *size += nfp_calc_fwname_tlv_size(pf); |
| break; |
| case NFP_DUMPSPEC_TYPE_CPP_CSR: |
| case NFP_DUMPSPEC_TYPE_XPB_CSR: |
| case NFP_DUMPSPEC_TYPE_ME_CSR: |
| spec_csr = (struct nfp_dumpspec_csr *)tl; |
| if (!nfp_csr_spec_valid(spec_csr)) |
| *size += nfp_dump_error_tlv_size(tl); |
| else |
| *size += ALIGN8(sizeof(struct nfp_dump_csr)) + |
| ALIGN8(be32_to_cpu(spec_csr->cpp.dump_length)); |
| break; |
| case NFP_DUMPSPEC_TYPE_INDIRECT_ME_CSR: |
| spec_csr = (struct nfp_dumpspec_csr *)tl; |
| if (!nfp_csr_spec_valid(spec_csr)) |
| *size += nfp_dump_error_tlv_size(tl); |
| else |
| *size += ALIGN8(sizeof(struct nfp_dump_csr)) + |
| ALIGN8(be32_to_cpu(spec_csr->cpp.dump_length) * |
| NFP_IND_NUM_CONTEXTS); |
| break; |
| case NFP_DUMPSPEC_TYPE_RTSYM: |
| *size += nfp_calc_rtsym_dump_sz(pf, tl); |
| break; |
| case NFP_DUMPSPEC_TYPE_HWINFO: |
| hwinfo_size = nfp_hwinfo_get_packed_str_size(pf->hwinfo); |
| *size += sizeof(struct nfp_dump_tl) + ALIGN8(hwinfo_size); |
| break; |
| case NFP_DUMPSPEC_TYPE_HWINFO_FIELD: |
| *size += nfp_calc_hwinfo_field_sz(pf, tl); |
| break; |
| default: |
| *size += nfp_dump_error_tlv_size(tl); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| nfp_calc_specific_level_size(struct nfp_pf *pf, struct nfp_dump_tl *dump_level, |
| void *param) |
| { |
| struct nfp_level_size *lev_sz = param; |
| |
| if (dump_level->type != lev_sz->requested_level) |
| return 0; |
| |
| return nfp_traverse_tlvs(pf, dump_level->data, |
| be32_to_cpu(dump_level->length), |
| &lev_sz->total_size, nfp_add_tlv_size); |
| } |
| |
| s64 nfp_net_dump_calculate_size(struct nfp_pf *pf, struct nfp_dumpspec *spec, |
| u32 flag) |
| { |
| struct nfp_level_size lev_sz; |
| int err; |
| |
| lev_sz.requested_level = cpu_to_be32(flag); |
| lev_sz.total_size = ALIGN8(sizeof(struct nfp_dump_prolog)); |
| |
| err = nfp_traverse_tlvs(pf, spec->data, spec->size, &lev_sz, |
| nfp_calc_specific_level_size); |
| if (err) |
| return err; |
| |
| return lev_sz.total_size; |
| } |
| |
| static int nfp_add_tlv(u32 type, u32 total_tlv_sz, struct nfp_dump_state *dump) |
| { |
| struct nfp_dump_tl *tl = dump->p; |
| |
| if (total_tlv_sz > dump->buf_size) |
| return -ENOSPC; |
| |
| if (dump->buf_size - total_tlv_sz < dump->dumped_size) |
| return -ENOSPC; |
| |
| tl->type = cpu_to_be32(type); |
| tl->length = cpu_to_be32(total_tlv_sz - sizeof(*tl)); |
| |
| dump->dumped_size += total_tlv_sz; |
| dump->p += total_tlv_sz; |
| |
| return 0; |
| } |
| |
| static int |
| nfp_dump_error_tlv(struct nfp_dump_tl *spec, int error, |
| struct nfp_dump_state *dump) |
| { |
| struct nfp_dump_error *dump_header = dump->p; |
| u32 total_spec_size, total_size; |
| int err; |
| |
| total_spec_size = sizeof(*spec) + be32_to_cpu(spec->length); |
| total_size = ALIGN8(sizeof(*dump_header) + total_spec_size); |
| |
| err = nfp_add_tlv(NFP_DUMPSPEC_TYPE_ERROR, total_size, dump); |
| if (err) |
| return err; |
| |
| dump_header->error = cpu_to_be32(error); |
| memcpy(dump_header->spec, spec, total_spec_size); |
| |
| return 0; |
| } |
| |
| static int nfp_dump_fwname(struct nfp_pf *pf, struct nfp_dump_state *dump) |
| { |
| struct nfp_dump_tl *dump_header = dump->p; |
| u32 fwname_len, total_size; |
| const char *fwname; |
| int err; |
| |
| fwname = nfp_mip_name(pf->mip); |
| fwname_len = strlen(fwname); |
| total_size = sizeof(*dump_header) + ALIGN8(fwname_len + 1); |
| |
| err = nfp_add_tlv(NFP_DUMPSPEC_TYPE_FWNAME, total_size, dump); |
| if (err) |
| return err; |
| |
| memcpy(dump_header->data, fwname, fwname_len); |
| |
| return 0; |
| } |
| |
| static int |
| nfp_dump_hwinfo(struct nfp_pf *pf, struct nfp_dump_tl *spec, |
| struct nfp_dump_state *dump) |
| { |
| struct nfp_dump_tl *dump_header = dump->p; |
| u32 hwinfo_size, total_size; |
| char *hwinfo; |
| int err; |
| |
| hwinfo = nfp_hwinfo_get_packed_strings(pf->hwinfo); |
| hwinfo_size = nfp_hwinfo_get_packed_str_size(pf->hwinfo); |
| total_size = sizeof(*dump_header) + ALIGN8(hwinfo_size); |
| |
| err = nfp_add_tlv(NFP_DUMPSPEC_TYPE_HWINFO, total_size, dump); |
| if (err) |
| return err; |
| |
| memcpy(dump_header->data, hwinfo, hwinfo_size); |
| |
| return 0; |
| } |
| |
| static int nfp_dump_hwinfo_field(struct nfp_pf *pf, struct nfp_dump_tl *spec, |
| struct nfp_dump_state *dump) |
| { |
| struct nfp_dump_tl *dump_header = dump->p; |
| u32 tl_len, key_len, val_len; |
| const char *key, *value; |
| u32 total_size; |
| int err; |
| |
| tl_len = be32_to_cpu(spec->length); |
| key_len = strnlen(spec->data, tl_len); |
| if (key_len == tl_len) |
| return nfp_dump_error_tlv(spec, -EINVAL, dump); |
| |
| key = spec->data; |
| value = nfp_hwinfo_lookup(pf->hwinfo, key); |
| if (!value) |
| return nfp_dump_error_tlv(spec, -ENOENT, dump); |
| |
| val_len = strlen(value); |
| total_size = sizeof(*dump_header) + ALIGN8(key_len + val_len + 2); |
| err = nfp_add_tlv(NFP_DUMPSPEC_TYPE_HWINFO_FIELD, total_size, dump); |
| if (err) |
| return err; |
| |
| memcpy(dump_header->data, key, key_len + 1); |
| memcpy(dump_header->data + key_len + 1, value, val_len + 1); |
| |
| return 0; |
| } |
| |
| static bool is_xpb_read(struct nfp_dumpspec_cpp_isl_id *cpp_id) |
| { |
| return cpp_id->target == NFP_CPP_TARGET_ISLAND_XPB && |
| cpp_id->action == 0 && cpp_id->token == 0; |
| } |
| |
| static int |
| nfp_dump_csr_range(struct nfp_pf *pf, struct nfp_dumpspec_csr *spec_csr, |
| struct nfp_dump_state *dump) |
| { |
| struct nfp_dump_tl *spec_csr_tl = |
| container_of(&spec_csr->tl, struct nfp_dump_tl, hdr); |
| struct nfp_dump_csr *dump_header = dump->p; |
| u32 reg_sz, header_size, total_size; |
| u32 cpp_rd_addr, max_rd_addr; |
| int bytes_read; |
| void *dest; |
| u32 cpp_id; |
| int err; |
| |
| if (!nfp_csr_spec_valid(spec_csr)) |
| return nfp_dump_error_tlv(spec_csr_tl, -EINVAL, dump); |
| |
| reg_sz = be32_to_cpu(spec_csr->register_width) / BITS_PER_BYTE; |
| header_size = ALIGN8(sizeof(*dump_header)); |
| total_size = header_size + |
| ALIGN8(be32_to_cpu(spec_csr->cpp.dump_length)); |
| dest = dump->p + header_size; |
| |
| err = nfp_add_tlv(be32_to_cpu(spec_csr_tl->type), total_size, dump); |
| if (err) |
| return err; |
| |
| dump_header->cpp = spec_csr->cpp; |
| dump_header->register_width = spec_csr->register_width; |
| |
| cpp_id = nfp_get_numeric_cpp_id(&spec_csr->cpp.cpp_id); |
| cpp_rd_addr = be32_to_cpu(spec_csr->cpp.offset); |
| max_rd_addr = cpp_rd_addr + be32_to_cpu(spec_csr->cpp.dump_length); |
| |
| while (cpp_rd_addr < max_rd_addr) { |
| if (is_xpb_read(&spec_csr->cpp.cpp_id)) { |
| err = nfp_xpb_readl(pf->cpp, cpp_rd_addr, (u32 *)dest); |
| } else { |
| bytes_read = nfp_cpp_read(pf->cpp, cpp_id, cpp_rd_addr, |
| dest, reg_sz); |
| err = bytes_read == reg_sz ? 0 : -EIO; |
| } |
| if (err) { |
| dump_header->error = cpu_to_be32(err); |
| dump_header->error_offset = cpu_to_be32(cpp_rd_addr); |
| break; |
| } |
| cpp_rd_addr += reg_sz; |
| dest += reg_sz; |
| } |
| |
| return 0; |
| } |
| |
| /* Write context to CSRCtxPtr, then read from it. Then the value can be read |
| * from IndCtxStatus. |
| */ |
| static int |
| nfp_read_indirect_csr(struct nfp_cpp *cpp, |
| struct nfp_dumpspec_cpp_isl_id cpp_params, u32 offset, |
| u32 reg_sz, u32 context, void *dest) |
| { |
| u32 csr_ctx_ptr_offs; |
| u32 cpp_id; |
| int result; |
| |
| csr_ctx_ptr_offs = nfp_get_ind_csr_ctx_ptr_offs(offset); |
| cpp_id = NFP_CPP_ISLAND_ID(cpp_params.target, |
| NFP_IND_ME_REFL_WR_SIG_INIT, |
| cpp_params.token, cpp_params.island); |
| result = nfp_cpp_writel(cpp, cpp_id, csr_ctx_ptr_offs, context); |
| if (result) |
| return result; |
| |
| cpp_id = nfp_get_numeric_cpp_id(&cpp_params); |
| result = nfp_cpp_read(cpp, cpp_id, csr_ctx_ptr_offs, dest, reg_sz); |
| if (result != reg_sz) |
| return result < 0 ? result : -EIO; |
| |
| result = nfp_cpp_read(cpp, cpp_id, offset, dest, reg_sz); |
| if (result != reg_sz) |
| return result < 0 ? result : -EIO; |
| |
| return 0; |
| } |
| |
| static int |
| nfp_read_all_indirect_csr_ctx(struct nfp_cpp *cpp, |
| struct nfp_dumpspec_csr *spec_csr, u32 address, |
| u32 reg_sz, void *dest) |
| { |
| u32 ctx; |
| int err; |
| |
| for (ctx = 0; ctx < NFP_IND_NUM_CONTEXTS; ctx++) { |
| err = nfp_read_indirect_csr(cpp, spec_csr->cpp.cpp_id, address, |
| reg_sz, ctx, dest + ctx * reg_sz); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| nfp_dump_indirect_csr_range(struct nfp_pf *pf, |
| struct nfp_dumpspec_csr *spec_csr, |
| struct nfp_dump_state *dump) |
| { |
| struct nfp_dump_tl *spec_csr_tl = |
| container_of(&spec_csr->tl, struct nfp_dump_tl, hdr); |
| struct nfp_dump_csr *dump_header = dump->p; |
| u32 reg_sz, header_size, total_size; |
| u32 cpp_rd_addr, max_rd_addr; |
| u32 reg_data_length; |
| void *dest; |
| int err; |
| |
| if (!nfp_csr_spec_valid(spec_csr)) |
| return nfp_dump_error_tlv(spec_csr_tl, -EINVAL, dump); |
| |
| reg_sz = be32_to_cpu(spec_csr->register_width) / BITS_PER_BYTE; |
| header_size = ALIGN8(sizeof(*dump_header)); |
| reg_data_length = be32_to_cpu(spec_csr->cpp.dump_length) * |
| NFP_IND_NUM_CONTEXTS; |
| total_size = header_size + ALIGN8(reg_data_length); |
| dest = dump->p + header_size; |
| |
| err = nfp_add_tlv(be32_to_cpu(spec_csr_tl->type), total_size, dump); |
| if (err) |
| return err; |
| |
| dump_header->cpp = spec_csr->cpp; |
| dump_header->register_width = spec_csr->register_width; |
| |
| cpp_rd_addr = be32_to_cpu(spec_csr->cpp.offset); |
| max_rd_addr = cpp_rd_addr + be32_to_cpu(spec_csr->cpp.dump_length); |
| while (cpp_rd_addr < max_rd_addr) { |
| err = nfp_read_all_indirect_csr_ctx(pf->cpp, spec_csr, |
| cpp_rd_addr, reg_sz, dest); |
| if (err) { |
| dump_header->error = cpu_to_be32(err); |
| dump_header->error_offset = cpu_to_be32(cpp_rd_addr); |
| break; |
| } |
| cpp_rd_addr += reg_sz; |
| dest += reg_sz * NFP_IND_NUM_CONTEXTS; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| nfp_dump_single_rtsym(struct nfp_pf *pf, struct nfp_dumpspec_rtsym *spec, |
| struct nfp_dump_state *dump) |
| { |
| struct nfp_dump_tl *spec_tl = |
| container_of(&spec->tl, struct nfp_dump_tl, hdr); |
| struct nfp_dump_rtsym *dump_header = dump->p; |
| struct nfp_dumpspec_cpp_isl_id cpp_params; |
| struct nfp_rtsym_table *rtbl = pf->rtbl; |
| u32 header_size, total_size, sym_size; |
| const struct nfp_rtsym *sym; |
| u32 tl_len, key_len; |
| int bytes_read; |
| void *dest; |
| int err; |
| |
| tl_len = be32_to_cpu(spec_tl->length); |
| key_len = strnlen(spec->rtsym, tl_len); |
| if (key_len == tl_len) |
| return nfp_dump_error_tlv(spec_tl, -EINVAL, dump); |
| |
| sym = nfp_rtsym_lookup(rtbl, spec->rtsym); |
| if (!sym) |
| return nfp_dump_error_tlv(spec_tl, -ENOENT, dump); |
| |
| sym_size = nfp_rtsym_size(sym); |
| header_size = |
| ALIGN8(offsetof(struct nfp_dump_rtsym, rtsym) + key_len + 1); |
| total_size = header_size + ALIGN8(sym_size); |
| dest = dump->p + header_size; |
| |
| err = nfp_add_tlv(be32_to_cpu(spec_tl->type), total_size, dump); |
| if (err) |
| return err; |
| |
| dump_header->padded_name_length = |
| header_size - offsetof(struct nfp_dump_rtsym, rtsym); |
| memcpy(dump_header->rtsym, spec->rtsym, key_len + 1); |
| dump_header->cpp.dump_length = cpu_to_be32(sym_size); |
| |
| if (sym->type != NFP_RTSYM_TYPE_ABS) { |
| cpp_params.target = sym->target; |
| cpp_params.action = NFP_CPP_ACTION_RW; |
| cpp_params.token = 0; |
| cpp_params.island = sym->domain; |
| dump_header->cpp.cpp_id = cpp_params; |
| dump_header->cpp.offset = cpu_to_be32(sym->addr); |
| } |
| |
| bytes_read = nfp_rtsym_read(pf->cpp, sym, 0, dest, sym_size); |
| if (bytes_read != sym_size) { |
| if (bytes_read >= 0) |
| bytes_read = -EIO; |
| dump_header->error = cpu_to_be32(bytes_read); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| nfp_dump_for_tlv(struct nfp_pf *pf, struct nfp_dump_tl *tl, void *param) |
| { |
| struct nfp_dumpspec_rtsym *spec_rtsym; |
| struct nfp_dump_state *dump = param; |
| struct nfp_dumpspec_csr *spec_csr; |
| int err; |
| |
| switch (be32_to_cpu(tl->type)) { |
| case NFP_DUMPSPEC_TYPE_FWNAME: |
| err = nfp_dump_fwname(pf, dump); |
| if (err) |
| return err; |
| break; |
| case NFP_DUMPSPEC_TYPE_CPP_CSR: |
| case NFP_DUMPSPEC_TYPE_XPB_CSR: |
| case NFP_DUMPSPEC_TYPE_ME_CSR: |
| spec_csr = (struct nfp_dumpspec_csr *)tl; |
| err = nfp_dump_csr_range(pf, spec_csr, dump); |
| if (err) |
| return err; |
| break; |
| case NFP_DUMPSPEC_TYPE_INDIRECT_ME_CSR: |
| spec_csr = (struct nfp_dumpspec_csr *)tl; |
| err = nfp_dump_indirect_csr_range(pf, spec_csr, dump); |
| if (err) |
| return err; |
| break; |
| case NFP_DUMPSPEC_TYPE_RTSYM: |
| spec_rtsym = (struct nfp_dumpspec_rtsym *)tl; |
| err = nfp_dump_single_rtsym(pf, spec_rtsym, dump); |
| if (err) |
| return err; |
| break; |
| case NFP_DUMPSPEC_TYPE_HWINFO: |
| err = nfp_dump_hwinfo(pf, tl, dump); |
| if (err) |
| return err; |
| break; |
| case NFP_DUMPSPEC_TYPE_HWINFO_FIELD: |
| err = nfp_dump_hwinfo_field(pf, tl, dump); |
| if (err) |
| return err; |
| break; |
| default: |
| err = nfp_dump_error_tlv(tl, -EOPNOTSUPP, dump); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| nfp_dump_specific_level(struct nfp_pf *pf, struct nfp_dump_tl *dump_level, |
| void *param) |
| { |
| struct nfp_dump_state *dump = param; |
| |
| if (dump_level->type != dump->requested_level) |
| return 0; |
| |
| return nfp_traverse_tlvs(pf, dump_level->data, |
| be32_to_cpu(dump_level->length), dump, |
| nfp_dump_for_tlv); |
| } |
| |
| static int nfp_dump_populate_prolog(struct nfp_dump_state *dump) |
| { |
| struct nfp_dump_prolog *prolog = dump->p; |
| u32 total_size; |
| int err; |
| |
| total_size = ALIGN8(sizeof(*prolog)); |
| |
| err = nfp_add_tlv(NFP_DUMPSPEC_TYPE_PROLOG, total_size, dump); |
| if (err) |
| return err; |
| |
| prolog->dump_level = dump->requested_level; |
| |
| return 0; |
| } |
| |
| int nfp_net_dump_populate_buffer(struct nfp_pf *pf, struct nfp_dumpspec *spec, |
| struct ethtool_dump *dump_param, void *dest) |
| { |
| struct nfp_dump_state dump; |
| int err; |
| |
| dump.requested_level = cpu_to_be32(dump_param->flag); |
| dump.dumped_size = 0; |
| dump.p = dest; |
| dump.buf_size = dump_param->len; |
| |
| err = nfp_dump_populate_prolog(&dump); |
| if (err) |
| return err; |
| |
| err = nfp_traverse_tlvs(pf, spec->data, spec->size, &dump, |
| nfp_dump_specific_level); |
| if (err) |
| return err; |
| |
| /* Set size of actual dump, to trigger warning if different from |
| * calculated size. |
| */ |
| dump_param->len = dump.dumped_size; |
| |
| return 0; |
| } |