/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Stub routines used to link in files from $SRC/common/mc */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include int mcamd_debug = 0; /* see mcamd_api.h for MCAMD_DBG_* values */ extern int x86gentopo_legacy; struct mc_offmap { int mcom_code; uint_t mcom_offset; }; static uint_t nodetype(mcamd_node_t *node) { mc_hdr_t *mch = (mc_hdr_t *)node; return (mch->mch_type); } static void * node2type(mcamd_node_t *node, int type) { mc_hdr_t *mch = (mc_hdr_t *)node; ASSERT(mch->mch_type == type); return (mch); } /* * Iterate over all memory controllers. */ /*ARGSUSED*/ mcamd_node_t * mcamd_mc_next(mcamd_hdl_t *hdl, mcamd_node_t *root, mcamd_node_t *last) { mc_t *mc; ASSERT(RW_LOCK_HELD(&mc_lock)); if (last == NULL) return ((mcamd_node_t *)mc_list); mc = node2type(last, MC_NT_MC); return ((mcamd_node_t *)mc->mc_next); } /* * Iterate over all chip-selects of a MC or all chip-selects of a DIMM * depending on the node type of 'node'. In the DIMM case we do not * have a linked list of associated chip-selects but an array of pointer * to them. */ /*ARGSUSED*/ mcamd_node_t * mcamd_cs_next(mcamd_hdl_t *hdl, mcamd_node_t *node, mcamd_node_t *last) { uint_t nt = nodetype(node); mc_t *mc; mc_cs_t *mccs; mc_dimm_t *mcd; int i; void *retval; ASSERT(nt == MC_NT_MC || nt == MC_NT_DIMM); if (last == NULL) { switch (nt) { case MC_NT_MC: mc = node2type(node, MC_NT_MC); retval = mc->mc_cslist; break; case MC_NT_DIMM: mcd = node2type(node, MC_NT_DIMM); retval = mcd->mcd_cs[0]; break; } } else { mccs = node2type(last, MC_NT_CS); switch (nt) { case MC_NT_MC: retval = mccs->mccs_next; break; case MC_NT_DIMM: mcd = node2type(node, MC_NT_DIMM); for (i = 0; i < MC_CHIP_DIMMRANKMAX; i++) { if (mcd->mcd_cs[i] == mccs) break; } if (i == MC_CHIP_DIMMRANKMAX) cmn_err(CE_PANIC, "Bad last value for " "mcamd_cs_next"); if (i == MC_CHIP_DIMMRANKMAX - 1) retval = NULL; else retval = mcd->mcd_cs[i + 1]; break; } } return ((mcamd_node_t *)retval); } /* * Iterate over all DIMMs of an MC or all DIMMs of a chip-select depending * on the node type of 'node'. In the chip-select case we do not have * a linked list of associated DIMMs but an array of pointers to them. */ /*ARGSUSED*/ mcamd_node_t * mcamd_dimm_next(mcamd_hdl_t *hdl, mcamd_node_t *node, mcamd_node_t *last) { uint_t nt = nodetype(node); mc_t *mc; mc_cs_t *mccs; mc_dimm_t *mcd; int i; void *retval; ASSERT(nt == MC_NT_MC || nt == MC_NT_CS); if (last == NULL) { switch (nt) { case MC_NT_MC: mc = node2type(node, MC_NT_MC); retval = mc->mc_dimmlist; break; case MC_NT_CS: mccs = node2type(node, MC_NT_CS); retval = mccs->mccs_dimm[0]; break; } } else { mcd = node2type(last, MC_NT_DIMM); switch (nt) { case MC_NT_MC: retval = mcd->mcd_next; break; case MC_NT_CS: mccs = node2type(node, MC_NT_CS); for (i = 0; i < MC_CHIP_DIMMPERCS; i++) { if (mccs->mccs_dimm[i] == mcd) break; } if (i == MC_CHIP_DIMMPERCS) cmn_err(CE_PANIC, "Bad last value for " "mcamd_dimm_next"); if (i == MC_CHIP_DIMMPERCS - 1) retval = NULL; else retval = mccs->mccs_dimm[i + 1]; break; } } return ((mcamd_node_t *)retval); } /*ARGSUSED*/ mcamd_node_t * mcamd_cs_mc(mcamd_hdl_t *hdl, mcamd_node_t *csnode) { mc_cs_t *mccs = node2type(csnode, MC_NT_CS); return ((mcamd_node_t *)mccs->mccs_mc); } /*ARGSUSED*/ mcamd_node_t * mcamd_dimm_mc(mcamd_hdl_t *hdl, mcamd_node_t *dnode) { mc_dimm_t *mcd = node2type(dnode, MC_NT_DIMM); return ((mcamd_node_t *)mcd->mcd_mc); } /* * Node properties. A property is accessed through a property number code; * we search these tables for a match (choosing table from node type) and * return the uint64_t property at the indicated offset into the node * structure. All properties must be of type uint64_t. It is assumed that * property lookup does not have to be super-fast - we search linearly * down the (small) lists. */ static const struct mc_offmap mcamd_mc_offmap[] = { { MCAMD_PROP_NUM, MCAMD_MC_OFF_NUM }, { MCAMD_PROP_REV, MCAMD_MC_OFF_REV }, { MCAMD_PROP_BASE_ADDR, MCAMD_MC_OFF_BASE_ADDR }, { MCAMD_PROP_LIM_ADDR, MCAMD_MC_OFF_LIM_ADDR }, { MCAMD_PROP_ILEN, MCAMD_MC_OFF_ILEN }, { MCAMD_PROP_ILSEL, MCAMD_MC_OFF_ILSEL }, { MCAMD_PROP_CSINTLVFCTR, MCAMD_MC_OFF_CSINTLVFCTR }, { MCAMD_PROP_DRAMHOLE_SIZE, MCAMD_MC_OFF_DRAMHOLE_SIZE }, { MCAMD_PROP_ACCESS_WIDTH, MCAMD_MC_OFF_ACCWIDTH }, { MCAMD_PROP_CSBANKMAPREG, MCAMD_MC_OFF_CSBANKMAPREG }, { MCAMD_PROP_BANKSWZL, MCAMD_MC_OFF_BNKSWZL }, { MCAMD_PROP_MOD64MUX, MCAMD_MC_OFF_MOD64MUX }, { MCAMD_PROP_SPARECS, MCAMD_MC_OFF_SPARECS }, { MCAMD_PROP_BADCS, MCAMD_MC_OFF_BADCS }, }; static const struct mc_offmap mcamd_cs_offmap[] = { { MCAMD_PROP_NUM, MCAMD_CS_OFF_NUM }, { MCAMD_PROP_BASE_ADDR, MCAMD_CS_OFF_BASE_ADDR }, { MCAMD_PROP_MASK, MCAMD_CS_OFF_MASK }, { MCAMD_PROP_SIZE, MCAMD_CS_OFF_SIZE }, { MCAMD_PROP_CSBE, MCAMD_CS_OFF_CSBE }, { MCAMD_PROP_SPARE, MCAMD_CS_OFF_SPARE }, { MCAMD_PROP_TESTFAIL, MCAMD_CS_OFF_TESTFAIL }, { MCAMD_PROP_CSDIMM1, MCAMD_CS_OFF_DIMMNUMS }, { MCAMD_PROP_CSDIMM2, MCAMD_CS_OFF_DIMMNUMS + MCAMD_CS_OFF_DIMMNUMS_INCR }, { MCAMD_PROP_DIMMRANK, MCAMD_CS_OFF_DIMMRANK }, }; static const struct mc_offmap mcamd_dimm_offmap[] = { { MCAMD_PROP_NUM, MCAMD_DIMM_OFF_NUM }, { MCAMD_PROP_SIZE, MCAMD_DIMM_OFF_SIZE }, }; struct nt_offmap { const struct mc_offmap *omp; int mapents; }; /*ARGSUSED*/ static int findoffset(mcamd_hdl_t *hdl, mcamd_node_t *node, struct nt_offmap *arr, int code, uint_t *offset) { int i; mc_hdr_t *mch = (mc_hdr_t *)node; int nt = mch->mch_type; const struct mc_offmap *omp; if (nt > MC_NT_NTYPES || (omp = arr[nt].omp) == NULL) return (0); for (i = 0; i < arr[nt].mapents; i++, omp++) { if (omp->mcom_code == code) { *offset = omp->mcom_offset; return (1); } } return (0); } /*ARGSUSED*/ int mcamd_get_numprop(mcamd_hdl_t *hdl, mcamd_node_t *node, mcamd_propcode_t code, mcamd_prop_t *valp) { int found; uint_t offset; struct nt_offmap props[] = { { mcamd_mc_offmap, /* MC_NT_MC */ sizeof (mcamd_mc_offmap) / sizeof (struct mc_offmap) }, { mcamd_cs_offmap, /* MC_NT_CS */ sizeof (mcamd_cs_offmap) / sizeof (struct mc_offmap) }, { mcamd_dimm_offmap, /* MC_NT_DIMM */ sizeof (mcamd_dimm_offmap) / sizeof (struct mc_offmap) } }; found = findoffset(hdl, node, &props[0], code, &offset); ASSERT(found); if (found) *valp = *(uint64_t *)((uintptr_t)node + offset); return (found == 1); } int mcamd_get_numprops(mcamd_hdl_t *hdl, ...) { va_list ap; mcamd_node_t *node; mcamd_propcode_t code; mcamd_prop_t *valp; va_start(ap, hdl); while ((node = va_arg(ap, mcamd_node_t *)) != NULL) { code = va_arg(ap, mcamd_propcode_t); valp = va_arg(ap, mcamd_prop_t *); if (!mcamd_get_numprop(hdl, node, code, valp)) return (0); } va_end(ap); return (1); } static const struct mc_offmap mcreg_offmap[] = { { MCAMD_REG_DRAMBASE, MCAMD_MC_OFF_DRAMBASE_REG }, { MCAMD_REG_DRAMLIMIT, MCAMD_MC_OFF_DRAMLIMIT_REG }, { MCAMD_REG_DRAMHOLE, MCAMD_MC_OFF_DRAMHOLE_REG }, { MCAMD_REG_DRAMCFGLO, MCAMD_MC_OFF_DRAMCFGLO_REG }, { MCAMD_REG_DRAMCFGHI, MCAMD_MC_OFF_DRAMCFGHI_REG }, }; static const struct mc_offmap csreg_offmap[] = { { MCAMD_REG_CSBASE, MCAMD_CS_OFF_CSBASE_REG }, { MCAMD_REG_CSMASK, MCAMD_CS_OFF_CSMASK_REG }, }; /*ARGSUSED*/ int mcamd_get_cfgreg(struct mcamd_hdl *hdl, mcamd_node_t *node, mcamd_regcode_t code, uint32_t *valp) { int found; uint_t offset; struct nt_offmap regs[] = { { mcreg_offmap, /* MC_NT_MC */ sizeof (mcreg_offmap) / sizeof (struct mc_offmap) }, { csreg_offmap, /* MC_NT_CS */ sizeof (csreg_offmap) / sizeof (struct mc_offmap) }, { NULL, 0 } /* MC_NT_DIMM */ }; found = findoffset(hdl, node, ®s[0], code, &offset); ASSERT(found); ASSERT(found); if (found) *valp = *(uint32_t *)((uintptr_t)node + offset); return (found == 1); } int mcamd_get_cfgregs(mcamd_hdl_t *hdl, ...) { va_list ap; mcamd_node_t *node; mcamd_regcode_t code; uint32_t *valp; va_start(ap, hdl); while ((node = va_arg(ap, mcamd_node_t *)) != NULL) { code = va_arg(ap, mcamd_regcode_t); valp = va_arg(ap, uint32_t *); if (!mcamd_get_cfgreg(hdl, node, code, valp)) return (0); } va_end(ap); return (1); } int mcamd_errno(mcamd_hdl_t *mcamd) { return (mcamd->mcamd_errno); } int mcamd_set_errno(mcamd_hdl_t *mcamd, int err) { mcamd->mcamd_errno = err; return (-1); } void mcamd_dprintf(mcamd_hdl_t *mcamd, int mask, const char *fmt, ...) { va_list ap; if (!(mcamd->mcamd_debug & mask)) return; va_start(ap, fmt); vcmn_err(mask & MCAMD_DBG_ERR ? CE_WARN : CE_NOTE, fmt, ap); va_end(ap); } void mcamd_mkhdl(mcamd_hdl_t *hdl) { hdl->mcamd_errno = 0; hdl->mcamd_debug = mcamd_debug; } cmi_errno_t mcamd_cmierr(int err, mcamd_hdl_t *hdl) { if (err == 0) return (CMI_SUCCESS); switch (mcamd_errno(hdl)) { case EMCAMD_SYNDINVALID: return (CMIERR_MC_SYNDROME); case EMCAMD_TREEINVALID: return (CMIERR_MC_BADSTATE); case EMCAMD_NOADDR: return (CMIERR_MC_NOADDR); case EMCAMD_INSUFF_RES: return (CMIERR_MC_ADDRBITS); default: return (CMIERR_UNKNOWN); } } /*ARGSUSED*/ cmi_errno_t mcamd_patounum_wrap(void *arg, uint64_t pa, uint8_t valid_hi, uint8_t valid_lo, uint32_t synd, int syndtype, mc_unum_t *unump) { mcamd_hdl_t mcamd; int rc; mcamd_mkhdl(&mcamd); rw_enter(&mc_lock, RW_READER); rc = mcamd_patounum(&mcamd, (mcamd_node_t *)mc_list, pa, valid_hi, valid_lo, synd, syndtype, unump); #ifdef DEBUG /* * Apply the reverse operation to verify the result. If there is * a problem complain but continue. */ if (rc == 0 && MCAMD_RC_OFFSET_VALID(unump->unum_offset)) { uint64_t rpa; if (mcamd_unumtopa(&mcamd, (mcamd_node_t *)mc_list, unump, &rpa) != 0 || rpa != pa) { mcamd_dprintf(&mcamd, MCAMD_DBG_ERR, "mcamd_patounum_wrap: offset calculation " "verification for PA 0x%llx failed\n", pa); } } #endif rw_exit(&mc_lock); return (mcamd_cmierr(rc, &mcamd)); } static int fmri2unum(nvlist_t *nvl, mc_unum_t *unump) { int i; uint64_t offset; nvlist_t *hcsp, **hcl; uint_t npr; if (nvlist_lookup_nvlist(nvl, FM_FMRI_HC_SPECIFIC, &hcsp) != 0 || (nvlist_lookup_uint64(hcsp, "asru-" FM_FMRI_HC_SPECIFIC_OFFSET, &offset) != 0 && nvlist_lookup_uint64(hcsp, FM_FMRI_HC_SPECIFIC_OFFSET, &offset) != 0) || nvlist_lookup_nvlist_array(nvl, FM_FMRI_HC_LIST, &hcl, &npr) != 0) return (0); bzero(unump, sizeof (mc_unum_t)); unump->unum_chan = MC_INVALNUM; for (i = 0; i < MC_UNUM_NDIMM; i++) unump->unum_dimms[i] = MC_INVALNUM; for (i = 0; i < npr; i++) { char *hcnm, *hcid; long v; if (nvlist_lookup_string(hcl[i], FM_FMRI_HC_NAME, &hcnm) != 0 || nvlist_lookup_string(hcl[i], FM_FMRI_HC_ID, &hcid) != 0 || ddi_strtol(hcid, NULL, 0, &v) != 0) return (0); if (strcmp(hcnm, "motherboard") == 0) unump->unum_board = (int)v; else if (strcmp(hcnm, "chip") == 0) unump->unum_chip = (int)v; else if (strcmp(hcnm, "memory-controller") == 0) unump->unum_mc = (int)v; else if (strcmp(hcnm, "chip-select") == 0) unump->unum_cs = (int)v; else if (strcmp(hcnm, "dimm") == 0) unump->unum_dimms[0] = (int)v; else if (strcmp(hcnm, "rank") == 0) unump->unum_rank = (int)v; } unump->unum_offset = offset; return (1); } /*ARGSUSED*/ cmi_errno_t mcamd_unumtopa_wrap(void *arg, mc_unum_t *unump, nvlist_t *nvl, uint64_t *pap) { mcamd_hdl_t mcamd; int rc; mc_unum_t unum; ASSERT(unump == NULL || nvl == NULL); /* enforced at cmi level */ if (unump == NULL) { if (!fmri2unum(nvl, &unum)) return (CMIERR_MC_INVALUNUM); unump = &unum; } mcamd_mkhdl(&mcamd); rw_enter(&mc_lock, RW_READER); rc = mcamd_unumtopa(&mcamd, (mcamd_node_t *)mc_list, unump, pap); rw_exit(&mc_lock); return (mcamd_cmierr(rc, &mcamd)); } static void mc_ereport_dimm_resource(mc_unum_t *unump, nvlist_t *elems[], int *nump, mc_t *mc) { int i; for (i = 0; i < MC_UNUM_NDIMM; i++) { if (unump->unum_dimms[i] == MC_INVALNUM) break; elems[(*nump)++] = fm_nvlist_create(NULL); if (!x86gentopo_legacy && mc->smb_bboard != NULL) { fm_fmri_hc_create(elems[i], FM_HC_SCHEME_VERSION, NULL, NULL, mc->smb_bboard, 4, "chip", mc->smb_chipid, "memory-controller", unump->unum_mc, "dimm", unump->unum_dimms[i], "rank", unump->unum_rank); } else { fm_fmri_hc_set(elems[i], FM_HC_SCHEME_VERSION, NULL, NULL, 5, "motherboard", unump->unum_board, "chip", unump->unum_chip, "memory-controller", unump->unum_mc, "dimm", unump->unum_dimms[i], "rank", unump->unum_rank); } } } static void mc_ereport_cs_resource(mc_unum_t *unump, nvlist_t *elems[], int *nump, mc_t *mc) { elems[0] = fm_nvlist_create(NULL); if (!x86gentopo_legacy && mc->smb_bboard != NULL) { fm_fmri_hc_create(elems[0], FM_HC_SCHEME_VERSION, NULL, NULL, mc->smb_bboard, 3, "chip", mc->smb_chipid, "memory-controller", unump->unum_mc, "chip-select", unump->unum_cs); } else { fm_fmri_hc_set(elems[0], FM_HC_SCHEME_VERSION, NULL, NULL, 4, "motherboard", unump->unum_board, "chip", unump->unum_chip, "memory-controller", unump->unum_mc, "chip-select", unump->unum_cs); } *nump = 1; } /* * Create the 'resource' payload member from the unum info. If valid * dimm numbers are present in the unum info then create members * identifying the dimm and rank; otherwise if a valid chip-select * number is indicated then create a member identifying the chip-select * topology node. */ static void mc_ereport_add_resource(nvlist_t *payload, mc_unum_t *unump, mc_t *mc) { nvlist_t *elems[MC_UNUM_NDIMM]; int nelems = 0; int i; if (unump->unum_dimms[0] != MC_INVALNUM) mc_ereport_dimm_resource(unump, elems, &nelems, mc); else if (unump->unum_cs != MC_INVALNUM) mc_ereport_cs_resource(unump, elems, &nelems, mc); if (nelems > 0) { fm_payload_set(payload, FM_EREPORT_PAYLOAD_NAME_RESOURCE, DATA_TYPE_NVLIST_ARRAY, nelems, elems, NULL); for (i = 0; i < nelems; i++) fm_nvlist_destroy(elems[i], FM_NVA_FREE); } } static void mc_ereport_add_payload(nvlist_t *ereport, uint64_t members, mc_unum_t *unump, mc_t *mc) { if (members & FM_EREPORT_PAYLOAD_FLAG_RESOURCE && unump != NULL) mc_ereport_add_resource(ereport, unump, mc); } static nvlist_t * mc_fmri_create(mc_t *mc) { nvlist_t *nvl = fm_nvlist_create(NULL); if (!x86gentopo_legacy && mc->smb_bboard != NULL) { fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION, NULL, NULL, mc->smb_bboard, 2, "chip", mc->smb_chipid, "memory-controller", 0); } else { fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, NULL, NULL, 3, "motherboard", 0, "chip", mc->mc_props.mcp_num, "memory-controller", 0); } return (nvl); } /* * Simple ereport generator for errors detected by the memory controller. * Posts an ereport of class ereport.cpu.amd. with a resource nvlist * derived from the given mc_unum_t. There are no other payload members. * The mc argument is used to formulate a detector and this mc should * correspond with that identified in the mc_unum_t. * * There is no control of which members to include the the resulting ereport - * it will be an ereport formed using the given class suffix, detector * indicated as the memory-controller and with a resource generated by * expanding the given mc_unum_t. * * We do not use any special nv allocator here and so this is not suitable * for use during panic. It is intended for use during MC topology * discovery and other controlled circumstances. */ void mcamd_ereport_post(mc_t *mc, const char *class_sfx, mc_unum_t *unump, uint64_t payload) { nvlist_t *ereport, *detector; char buf[FM_MAX_CLASS]; ereport = fm_nvlist_create(NULL); detector = mc_fmri_create(mc); (void) snprintf(buf, FM_MAX_CLASS, "%s.%s.%s", FM_ERROR_CPU, "amd", class_sfx); fm_ereport_set(ereport, FM_EREPORT_VERSION, buf, fm_ena_generate(gethrtime(), FM_ENA_FMT1), detector, NULL); fm_nvlist_destroy(detector, FM_NVA_FREE); mc_ereport_add_payload(ereport, payload, unump, mc); (void) fm_ereport_post(ereport, EVCH_TRYHARD); fm_nvlist_destroy(ereport, FM_NVA_FREE); } static const cmi_mc_ops_t mcamd_mc_ops = { mcamd_patounum_wrap, /* cmi_mc_patounum */ mcamd_unumtopa_wrap, /* cmi_mc_unumtopa */ NULL /* cmi_mc_logout */ }; void mcamd_mc_register(cmi_hdl_t hdl, mc_t *mc) { cmi_mc_register(hdl, &mcamd_mc_ops, mc); }