/* * 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 2023 Oxide Computer Company */ /* * This implements common DIMM creation for the hc tree. Currently this is based * primarily on providing SPD data. */ #include #include #include #include #include #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 }; /* * Translate a subset of the DDR types that we're likely to support into the * corresponding current DDR information. We only really support taking these * apart, so that's OK. */ 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); } } /* * Various string functions for different component types. */ 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"); 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"); 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"); 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"); default: return ("unknown"); } } static const char * topo_dimm_mrcd2str(uint32_t val) { switch (val) { case SPD_MRCD_T_DDR5MRCD01: return ("DDR5MRCD01"); default: return ("unknown"); } } static const char * topo_dimm_mdb2str(uint32_t val) { switch (val) { case SPD_MDB_T_DDR5MDB01: return ("DDR5MDB01"); 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_EE1004: return ("EE1004"); case SPD_SPD_T_SPD5118: return ("SPD5118"); case SPD_SPD_T_ESPD5216: return ("ESPD5216"); default: return ("unknown"); } } /* * DDR4 and DDR5 have a fixed voltage. DDR3 had a range of voltages that could * be selected. In addition, LPDDR4 and LPDDR5 depend on the specifics of the * memory controller as they allow for variable options here. */ 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_72b_SO_RDIMM: return ("72b-SO-RDIMM"); case SPD_MOD_TYPE_72b_SO_UDIMM: return ("72b-SO-UDIMM"); case SPD_MOD_TYPE_16b_SO_DIMM: return ("16b-SO-DIMM"); case SPD_MOD_TYPE_32b_SO_DIMM: return ("32b-SO-DIMM"); default: return (NULL); } } /* * Go through and cache common properties that we would look up in the NVL into * a structure. We do this once and then reuse this for common settings. We * don't generally include PN/SN/Rev information in here since not having that * is OK and we can still create nodes and due to the fact that we generally * only use it once. */ static bool topo_dimm_cache_spd(topo_mod_t *mod, nvlist_t *spd, spd_cache_t *cache) { /* * First go through and look up values that we expect to always be * present. */ 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); } /* * Set information that should be valid based on the types that we * support right now. */ 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); /* * Next we have keys that may or may not be present. */ 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++; /* * Now go through and look up keys that we believe should always be * present given that we have an asymmetric configuration. */ 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); } /* * Calculating the size here is a little nuanced. The rough formula is provided * by JEDEC in the various SPD Annexes. The rough formula is: * * (SDRAM Capacity / 8) * (Bus width / SDRAM width) * Logical ranks * * Phrased in terms of SPD macros this is really: * * SPD_KEY_DIE_SIZE / 8 * (SPD_KEY_DATA_WIDTH / SPD_KEY_DRAM_WIDTH) * Logical * Ranks * * The DIMM operates in chunks that are equal to its data width multiplied by * the number of sub-channels. In general for DDR4/5 this is always going to be * 64-bits or 8 bytes. The ECC is not included in this. The SDRAM width is * fairly straightforward. The logical ranks depends on the die type and the * number of actual ranks present. This is basically SPD_KEY_PKG_NDIE * * SPD_KEY_NRANKS. * * However, there are two small wrinkles: the calculation of logical ranks and * asymmetrical modules. With asymmetrical modules the data width doesn't * change, the capacity and SDRAM width may change. In addition, calculating * logical ranks is a bit nuanced here. First, each module declares the number * of ranks that exist in the package. This has to then be transformed into * logical ranks, which happens if we're using 3DS based DIMMs, which is * determined based on the SPD_KEY_PKG_SL key. When using 3DS we need to * multiple the number of dies by the number of ranks, otherwise it stays at * 1x. * * When we're using asymmetrical DIMMs, the primary fields nominally apply to * the even ranks and the secondary fields to the odd ranks. This is explicitly * the case in DDR5. It is less explicit in DDR4, but we treat it the same way. */ 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); } /* * Add basic information to the DIMM. Some information like the current memory * speed or LPDDR voltage can only be derived from the memory controller or * systems firmware (i.e. SMBIOS). */ 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); } /* * This indicates that we have an asymmetrical DIMM configuration. This * implies that the number of banks and bank groups actually vary based * on whether it's an odd/even rank. */ 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 }; /* * Note: Because this function determines which forms of SPD we support, * if you end up adding something to the list you should update * topo_dimm_add_props() to make sure that any additional variants have * been added there or that we have information from their corresponding * memory controllers. */ 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 there are no errors then we're likely OK and we can continue. */ 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 *); /* XXX determine if cache is needed */ 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); } /* * We'd like to determine the number of dies that are actually present. One * way to calculate this is to look at the data bits and ecc bits that are * required and divide that by the DRAM width. There should be one set of such * dies for each primary rank. In DDR4/5 these contain the banks/groups. * * In a physical sense, even when using DDP or 3DS stacked modules, then there * is still only a single refdes basically on the board so we create it that * way. In the DDR4/5 world when there are more than two ranks, they are stacked * or using the older DDP technology. So basically we assume there are only up * to two ranks worth of dies at most. */ 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); } /* * In the DDR4 SPD information, there is an explicit key for the number of * registers that actually exist in the system. If the key exists then we return * that, otherwise we don't do anything. */ 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); } /* * This enum indicates the possible state for all the keys of a given type. * Basically we need to make sure that for the given range of keys they are * generally consistent. */ 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 we have changed disposition that is a problem. However, we * will allow a partial ENOENT to exist if we've been given the * flag to cover for the case where we don't have a translation * for a given manufacturer's JEDEC ID name. */ if (ret != cur) { if (partial_enoent) { ret = DIMM_COMP_K_VALID; } else { return (DIMM_COMP_K_ERR); } } } return (ret); } /* * The JEDEC IDs are a pair of two digits. Because we don't really have arrays * of arrays in topo, we instead convert this into a string of the form * 0x%x:0x%x with the continuation first and then the specific value. */ 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); } /* * The type of a part is encoded as a uint32_t and has a corresponding enum. We * want to translate that into a string. The table for that is stored in the * dimm_comp_t. */ 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); } /* * Given a number of keys to check for each item type, attempt to look up each * item and add a property based on it. Prior to DDR5, we generally won't have * information for the manufacturers or revisions. As such, when we fail to get * any keys of a given type, that's fine. However, we do want to make sure that * we are always adding things consistently, that is if we are told we have * three keys for something and sometimes only look up two, that's an error. */ 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 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, .dc_mfg = dimm_comp_mfg_single, .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 } }; /* * Go through and add the different information that exists for each type of * component that we might have. For most items on here, we can know they are * present, but we may not be able to get the count and much more than a * revision string or type. See additional discussion at the definition of * TOPO_PGROUP_DIMM_COMPONENTS for this property group and a bit of the design. */ 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; /* * Always create the pgroup, as we'll at least have information about * the DRAM dies to add. */ 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 we have SPD data, we'd expect all of the basic part, serial, and * revision information to be available for the module. However, if * there was bad data for some reason, we allow ourselves to not be able * to look it up. */ 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); }