root/drivers/net/ethernet/netronome/nfp/nfp_net_debugdump.c
// 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;
}