root/usr/src/common/mc/zen_umc/zen_umc_dump.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2025 Oxide Computer Company
 */

/*
 * Dump and restore logic for external processing. Dump generally runs in kernel
 * context from a well formed structure created by the driver. Restore is used
 * in userland as part of testing and related.
 *
 * Note, there are a lot of fields in these structures that are not serialized
 * because they are not used as part of the decoder (e.g. the various raw values
 * which are captured to aid future debugging).
 */

#include "zen_umc.h"
#ifndef _KERNEL
#include <string.h>
#include <strings.h>
#include <libnvpair.h>
#endif

static nvlist_t *
zen_umc_dump_dram_rule(df_dram_rule_t *rule)
{
        nvlist_t *nvl;

        nvl = fnvlist_alloc();
        fnvlist_add_uint32(nvl, "ddr_flags", rule->ddr_flags);
        fnvlist_add_uint64(nvl, "ddr_base", rule->ddr_base);
        fnvlist_add_uint64(nvl, "ddr_limit", rule->ddr_limit);
        fnvlist_add_uint16(nvl, "ddr_dest_fabid", rule->ddr_dest_fabid);
        fnvlist_add_uint8(nvl, "ddr_sock_ileave_bits",
            rule->ddr_sock_ileave_bits);
        fnvlist_add_uint8(nvl, "ddr_die_ileave_bits",
            rule->ddr_die_ileave_bits);
        fnvlist_add_uint8(nvl, "ddr_addr_start", rule->ddr_addr_start);
        fnvlist_add_uint8(nvl, "ddr_remap_ent", rule->ddr_remap_ent);
        fnvlist_add_uint32(nvl, "ddr_chan_ileave", rule->ddr_chan_ileave);

        return (nvl);
}

static nvlist_t *
zen_umc_dump_cs(umc_cs_t *cs)
{
        nvlist_t *nvl = fnvlist_alloc();
        nvlist_t *base = fnvlist_alloc();
        nvlist_t *sec = fnvlist_alloc();

        fnvlist_add_uint32(nvl, "ucs_flags", cs->ucs_flags);
        fnvlist_add_uint64(base, "udb_base", cs->ucs_base.udb_base);
        fnvlist_add_uint8(base, "udb_valid", cs->ucs_base.udb_valid);
        fnvlist_add_nvlist(nvl, "ucs_base", base);
        nvlist_free(base);
        fnvlist_add_uint64(sec, "udb_base", cs->ucs_sec.udb_base);
        fnvlist_add_uint8(sec, "udb_valid", cs->ucs_sec.udb_valid);
        fnvlist_add_nvlist(nvl, "ucs_sec", sec);
        nvlist_free(sec);
        fnvlist_add_uint64(nvl, "ucs_base_mask", cs->ucs_base_mask);
        fnvlist_add_uint64(nvl, "ucs_sec_mask", cs->ucs_sec_mask);
        fnvlist_add_uint8(nvl, "ucs_nrow_lo", cs->ucs_nrow_lo);
        fnvlist_add_uint8(nvl, "ucs_nrow_hi", cs->ucs_nrow_hi);
        fnvlist_add_uint8(nvl, "ucs_nbank_groups", cs->ucs_nbank_groups);
        fnvlist_add_uint8(nvl, "ucs_cs_xor", cs->ucs_cs_xor);
        fnvlist_add_uint8(nvl, "ucs_row_hi_bit", cs->ucs_row_hi_bit);
        fnvlist_add_uint8(nvl, "ucs_row_low_bit", cs->ucs_row_low_bit);
        fnvlist_add_uint8_array(nvl, "ucs_bank_bits", cs->ucs_bank_bits,
            cs->ucs_nbanks);
        fnvlist_add_uint8_array(nvl, "ucs_col_bits", cs->ucs_col_bits,
            cs->ucs_ncol);
        fnvlist_add_uint8(nvl, "ucs_inv_msbs", cs->ucs_inv_msbs);
        fnvlist_add_uint8_array(nvl, "ucs_rm_bits", cs->ucs_rm_bits,
            cs->ucs_nrm);
        fnvlist_add_uint8(nvl, "ucs_inv_msbs_sec", cs->ucs_inv_msbs_sec);
        fnvlist_add_uint8_array(nvl, "ucs_rm_bits_sec", cs->ucs_rm_bits_sec,
            cs->ucs_nrm);
        fnvlist_add_uint8(nvl, "ucs_subchan", cs->ucs_subchan);

        return (nvl);
}

static nvlist_t *
zen_umc_dump_dimm(umc_dimm_t *dimm)
{
        nvlist_t *nvl = fnvlist_alloc();
        nvlist_t *cs[ZEN_UMC_MAX_CS_PER_DIMM];

        fnvlist_add_uint32(nvl, "ud_flags", dimm->ud_flags);
        fnvlist_add_uint32(nvl, "ud_width", dimm->ud_width);
        fnvlist_add_uint32(nvl, "ud_kind", dimm->ud_kind);
        fnvlist_add_uint32(nvl, "ud_dimmno", dimm->ud_dimmno);

        for (uint_t i = 0; i < ZEN_UMC_MAX_CS_PER_DIMM; i++) {
                cs[i] = zen_umc_dump_cs(&dimm->ud_cs[i]);
        }
        fnvlist_add_nvlist_array(nvl, "ud_cs", cs, ZEN_UMC_MAX_CS_PER_DIMM);
        for (uint_t i = 0; i < ZEN_UMC_MAX_CS_PER_DIMM; i++) {
                nvlist_free(cs[i]);
        }

        return (nvl);
}

static nvlist_t *
zen_umc_dump_chan_hash(umc_chan_hash_t *hash)
{
        nvlist_t *nvl = fnvlist_alloc();

        fnvlist_add_uint32(nvl, "uch_flags", hash->uch_flags);

        if (hash->uch_flags & UMC_CHAN_HASH_F_BANK) {
                nvlist_t *banks[ZEN_UMC_MAX_CHAN_BANK_HASH];
                for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_BANK_HASH; i++) {
                        banks[i] = fnvlist_alloc();

                        fnvlist_add_uint32(banks[i], "ubh_row_xor",
                            hash->uch_bank_hashes[i].ubh_row_xor);
                        fnvlist_add_uint32(banks[i], "ubh_col_xor",
                            hash->uch_bank_hashes[i].ubh_col_xor);
                        fnvlist_add_boolean_value(banks[i], "ubh_en",
                            hash->uch_bank_hashes[i].ubh_en);
                }
                fnvlist_add_nvlist_array(nvl, "uch_bank_hashes", banks,
                    ZEN_UMC_MAX_CHAN_BANK_HASH);

                for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_BANK_HASH; i++) {
                        nvlist_free(banks[i]);
                }
        }

        if (hash->uch_flags & UMC_CHAN_HASH_F_RM) {
                nvlist_t *rm[ZEN_UMC_MAX_CHAN_RM_HASH];
                for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_RM_HASH; i++) {
                        rm[i] = fnvlist_alloc();

                        fnvlist_add_uint64(rm[i], "uah_addr_xor",
                            hash->uch_rm_hashes[i].uah_addr_xor);
                        fnvlist_add_boolean_value(rm[i], "uah_en",
                            hash->uch_rm_hashes[i].uah_en);
                }
                fnvlist_add_nvlist_array(nvl, "uch_rm_hashes", rm,
                    ZEN_UMC_MAX_CHAN_RM_HASH);

                for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_RM_HASH; i++) {
                        nvlist_free(rm[i]);
                }
        }

        if (hash->uch_flags & UMC_CHAN_HASH_F_CS) {
                nvlist_t *cs[ZEN_UMC_MAX_CHAN_CS_HASH];
                for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_CS_HASH; i++) {
                        cs[i] = fnvlist_alloc();

                        fnvlist_add_uint64(cs[i], "uah_addr_xor",
                            hash->uch_rm_hashes[i].uah_addr_xor);
                        fnvlist_add_boolean_value(cs[i], "uah_en",
                            hash->uch_rm_hashes[i].uah_en);
                }
                fnvlist_add_nvlist_array(nvl, "uch_cs_hashes", cs,
                    ZEN_UMC_MAX_CHAN_CS_HASH);

                for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_CS_HASH; i++) {
                        nvlist_free(cs[i]);
                }
        }

        if (hash->uch_flags & UMC_CHAN_HASH_F_PC) {
                nvlist_t *pc = fnvlist_alloc();

                fnvlist_add_uint32(pc, "uph_row_xor",
                    hash->uch_pc_hash.uph_row_xor);
                fnvlist_add_uint32(pc, "uph_col_xor",
                    hash->uch_pc_hash.uph_col_xor);
                fnvlist_add_uint8(pc, "uph_bank_xor",
                    hash->uch_pc_hash.uph_bank_xor);
                fnvlist_add_boolean_value(pc, "uph_en",
                    hash->uch_pc_hash.uph_en);

                fnvlist_add_nvlist(nvl, "uch_pch_hash", pc);
                fnvlist_free(pc);

        }

        return (nvl);
}

static nvlist_t *
zen_umc_dump_chan(zen_umc_chan_t *chan)
{
        nvlist_t *nvl, *hash;
        nvlist_t *rules[ZEN_UMC_MAX_CS_RULES];
        nvlist_t *offsets[ZEN_UMC_MAX_DRAM_OFFSET];
        nvlist_t *dimms[ZEN_UMC_MAX_DIMMS];

        nvl = fnvlist_alloc();
        fnvlist_add_uint32(nvl, "chan_flags", chan->chan_flags);
        fnvlist_add_uint32(nvl, "chan_fabid", chan->chan_fabid);
        fnvlist_add_uint32(nvl, "chan_instid", chan->chan_instid);
        fnvlist_add_uint32(nvl, "chan_logid", chan->chan_logid);
        fnvlist_add_uint32(nvl, "chan_np2_space0", chan->chan_np2_space0);
        fnvlist_add_uint32(nvl, "chan_type", chan->chan_type);

        for (uint_t i = 0; i < chan->chan_nrules; i++) {
                rules[i] = zen_umc_dump_dram_rule(&chan->chan_rules[i]);
        }

        for (uint_t i = 0; i < chan->chan_nrules - 1; i++) {
                offsets[i] = fnvlist_alloc();
                fnvlist_add_boolean_value(offsets[i], "cho_valid",
                    chan->chan_offsets[i].cho_valid);
                fnvlist_add_uint64(offsets[i], "cho_offset",
                    chan->chan_offsets[i].cho_offset);
        }

        for (uint_t i = 0; i < ZEN_UMC_MAX_DIMMS; i++) {
                dimms[i] = zen_umc_dump_dimm(&chan->chan_dimms[i]);
        }

        fnvlist_add_nvlist_array(nvl, "chan_rules", rules, chan->chan_nrules);
        fnvlist_add_nvlist_array(nvl, "chan_offsets", offsets,
            chan->chan_nrules - 1);
        fnvlist_add_nvlist_array(nvl, "chan_dimms", dimms, ZEN_UMC_MAX_DIMMS);
        hash = zen_umc_dump_chan_hash(&chan->chan_hash);
        fnvlist_add_nvlist(nvl, "chan_hash", hash);

        for (uint_t i = 0; i < chan->chan_nrules; i++) {
                nvlist_free(rules[i]);
        }

        for (uint_t i = 0; i < chan->chan_nrules - 1; i++) {
                nvlist_free(offsets[i]);
        }

        for (uint_t i = 0; i < ZEN_UMC_MAX_DIMMS; i++) {
                nvlist_free(dimms[i]);
        }

        nvlist_free(hash);

        return (nvl);
}

static nvlist_t *
zen_umc_dump_df(zen_umc_df_t *df)
{
        nvlist_t *nvl;
        nvlist_t *rules[ZEN_UMC_MAX_DRAM_RULES];
        nvlist_t *remap[ZEN_UMC_MAX_CS_REMAPS];
        nvlist_t *chan[ZEN_UMC_MAX_UMCS];

        nvl = fnvlist_alloc();
        fnvlist_add_uint32(nvl, "zud_flags", df->zud_flags);
        fnvlist_add_uint32(nvl, "zud_dfno", df->zud_dfno);
        fnvlist_add_uint32(nvl, "zud_ccm_inst", df->zud_ccm_inst);
        fnvlist_add_uint64(nvl, "zud_hole_base", df->zud_hole_base);

        for (uint_t i = 0; i < df->zud_dram_nrules; i++) {
                rules[i] = zen_umc_dump_dram_rule(&df->zud_rules[i]);
        }

        for (uint_t i = 0; i < df->zud_cs_nremap; i++) {
                remap[i] = fnvlist_alloc();
                fnvlist_add_uint16_array(remap[i], "csr_remaps",
                    df->zud_remap[i].csr_remaps, df->zud_remap[i].csr_nremaps);
        }

        for (uint_t i = 0; i < df->zud_nchan; i++) {
                chan[i] = zen_umc_dump_chan(&df->zud_chan[i]);
        }

        fnvlist_add_nvlist_array(nvl, "zud_rules", rules, df->zud_dram_nrules);
        fnvlist_add_nvlist_array(nvl, "zud_remap", remap, df->zud_cs_nremap);
        fnvlist_add_nvlist_array(nvl, "zud_chan", chan, df->zud_nchan);

        for (uint_t i = 0; i < df->zud_dram_nrules; i++) {
                nvlist_free(rules[i]);
        }

        for (uint_t i = 0; i < df->zud_cs_nremap; i++) {
                nvlist_free(remap[i]);
        }

        for (uint_t i = 0; i < df->zud_nchan; i++) {
                nvlist_free(chan[i]);
        }

        return (nvl);
}

nvlist_t *
zen_umc_dump_decoder(zen_umc_t *umc)
{
        nvlist_t *nvl, *umc_nvl, *decomp;
        nvlist_t *dfs[ZEN_UMC_MAX_DFS];

        nvl = fnvlist_alloc();
        fnvlist_add_uint32(nvl, "mc_dump_version", 0);
        fnvlist_add_string(nvl, "mc_dump_driver", "zen_umc");

        umc_nvl = fnvlist_alloc();
        fnvlist_add_uint64(umc_nvl, "umc_tom", umc->umc_tom);
        fnvlist_add_uint64(umc_nvl, "umc_tom2", umc->umc_tom2);
        fnvlist_add_uint32(umc_nvl, "umc_family", umc->umc_family);
        fnvlist_add_uint32(umc_nvl, "umc_df_rev", umc->umc_df_rev);

        decomp = fnvlist_alloc();
        fnvlist_add_uint32(decomp, "dfd_sock_mask",
            umc->umc_decomp.dfd_sock_mask);
        fnvlist_add_uint32(decomp, "dfd_die_mask",
            umc->umc_decomp.dfd_die_mask);
        fnvlist_add_uint32(decomp, "dfd_node_mask",
            umc->umc_decomp.dfd_node_mask);
        fnvlist_add_uint32(decomp, "dfd_comp_mask",
            umc->umc_decomp.dfd_comp_mask);
        fnvlist_add_uint8(decomp, "dfd_sock_shift",
            umc->umc_decomp.dfd_sock_shift);
        fnvlist_add_uint8(decomp, "dfd_die_shift",
            umc->umc_decomp.dfd_die_shift);
        fnvlist_add_uint8(decomp, "dfd_node_shift",
            umc->umc_decomp.dfd_node_shift);
        fnvlist_add_uint8(decomp, "dfd_comp_shift",
            umc->umc_decomp.dfd_comp_shift);
        fnvlist_add_nvlist(umc_nvl, "umc_decomp", decomp);
        nvlist_free(decomp);

        for (uint_t i = 0; i < umc->umc_ndfs; i++) {
                dfs[i] = zen_umc_dump_df(&umc->umc_dfs[i]);
        }

        fnvlist_add_nvlist_array(umc_nvl, "umc_dfs", dfs, umc->umc_ndfs);
        fnvlist_add_nvlist(nvl, "zen_umc", umc_nvl);
        for (uint_t i = 0; i < umc->umc_ndfs; i++) {
                nvlist_free(dfs[i]);
        }

        return (nvl);
}

static boolean_t
zen_umc_restore_dram_rule(nvlist_t *nvl, df_dram_rule_t *rule)
{
        return (nvlist_lookup_pairs(nvl, 0,
            "ddr_flags", DATA_TYPE_UINT32, &rule->ddr_flags,
            "ddr_base", DATA_TYPE_UINT64, &rule->ddr_base,
            "ddr_limit", DATA_TYPE_UINT64, &rule->ddr_limit,
            "ddr_dest_fabid", DATA_TYPE_UINT16, &rule->ddr_dest_fabid,
            "ddr_sock_ileave_bits", DATA_TYPE_UINT8,
            &rule->ddr_sock_ileave_bits,
            "ddr_die_ileave_bits", DATA_TYPE_UINT8, &rule->ddr_die_ileave_bits,
            "ddr_addr_start", DATA_TYPE_UINT8, &rule->ddr_addr_start,
            "ddr_remap_ent", DATA_TYPE_UINT8, &rule->ddr_remap_ent,
            "ddr_chan_ileave", DATA_TYPE_UINT32, &rule->ddr_chan_ileave,
            NULL) == 0);
}

static boolean_t
zen_umc_restore_cs(nvlist_t *nvl, umc_cs_t *cs)
{
        nvlist_t *base, *sec;
        uint8_t *bank_bits, *col_bits, *rm_bits, *rm_bits_sec;
        uint_t nbanks, ncols, nrm, nrm_sec;

        if (nvlist_lookup_pairs(nvl, 0,
            "ucs_flags", DATA_TYPE_UINT32, &cs->ucs_flags,
            "ucs_base", DATA_TYPE_NVLIST, &base,
            "ucs_sec", DATA_TYPE_NVLIST, &sec,
            "ucs_base_mask", DATA_TYPE_UINT64, &cs->ucs_base_mask,
            "ucs_sec_mask", DATA_TYPE_UINT64, &cs->ucs_sec_mask,
            "ucs_nrow_lo", DATA_TYPE_UINT8, &cs->ucs_nrow_lo,
            "ucs_nrow_hi", DATA_TYPE_UINT8, &cs->ucs_nrow_hi,
            "ucs_nbank_groups", DATA_TYPE_UINT8, &cs->ucs_nbank_groups,
            "ucs_cs_xor", DATA_TYPE_UINT8, &cs->ucs_cs_xor,
            "ucs_row_hi_bit", DATA_TYPE_UINT8, &cs->ucs_row_hi_bit,
            "ucs_row_low_bit", DATA_TYPE_UINT8, &cs->ucs_row_low_bit,
            "ucs_bank_bits", DATA_TYPE_UINT8_ARRAY, &bank_bits, &nbanks,
            "ucs_col_bits", DATA_TYPE_UINT8_ARRAY, &col_bits, &ncols,
            "ucs_inv_msbs", DATA_TYPE_UINT8, &cs->ucs_inv_msbs,
            "ucs_rm_bits", DATA_TYPE_UINT8_ARRAY, &rm_bits, &nrm,
            "ucs_inv_msbs_sec", DATA_TYPE_UINT8, &cs->ucs_inv_msbs_sec,
            "ucs_rm_bits_sec", DATA_TYPE_UINT8_ARRAY, &rm_bits_sec, &nrm_sec,
            "ucs_subchan", DATA_TYPE_UINT8, &cs->ucs_subchan,
            NULL) != 0) {
                return (B_FALSE);
        }

        if (nbanks > ZEN_UMC_MAX_BANK_BITS ||
            ncols > ZEN_UMC_MAX_COL_BITS ||
            nrm > ZEN_UMC_MAX_RM_BITS ||
            nrm != nrm_sec) {
                return (B_FALSE);
        }

        cs->ucs_nbanks = nbanks;
        cs->ucs_ncol = ncols;
        cs->ucs_nrm = nrm;

        bcopy(bank_bits, cs->ucs_bank_bits, cs->ucs_nbanks *
            sizeof (uint8_t));
        bcopy(col_bits, cs->ucs_col_bits, cs->ucs_ncol * sizeof (uint8_t));
        bcopy(rm_bits, cs->ucs_rm_bits, cs->ucs_nrm * sizeof (uint8_t));
        bcopy(rm_bits_sec, cs->ucs_rm_bits_sec, cs->ucs_nrm *
            sizeof (uint8_t));

        if (nvlist_lookup_pairs(base, 0,
            "udb_base", DATA_TYPE_UINT64, &cs->ucs_base.udb_base,
            "udb_valid", DATA_TYPE_UINT8, &cs->ucs_base.udb_valid,
            NULL) != 0) {
                return (B_FALSE);
        }

        if (nvlist_lookup_pairs(sec, 0,
            "udb_base", DATA_TYPE_UINT64, &cs->ucs_sec.udb_base,
            "udb_valid", DATA_TYPE_UINT8, &cs->ucs_sec.udb_valid,
            NULL) != 0) {
                return (B_FALSE);
        }

        return (B_TRUE);
}

static boolean_t
zen_umc_restore_dimm(nvlist_t *nvl, umc_dimm_t *dimm)
{
        nvlist_t **cs;
        uint_t ncs;

        if (nvlist_lookup_pairs(nvl, 0,
            "ud_flags", DATA_TYPE_UINT32, &dimm->ud_flags,
            "ud_width", DATA_TYPE_UINT32, &dimm->ud_width,
            "ud_kind", DATA_TYPE_UINT32, &dimm->ud_kind,
            "ud_dimmno", DATA_TYPE_UINT32, &dimm->ud_dimmno,
            "ud_cs", DATA_TYPE_NVLIST_ARRAY, &cs, &ncs,
            NULL) != 0) {
                return (B_FALSE);
        }

        if (ncs != ZEN_UMC_MAX_CS_PER_DIMM) {
                return (B_FALSE);
        }

        for (uint_t i = 0; i < ZEN_UMC_MAX_CS_PER_DIMM; i++) {
                if (!zen_umc_restore_cs(cs[i], &dimm->ud_cs[i])) {
                        return (B_FALSE);
                }
        }

        return (B_TRUE);
}

static boolean_t
zen_umc_restore_hash(nvlist_t *nvl, umc_chan_hash_t *hash)
{
        if (nvlist_lookup_uint32(nvl, "uch_flags", &hash->uch_flags) != 0) {
                return (B_FALSE);
        }

        if (hash->uch_flags & UMC_CHAN_HASH_F_BANK) {
                nvlist_t **banks;
                uint_t nbanks;

                if (nvlist_lookup_nvlist_array(nvl, "uch_bank_hashes", &banks,
                    &nbanks) != 0) {
                        return (B_FALSE);
                }

                if (nbanks != ZEN_UMC_MAX_CHAN_BANK_HASH) {
                        return (B_FALSE);
                }

                for (uint_t i = 0; i < nbanks; i++) {
                        if (nvlist_lookup_pairs(banks[i], 0,
                            "ubh_row_xor", DATA_TYPE_UINT32,
                            &hash->uch_bank_hashes[i].ubh_row_xor,
                            "ubh_col_xor", DATA_TYPE_UINT32,
                            &hash->uch_bank_hashes[i].ubh_col_xor,
                            "ubh_en", DATA_TYPE_BOOLEAN_VALUE,
                            &hash->uch_bank_hashes[i].ubh_en,
                            NULL) != 0) {
                                return (B_FALSE);
                        }
                }
        }

        if (hash->uch_flags & UMC_CHAN_HASH_F_RM) {
                nvlist_t **rm;
                uint_t nrm;

                if (nvlist_lookup_nvlist_array(nvl, "uch_rm_hashes", &rm,
                    &nrm) != 0) {
                        return (B_FALSE);
                }

                if (nrm != ZEN_UMC_MAX_CHAN_RM_HASH) {
                        return (B_FALSE);
                }

                for (uint_t i = 0; i < nrm; i++) {
                        if (nvlist_lookup_pairs(rm[i], 0,
                            "uah_addr_xor", DATA_TYPE_UINT64,
                            &hash->uch_rm_hashes[i].uah_addr_xor,
                            "uah_en", DATA_TYPE_BOOLEAN_VALUE,
                            &hash->uch_rm_hashes[i].uah_en,
                            NULL) != 0) {
                                return (B_FALSE);
                        }
                }
        }

        if (hash->uch_flags & UMC_CHAN_HASH_F_CS) {
                nvlist_t **cs;
                uint_t ncs;

                if (nvlist_lookup_nvlist_array(nvl, "uch_cs_hashes", &cs,
                    &ncs) != 0) {
                        return (B_FALSE);
                }

                if (ncs != ZEN_UMC_MAX_CHAN_CS_HASH) {
                        return (B_FALSE);
                }

                for (uint_t i = 0; i < ncs; i++) {
                        if (nvlist_lookup_pairs(cs[i], 0,
                            "uah_addr_xor", DATA_TYPE_UINT64,
                            &hash->uch_cs_hashes[i].uah_addr_xor,
                            "uah_en", DATA_TYPE_BOOLEAN_VALUE,
                            &hash->uch_cs_hashes[i].uah_en,
                            NULL) != 0) {
                                return (B_FALSE);
                        }
                }
        }

        if (hash->uch_flags & UMC_CHAN_HASH_F_PC) {
                nvlist_t *pc;

                if (nvlist_lookup_nvlist(nvl, "uch_pch_hash", &pc) != 0) {
                        return (B_FALSE);
                }

                if (nvlist_lookup_pairs(pc, 0,
                    "uph_row_xor", DATA_TYPE_UINT32,
                    &hash->uch_pc_hash.uph_row_xor,
                    "uph_col_xor", DATA_TYPE_UINT32,
                    &hash->uch_pc_hash.uph_col_xor,
                    "uph_bank_xor", DATA_TYPE_UINT32,
                    &hash->uch_pc_hash.uph_bank_xor,
                    "uph_en", DATA_TYPE_BOOLEAN_VALUE,
                    &hash->uch_pc_hash.uph_en,
                    NULL) != 0) {
                        return (B_FALSE);
                }
        }
        return (B_TRUE);
}

static boolean_t
zen_umc_restore_chan(nvlist_t *nvl, zen_umc_chan_t *chan)
{
        uint_t noffsets, ndimms;
        nvlist_t **rules, **offsets, **dimms, *hash;

        if (nvlist_lookup_pairs(nvl, 0,
            "chan_flags", DATA_TYPE_UINT32, &chan->chan_flags,
            "chan_fabid", DATA_TYPE_UINT32, &chan->chan_fabid,
            "chan_instid", DATA_TYPE_UINT32, &chan->chan_instid,
            "chan_logid", DATA_TYPE_UINT32, &chan->chan_logid,
            "chan_rules", DATA_TYPE_NVLIST_ARRAY, &rules, &chan->chan_nrules,
            "chan_np2_space0", DATA_TYPE_UINT32, &chan->chan_np2_space0,
            "chan_type", DATA_TYPE_UINT32, &chan->chan_np2_space0,
            "chan_offsets", DATA_TYPE_NVLIST_ARRAY, &offsets, &noffsets,
            "chan_dimms", DATA_TYPE_NVLIST_ARRAY, &dimms, &ndimms,
            "chan_hash", DATA_TYPE_NVLIST, &hash,
            NULL) != 0) {
                return (B_FALSE);
        }

        if (chan->chan_nrules > ZEN_UMC_MAX_CS_RULES ||
            noffsets != chan->chan_nrules - 1 || ndimms != ZEN_UMC_MAX_DIMMS) {
                return (B_FALSE);
        }

        for (uint_t i = 0; i < chan->chan_nrules; i++) {
                if (!zen_umc_restore_dram_rule(rules[i],
                    &chan->chan_rules[i])) {
                        return (B_FALSE);
                }
        }

        for (uint_t i = 0; i < chan->chan_nrules - 1; i++) {
                chan_offset_t *coff = &chan->chan_offsets[i];

                if (nvlist_lookup_pairs(offsets[i], 0,
                    "cho_valid", DATA_TYPE_BOOLEAN_VALUE, &coff->cho_valid,
                    "cho_offset", DATA_TYPE_UINT64, &coff->cho_offset,
                    NULL) != 0) {
                        return (B_FALSE);
                }
        }

        for (uint_t i = 0; i < ZEN_UMC_MAX_DIMMS; i++) {
                if (!zen_umc_restore_dimm(dimms[i], &chan->chan_dimms[i])) {
                        return (B_FALSE);
                }
        }

        if (!zen_umc_restore_hash(hash, &chan->chan_hash)) {
                return (B_FALSE);
        }

        return (B_TRUE);
}

static boolean_t
zen_umc_restore_df(nvlist_t *nvl, zen_umc_df_t *df)
{
        nvlist_t **rules, **chan, **remap;

        if (nvlist_lookup_pairs(nvl, 0,
            "zud_flags", DATA_TYPE_UINT32, &df->zud_flags,
            "zud_dfno", DATA_TYPE_UINT32, &df->zud_dfno,
            "zud_ccm_inst", DATA_TYPE_UINT32, &df->zud_ccm_inst,
            "zud_hole_base", DATA_TYPE_UINT64, &df->zud_hole_base,
            "zud_rules", DATA_TYPE_NVLIST_ARRAY, &rules, &df->zud_dram_nrules,
            "zud_remap", DATA_TYPE_NVLIST_ARRAY, &remap, &df->zud_cs_nremap,
            "zud_chan", DATA_TYPE_NVLIST_ARRAY, &chan, &df->zud_nchan,
            NULL != 0) ||
            df->zud_dram_nrules > ZEN_UMC_MAX_DRAM_RULES ||
            df->zud_cs_nremap > ZEN_UMC_MAX_CS_REMAPS ||
            df->zud_nchan > ZEN_UMC_MAX_UMCS) {
                return (B_FALSE);
        }

        for (uint_t i = 0; i < df->zud_dram_nrules; i++) {
                if (!zen_umc_restore_dram_rule(rules[i], &df->zud_rules[i])) {
                        return (B_FALSE);
                }
        }

        for (uint_t i = 0; i < df->zud_cs_nremap; i++) {
                uint16_t *u16p;
                if (nvlist_lookup_uint16_array(remap[i], "csr_remaps", &u16p,
                    &df->zud_remap[i].csr_nremaps) != 0 ||
                    df->zud_remap[i].csr_nremaps > ZEN_UMC_MAX_REMAP_ENTS) {
                        return (B_FALSE);
                }
                bcopy(u16p, df->zud_remap[i].csr_remaps,
                    df->zud_remap[i].csr_nremaps);
        }

        for (uint_t i = 0; i < df->zud_nchan; i++) {
                if (!zen_umc_restore_chan(chan[i], &df->zud_chan[i])) {
                        return (B_FALSE);
                }
        }

        return (B_TRUE);
}

boolean_t
zen_umc_restore_decoder(nvlist_t *nvl, zen_umc_t *umc)
{
        uint32_t vers;
        char *driver;
        nvlist_t *umc_nvl, *decomp, **dfs;
        bzero(umc, sizeof (zen_umc_t));

        if (nvlist_lookup_pairs(nvl, 0,
            "mc_dump_version", DATA_TYPE_UINT32, &vers,
            "mc_dump_driver", DATA_TYPE_STRING, &driver,
            NULL) != 0 || vers != 0 || strcmp(driver, "zen_umc") != 0 ||
            nvlist_lookup_nvlist(nvl, "zen_umc", &umc_nvl) != 0) {
                return (B_FALSE);
        }

        if (nvlist_lookup_pairs(umc_nvl, 0,
            "umc_tom", DATA_TYPE_UINT64, &umc->umc_tom,
            "umc_tom2", DATA_TYPE_UINT64, &umc->umc_tom2,
            "umc_family", DATA_TYPE_UINT32, &umc->umc_family,
            "umc_df_rev", DATA_TYPE_UINT32, &umc->umc_df_rev,
            "umc_decomp", DATA_TYPE_NVLIST, &decomp,
            "umc_dfs", DATA_TYPE_NVLIST_ARRAY, &dfs, &umc->umc_ndfs,
            NULL) != 0 || umc->umc_ndfs > ZEN_UMC_MAX_DFS) {
                return (B_FALSE);
        }


        if (nvlist_lookup_pairs(decomp, 0,
            "dfd_sock_mask", DATA_TYPE_UINT32, &umc->umc_decomp.dfd_sock_mask,
            "dfd_die_mask", DATA_TYPE_UINT32, &umc->umc_decomp.dfd_die_mask,
            "dfd_node_mask", DATA_TYPE_UINT32, &umc->umc_decomp.dfd_node_mask,
            "dfd_comp_mask", DATA_TYPE_UINT32, &umc->umc_decomp.dfd_comp_mask,
            "dfd_sock_shift", DATA_TYPE_UINT8, &umc->umc_decomp.dfd_sock_shift,
            "dfd_die_shift", DATA_TYPE_UINT8, &umc->umc_decomp.dfd_die_shift,
            "dfd_node_shift", DATA_TYPE_UINT8, &umc->umc_decomp.dfd_node_shift,
            "dfd_comp_shift", DATA_TYPE_UINT8, &umc->umc_decomp.dfd_comp_shift,
            NULL) != 0) {
                return (B_FALSE);
        }

        for (uint_t i = 0; i < umc->umc_ndfs; i++) {
                if (!zen_umc_restore_df(dfs[i], &umc->umc_dfs[i])) {
                        return (B_FALSE);
                }
        }

        return (B_TRUE);
}