/* * 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 #include #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); }