root/usr/src/common/mc/imc/imc_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 2019 Joyent, Inc.
 */

/*
 * This implements logic to allow us to dump IMC data for decoding purposes,
 * such that we can later encode it elsewhere. In general, dumping is done by
 * the kernel and reconstituting this data is done by user land.
 */

#include "imc.h"

#ifndef _KERNEL
#include <stdint.h>
#include <strings.h>
#endif  /* !_KERNEL */


static nvlist_t *
imc_dump_sad(imc_sad_t *sad)
{
        uint_t i;
        nvlist_t *nvl;
        nvlist_t *rules[IMC_MAX_SAD_RULES];
        nvlist_t *routes[IMC_MAX_SAD_MCROUTES];

        nvl = fnvlist_alloc();
        fnvlist_add_uint32(nvl, "isad_flags", sad->isad_flags);
        fnvlist_add_uint32(nvl, "isad_valid", sad->isad_valid);
        fnvlist_add_uint64(nvl, "isad_tolm", sad->isad_tolm);
        fnvlist_add_uint64(nvl, "isad_tohm", sad->isad_tohm);

        for (i = 0; i < sad->isad_nrules; i++) {
                nvlist_t *n = fnvlist_alloc();
                imc_sad_rule_t *r = &sad->isad_rules[i];

                fnvlist_add_boolean_value(n, "isr_enable", r->isr_enable);
                fnvlist_add_boolean_value(n, "isr_a7mode", r->isr_a7mode);
                fnvlist_add_boolean_value(n, "isr_need_mod3", r->isr_need_mod3);
                fnvlist_add_uint64(n, "isr_limit", r->isr_limit);
                fnvlist_add_uint32(n, "isr_type", r->isr_type);
                fnvlist_add_uint32(n, "isr_imode", r->isr_imode);
                fnvlist_add_uint32(n, "isr_mod_mode", r->isr_mod_mode);
                fnvlist_add_uint32(n, "isr_mod_type", r->isr_mod_type);
                fnvlist_add_uint8_array(n, "isr_targets", r->isr_targets,
                    r->isr_ntargets);

                rules[i] = n;
        }
        fnvlist_add_nvlist_array(nvl, "isad_rules", rules, sad->isad_nrules);
        for (i = 0; i < sad->isad_nrules; i++) {
                nvlist_free(rules[i]);
        }

        if (sad->isad_mcroute.ismc_nroutes == 0) {
                return (nvl);
        }

        for (i = 0; i <  sad->isad_mcroute.ismc_nroutes; i++) {
                nvlist_t *r = fnvlist_alloc();
                imc_sad_mcroute_entry_t *e =
                    &sad->isad_mcroute.ismc_mcroutes[i];

                fnvlist_add_uint8(r, "ismce_imc", e->ismce_imc);
                fnvlist_add_uint8(r, "ismce_pchannel", e->ismce_pchannel);
                routes[i] = r;
        }
        fnvlist_add_nvlist_array(nvl, "isad_mcroute", routes, i);
        for (i = 0; i <  sad->isad_mcroute.ismc_nroutes; i++) {
                nvlist_free(routes[i]);
        }

        return (nvl);
}

static nvlist_t *
imc_dump_tad(imc_tad_t *tad)
{
        uint_t i;
        nvlist_t *nvl;
        nvlist_t *rules[IMC_MAX_TAD_RULES];

        nvl = fnvlist_alloc();
        fnvlist_add_uint32(nvl, "itad_valid", tad->itad_valid);
        fnvlist_add_uint32(nvl, "itad_flags", tad->itad_flags);
        for (i = 0; i < tad->itad_nrules; i++) {
                nvlist_t *t = fnvlist_alloc();
                imc_tad_rule_t *r = &tad->itad_rules[i];

                fnvlist_add_uint64(t, "itr_base", r->itr_base);
                fnvlist_add_uint64(t, "itr_limit", r->itr_limit);
                fnvlist_add_uint8(t, "itr_sock_way", r->itr_sock_way);
                fnvlist_add_uint8(t, "itr_chan_way", r->itr_chan_way);
                fnvlist_add_uint32(t, "itr_sock_gran", r->itr_sock_gran);
                fnvlist_add_uint32(t, "itr_chan_gran", r->itr_chan_gran);
                fnvlist_add_uint8_array(t, "itr_targets", r->itr_targets,
                    r->itr_ntargets);

                rules[i] = t;
        }
        fnvlist_add_nvlist_array(nvl, "itad_rules", rules, tad->itad_nrules);
        for (i = 0; i < tad->itad_nrules; i++) {
                nvlist_free(rules[i]);
        }

        return (nvl);
}

static nvlist_t *
imc_dump_channel(imc_channel_t *chan)
{
        uint_t i;
        nvlist_t *nvl;
        nvlist_t *dimms[IMC_MAX_DIMMPERCHAN];
        nvlist_t *ranks[IMC_MAX_RANK_WAYS];

        nvl = fnvlist_alloc();
        fnvlist_add_uint32(nvl, "ich_valid", chan->ich_valid);
        for (i = 0; i < chan->ich_ndimms; i++) {
                nvlist_t *d = fnvlist_alloc();
                imc_dimm_t *dimm = &chan->ich_dimms[i];

                fnvlist_add_uint32(d, "idimm_valid", dimm->idimm_valid);
                fnvlist_add_boolean_value(d, "idimm_present",
                    dimm->idimm_present);
                if (!dimm->idimm_present)
                        goto add;

                fnvlist_add_uint8(d, "idimm_nbanks", dimm->idimm_nbanks);
                fnvlist_add_uint8(d, "idimm_nranks", dimm->idimm_nranks);
                fnvlist_add_uint8(d, "idimm_width", dimm->idimm_width);
                fnvlist_add_uint8(d, "idimm_density", dimm->idimm_density);
                fnvlist_add_uint8(d, "idimm_nrows", dimm->idimm_nrows);
                fnvlist_add_uint8(d, "idimm_ncolumns", dimm->idimm_ncolumns);
                fnvlist_add_uint64(d, "idimm_size", dimm->idimm_size);
add:
                dimms[i] = d;
        }
        fnvlist_add_nvlist_array(nvl, "ich_dimms", dimms, i);
        for (i = 0; i < chan->ich_ndimms; i++) {
                nvlist_free(dimms[i]);
        }

        fnvlist_add_uint64_array(nvl, "ich_tad_offsets", chan->ich_tad_offsets,
            chan->ich_ntad_offsets);

        for (i = 0; i < chan->ich_nrankileaves; i++) {
                uint_t j;
                nvlist_t *r = fnvlist_alloc();
                nvlist_t *ileaves[IMC_MAX_RANK_INTERLEAVES];
                imc_rank_ileave_t *rank = &chan->ich_rankileaves[i];

                fnvlist_add_boolean_value(r, "irle_enabled",
                    rank->irle_enabled);
                fnvlist_add_uint8(r, "irle_nways", rank->irle_nways);
                fnvlist_add_uint8(r, "irle_nwaysbits", rank->irle_nwaysbits);
                fnvlist_add_uint64(r, "irle_limit", rank->irle_limit);

                for (j = 0; j < rank->irle_nentries; j++) {
                        nvlist_t *e = fnvlist_alloc();

                        fnvlist_add_uint8(e, "irle_target",
                            rank->irle_entries[j].irle_target);
                        fnvlist_add_uint64(e, "irle_offset",
                            rank->irle_entries[j].irle_offset);
                        ileaves[j] = e;
                }
                fnvlist_add_nvlist_array(r, "irle_entries", ileaves, j);
                for (j = 0; j < rank->irle_nentries; j++) {
                        nvlist_free(ileaves[j]);
                }

                ranks[i] = r;
        }
        fnvlist_add_nvlist_array(nvl, "ich_rankileaves", ranks, i);
        for (i = 0; i < chan->ich_nrankileaves; i++) {
                nvlist_free(ranks[i]);
        }

        return (nvl);
}

static nvlist_t *
imc_dump_mc(imc_mc_t *mc)
{
        uint_t i;
        nvlist_t *nvl;
        nvlist_t *channels[IMC_MAX_CHANPERMC];

        nvl = fnvlist_alloc();
        fnvlist_add_boolean_value(nvl, "icn_ecc", mc->icn_ecc);
        fnvlist_add_boolean_value(nvl, "icn_lockstep", mc->icn_lockstep);
        fnvlist_add_boolean_value(nvl, "icn_closed", mc->icn_closed);
        fnvlist_add_uint32(nvl, "icn_dimm_type", mc->icn_dimm_type);

        for (i = 0; i < mc->icn_nchannels; i++) {
                channels[i] = imc_dump_channel(&mc->icn_channels[i]);
        }
        fnvlist_add_nvlist_array(nvl, "icn_channels", channels, i);
        for (i = 0; i < mc->icn_nchannels; i++) {
                nvlist_free(channels[i]);
        }

        return (nvl);
}

static nvlist_t *
imc_dump_socket(imc_socket_t *sock)
{
        uint_t i;
        nvlist_t *nvl, *sad;
        nvlist_t *tad[IMC_MAX_TAD];
        nvlist_t *mc[IMC_MAX_IMCPERSOCK];

        nvl = fnvlist_alloc();

        sad = imc_dump_sad(&sock->isock_sad);
        fnvlist_add_nvlist(nvl, "isock_sad", sad);
        nvlist_free(sad);

        for (i = 0; i < sock->isock_ntad; i++) {
                tad[i] = imc_dump_tad(&sock->isock_tad[i]);
        }
        fnvlist_add_nvlist_array(nvl, "isock_tad", tad, i);
        for (i = 0; i < sock->isock_ntad; i++) {
                fnvlist_free(tad[i]);
        }

        fnvlist_add_uint32(nvl, "isock_nodeid", sock->isock_nodeid);

        for (i = 0; i  < sock->isock_nimc; i++) {
                mc[i] = imc_dump_mc(&sock->isock_imcs[i]);
        }
        fnvlist_add_nvlist_array(nvl, "isock_imcs", mc, i);
        for (i = 0; i < sock->isock_nimc; i++) {
                fnvlist_free(mc[i]);
        }
        return (nvl);
}

nvlist_t *
imc_dump_decoder(imc_t *imc)
{
        uint_t i;
        nvlist_t *nvl, *invl;
        nvlist_t *sockets[IMC_MAX_SOCKETS];

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

        invl = fnvlist_alloc();
        fnvlist_add_uint32(invl, "imc_gen", imc->imc_gen);

        for (i = 0; i < imc->imc_nsockets; i++) {
                sockets[i] = imc_dump_socket(&imc->imc_sockets[i]);
        }
        fnvlist_add_nvlist_array(invl, "imc_sockets", sockets, i);
        fnvlist_add_nvlist(nvl, "imc", invl);

        for (i = 0; i < imc->imc_nsockets; i++) {
                nvlist_free(sockets[i]);
        }
        nvlist_free(invl);

        return (nvl);
}

static boolean_t
imc_restore_sad(nvlist_t *nvl, imc_sad_t *sad)
{
        nvlist_t **rules, **routes;
        uint_t i, nroutes;

        if (nvlist_lookup_uint32(nvl, "isad_flags", &sad->isad_flags) != 0 ||
            nvlist_lookup_uint32(nvl, "isad_valid", &sad->isad_valid) != 0 ||
            nvlist_lookup_uint64(nvl, "isad_tolm", &sad->isad_tolm) != 0 ||
            nvlist_lookup_uint64(nvl, "isad_tohm", &sad->isad_tohm) != 0 ||
            nvlist_lookup_nvlist_array(nvl, "isad_rules",
            &rules, &sad->isad_nrules) != 0) {
                return (B_FALSE);
        }

        for (i = 0; i < sad->isad_nrules; i++) {
                imc_sad_rule_t *r = &sad->isad_rules[i];
                uint8_t *targs;

                if (nvlist_lookup_boolean_value(rules[i], "isr_enable",
                    &r->isr_enable) != 0 ||
                    nvlist_lookup_boolean_value(rules[i], "isr_a7mode",
                    &r->isr_a7mode) != 0 ||
                    nvlist_lookup_boolean_value(rules[i], "isr_need_mod3",
                    &r->isr_need_mod3) != 0 ||
                    nvlist_lookup_uint64(rules[i], "isr_limit",
                    &r->isr_limit) != 0 ||
                    nvlist_lookup_uint32(rules[i], "isr_type",
                    &r->isr_type) != 0 ||
                    nvlist_lookup_uint32(rules[i], "isr_imode",
                    &r->isr_imode) != 0 ||
                    nvlist_lookup_uint32(rules[i], "isr_mod_mode",
                    &r->isr_mod_mode) != 0 ||
                    nvlist_lookup_uint32(rules[i], "isr_mod_type",
                    &r->isr_mod_type) != 0 ||
                    nvlist_lookup_uint8_array(rules[i], "isr_targets", &targs,
                    &r->isr_ntargets) != 0 ||
                    r->isr_ntargets > IMC_MAX_SAD_RULES) {
                        return (B_FALSE);
                }

                bcopy(targs, r->isr_targets, r->isr_ntargets *
                    sizeof (uint8_t));
        }

        /*
         * The mcroutes entry right now is only included conditionally.
         */
        if (nvlist_lookup_nvlist_array(nvl, "isad_mcroute", &routes,
            &nroutes) == 0) {
                if (nroutes > IMC_MAX_SAD_MCROUTES)
                        return (B_FALSE);
                sad->isad_mcroute.ismc_nroutes = nroutes;
                for (i = 0; i < nroutes; i++) {
                        imc_sad_mcroute_entry_t *r =
                            &sad->isad_mcroute.ismc_mcroutes[i];
                        if (nvlist_lookup_uint8(routes[i], "ismce_imc",
                            &r->ismce_imc) != 0 ||
                            nvlist_lookup_uint8(routes[i], "ismce_pchannel",
                            &r->ismce_pchannel) != 0) {
                                return (B_FALSE);
                        }
                }
        }

        return (B_TRUE);
}

static boolean_t
imc_restore_tad(nvlist_t *nvl, imc_tad_t *tad)
{
        nvlist_t **rules;

        if (nvlist_lookup_uint32(nvl, "itad_valid", &tad->itad_valid) != 0 ||
            nvlist_lookup_uint32(nvl, "itad_flags", &tad->itad_flags) != 0 ||
            nvlist_lookup_nvlist_array(nvl, "itad_rules", &rules,
            &tad->itad_nrules) != 0 || tad->itad_nrules > IMC_MAX_TAD_RULES) {
                return (B_FALSE);
        }

        for (uint_t i = 0; i < tad->itad_nrules; i++) {
                imc_tad_rule_t *r = &tad->itad_rules[i];
                uint8_t *targs;

                if (nvlist_lookup_uint64(rules[i], "itr_base",
                    &r->itr_base) != 0 ||
                    nvlist_lookup_uint64(rules[i], "itr_limit",
                    &r->itr_limit) != 0 ||
                    nvlist_lookup_uint8(rules[i], "itr_sock_way",
                    &r->itr_sock_way) != 0 ||
                    nvlist_lookup_uint8(rules[i], "itr_chan_way",
                    &r->itr_chan_way) != 0 ||
                    nvlist_lookup_uint32(rules[i], "itr_sock_gran",
                    &r->itr_sock_gran) != 0 ||
                    nvlist_lookup_uint32(rules[i], "itr_chan_gran",
                    &r->itr_chan_gran) != 0 ||
                    nvlist_lookup_uint8_array(rules[i], "itr_targets",
                    &targs, &r->itr_ntargets) != 0 ||
                    r->itr_ntargets > IMC_MAX_TAD_TARGETS) {
                        return (B_FALSE);
                }

                bcopy(targs, r->itr_targets, r->itr_ntargets *
                    sizeof (uint8_t));
        }

        return (B_TRUE);
}

static boolean_t
imc_restore_channel(nvlist_t *nvl, imc_channel_t *chan)
{
        nvlist_t **dimms, **rir;
        uint64_t *tadoff;

        if (nvlist_lookup_uint32(nvl, "ich_valid", &chan->ich_valid) != 0 ||
            nvlist_lookup_nvlist_array(nvl, "ich_dimms", &dimms,
            &chan->ich_ndimms) != 0 ||
            chan->ich_ndimms > IMC_MAX_DIMMPERCHAN ||
            nvlist_lookup_uint64_array(nvl, "ich_tad_offsets", &tadoff,
            &chan->ich_ntad_offsets) != 0 ||
            chan->ich_ntad_offsets > IMC_MAX_TAD_RULES ||
            nvlist_lookup_nvlist_array(nvl, "ich_rankileaves", &rir,
            &chan->ich_nrankileaves) != 0 ||
            chan->ich_nrankileaves > IMC_MAX_RANK_WAYS) {
                return (B_FALSE);
        }

        for (uint_t i = 0; i < chan->ich_ndimms; i++) {
                imc_dimm_t *d = &chan->ich_dimms[i];

                if (nvlist_lookup_uint32(dimms[i], "idimm_valid",
                    &d->idimm_valid) != 0 ||
                    nvlist_lookup_boolean_value(dimms[i], "idimm_present",
                    &d->idimm_present) != 0) {
                        return (B_FALSE);
                }

                if (!d->idimm_present)
                        continue;

                if (nvlist_lookup_uint8(dimms[i], "idimm_nbanks",
                    &d->idimm_nbanks) != 0 ||
                    nvlist_lookup_uint8(dimms[i], "idimm_nranks",
                    &d->idimm_nranks) != 0 ||
                    nvlist_lookup_uint8(dimms[i], "idimm_width",
                    &d->idimm_width) != 0 ||
                    nvlist_lookup_uint8(dimms[i], "idimm_density",
                    &d->idimm_density) != 0 ||
                    nvlist_lookup_uint8(dimms[i], "idimm_nrows",
                    &d->idimm_nrows) != 0 ||
                    nvlist_lookup_uint8(dimms[i], "idimm_ncolumns",
                    &d->idimm_ncolumns) != 0 ||
                    nvlist_lookup_uint64(dimms[i], "idimm_size",
                    &d->idimm_size) != 0) {
                        return (B_FALSE);
                }
        }

        bcopy(tadoff, chan->ich_tad_offsets, chan->ich_ntad_offsets *
            sizeof (uint64_t));

        for (uint_t i = 0; i < chan->ich_nrankileaves; i++) {
                nvlist_t **ileaves;
                imc_rank_ileave_t *r = &chan->ich_rankileaves[i];

                if (nvlist_lookup_boolean_value(rir[i], "irle_enabled",
                    &r->irle_enabled) != 0 ||
                    nvlist_lookup_uint8(rir[i], "irle_nways",
                    &r->irle_nways) != 0 ||
                    nvlist_lookup_uint8(rir[i], "irle_nwaysbits",
                    &r->irle_nwaysbits) != 0 ||
                    nvlist_lookup_uint64(rir[i], "irle_limit",
                    &r->irle_limit) != 0 ||
                    nvlist_lookup_nvlist_array(rir[i], "irle_entries",
                    &ileaves, &r->irle_nentries) != 0 ||
                    r->irle_nentries > IMC_MAX_RANK_INTERLEAVES) {
                        return (B_FALSE);
                }

                for (uint_t j = 0; j < r->irle_nentries; j++) {
                        imc_rank_ileave_entry_t *ril = &r->irle_entries[j];

                        if (nvlist_lookup_uint8(ileaves[j], "irle_target",
                            &ril->irle_target) != 0 ||
                            nvlist_lookup_uint64(ileaves[j], "irle_offset",
                            &ril->irle_offset) != 0) {
                                return (B_FALSE);
                        }
                }
        }

        return (B_TRUE);
}

static boolean_t
imc_restore_mc(nvlist_t *nvl, imc_mc_t *mc)
{
        nvlist_t **channels;

        if (nvlist_lookup_boolean_value(nvl, "icn_ecc", &mc->icn_ecc) != 0 ||
            nvlist_lookup_boolean_value(nvl, "icn_lockstep",
            &mc->icn_lockstep) != 0 ||
            nvlist_lookup_boolean_value(nvl, "icn_closed",
            &mc->icn_closed) != 0 ||
            nvlist_lookup_uint32(nvl, "icn_dimm_type",
            &mc->icn_dimm_type) != 0 ||
            nvlist_lookup_nvlist_array(nvl, "icn_channels", &channels,
            &mc->icn_nchannels) != 0 || mc->icn_nchannels > IMC_MAX_CHANPERMC) {
                return (B_FALSE);
        }

        for (uint_t i = 0; i < mc->icn_nchannels; i++) {
                if (!imc_restore_channel(channels[i], &mc->icn_channels[i])) {
                        return (B_FALSE);
                }
        }

        return (B_TRUE);
}

static boolean_t
imc_restore_socket(nvlist_t *nvl, imc_socket_t *sock)
{
        uint_t i;
        nvlist_t *sad, **tads, **imcs;

        if (nvlist_lookup_nvlist(nvl, "isock_sad", &sad) != 0 ||
            nvlist_lookup_nvlist_array(nvl, "isock_tad", &tads,
            &sock->isock_ntad) != 0 ||
            nvlist_lookup_uint32(nvl, "isock_nodeid",
            &sock->isock_nodeid) != 0 ||
            nvlist_lookup_nvlist_array(nvl, "isock_imcs", &imcs,
            &sock->isock_nimc) != 0 ||
            sock->isock_ntad > IMC_MAX_TAD ||
            sock->isock_nimc > IMC_MAX_IMCPERSOCK) {
                return (B_FALSE);
        }

        if (!imc_restore_sad(sad, &sock->isock_sad)) {
                return (B_FALSE);
        }

        for (i = 0; i < sock->isock_ntad; i++) {
                if (!imc_restore_tad(tads[i], &sock->isock_tad[i])) {
                        return (B_FALSE);
                }
        }

        for (i = 0; i < sock->isock_nimc; i++) {
                if (!imc_restore_mc(imcs[i], &sock->isock_imcs[i])) {
                        return (B_FALSE);
                }
        }

        return (B_TRUE);
}

boolean_t
imc_restore_decoder(nvlist_t *nvl, imc_t *imc)
{
        uint_t i;
        uint32_t vers;
        nvlist_t *invl, **socks;
        char *driver;

        bzero(imc, sizeof (imc_t));

        if (nvlist_lookup_uint32(nvl, "mc_dump_version", &vers) != 0 ||
            vers != 0 ||
            nvlist_lookup_string(nvl, "mc_dump_driver", &driver) != 0 ||
            strcmp(driver, "imc") != 0 ||
            nvlist_lookup_nvlist(nvl, "imc", &invl) != 0) {
                return (B_FALSE);
        }

        if (nvlist_lookup_uint32(invl, "imc_gen", &imc->imc_gen) != 0 ||
            nvlist_lookup_nvlist_array(invl, "imc_sockets", &socks,
            &imc->imc_nsockets) != 0 ||
            imc->imc_nsockets > IMC_MAX_SOCKETS) {
                return (B_FALSE);
        }

        for (i = 0; i < imc->imc_nsockets; i++) {
                if (!imc_restore_socket(socks[i], &imc->imc_sockets[i]))
                        return (B_FALSE);
        }

        return (B_TRUE);
}