#include <sys/fm/protocol.h>
#include <fm/topo_mod.h>
#include <libjedec.h>
#include <string.h>
#include <stdbool.h>
#include "topo_dimm.h"
typedef struct {
uint32_t sc_dram_type;
uint32_t sc_mod_type;
const char *sc_dram_str;
const char *sc_mod_str;
bool sc_asym;
uint32_t sc_nranks;
uint32_t sc_even_ranks;
uint32_t sc_odd_ranks;
uint32_t sc_data_bits;
uint32_t sc_ecc_bits;
uint32_t sc_nsubchan;
uint32_t sc_pkg_sl[2];
uint32_t sc_pkg_ndie[2];
uint64_t sc_die_size[2];
uint32_t sc_dram_width[2];
uint32_t sc_nrows[2];
uint32_t sc_ncols[2];
uint32_t sc_nbank_bits[2];
uint32_t sc_nbgrp_bits[2];
uint32_t sc_vdd;
uint32_t sc_devices;
} spd_cache_t;
static const topo_pgroup_info_t topo_dimm_pgroup = {
TOPO_PGROUP_DIMM_PROPS,
TOPO_STABILITY_PRIVATE,
TOPO_STABILITY_PRIVATE,
1
};
static const topo_pgroup_info_t topo_dimm_comps_pgroup = {
TOPO_PGROUP_DIMM_COMPONENTS,
TOPO_STABILITY_PRIVATE,
TOPO_STABILITY_PRIVATE,
1
};
static const char *
topo_dimm_dram_type2str(spd_dram_type_t type)
{
switch (type) {
case SPD_DT_DDR4_SDRAM:
return (TOPO_DIMM_TYPE_DDR4);
case SPD_DT_LPDDR4_SDRAM:
return (TOPO_DIMM_TYPE_LPDDR4);
case SPD_DT_DDR5_SDRAM:
return (TOPO_DIMM_TYPE_DDR5);
case SPD_DT_LPDDR5_SDRAM:
return (TOPO_DIMM_TYPE_LPDDR5);
default:
return (NULL);
}
}
static const char *
topo_dimm_temp2str(uint32_t val)
{
switch (val) {
case SPD_TEMP_T_TSE2002:
return ("TSE2002");
case SPD_TEMP_T_TSE2004av:
return ("TSE2004av");
case SPD_TEMP_T_TS5111:
return ("TS5111");
case SPD_TEMP_T_TS5110:
return ("TS5110");
case SPD_TEMP_T_TS5210:
return ("TS5210");
case SPD_TEMP_T_TS5211:
return ("TS5211");
default:
return ("unknown");
}
}
static const char *
topo_dimm_pmic2str(uint32_t val)
{
switch (val) {
case SPD_PMIC_T_PMIC5000:
return ("PMIC5000");
case SPD_PMIC_T_PMIC5010:
return ("PMIC5010");
case SPD_PMIC_T_PMIC5100:
return ("PMIC5100");
case SPD_PMIC_T_PMIC5020:
return ("PMIC5020");
case SPD_PMIC_T_PMIC5120:
return ("PMIC5120");
case SPD_PMIC_T_PMIC5200:
return ("PMIC5200");
case SPD_PMIC_T_PMIC5030:
return ("PMIC5030");
default:
return ("unknown");
}
}
static const char *
topo_dimm_cd2str(uint32_t val)
{
switch (val) {
case SPD_CD_T_DDR5CK01:
return ("DDR5CK01");
default:
return ("unknown");
}
}
static const char *
topo_dimm_rcd2str(uint32_t val)
{
switch (val) {
case SPD_RCD_T_SSTE32882:
return ("SSTE32882");
case SPD_RCD_T_DDR4RCD01:
return ("DDR4RCD01");
case SPD_RCD_T_DDR4RCD02:
return ("DDR4RCD02");
case SPD_RCD_T_DDR5RCD01:
return ("DDR5RCD01");
case SPD_RCD_T_DDR5RCD02:
return ("DDR5RCD02");
case SPD_RCD_T_DDR5RCD03:
return ("DDR5RCD03");
case SPD_RCD_T_DDR5RCD04:
return ("DDR5RCD04");
case SPD_RCD_T_DDR5RCD05:
return ("DDR5RCD05");
default:
return ("unknown");
}
}
static const char *
topo_dimm_db2str(uint32_t val)
{
switch (val) {
case SPD_DB_T_DDR4DB01:
return ("DDR4DB01");
case SPD_DB_T_DDR4DB02:
return ("DDR4DB02");
case SPD_DB_T_DDR5DB01:
return ("DDR5DB01");
case SPD_DB_T_DDR5DB02:
return ("DDR5DB02");
case SPD_DB_T_DDR3MB:
return ("DDR3MB");
default:
return ("unknown");
}
}
static const char *
topo_dimm_mrcd2str(uint32_t val)
{
switch (val) {
case SPD_MRCD_T_DDR5MRCD01:
return ("DDR5MRCD01");
case SPD_MRCD_T_DDR5MRCD02:
return ("DDR5MRCD02");
default:
return ("unknown");
}
}
static const char *
topo_dimm_mdb2str(uint32_t val)
{
switch (val) {
case SPD_MDB_T_DDR5MDB01:
return ("DDR5MDB01");
case SPD_MDB_T_DDR5MDB02:
return ("DDR5MDB02");
default:
return ("unknown");
}
}
static const char *
topo_dimm_dmb2str(uint32_t val)
{
switch (val) {
case SPD_DMB_T_DMB5011:
return ("DMB5011");
default:
return ("unknown");
}
}
static const char *
topo_dimm_spd2str(uint32_t val)
{
switch (val) {
case SPD_SPD_T_EE1002:
return ("EE1002");
case SPD_SPD_T_EE1004:
return ("EE1004");
case SPD_SPD_T_SPD5118:
return ("SPD5118");
case SPD_SPD_T_ESPD5216:
return ("ESPD5216");
default:
return ("unknown");
}
}
static uint32_t
topo_dimm_mod_vdd(spd_dram_type_t type)
{
switch (type) {
case SPD_DT_DDR4_SDRAM:
return (1200);
case SPD_DT_DDR5_SDRAM:
return (1100);
default:
return (0);
}
}
static const char *
topo_dimm_mod_type2str(spd_module_type_t type)
{
switch (type) {
case SPD_MOD_TYPE_RDIMM:
return ("RDIMM");
case SPD_MOD_TYPE_UDIMM:
return ("UDIMM");
case SPD_MOD_TYPE_SODIMM:
return ("SO-DIMM");
case SPD_MOD_TYPE_LRDIMM:
return ("LRDIMM");
case SPD_MOD_TYPE_MRDIMM:
return ("MRDIMM");
case SPD_MOD_TYPE_DDIMM:
return ("DDIMM");
case SPD_MOD_TYPE_SOLDER:
return ("solder-down");
case SPD_MOD_TYPE_MINI_RDIMM:
return ("Mini-RDIMM");
case SPD_MOD_TYPE_MINI_UDIMM:
return ("Mini-UDIMM");
case SPD_MOD_TYPE_MINI_CDIMM:
return ("Mini-CDIMM");
case SPD_MOD_TYPE_72b_SO_RDIMM:
return ("72b-SO-RDIMM");
case SPD_MOD_TYPE_72b_SO_UDIMM:
return ("72b-SO-UDIMM");
case SPD_MOD_TYPE_72b_SO_CDIMM:
return ("72b-SO-CDIMM");
case SPD_MOD_TYPE_16b_SO_DIMM:
return ("16b-SO-DIMM");
case SPD_MOD_TYPE_32b_SO_DIMM:
return ("32b-SO-DIMM");
case SPD_MOD_TYPE_CUDIMM:
return ("CUDIMM");
case SPD_MOD_TYPE_CSODIMM:
return ("CSODIMM");
case SPD_MOD_TYPE_CAMM2:
return ("CAMM2");
case SPD_MOD_TYPE_LPDIMM:
return ("LP-DIMM");
case SPD_MOD_TYPE_MICRO_DIMM:
return ("Micro-DIMM");
default:
return (NULL);
}
}
static bool
topo_dimm_cache_spd(topo_mod_t *mod, nvlist_t *spd, spd_cache_t *cache)
{
if (nvlist_lookup_pairs(spd, 0,
SPD_KEY_MOD_TYPE, DATA_TYPE_UINT32, &cache->sc_mod_type,
SPD_KEY_NRANKS, DATA_TYPE_UINT32, &cache->sc_nranks,
SPD_KEY_NSUBCHAN, DATA_TYPE_UINT32, &cache->sc_nsubchan,
SPD_KEY_DATA_WIDTH, DATA_TYPE_UINT32, &cache->sc_data_bits,
SPD_KEY_ECC_WIDTH, DATA_TYPE_UINT32, &cache->sc_ecc_bits,
SPD_KEY_NBANK_BITS, DATA_TYPE_UINT32, &cache->sc_nbank_bits[0],
SPD_KEY_NBGRP_BITS, DATA_TYPE_UINT32, &cache->sc_nbgrp_bits[0],
SPD_KEY_NROW_BITS, DATA_TYPE_UINT32, &cache->sc_nrows[0],
SPD_KEY_NCOL_BITS, DATA_TYPE_UINT32, &cache->sc_ncols[0],
SPD_KEY_PKG_SL, DATA_TYPE_UINT32, &cache->sc_pkg_sl[0],
SPD_KEY_PKG_NDIE, DATA_TYPE_UINT32, &cache->sc_pkg_ndie[0],
SPD_KEY_DRAM_WIDTH, DATA_TYPE_UINT32, &cache->sc_dram_width[0],
SPD_KEY_DIE_SIZE, DATA_TYPE_UINT64, &cache->sc_die_size[0],
SPD_KEY_DEVS, DATA_TYPE_UINT32, &cache->sc_devices,
NULL) != 0) {
topo_mod_dprintf(mod, "failed to find expected primary SPD "
"keys");
return (false);
}
cache->sc_dram_str = topo_dimm_dram_type2str(cache->sc_dram_type);
cache->sc_mod_str = topo_dimm_mod_type2str(cache->sc_mod_type);
cache->sc_vdd = topo_dimm_mod_vdd(cache->sc_dram_type);
cache->sc_asym = nvlist_lookup_boolean(spd, SPD_KEY_RANK_ASYM) == 0;
if (!cache->sc_asym)
return (true);
cache->sc_even_ranks = cache->sc_odd_ranks = cache->sc_nranks / 2;
if (cache->sc_nranks % 2 == 1)
cache->sc_even_ranks++;
if (nvlist_lookup_pairs(spd, 0,
SPD_KEY_SEC_NBANK_BITS, DATA_TYPE_UINT32, &cache->sc_nbank_bits[1],
SPD_KEY_SEC_NBGRP_BITS, DATA_TYPE_UINT32, &cache->sc_nbgrp_bits[1],
SPD_KEY_SEC_NROW_BITS, DATA_TYPE_UINT32, &cache->sc_nrows[1],
SPD_KEY_SEC_NCOL_BITS, DATA_TYPE_UINT32, &cache->sc_ncols[1],
SPD_KEY_SEC_PKG_SL, DATA_TYPE_UINT32, &cache->sc_pkg_sl[1],
SPD_KEY_SEC_PKG_NDIE, DATA_TYPE_UINT32, &cache->sc_pkg_ndie[1],
SPD_KEY_SEC_DRAM_WIDTH, DATA_TYPE_UINT32, &cache->sc_dram_width[1],
SPD_KEY_SEC_DIE_SIZE, DATA_TYPE_UINT32, &cache->sc_die_size[1],
NULL) != 0) {
topo_mod_dprintf(mod, "failed to get secondary keys for SPD "
"size calculation");
return (false);
}
return (true);
}
static bool
topo_dimm_calc_size(topo_mod_t *mod, const spd_cache_t *cache, uint64_t *sizep)
{
uint32_t pndie = cache->sc_pkg_ndie[0];
uint32_t width = cache->sc_data_bits * cache->sc_nsubchan /
cache->sc_dram_width[0];
*sizep = 0;
if (cache->sc_pkg_sl[0] != SPD_SL_3DS)
pndie = 1;
if (!cache->sc_asym) {
*sizep = pndie * width * cache->sc_nranks *
cache->sc_die_size[0] / 8;
return (true);
}
if (cache->sc_nranks < 2) {
topo_mod_dprintf(mod, "encountered asymmetrical module but it "
"only has %u ranks", cache->sc_nranks);
return (false);
}
*sizep = pndie * width * cache->sc_even_ranks *
cache->sc_die_size[0] / 8;
pndie = cache->sc_pkg_ndie[1];
if (cache->sc_pkg_sl[1] != SPD_SL_3DS)
pndie = 1;
*sizep += pndie * width * cache->sc_odd_ranks *
cache->sc_die_size[1] / 8;
return (true);
}
static bool
topo_dimm_add_props(topo_mod_t *mod, tnode_t *dimm, const spd_cache_t *cache)
{
uint32_t nbanks[2], nbgrps[2], nbpbg[2];
uint_t arr_len = 1;
uint64_t size;
nbgrps[0] = 1 << cache->sc_nbgrp_bits[0];
nbpbg[0] = 1 << cache->sc_nbank_bits[0];
nbanks[0] = nbgrps[0] * nbpbg[0];
if (!topo_dimm_calc_size(mod, cache, &size)) {
return (false);
}
if (cache->sc_asym) {
arr_len = 2;
nbgrps[1] = 1 << cache->sc_nbgrp_bits[1];
nbpbg[1] = 1 << cache->sc_nbank_bits[1];
nbanks[1] = nbgrps[1] * nbpbg[1];
}
if (topo_create_props(mod, dimm, TOPO_PROP_IMMUTABLE, &topo_dimm_pgroup,
TOPO_PROP_DIMM_RANKS, TOPO_TYPE_UINT32, cache->sc_nranks,
TOPO_PROP_DIMM_BANKS, TOPO_TYPE_UINT32_ARRAY, nbanks, arr_len,
TOPO_PROP_DIMM_BANK_GROUPS, TOPO_TYPE_UINT32_ARRAY, nbgrps, arr_len,
TOPO_PROP_DIMM_BANKS_PER_GROUP, TOPO_TYPE_UINT32_ARRAY, nbpbg,
arr_len,
TOPO_PROP_DIMM_SUBCHANNELS, TOPO_TYPE_UINT32, cache->sc_nsubchan,
TOPO_PROP_DIMM_DATA_WIDTH, TOPO_TYPE_UINT32, cache->sc_data_bits,
TOPO_PROP_DIMM_ECC_WIDTH, TOPO_TYPE_UINT32, cache->sc_ecc_bits,
TOPO_PROP_DIMM_VDD, TOPO_TYPE_UINT32, cache->sc_vdd,
TOPO_PROP_DIMM_SIZE, TOPO_TYPE_UINT64, size,
TOPO_PROP_DIMM_TYPE, TOPO_TYPE_STRING, cache->sc_dram_str,
TOPO_PROP_DIMM_MODULE_TYPE, TOPO_TYPE_STRING, cache->sc_mod_str,
NULL) != 0) {
topo_mod_dprintf(mod, "failed to set basic DIMM properties: %s",
topo_mod_errmsg(mod));
return (false);
}
return (true);
}
static int
topo_dimm_create_tn(topo_mod_t *mod, tnode_t *pn, tnode_t **tnp,
const char *name, topo_instance_t inst, const char *part, const char *rev,
const char *serial)
{
int ret;
nvlist_t *auth = NULL;
nvlist_t *fmri = NULL;
tnode_t *tn;
if ((auth = topo_mod_auth(mod, pn)) == NULL) {
topo_mod_dprintf(mod, "failed to get auth data: %s",
topo_mod_errmsg(mod));
ret = -1;
goto out;
}
if ((fmri = topo_mod_hcfmri(mod, pn, FM_HC_SCHEME_VERSION, name,
inst, NULL, auth, part, rev, serial)) == NULL) {
topo_mod_dprintf(mod, "failed to create fmri for %s[%" PRIu64
"]: %s\n", name, inst, topo_mod_errmsg(mod));
ret = -1;
goto out;
}
if ((tn = topo_node_bind(mod, pn, name, inst, fmri)) == NULL) {
topo_mod_dprintf(mod, "failed to bind fmri for %s[%" PRIu64
"]: %s\n", name, inst, topo_mod_errmsg(mod));
ret = -1;
goto out;
}
topo_pgroup_hcset(tn, auth);
if (topo_node_fru_set(tn, fmri, 0, &ret) != 0) {
topo_mod_dprintf(mod, "failed to set FRU: %s\n",
topo_strerror(ret));
ret = topo_mod_seterrno(mod, ret);
goto out;
}
*tnp = tn;
ret = 0;
out:
nvlist_free(auth);
nvlist_free(fmri);
return (ret);
}
static bool
topo_dimm_crc_ok(topo_mod_t *mod, nvlist_t *nvl, spd_dram_type_t type)
{
nvlist_t *errs;
const char *crc_keys[2] = { NULL };
switch (type) {
case SPD_DT_DDR4_SDRAM:
crc_keys[0] = SPD_KEY_CRC_DDR4_BASE;
crc_keys[1] = SPD_KEY_CRC_DDR4_BLK1;
break;
case SPD_DT_DDR5_SDRAM:
crc_keys[0] = SPD_KEY_CRC_DDR5;
break;
default:
topo_mod_dprintf(mod, "unsupported DRAM type: 0x%x", type);
return (false);
}
if (nvlist_lookup_nvlist(nvl, SPD_KEY_ERRS, &errs) != 0) {
return (true);
}
for (size_t i = 0; i < ARRAY_SIZE(crc_keys); i++) {
nvlist_t *key;
if (crc_keys[i] == NULL)
continue;
if (nvlist_lookup_nvlist(errs, crc_keys[i], &key) == 0) {
return (false);
}
}
return (true);
}
typedef struct dimm_comp {
const char *dc_comp;
spd_device_t dc_mask;
bool dc_always;
uint32_t (*dc_count)(const struct dimm_comp *, const spd_cache_t *,
nvlist_t *);
bool (*dc_mfg)(topo_mod_t *, tnode_t *, const struct dimm_comp *,
const spd_cache_t *, nvlist_t *, void *);
const char *(*dc_type2str)(uint32_t);
void *dc_mfg_arg;
} dimm_comp_t;
static uint32_t
dimm_comp_count_solo(const dimm_comp_t *comp, const spd_cache_t *cache,
nvlist_t *spd)
{
return (1);
}
static uint32_t
dimm_comp_count_dies(const dimm_comp_t *comp, const spd_cache_t *cache,
nvlist_t *spd)
{
uint32_t chan_width = (cache->sc_ecc_bits + cache->sc_data_bits) *
cache->sc_nsubchan;
uint32_t ndies_rank[2] = { 0, 0 };
ndies_rank[0] = chan_width / cache->sc_dram_width[0];
if (cache->sc_asym) {
ndies_rank[1] = chan_width / cache->sc_dram_width[1];
} else if (cache->sc_nranks >= 2) {
ndies_rank[1] = ndies_rank[0];
}
return (ndies_rank[0] + ndies_rank[1]);
}
static uint32_t
dimm_comp_count_mask(const dimm_comp_t *comp, const spd_cache_t *cache,
nvlist_t *spd)
{
uint32_t ret = 0;
uint32_t combo_mask = cache->sc_devices & comp->dc_mask;
for (uint32_t i = 0; i < sizeof (uint32_t) * NBBY; i++) {
if (((1 << i) & combo_mask) != 0)
ret++;
}
return (ret);
}
static uint32_t
dimm_comp_count_regs(const dimm_comp_t *comp, const spd_cache_t *cache,
nvlist_t *spd)
{
uint32_t ret;
if (nvlist_lookup_uint32(spd, SPD_KEY_MOD_NREGS, &ret) != 0)
return (0);
return (ret);
}
typedef enum {
DIMM_COMP_K_VALID,
DIMM_COMP_K_ERR,
DIMM_COMP_K_ENOENT
} dimm_comp_key_state_t;
static dimm_comp_key_state_t
dimm_comp_keys_exist(nvlist_t *spd, const char *const *keys, uint_t nents,
bool partial_enoent)
{
dimm_comp_key_state_t ret;
if (nents == 0) {
return (DIMM_COMP_K_ERR);
}
if (keys == NULL) {
return (DIMM_COMP_K_ENOENT);
}
for (uint_t i = 0; i < nents; i++) {
dimm_comp_key_state_t cur;
cur = nvlist_exists(spd, keys[i]) ? DIMM_COMP_K_VALID :
DIMM_COMP_K_ENOENT;
if (i == 0) {
ret = cur;
continue;
}
if (ret != cur) {
if (partial_enoent) {
ret = DIMM_COMP_K_VALID;
} else {
return (DIMM_COMP_K_ERR);
}
}
}
return (ret);
}
static bool
dimm_comp_mfg_common_ids(topo_mod_t *mod, tnode_t *dimm, nvlist_t *spd,
const char *prop, const char *const *keys, uint_t nents)
{
char **strs = NULL;
bool ret = false;
int err;
if ((strs = topo_mod_zalloc(mod, sizeof (char *) * nents)) == NULL) {
topo_mod_dprintf(mod, "failed to allocate memory for %s string "
"array: %s", prop, topo_strerror(EMOD_NOMEM));
(void) topo_mod_seterrno(mod, EMOD_NOMEM);
return (false);
}
for (size_t i = 0; i < nents; i++) {
uint32_t *data;
uint_t nvals;
int nret = nvlist_lookup_uint32_array(spd, keys[i], &data,
&nvals);
if (nret != 0) {
topo_mod_dprintf(mod, "failed to look up %s: %s",
keys[i], strerror(nret));
(void) topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto out;
}
if (nvals != 2) {
topo_mod_dprintf(mod, "key %s has wrong number of "
"array entries: found %u, expected %u", keys[i],
nvals, 2);
(void) topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto out;
}
if (topo_mod_asprintf(mod, &strs[i], "0x%x:0x%x", data[0],
data[1]) == -1) {
topo_mod_dprintf(mod, "failed to construct ID string "
"for %s: %s\n", keys[i], strerror(errno));
(void) topo_mod_seterrno(mod, EMOD_NOMEM);
goto out;
}
}
if (topo_prop_set_string_array(dimm, TOPO_PGROUP_DIMM_COMPONENTS, prop,
TOPO_PROP_IMMUTABLE, (const char **)strs, nents, &err) != 0) {
topo_mod_dprintf(mod, "failed to set property %s: %s", prop,
topo_strerror(err));
(void) topo_mod_seterrno(mod, err);
goto out;
}
ret = true;
out:
for (uint_t i = 0; i < nents; i++) {
topo_mod_strfree(mod, strs[i]);
}
topo_mod_free(mod, strs, sizeof (char *) * nents);
return (ret);
}
static bool
dimm_comp_mfg_common_strings(topo_mod_t *mod, tnode_t *dimm, nvlist_t *spd,
const char *prop, const char *const *keys, uint_t nents, bool allow_enoent)
{
char **strs = NULL;
int err;
bool ret = false;
if ((strs = topo_mod_zalloc(mod, sizeof (char *) * nents)) == NULL) {
topo_mod_dprintf(mod, "failed to allocate memory for %s string "
"array: %s", prop, topo_strerror(EMOD_NOMEM));
(void) topo_mod_seterrno(mod, EMOD_NOMEM);
return (false);
}
for (size_t i = 0; i < nents; i++) {
int nret = nvlist_lookup_string(spd, keys[i], &strs[i]);
if (nret != 0 && !(allow_enoent && nret == ENOENT)) {
topo_mod_dprintf(mod, "failed to look up %s: %s",
keys[i], strerror(nret));
(void) topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto out;
}
}
if (topo_prop_set_string_array(dimm, TOPO_PGROUP_DIMM_COMPONENTS, prop,
TOPO_PROP_IMMUTABLE, (const char **)strs, nents, &err) != 0) {
topo_mod_dprintf(mod, "failed to set property %s: %s", prop,
topo_strerror(err));
(void) topo_mod_seterrno(mod, err);
goto out;
}
ret = true;
out:
topo_mod_free(mod, strs, sizeof (char *) * nents);
return (ret);
}
static bool
dimm_comp_mfg_common_type(topo_mod_t *mod, tnode_t *dimm, nvlist_t *spd,
const dimm_comp_t *comp, const char *const *keys, uint_t nents)
{
const char **strs = NULL;
int err;
bool ret = false;
char prop[64];
if (comp->dc_type2str == NULL) {
(void) topo_mod_dprintf(mod, "missing type2str function for "
"component type %s", comp->dc_comp);
(void) topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
return (false);
}
(void) snprintf(prop, sizeof (prop), "%s-type", comp->dc_comp);
if ((strs = topo_mod_zalloc(mod, sizeof (char *) * nents)) == NULL) {
topo_mod_dprintf(mod, "failed to allocate memory for %s string "
"array: %s", prop, topo_strerror(EMOD_NOMEM));
(void) topo_mod_seterrno(mod, EMOD_NOMEM);
return (false);
}
for (size_t i = 0; i < nents; i++) {
uint32_t raw;
int nret = nvlist_lookup_uint32(spd, keys[i], &raw);
if (nret != 0) {
topo_mod_dprintf(mod, "failed to look up %s: %s",
keys[i], strerror(nret));
(void) topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto out;
}
strs[i] = comp->dc_type2str(raw);
}
if (topo_prop_set_string_array(dimm, TOPO_PGROUP_DIMM_COMPONENTS, prop,
TOPO_PROP_IMMUTABLE, strs, nents, &err) != 0) {
topo_mod_dprintf(mod, "failed to set property %s: %s", prop,
topo_strerror(err));
(void) topo_mod_seterrno(mod, err);
goto out;
}
ret = true;
out:
topo_mod_free(mod, strs, sizeof (char *) * nents);
return (ret);
}
static bool
dimm_comp_mfg_common(topo_mod_t *mod, tnode_t *dimm, const dimm_comp_t *comp,
nvlist_t *spd, const char *const *mfg_id_key,
const char *const *mfg_name_key, const char *const *type_key,
const char *const *rev_key, uint_t nents)
{
dimm_comp_key_state_t mfg_id_valid, mfg_name_valid, type_valid;
dimm_comp_key_state_t rev_valid;
if (nents == 0) {
return (true);
}
mfg_id_valid = dimm_comp_keys_exist(spd, mfg_id_key, nents, false);
mfg_name_valid = dimm_comp_keys_exist(spd, mfg_name_key, nents, true);
type_valid = dimm_comp_keys_exist(spd, type_key, nents, false);
rev_valid = dimm_comp_keys_exist(spd, rev_key, nents, false);
if (mfg_name_valid == DIMM_COMP_K_ERR || rev_valid == DIMM_COMP_K_ERR ||
mfg_id_valid == DIMM_COMP_K_ERR || type_valid == DIMM_COMP_K_ERR) {
topo_mod_dprintf(mod, "encountered erroneous keys: 0x%x 0x%x "
"0x%x 0x%x", mfg_name_valid, rev_valid, mfg_id_valid,
type_valid);
(void) topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
return (false);
}
if (mfg_id_valid == DIMM_COMP_K_VALID) {
char key[64];
(void) snprintf(key, sizeof (key), "%s-id",
comp->dc_comp);
if (!dimm_comp_mfg_common_ids(mod, dimm, spd, key,
mfg_id_key, nents)) {
return (false);
}
}
if (mfg_name_valid == DIMM_COMP_K_VALID) {
char key[64];
(void) snprintf(key, sizeof (key), "%s-mfg-name",
comp->dc_comp);
if (!dimm_comp_mfg_common_strings(mod, dimm, spd, key,
mfg_name_key, nents, true)) {
return (false);
}
}
if (rev_valid == DIMM_COMP_K_VALID) {
char key[64];
(void) snprintf(key, sizeof (key), "%s-revision",
comp->dc_comp);
if (!dimm_comp_mfg_common_strings(mod, dimm, spd, key, rev_key,
nents, false)) {
return (false);
}
}
if (type_valid == DIMM_COMP_K_VALID) {
if (!dimm_comp_mfg_common_type(mod, dimm, spd, comp, type_key,
nents)) {
return (false);
}
}
return (true);
}
static bool
dimm_comp_mfg_die(topo_mod_t *mod, tnode_t *dimm, const dimm_comp_t *comp,
const spd_cache_t *cache, nvlist_t *spd, void *arg)
{
const char *mfg_id = SPD_KEY_MFG_DRAM_MFG_ID;
const char *mfg_name = SPD_KEY_MFG_DRAM_MFG_NAME;
const char *rev = SPD_KEY_MFG_DRAM_STEP;
return (dimm_comp_mfg_common(mod, dimm, comp, spd, &mfg_id, &mfg_name,
NULL, &rev, 1));
}
static bool
dimm_comp_mfg_single(topo_mod_t *mod, tnode_t *dimm, const dimm_comp_t *comp,
const spd_cache_t *cache, nvlist_t *spd, void *arg)
{
char *mfg_key = NULL, *mfg_str_key = NULL, *type_key = NULL;
char *rev_key = NULL;
const char *name = arg;
bool ret;
if (name == NULL) {
name = comp->dc_comp;
}
if (topo_mod_asprintf(mod, &mfg_key, "module.%s.mfg-id", name) == -1 ||
topo_mod_asprintf(mod, &mfg_str_key, "module.%s.mfg-name",
name) == -1 ||
topo_mod_asprintf(mod, &type_key, "module.%s.type", name) == -1 ||
topo_mod_asprintf(mod, &rev_key, "module.%s.revision", name) ==
-1) {
ret = false;
goto done;
}
ret = dimm_comp_mfg_common(mod, dimm, comp, spd,
(const char **)&mfg_key, (const char **)&mfg_str_key,
(const char **)&type_key, (const char **)&rev_key, 1);
done:
topo_mod_strfree(mod, mfg_key);
topo_mod_strfree(mod, mfg_str_key);
topo_mod_strfree(mod, type_key);
topo_mod_strfree(mod, rev_key);
return (ret);
}
static bool
dimm_comp_mfg_pmic(topo_mod_t *mod, tnode_t *dimm, const dimm_comp_t *comp,
const spd_cache_t *cache, nvlist_t *spd, void *arg)
{
const char **mfg_keys = NULL, **mfg_str_keys = NULL, **type_keys = NULL;
const char **rev_keys = NULL;
bool ret = false;
uint32_t nents = 0, curent = 0;
size_t alen;
if ((cache->sc_devices & SPD_DEVICE_PMIC_0) != 0)
nents++;
if ((cache->sc_devices & SPD_DEVICE_PMIC_1) != 0)
nents++;
if ((cache->sc_devices & SPD_DEVICE_PMIC_2) != 0)
nents++;
if (nents == 0) {
return (true);
}
alen = sizeof (char *) * nents;
if ((mfg_keys = topo_mod_zalloc(mod, alen)) == NULL ||
(mfg_str_keys = topo_mod_zalloc(mod, alen)) == NULL ||
(type_keys = topo_mod_zalloc(mod, alen)) == NULL ||
(rev_keys = topo_mod_zalloc(mod, alen)) == NULL) {
goto done;
}
if ((cache->sc_devices & SPD_DEVICE_PMIC_0) != 0) {
mfg_keys[curent] = SPD_KEY_DEV_PMIC0_MFG;
mfg_str_keys[curent] = SPD_KEY_DEV_PMIC0_MFG_NAME;
type_keys[curent] = SPD_KEY_DEV_PMIC0_TYPE;
rev_keys[curent] = SPD_KEY_DEV_PMIC0_REV;
curent++;
}
if ((cache->sc_devices & SPD_DEVICE_PMIC_1) != 0) {
mfg_keys[curent] = SPD_KEY_DEV_PMIC1_MFG;
mfg_str_keys[curent] = SPD_KEY_DEV_PMIC1_MFG_NAME;
type_keys[curent] = SPD_KEY_DEV_PMIC1_TYPE;
rev_keys[curent] = SPD_KEY_DEV_PMIC1_REV;
curent++;
}
if ((cache->sc_devices & SPD_DEVICE_PMIC_2) != 0) {
mfg_keys[curent] = SPD_KEY_DEV_PMIC2_MFG;
mfg_str_keys[curent] = SPD_KEY_DEV_PMIC2_MFG_NAME;
type_keys[curent] = SPD_KEY_DEV_PMIC2_TYPE;
rev_keys[curent] = SPD_KEY_DEV_PMIC2_REV;
curent++;
}
ret = dimm_comp_mfg_common(mod, dimm, comp, spd, mfg_keys,
mfg_str_keys, type_keys, rev_keys, nents);
done:
topo_mod_free(mod, mfg_keys, alen);
topo_mod_free(mod, mfg_str_keys, alen);
topo_mod_free(mod, type_keys, alen);
topo_mod_free(mod, rev_keys, alen);
return (ret);
}
static bool
dimm_comp_mfg_cd(topo_mod_t *mod, tnode_t *dimm, const dimm_comp_t *comp,
const spd_cache_t *cache, nvlist_t *spd, void *arg)
{
const char **mfg_keys = NULL, **mfg_str_keys = NULL, **type_keys = NULL;
const char **rev_keys = NULL;
bool ret = false;
uint32_t nents = 0, curent = 0;
size_t alen;
if ((cache->sc_devices & SPD_DEVICE_CD_0) != 0)
nents++;
if ((cache->sc_devices & SPD_DEVICE_CD_1) != 0)
nents++;
if (nents == 0) {
return (true);
}
alen = sizeof (char *) * nents;
if ((mfg_keys = topo_mod_zalloc(mod, alen)) == NULL ||
(mfg_str_keys = topo_mod_zalloc(mod, alen)) == NULL ||
(type_keys = topo_mod_zalloc(mod, alen)) == NULL ||
(rev_keys = topo_mod_zalloc(mod, alen)) == NULL) {
goto done;
}
if ((cache->sc_devices & SPD_DEVICE_CD_0) != 0) {
mfg_keys[curent] = SPD_KEY_DEV_CD0_MFG;
mfg_str_keys[curent] = SPD_KEY_DEV_CD0_MFG_NAME;
type_keys[curent] = SPD_KEY_DEV_CD0_TYPE;
rev_keys[curent] = SPD_KEY_DEV_CD0_REV;
curent++;
}
if ((cache->sc_devices & SPD_DEVICE_CD_1) != 0) {
mfg_keys[curent] = SPD_KEY_DEV_CD1_MFG;
mfg_str_keys[curent] = SPD_KEY_DEV_CD1_MFG_NAME;
type_keys[curent] = SPD_KEY_DEV_CD1_TYPE;
rev_keys[curent] = SPD_KEY_DEV_CD1_REV;
curent++;
}
ret = dimm_comp_mfg_common(mod, dimm, comp, spd, mfg_keys,
mfg_str_keys, type_keys, rev_keys, nents);
done:
topo_mod_free(mod, mfg_keys, alen);
topo_mod_free(mod, mfg_str_keys, alen);
topo_mod_free(mod, type_keys, alen);
topo_mod_free(mod, rev_keys, alen);
return (ret);
}
static const dimm_comp_t dimm_comps[] = {
{ .dc_comp = TOPO_PROP_DIMM_COMP_DIE, .dc_always = true,
.dc_count = dimm_comp_count_dies, .dc_mfg = dimm_comp_mfg_die },
{ .dc_comp = TOPO_PROP_DIMM_COMP_SPD, .dc_mask = SPD_DEVICE_SPD,
.dc_count = dimm_comp_count_solo, .dc_mfg = dimm_comp_mfg_single,
.dc_type2str = topo_dimm_spd2str },
{ .dc_comp = TOPO_PROP_DIMM_COMP_TS, .dc_mask = SPD_DEVICE_TEMP_1 |
SPD_DEVICE_TEMP_2, .dc_count = dimm_comp_count_mask,
.dc_mfg = dimm_comp_mfg_single, .dc_mfg_arg = "temp",
.dc_type2str = topo_dimm_temp2str },
{ .dc_comp = TOPO_PROP_DIMM_COMP_HS, .dc_mask = SPD_DEVICE_HS },
{ .dc_comp = TOPO_PROP_DIMM_COMP_PMIC, .dc_mask = SPD_DEVICE_PMIC_0 |
SPD_DEVICE_PMIC_1 | SPD_DEVICE_PMIC_2, .dc_mfg = dimm_comp_mfg_pmic,
.dc_type2str = topo_dimm_pmic2str },
{ .dc_comp = TOPO_PROP_DIMM_COMP_CD, .dc_mask = SPD_DEVICE_CD_0 |
SPD_DEVICE_CD_1, .dc_mfg = dimm_comp_mfg_cd,
.dc_type2str = topo_dimm_cd2str },
{ .dc_comp = TOPO_PROP_DIMM_COMP_RCD, .dc_mask = SPD_DEVICE_RCD,
.dc_count = dimm_comp_count_regs, .dc_mfg = dimm_comp_mfg_single,
.dc_type2str = topo_dimm_rcd2str },
{ .dc_comp = TOPO_PROP_DIMM_COMP_DB, .dc_mask = SPD_DEVICE_DB,
.dc_mfg = dimm_comp_mfg_single, .dc_type2str = topo_dimm_db2str },
{ .dc_comp = TOPO_PROP_DIMM_COMP_MRCD, .dc_mask = SPD_DEVICE_MRCD,
.dc_mfg = dimm_comp_mfg_single, .dc_type2str = topo_dimm_mrcd2str },
{ .dc_comp = TOPO_PROP_DIMM_COMP_MDB, .dc_mask = SPD_DEVICE_MDB,
.dc_mfg = dimm_comp_mfg_single, .dc_type2str = topo_dimm_mdb2str },
{ .dc_comp = TOPO_PROP_DIMM_COMP_DMB, .dc_mask = SPD_DEVICE_DMB,
.dc_mfg = dimm_comp_mfg_single, .dc_type2str = topo_dimm_dmb2str }
};
static bool
topo_dimm_add_comps(topo_mod_t *mod, tnode_t *dimm, nvlist_t *spd,
const spd_cache_t *cache)
{
int ret;
const char *devs[ARRAY_SIZE(dimm_comps)];
uint_t ndevs = 0;
const char *pg = topo_dimm_comps_pgroup.tpi_name;
if (topo_pgroup_create(dimm, &topo_dimm_comps_pgroup, &ret) != 0) {
topo_mod_dprintf(mod, "failed to create property group %s: %s",
pg, topo_strerror(ret));
(void) topo_mod_seterrno(mod, ret);
return (false);
}
for (size_t i = 0; i < ARRAY_SIZE(dimm_comps); i++) {
const dimm_comp_t *c = &dimm_comps[i];
char prop[64];
bool pres = false;
if (c->dc_always || (cache->sc_devices & c->dc_mask) != 0) {
pres = true;
devs[ndevs] = dimm_comps[i].dc_comp;
ndevs++;
}
if (pres && c->dc_count != NULL) {
uint32_t count = c->dc_count(c, cache, spd);
(void) snprintf(prop, sizeof (prop), "%s-count",
c->dc_comp);
if (count != 0 && topo_prop_set_uint32(dimm, pg, prop,
TOPO_PROP_IMMUTABLE, count, &ret) != 0) {
topo_mod_dprintf(mod, "failed to set property "
"%s: %s", prop, topo_strerror(ret));
(void) topo_mod_seterrno(mod, ret);
return (false);
}
}
if (pres && c->dc_mfg != NULL && !c->dc_mfg(mod, dimm, c, cache,
spd, c->dc_mfg_arg)) {
return (false);
}
}
if (topo_prop_set_string_array(dimm, pg, TOPO_PROP_DIMM_COMP,
TOPO_PROP_IMMUTABLE, devs, ndevs, &ret) != 0) {
topo_mod_dprintf(mod, "failed to create components array: %s",
topo_strerror(ret));
(void) topo_mod_seterrno(mod, ret);
return (false);
}
return (true);
}
static int
topo_dimm_enum(topo_mod_t *mod, tnode_t *pn, const char *name,
topo_instance_t min, topo_instance_t max, void *modarg, void *data)
{
int ret;
const topo_dimm_t *dimm;
spd_error_t spd_err;
nvlist_t *spd_nvl = NULL;
uint32_t dram_type;
char *mod_pn = NULL, *mod_sn = NULL, *mod_rev = NULL;
char *mod_c_pn = NULL, *mod_c_sn = NULL, *mod_c_rev = NULL;
tnode_t *dimm_tn;
spd_cache_t spd_cache;
topo_mod_dprintf(mod, "asked to enum %s [%" PRIu64 ", %" PRIu64 "] on "
"%s%" PRIu64 "\n", name, min, max, topo_node_name(pn),
topo_node_instance(pn));
if (strcmp(name, DIMM) != 0) {
topo_mod_dprintf(mod, "cannot enumerate %s: unknown type\n",
name);
ret = -1;
goto out;
}
if (data == NULL) {
topo_mod_dprintf(mod, "cannot enumerate %s: missing required "
"data\n", name);
ret = topo_mod_seterrno(mod, EMOD_METHOD_INVAL);
goto out;
}
if (min != max) {
topo_mod_dprintf(mod, "cannot enumerate %s: multiple instances "
"requested\n", name);
ret = topo_mod_seterrno(mod, EMOD_METHOD_INVAL);
goto out;
}
dimm = data;
if (dimm->td_nspd == 0 || dimm->td_spd == NULL) {
topo_mod_dprintf(mod, "cannot enumerate %s: no valid DIMM "
"data provided", name);
ret = topo_mod_seterrno(mod, EMOD_METHOD_INVAL);
goto out;
}
spd_nvl = libjedec_spd(dimm->td_spd, dimm->td_nspd, &spd_err);
if (spd_nvl == NULL) {
topo_mod_dprintf(mod, "failed to parse SPD information: got "
"error 0x%x", spd_err);
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
}
if ((ret = nvlist_lookup_uint32(spd_nvl, SPD_KEY_DRAM_TYPE,
&dram_type)) != 0) {
topo_mod_dprintf(mod, "failed to get SPD key %s: %s",
SPD_KEY_DRAM_TYPE, strerror(ret));
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto out;
}
if (!topo_dimm_crc_ok(mod, spd_nvl, dram_type)) {
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto out;
}
if (nvlist_lookup_pairs(spd_nvl, NV_FLAG_NOENTOK,
SPD_KEY_MFG_MOD_PN, DATA_TYPE_STRING, &mod_pn,
SPD_KEY_MFG_MOD_SN, DATA_TYPE_STRING, &mod_sn,
SPD_KEY_MFG_MOD_REV, DATA_TYPE_STRING, &mod_rev, NULL) != 0) {
topo_mod_dprintf(mod, "failed to look up basic DIMM FMRI "
"information");
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto out;
}
mod_c_pn = topo_mod_clean_str(mod, mod_pn);
mod_c_sn = topo_mod_clean_str(mod, mod_sn);
mod_c_rev = topo_mod_clean_str(mod, mod_rev);
if ((ret = topo_node_range_create(mod, pn, DIMM, 0, 0)) != 0) {
topo_mod_dprintf(mod, "failed to create DIMM range: %s",
topo_mod_errmsg(mod));
goto out;
}
if ((ret = topo_dimm_create_tn(mod, pn, &dimm_tn, DIMM, 0, mod_c_pn,
mod_c_rev, mod_c_sn)) != 0) {
goto out;
}
if (topo_node_label_set(dimm_tn, NULL, &ret) != 0) {
topo_mod_dprintf(mod, "failed to set label on DIMM: %s",
topo_mod_errmsg(mod));
ret = topo_mod_seterrno(mod, ret);
goto out;
}
(void) memset(&spd_cache, 0, sizeof (spd_cache));
spd_cache.sc_dram_type = dram_type;
if (!topo_dimm_cache_spd(mod, spd_nvl, &spd_cache))
goto out;
if (!topo_dimm_add_props(mod, dimm_tn, &spd_cache))
goto out;
if (!topo_dimm_add_comps(mod, dimm_tn, spd_nvl, &spd_cache))
goto out;
ret = 0;
out:
topo_mod_strfree(mod, mod_c_sn);
topo_mod_strfree(mod, mod_c_pn);
topo_mod_strfree(mod, mod_c_rev);
nvlist_free(spd_nvl);
return (ret);
}
static const topo_modops_t topo_dimm_ops = {
topo_dimm_enum, NULL
};
static topo_modinfo_t topo_dimm_mod = {
"Common DIMM Enumerator", FM_FMRI_SCHEME_HC, TOPO_MOD_DIMM_VERS,
&topo_dimm_ops
};
int
_topo_init(topo_mod_t *mod, topo_version_t version)
{
if (getenv("TOPODIMMDEBUG") != NULL) {
topo_mod_setdebug(mod);
}
topo_mod_dprintf(mod, "module initializing\n");
return (topo_mod_register(mod, &topo_dimm_mod, TOPO_VERSION));
}
void
_topo_fini(topo_mod_t *mod)
{
topo_mod_unregister(mod);
}