/* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* * Copyright (c) 2010, Intel Corporation. * All rights reserved. */ /* * Copyright (c) 2018, Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include /* * Set to prevent model-specific support from initialising. */ int cms_no_model_specific = 0; /* * Subdirectory (relative to the module search path) in which we will * look for model-specific modules. */ #define CPUMOD_MS_SUBDIR "cpu" /* * Cpu model-specific modules have filenames beginning with the following. */ #define CPUMOD_MS_PREFIX "cpu_ms" #define HDL2CMS(hdl) cms_hdl_getcms(hdl) #define CMS_OPS(cms) (cms)->cms_ops #define CMS_OP_PRESENT(cms, op) ((cms) && CMS_OPS(cms)->op != NULL) struct cms_cpuid { const char *vendor; uint_t family; uint_t model; uint_t stepping; }; #define CMS_MATCH_VENDOR 0 /* Just match on vendor */ #define CMS_MATCH_FAMILY 1 /* Match down to family */ #define CMS_MATCH_MODEL 2 /* Match down to model */ #define CMS_MATCH_STEPPING 3 /* Match down to stepping */ /* * Structure used to keep track of modules we have loaded. */ typedef struct cms { struct cms *cms_next; struct cms *cms_prev; const cms_ops_t *cms_ops; struct modctl *cms_modp; uint_t cms_refcnt; } cms_t; static cms_t *cms_list; static kmutex_t cms_load_lock; /* * We stash a cms_t and associated private data via cmi_hdl_setspecific. */ struct cms_ctl { cms_t *cs_cms; void *cs_cmsdata; }; static cms_t * cms_hdl_getcms(cmi_hdl_t hdl) { struct cms_ctl *cdp = cmi_hdl_getspecific(hdl); return (cdp != NULL ? cdp->cs_cms : NULL); } void * cms_hdl_getcmsdata(cmi_hdl_t hdl) { struct cms_ctl *cdp = cmi_hdl_getspecific(hdl); return (cdp != NULL ? cdp->cs_cmsdata : NULL); } static void cms_link(cms_t *cms) { ASSERT(MUTEX_HELD(&cms_load_lock)); cms->cms_prev = NULL; cms->cms_next = cms_list; if (cms_list != NULL) cms_list->cms_prev = cms; cms_list = cms; } static void cms_unlink(cms_t *cms) { ASSERT(MUTEX_HELD(&cms_load_lock)); ASSERT(cms->cms_refcnt == 0); if (cms->cms_prev != NULL) cms->cms_prev->cms_next = cms->cms_next; if (cms->cms_next != NULL) cms->cms_next->cms_prev = cms->cms_prev; if (cms_list == cms) cms_list = cms->cms_next; } /* * Hold the module in memory. We call to CPU modules without using the * stubs mechanism, so these modules must be manually held in memory. * The mod_ref acts as if another loaded module has a dependency on us. */ static void cms_hold(cms_t *cms) { ASSERT(MUTEX_HELD(&cms_load_lock)); mutex_enter(&mod_lock); cms->cms_modp->mod_ref++; mutex_exit(&mod_lock); cms->cms_refcnt++; } static void cms_rele(cms_t *cms) { ASSERT(MUTEX_HELD(&cms_load_lock)); mutex_enter(&mod_lock); cms->cms_modp->mod_ref--; mutex_exit(&mod_lock); if (--cms->cms_refcnt == 0) { cms_unlink(cms); kmem_free(cms, sizeof (cms_t)); } } static cms_ops_t * cms_getops(modctl_t *modp) { cms_ops_t *ops; if ((ops = (cms_ops_t *)modlookup_by_modctl(modp, "_cms_ops")) == NULL) { cmn_err(CE_WARN, "cpu_ms module '%s' is invalid: no _cms_ops " "found", modp->mod_modname); return (NULL); } if (ops->cms_init == NULL) { cmn_err(CE_WARN, "cpu_ms module '%s' is invalid: no cms_init " "entry point", modp->mod_modname); return (NULL); } return (ops); } static cms_t * cms_load_modctl(modctl_t *modp) { cms_ops_t *ops; uintptr_t ver; cms_t *cms; cms_api_ver_t apiver; ASSERT(MUTEX_HELD(&cms_load_lock)); for (cms = cms_list; cms != NULL; cms = cms->cms_next) { if (cms->cms_modp == modp) return (cms); } if ((ver = modlookup_by_modctl(modp, "_cms_api_version")) == 0) { cmn_err(CE_WARN, "cpu model-specific module '%s' is invalid: " "no _cms_api_version", modp->mod_modname); return (NULL); } else { apiver = *((cms_api_ver_t *)ver); if (!CMS_API_VERSION_CHKMAGIC(apiver)) { cmn_err(CE_WARN, "cpu model-specific module '%s' is " "invalid: _cms_api_version 0x%x has bad magic", modp->mod_modname, apiver); return (NULL); } } if (apiver != CMS_API_VERSION) { cmn_err(CE_WARN, "cpu model-specific module '%s' has API " "version %d, kernel requires API version %d", modp->mod_modname, CMS_API_VERSION_TOPRINT(apiver), CMS_API_VERSION_TOPRINT(CMS_API_VERSION)); return (NULL); } if ((ops = cms_getops(modp)) == NULL) return (NULL); cms = kmem_zalloc(sizeof (cms_t), KM_SLEEP); cms->cms_ops = ops; cms->cms_modp = modp; cms_link(cms); return (cms); } static int cms_cpu_match(cmi_hdl_t hdl1, cmi_hdl_t hdl2, int match) { if (match >= CMS_MATCH_VENDOR && cmi_hdl_vendor(hdl1) != cmi_hdl_vendor(hdl2)) return (0); if (match >= CMS_MATCH_FAMILY && cmi_hdl_family(hdl1) != cmi_hdl_family(hdl2)) return (0); if (match >= CMS_MATCH_MODEL && cmi_hdl_model(hdl1) != cmi_hdl_model(hdl2)) return (0); if (match >= CMS_MATCH_STEPPING && cmi_hdl_stepping(hdl1) != cmi_hdl_stepping(hdl2)) return (0); return (1); } static int cms_search_list_cb(cmi_hdl_t whdl, void *arg1, void *arg2, void *arg3) { cmi_hdl_t thdl = (cmi_hdl_t)arg1; int match = *((int *)arg2); cmi_hdl_t *rsltp = (cmi_hdl_t *)arg3; if (cms_cpu_match(thdl, whdl, match)) { cmi_hdl_hold(whdl); /* short-term hold */ *rsltp = whdl; return (CMI_HDL_WALK_DONE); } else { return (CMI_HDL_WALK_NEXT); } } /* * Look to see if we've already got a module loaded for a CPU just * like this one. If we do, then we'll re-use it. */ static cms_t * cms_search_list(cmi_hdl_t hdl, int match) { cmi_hdl_t dhdl = NULL; cms_t *cms = NULL; ASSERT(MUTEX_HELD(&cms_load_lock)); cmi_hdl_walk(cms_search_list_cb, (void *)hdl, (void *)&match, &dhdl); if (dhdl) { cms = HDL2CMS(dhdl); cmi_hdl_rele(dhdl); /* held in cms_search_list_cb */ } return (cms); } /* * Try to find or load a module that offers model-specific support for * this vendor/family/model/stepping combination. When attempting to load * a module we look in CPUMOD_MS_SUBDIR first for a match on * vendor/family/model/stepping, then on vendor/family/model (ignoring * stepping), then on vendor/family (ignoring model and stepping), then * on vendor alone. */ static cms_t * cms_load_module(cmi_hdl_t hdl, int match, int *chosenp) { modctl_t *modp; cms_t *cms; int modid; uint_t s[3]; ASSERT(MUTEX_HELD(&cms_load_lock)); ASSERT(match == CMS_MATCH_STEPPING || match == CMS_MATCH_MODEL || match == CMS_MATCH_FAMILY || match == CMS_MATCH_VENDOR); s[0] = cmi_hdl_family(hdl); s[1] = cmi_hdl_model(hdl); s[2] = cmi_hdl_stepping(hdl); /* * Have we already loaded a module for a cpu with the same * vendor/family/model/stepping? */ if ((cms = cms_search_list(hdl, match)) != NULL) { cms_hold(cms); return (cms); } modid = modload_qualified(CPUMOD_MS_SUBDIR, CPUMOD_MS_PREFIX, cmi_hdl_vendorstr(hdl), ".", s, match, chosenp); if (modid == -1) return (NULL); modp = mod_hold_by_id(modid); cms = cms_load_modctl(modp); if (cms) cms_hold(cms); mod_release_mod(modp); return (cms); } static cms_t * cms_load_specific(cmi_hdl_t hdl, void **datap) { cms_t *cms; int err; int i; ASSERT(MUTEX_HELD(&cms_load_lock)); for (i = CMS_MATCH_STEPPING; i >= CMS_MATCH_VENDOR; i--) { int suffixlevel; if ((cms = cms_load_module(hdl, i, &suffixlevel)) == NULL) return (NULL); /* * A module has loaded and has a _cms_ops structure, and the * module has been held for this instance. Call the cms_init * entry point - we expect success (0) or ENOTSUP. */ if ((err = cms->cms_ops->cms_init(hdl, datap)) == 0) { if (boothowto & RB_VERBOSE) { printf("initialized model-specific " "module '%s' on chip %d core %d " "strand %d\n", cms->cms_modp->mod_modname, cmi_hdl_chipid(hdl), cmi_hdl_coreid(hdl), cmi_hdl_strandid(hdl)); } return (cms); } else if (err != ENOTSUP) { cmn_err(CE_WARN, "failed to init model-specific " "module '%s' on chip %d core %d strand %d: err=%d", cms->cms_modp->mod_modname, cmi_hdl_chipid(hdl), cmi_hdl_coreid(hdl), cmi_hdl_strandid(hdl), err); } /* * The module failed or declined to init, so release * it and potentially change i to be equal to the number * of suffices actually used in the last module path. */ cms_rele(cms); i = suffixlevel; } return (NULL); } void cms_init(cmi_hdl_t hdl) { cms_t *cms; void *data; if (cms_no_model_specific != 0) return; mutex_enter(&cms_load_lock); if ((cms = cms_load_specific(hdl, &data)) != NULL) { struct cms_ctl *cdp; ASSERT(cmi_hdl_getspecific(hdl) == NULL); cdp = kmem_alloc(sizeof (*cdp), KM_SLEEP); cdp->cs_cms = cms; cdp->cs_cmsdata = data; cmi_hdl_setspecific(hdl, cdp); } mutex_exit(&cms_load_lock); } void cms_fini(cmi_hdl_t hdl) { cms_t *cms = HDL2CMS(hdl); struct cms_ctl *cdp; if (CMS_OP_PRESENT(cms, cms_fini)) CMS_OPS(cms)->cms_fini(hdl); mutex_enter(&cms_load_lock); cdp = (struct cms_ctl *)cmi_hdl_getspecific(hdl); if (cdp != NULL) { if (cdp->cs_cms != NULL) cms_rele(cdp->cs_cms); kmem_free(cdp, sizeof (*cdp)); } mutex_exit(&cms_load_lock); } boolean_t cms_present(cmi_hdl_t hdl) { return (HDL2CMS(hdl) != NULL ? B_TRUE : B_FALSE); } void cms_post_startup(cmi_hdl_t hdl) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_post_startup)) CMS_OPS(cms)->cms_post_startup(hdl); } void cms_post_mpstartup(cmi_hdl_t hdl) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_post_mpstartup)) CMS_OPS(cms)->cms_post_mpstartup(hdl); } size_t cms_logout_size(cmi_hdl_t hdl) { cms_t *cms = HDL2CMS(hdl); if (!CMS_OP_PRESENT(cms, cms_logout_size)) return (0); return (CMS_OPS(cms)->cms_logout_size(hdl)); } uint64_t cms_mcgctl_val(cmi_hdl_t hdl, int nbanks, uint64_t def) { cms_t *cms = HDL2CMS(hdl); if (!CMS_OP_PRESENT(cms, cms_mcgctl_val)) return (def); return (CMS_OPS(cms)->cms_mcgctl_val(hdl, nbanks, def)); } boolean_t cms_bankctl_skipinit(cmi_hdl_t hdl, int banknum) { cms_t *cms = HDL2CMS(hdl); if (!CMS_OP_PRESENT(cms, cms_bankctl_skipinit)) return (B_FALSE); return (CMS_OPS(cms)->cms_bankctl_skipinit(hdl, banknum)); } uint64_t cms_bankctl_val(cmi_hdl_t hdl, int banknum, uint64_t def) { cms_t *cms = HDL2CMS(hdl); if (!CMS_OP_PRESENT(cms, cms_bankctl_val)) return (def); return (CMS_OPS(cms)->cms_bankctl_val(hdl, banknum, def)); } boolean_t cms_bankstatus_skipinit(cmi_hdl_t hdl, int banknum) { cms_t *cms = HDL2CMS(hdl); if (!CMS_OP_PRESENT(cms, cms_bankstatus_skipinit)) return (B_FALSE); return (CMS_OPS(cms)->cms_bankstatus_skipinit(hdl, banknum)); } uint64_t cms_bankstatus_val(cmi_hdl_t hdl, int banknum, uint64_t def) { cms_t *cms = HDL2CMS(hdl); if (!CMS_OP_PRESENT(cms, cms_bankstatus_val)) return (def); return (CMS_OPS(cms)->cms_bankstatus_val(hdl, banknum, def)); } void cms_mca_init(cmi_hdl_t hdl, int nbanks) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_mca_init)) CMS_OPS(cms)->cms_mca_init(hdl, nbanks); } uint64_t cms_poll_ownermask(cmi_hdl_t hdl, hrtime_t poll_interval) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_poll_ownermask)) return (CMS_OPS(cms)->cms_poll_ownermask(hdl, poll_interval)); else return (-1ULL); /* poll all banks by default */ } void cms_bank_logout(cmi_hdl_t hdl, int banknum, uint64_t status, uint64_t addr, uint64_t misc, void *mslogout) { cms_t *cms = HDL2CMS(hdl); if (mslogout != NULL && CMS_OP_PRESENT(cms, cms_bank_logout)) CMS_OPS(cms)->cms_bank_logout(hdl, banknum, status, addr, misc, mslogout); } cms_errno_t cms_msrinject(cmi_hdl_t hdl, uint_t msr, uint64_t val) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_msrinject)) return (CMS_OPS(cms)->cms_msrinject(hdl, msr, val)); else return (CMSERR_NOTSUP); } uint32_t cms_error_action(cmi_hdl_t hdl, int ismc, int banknum, uint64_t status, uint64_t addr, uint64_t misc, void *mslogout) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_error_action)) return (CMS_OPS(cms)->cms_error_action(hdl, ismc, banknum, status, addr, misc, mslogout)); else return (0); } cms_cookie_t cms_disp_match(cmi_hdl_t hdl, int ismc, int banknum, uint64_t status, uint64_t addr, uint64_t misc, void *mslogout) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_disp_match)) return (CMS_OPS(cms)->cms_disp_match(hdl, ismc, banknum, status, addr, misc, mslogout)); else return (NULL); } void cms_ereport_class(cmi_hdl_t hdl, cms_cookie_t mscookie, const char **cpuclsp, const char **leafclsp) { cms_t *cms = HDL2CMS(hdl); if (cpuclsp == NULL || leafclsp == NULL) return; *cpuclsp = *leafclsp = NULL; if (CMS_OP_PRESENT(cms, cms_ereport_class)) { CMS_OPS(cms)->cms_ereport_class(hdl, mscookie, cpuclsp, leafclsp); } } nvlist_t * cms_ereport_detector(cmi_hdl_t hdl, int bankno, cms_cookie_t mscookie, nv_alloc_t *nva) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_ereport_detector)) return (CMS_OPS(cms)->cms_ereport_detector(hdl, bankno, mscookie, nva)); else return (NULL); } boolean_t cms_ereport_includestack(cmi_hdl_t hdl, cms_cookie_t mscookie) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_ereport_includestack)) { return (CMS_OPS(cms)->cms_ereport_includestack(hdl, mscookie)); } else { return (B_FALSE); } } void cms_ereport_add_logout(cmi_hdl_t hdl, nvlist_t *nvl, nv_alloc_t *nva, int banknum, uint64_t status, uint64_t addr, uint64_t misc, void *mslogout, cms_cookie_t mscookie) { cms_t *cms = HDL2CMS(hdl); if (CMS_OP_PRESENT(cms, cms_ereport_add_logout)) CMS_OPS(cms)->cms_ereport_add_logout(hdl, nvl, nva, banknum, status, addr, misc, mslogout, mscookie); }