#include <sys/mca_x86.h>
#include <sys/cpu_module_impl.h>
#include <sys/cpu_module_ms.h>
#include <sys/cmn_err.h>
#include <sys/cpuvar.h>
#include <sys/pghw.h>
#include <sys/x86_archext.h>
#include <sys/sysmacros.h>
#include <sys/regset.h>
#include <sys/privregs.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/log.h>
#include <sys/psw.h>
#include <sys/fm/protocol.h>
#include <sys/fm/util.h>
#include <sys/errorq.h>
#include <sys/mca_x86.h>
#include <sys/fm/cpu/GMCA.h>
#include <sys/fm/smb/fmsmb.h>
#include <sys/sysevent.h>
#include <sys/ontrap.h>
#include <sys/smp_impldefs.h>
#include "gcpu.h"
extern int x86gentopo_legacy;
static uint_t gcpu_force_addr_in_payload = 0;
int gcpu_suppress_log_on_init = 1;
int gcpu_mca_stack_flag = 0;
int gcpu_mca_stack_ereport_include = 0;
int gcpu_mca_telemetry_retries = 5;
#ifndef __xpv
int gcpu_mca_cmci_throttling_threshold = 10;
int gcpu_mca_cmci_reenable_threshold = 1000;
static boolean_t gcpu_mca_cpu_registered = B_FALSE;
#endif
static gcpu_error_disp_t gcpu_errtypes[] = {
{
FM_EREPORT_CPU_GENERIC_UNCLASSIFIED,
NULL,
FM_EREPORT_PAYLOAD_FLAGS_COMMON,
MCAX86_SIMPLE_UNCLASSIFIED_MASKON,
MCAX86_SIMPLE_UNCLASSIFIED_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_MC_CODE_PARITY,
NULL,
FM_EREPORT_PAYLOAD_FLAGS_COMMON,
MCAX86_SIMPLE_MC_CODE_PARITY_MASKON,
MCAX86_SIMPLE_MC_CODE_PARITY_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_EXTERNAL,
NULL,
FM_EREPORT_PAYLOAD_FLAGS_COMMON,
MCAX86_SIMPLE_EXTERNAL_MASKON,
MCAX86_SIMPLE_EXTERNAL_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_FRC,
NULL,
FM_EREPORT_PAYLOAD_FLAGS_COMMON,
MCAX86_SIMPLE_FRC_MASKON,
MCAX86_SIMPLE_FRC_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_INTERNAL_PARITY,
NULL,
FM_EREPORT_PAYLOAD_FLAGS_COMMON,
MCAX86_SIMPLE_INTERNAL_PARITY_MASKON,
MCAX86_SIMPLE_INTERNAL_PARITY_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_INTERNAL_TIMER,
NULL,
FM_EREPORT_PAYLOAD_FLAGS_COMMON,
MCAX86_SIMPLE_INTERNAL_TIMER_MASKON,
MCAX86_SIMPLE_INTERNAL_TIMER_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_INTERNAL_UNCLASS,
NULL,
FM_EREPORT_PAYLOAD_FLAGS_COMMON,
MCAX86_SIMPLE_INTERNAL_UNCLASS_MASK_MASKON,
MCAX86_SIMPLE_INTERNAL_UNCLASS_MASK_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_GENMEMHIER,
NULL,
FM_EREPORT_PAYLOAD_FLAGS_COMMON,
MCAX86_COMPOUND_GENERIC_MEMHIER_MASKON,
MCAX86_COMPOUND_GENERIC_MEMHIER_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_TLB,
"%1$s" "TLB" "%2$s" "_ERR",
FM_EREPORT_PAYLOAD_FLAGS_COMPOUND_ERR,
MCAX86_COMPOUND_TLB_MASKON,
MCAX86_COMPOUND_TLB_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_MEMHIER,
"%1$s" "CACHE" "%2$s" "_" "%3$s" "_ERR",
FM_EREPORT_PAYLOAD_FLAGS_COMPOUND_ERR,
MCAX86_COMPOUND_MEMHIER_MASKON,
MCAX86_COMPOUND_MEMHIER_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_BUS_INTERCONNECT,
"BUS" "%2$s" "_" "%4$s" "_" "%3$s" "_" "%5$s" "_" "%6$s" "_ERR",
FM_EREPORT_PAYLOAD_FLAGS_COMPOUND_ERR,
MCAX86_COMPOUND_BUS_INTERCONNECT_MASKON,
MCAX86_COMPOUND_BUS_INTERCONNECT_MASKOFF
},
{
FM_EREPORT_CPU_GENERIC_MEMORY_CONTROLLER,
"MC" "_" "%8$s" "_" "%9$s" "_ERR",
FM_EREPORT_PAYLOAD_FLAGS_COMPOUND_ERR,
MCAX86_COMPOUND_MEMORY_CONTROLLER_MASKON,
MCAX86_COMPOUND_MEMORY_CONTROLLER_MASKOFF
},
};
static gcpu_error_disp_t gcpu_unknown = {
FM_EREPORT_CPU_GENERIC_UNKNOWN,
"UNKNOWN",
FM_EREPORT_PAYLOAD_FLAGS_COMMON,
0,
0
};
static errorq_t *gcpu_mca_queue;
static kmutex_t gcpu_mca_queue_lock;
#ifdef __xpv
static int isxpv = 1;
#else
static int isxpv = 0;
#endif
static const gcpu_error_disp_t *
gcpu_disp_match(uint16_t code)
{
const gcpu_error_disp_t *ged = gcpu_errtypes;
int i;
for (i = 0; i < sizeof (gcpu_errtypes) / sizeof (gcpu_error_disp_t);
i++, ged++) {
uint16_t on = ged->ged_errcode_mask_on;
uint16_t off = ged->ged_errcode_mask_off;
if ((code & on) == on && (code & off) == 0)
return (ged);
}
return (NULL);
}
static uint16_t
bit_strip(uint16_t code, uint16_t mask, uint16_t shift)
{
return ((code & mask) >> shift);
}
#define BIT_STRIP(code, name) \
bit_strip(code, MCAX86_ERRCODE_##name##_MASK, \
MCAX86_ERRCODE_##name##_SHIFT)
#define GCPU_MNEMONIC_UNDEF "undefined"
#define GCPU_MNEMONIC_RESVD "reserved"
struct gcpu_mnexp {
const char *mne_compound;
const char *mne_ereport;
};
static struct gcpu_mnexp gcpu_TT_mnemonics[] = {
{ "I", FM_EREPORT_CPU_GENERIC_TT_INSTR },
{ "D", FM_EREPORT_CPU_GENERIC_TT_DATA },
{ "G", FM_EREPORT_CPU_GENERIC_TT_GEN },
{ GCPU_MNEMONIC_UNDEF, "" }
};
static struct gcpu_mnexp gcpu_LL_mnemonics[] = {
{ "LO", FM_EREPORT_CPU_GENERIC_LL_L0 },
{ "L1", FM_EREPORT_CPU_GENERIC_LL_L1 },
{ "L2", FM_EREPORT_CPU_GENERIC_LL_L2 },
{ "LG", FM_EREPORT_CPU_GENERIC_LL_LG }
};
static struct gcpu_mnexp gcpu_RRRR_mnemonics[] = {
{ "ERR", FM_EREPORT_CPU_GENERIC_RRRR_ERR },
{ "RD", FM_EREPORT_CPU_GENERIC_RRRR_RD },
{ "WR", FM_EREPORT_CPU_GENERIC_RRRR_WR },
{ "DRD", FM_EREPORT_CPU_GENERIC_RRRR_DRD },
{ "DWR", FM_EREPORT_CPU_GENERIC_RRRR_DWR },
{ "IRD", FM_EREPORT_CPU_GENERIC_RRRR_IRD },
{ "PREFETCH", FM_EREPORT_CPU_GENERIC_RRRR_PREFETCH },
{ "EVICT", FM_EREPORT_CPU_GENERIC_RRRR_EVICT },
{ "SNOOP", FM_EREPORT_CPU_GENERIC_RRRR_SNOOP },
};
static struct gcpu_mnexp gcpu_PP_mnemonics[] = {
{ "SRC", FM_EREPORT_CPU_GENERIC_PP_SRC },
{ "RES", FM_EREPORT_CPU_GENERIC_PP_RES },
{ "OBS", FM_EREPORT_CPU_GENERIC_PP_OBS },
{ "", FM_EREPORT_CPU_GENERIC_PP_GEN }
};
static struct gcpu_mnexp gcpu_II_mnemonics[] = {
{ "M", FM_EREPORT_CPU_GENERIC_II_MEM },
{ GCPU_MNEMONIC_RESVD, "" },
{ "IO", FM_EREPORT_CPU_GENERIC_II_IO },
{ "", FM_EREPORT_CPU_GENERIC_II_GEN }
};
static struct gcpu_mnexp gcpu_T_mnemonics[] = {
{ "NOTIMEOUT", FM_EREPORT_CPU_GENERIC_T_NOTIMEOUT },
{ "TIMEOUT", FM_EREPORT_CPU_GENERIC_T_TIMEOUT }
};
static struct gcpu_mnexp gcpu_CCCC_mnemonics[] = {
{ "CH0", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH1", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH2", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH3", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH4", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH5", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH6", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH7", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH8", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH9", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH10", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH11", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH12", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH13", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH14", FM_EREPORT_CPU_GENERIC_CCCC },
{ "CH", FM_EREPORT_CPU_GENERIC_CCCC }
};
static struct gcpu_mnexp gcpu_MMM_mnemonics[] = {
{ "GEN", FM_EREPORT_CPU_GENERIC_MMM_ERR },
{ "RD", FM_EREPORT_CPU_GENERIC_MMM_RD },
{ "WR", FM_EREPORT_CPU_GENERIC_MMM_WR },
{ "ADDR_CMD", FM_EREPORT_CPU_GENERIC_MMM_ADRCMD },
{ "SCRUB", FM_EREPORT_CPU_GENERIC_MMM_SCRUB },
{ GCPU_MNEMONIC_RESVD, ""},
{ GCPU_MNEMONIC_RESVD, ""},
{ GCPU_MNEMONIC_RESVD, ""}
};
enum gcpu_mn_namespace {
GCPU_MN_NAMESPACE_COMPOUND,
GCPU_MN_NAMESPACE_EREPORT
};
static const char *
gcpu_mnemonic(const struct gcpu_mnexp *tbl, size_t tbl_sz, uint16_t val,
enum gcpu_mn_namespace nspace)
{
if (val >= tbl_sz || val > 0xff)
return (GCPU_MNEMONIC_UNDEF);
switch (nspace) {
case GCPU_MN_NAMESPACE_COMPOUND:
return (tbl[val].mne_compound);
case GCPU_MN_NAMESPACE_EREPORT:
return (tbl[val].mne_ereport);
default:
return (GCPU_MNEMONIC_UNDEF);
}
}
#define GCPU_CLASS_VARCOMPS 9
#define GCPU_MNEMONIC(code, name, nspace) \
gcpu_mnemonic(gcpu_##name##_mnemonics, \
sizeof (gcpu_##name##_mnemonics) / sizeof (struct gcpu_mnexp), \
BIT_STRIP(code, name), nspace)
static void
gcpu_mn_fmt(const char *fmt, char *buf, size_t buflen, uint64_t status,
enum gcpu_mn_namespace nspace)
{
uint16_t code = MCAX86_ERRCODE(status);
const char *mn[GCPU_CLASS_VARCOMPS];
char *p = buf;
char *q = buf + buflen;
int which, expfmtchar, error;
char c;
mn[0] = GCPU_MNEMONIC(code, TT, nspace);
mn[1] = GCPU_MNEMONIC(code, LL, nspace);
mn[2] = GCPU_MNEMONIC(code, RRRR, nspace);
mn[3] = GCPU_MNEMONIC(code, PP, nspace);
mn[4] = GCPU_MNEMONIC(code, II, nspace);
mn[5] = GCPU_MNEMONIC(code, T, nspace);
mn[6] = (status & MSR_MC_STATUS_UC) ? "_uc" : "";
mn[7] = GCPU_MNEMONIC(code, CCCC, nspace);
mn[8] = GCPU_MNEMONIC(code, MMM, nspace);
while (p < q - 1 && (c = *fmt++) != '\0') {
if (c != '%') {
*p++ = c;
continue;
}
error = 0;
which = -1;
expfmtchar = -1;
nextfmt:
if ((c = *fmt++) == '\0')
break;
switch (c) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (which != -1) {
error++;
break;
}
which = c - '1';
goto nextfmt;
case '$':
if (which == -1) {
error++;
break;
}
expfmtchar = 's';
goto nextfmt;
case 's':
if (expfmtchar != 's') {
error++;
break;
}
(void) snprintf(p, (uintptr_t)q - (uintptr_t)p, "%s",
mn[which]);
p += strlen(p);
break;
default:
error++;
break;
}
if (error)
break;
}
*p = '\0';
}
static void
gcpu_erpt_clsfmt(const char *fmt, char *buf, size_t buflen, uint64_t status,
const char *cpuclass, const char *leafclass)
{
char *p = buf;
char *q = buf + buflen;
(void) snprintf(buf, (uintptr_t)q - (uintptr_t)p, "%s.%s.",
FM_ERROR_CPU, cpuclass ? cpuclass : FM_EREPORT_CPU_GENERIC);
p += strlen(p);
if (p >= q)
return;
if (leafclass == NULL) {
gcpu_mn_fmt(fmt, p, (uintptr_t)q - (uintptr_t)p, status,
GCPU_MN_NAMESPACE_EREPORT);
} else {
(void) snprintf(p, (uintptr_t)q - (uintptr_t)p, "%s",
leafclass);
}
}
static nvlist_t *
gcpu_fmri_create(cmi_hdl_t hdl, nv_alloc_t *nva)
{
nvlist_t *nvl, *fmri;
if ((nvl = fm_nvlist_create(nva)) == NULL)
return (NULL);
if (!x86gentopo_legacy) {
fmri = cmi_hdl_smb_bboard(hdl);
if (fmri == NULL)
return (NULL);
fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION,
NULL, NULL, fmri, 3,
"chip", cmi_hdl_smb_chipid(hdl),
"core", cmi_hdl_coreid(hdl),
"strand", cmi_hdl_strandid(hdl));
} else {
fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, NULL, NULL, 4,
"motherboard", 0,
"chip", cmi_hdl_chipid(hdl),
"core", cmi_hdl_coreid(hdl),
"strand", cmi_hdl_strandid(hdl));
}
return (nvl);
}
int gcpu_bleat_count_thresh = 5;
hrtime_t gcpu_bleat_min_interval = 10 * 1000000000ULL;
static void
gcpu_bleat(cmi_hdl_t hdl, gcpu_logout_t *gcl)
{
hrtime_t now = gethrtime_waitfree();
static hrtime_t gcpu_last_bleat;
gcpu_bank_logout_t *gbl;
static int bleatcount;
int i;
if (++bleatcount > gcpu_bleat_count_thresh) {
if (now - gcpu_last_bleat < gcpu_bleat_min_interval)
return;
else
bleatcount = 0;
}
gcpu_last_bleat = now;
cmn_err(CE_WARN,
"Machine-Check Errors unlogged on chip %d core %d strand %d, "
"raw dump follows", cmi_hdl_chipid(hdl), cmi_hdl_coreid(hdl),
cmi_hdl_strandid(hdl));
cmn_err(CE_WARN, "MCG_STATUS 0x%016llx",
(u_longlong_t)gcl->gcl_mcg_status);
for (i = 0, gbl = &gcl->gcl_data[0]; i < gcl->gcl_nbanks; i++, gbl++) {
uint64_t status = gbl->gbl_status;
if (!(status & MSR_MC_STATUS_VAL))
continue;
if (gcpu_force_addr_in_payload)
status = status | MSR_MC_STATUS_ADDRV;
switch (status & (MSR_MC_STATUS_ADDRV | MSR_MC_STATUS_MISCV)) {
case MSR_MC_STATUS_ADDRV | MSR_MC_STATUS_MISCV:
cmn_err(CE_WARN, "Bank %d (offset 0x%llx) "
"STAT 0x%016llx ADDR 0x%016llx MISC 0x%016llx",
i, IA32_MSR_MC(i, STATUS),
(u_longlong_t)gbl->gbl_status,
(u_longlong_t)gbl->gbl_addr,
(u_longlong_t)gbl->gbl_misc);
break;
case MSR_MC_STATUS_ADDRV:
cmn_err(CE_WARN, "Bank %d (offset 0x%llx) "
"STAT 0x%016llx ADDR 0x%016llx",
i, IA32_MSR_MC(i, STATUS),
(u_longlong_t)gbl->gbl_status,
(u_longlong_t)gbl->gbl_addr);
break;
case MSR_MC_STATUS_MISCV:
cmn_err(CE_WARN, "Bank %d (offset 0x%llx) "
"STAT 0x%016llx MISC 0x%016llx",
i, IA32_MSR_MC(i, STATUS),
(u_longlong_t)gbl->gbl_status,
(u_longlong_t)gbl->gbl_misc);
break;
default:
cmn_err(CE_WARN, "Bank %d (offset 0x%llx) "
"STAT 0x%016llx",
i, IA32_MSR_MC(i, STATUS),
(u_longlong_t)gbl->gbl_status);
break;
}
}
}
#define _GCPU_BSTATUS(status, what) \
FM_EREPORT_PAYLOAD_NAME_MC_STATUS_##what, DATA_TYPE_BOOLEAN_VALUE, \
(status) & MSR_MC_STATUS_##what ? B_TRUE : B_FALSE
static void
gcpu_ereport_add_logout(nvlist_t *ereport, const gcpu_logout_t *gcl,
uint_t bankno, const gcpu_error_disp_t *ged, uint16_t code)
{
uint64_t members = ged ? ged->ged_ereport_members :
FM_EREPORT_PAYLOAD_FLAGS_COMMON;
uint64_t mcg = gcl->gcl_mcg_status;
int mcip = mcg & MCG_STATUS_MCIP;
const gcpu_bank_logout_t *gbl = &gcl->gcl_data[bankno];
uint64_t bstat = gbl->gbl_status;
if (members & FM_EREPORT_PAYLOAD_FLAG_COMPOUND_ERR && ged &&
ged->ged_compound_fmt != NULL) {
char buf[FM_MAX_CLASS];
gcpu_mn_fmt(ged->ged_compound_fmt, buf, sizeof (buf), code,
GCPU_MN_NAMESPACE_COMPOUND);
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_NAME_COMPOUND_ERR,
DATA_TYPE_STRING, buf, NULL);
}
if (members & FM_EREPORT_PAYLOAD_FLAG_DISP &&
gbl->gbl_disp != 0) {
int i, empty = 1;
char buf[128];
char *p = buf, *q = buf + 128;
static struct _gcpu_disp_name {
uint64_t dv;
const char *dn;
} disp_names[] = {
{ CMI_ERRDISP_CURCTXBAD,
"processor_context_corrupt" },
{ CMI_ERRDISP_RIPV_INVALID,
"return_ip_invalid" },
{ CMI_ERRDISP_UC_UNCONSTRAINED,
"unconstrained" },
{ CMI_ERRDISP_FORCEFATAL,
"forcefatal" },
{ CMI_ERRDISP_IGNORED,
"ignored" },
{ CMI_ERRDISP_PCC_CLEARED,
"corrupt_context_cleared" },
{ CMI_ERRDISP_UC_CLEARED,
"uncorrected_data_cleared" },
{ CMI_ERRDISP_POISONED,
"poisoned" },
{ CMI_ERRDISP_INCONSISTENT,
"telemetry_unstable" },
};
for (i = 0; i < sizeof (disp_names) /
sizeof (struct _gcpu_disp_name); i++) {
if ((gbl->gbl_disp & disp_names[i].dv) == 0)
continue;
(void) snprintf(p, (uintptr_t)q - (uintptr_t)p,
"%s%s", empty ? "" : ",", disp_names[i].dn);
p += strlen(p);
empty = 0;
}
if (p != buf)
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_NAME_DISP,
DATA_TYPE_STRING, buf, NULL);
}
if (members & FM_EREPORT_PAYLOAD_FLAG_MCG_STATUS) {
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_NAME_MCG_STATUS,
DATA_TYPE_UINT64, mcg, NULL);
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_NAME_MCG_STATUS_MCIP,
DATA_TYPE_BOOLEAN_VALUE, mcip ? B_TRUE : B_FALSE, NULL);
}
if (mcip && members & FM_EREPORT_PAYLOAD_FLAG_IP &&
mcg & MCG_STATUS_EIPV) {
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_NAME_IP,
DATA_TYPE_UINT64, gcl->gcl_ip, NULL);
}
if (mcip && members & FM_EREPORT_PAYLOAD_FLAG_PRIV) {
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_NAME_PRIV,
DATA_TYPE_BOOLEAN_VALUE,
gcl->gcl_flags & GCPU_GCL_F_PRIV ? B_TRUE : B_FALSE, NULL);
}
if (members & FM_EREPORT_PAYLOAD_FLAG_BANK_NUM) {
fm_payload_set(ereport,
FM_EREPORT_PAYLOAD_NAME_BANK_NUM, DATA_TYPE_UINT8, bankno,
FM_EREPORT_PAYLOAD_NAME_BANK_MSR_OFFSET, DATA_TYPE_UINT64,
IA32_MSR_MC(bankno, CTL),
NULL);
}
if (members & FM_EREPORT_PAYLOAD_FLAG_MC_STATUS) {
const char *tbes[] = {
"No tracking",
"Green - below threshold",
"Yellow - above threshold",
"Reserved"
};
fm_payload_set(ereport,
FM_EREPORT_PAYLOAD_NAME_MC_STATUS, DATA_TYPE_UINT64, bstat,
_GCPU_BSTATUS(bstat, OVER),
_GCPU_BSTATUS(bstat, UC),
_GCPU_BSTATUS(bstat, EN),
_GCPU_BSTATUS(bstat, PCC),
FM_EREPORT_PAYLOAD_NAME_MC_STATUS_ERRCODE,
DATA_TYPE_UINT16, MCAX86_ERRCODE(bstat),
FM_EREPORT_PAYLOAD_NAME_MC_STATUS_EXTERRCODE,
DATA_TYPE_UINT16, MCAX86_MSERRCODE(bstat),
NULL);
if (gcl->gcl_flags & GCPU_GCL_F_TES_P) {
fm_payload_set(ereport,
FM_EREPORT_PAYLOAD_NAME_MC_STATUS_TES,
DATA_TYPE_STRING, tbes[MCAX86_TBES_VALUE(bstat)],
NULL);
}
}
if (members & FM_EREPORT_PAYLOAD_FLAG_MC_ADDR &&
((bstat & MSR_MC_STATUS_ADDRV) ||
gcpu_force_addr_in_payload)) {
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_NAME_MC_ADDR,
DATA_TYPE_UINT64, gbl->gbl_addr, NULL);
}
if (members & FM_EREPORT_PAYLOAD_FLAG_MC_MISC &&
bstat & MSR_MC_STATUS_MISCV) {
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_NAME_MC_MISC,
DATA_TYPE_UINT64, gbl->gbl_misc, NULL);
}
}
static void
gcpu_ereport_post(const gcpu_logout_t *gcl, int bankidx,
const gcpu_error_disp_t *ged, cms_cookie_t mscookie, uint64_t status)
{
gcpu_data_t *gcpu = gcl->gcl_gcpu;
cmi_hdl_t hdl = gcpu->gcpu_hdl;
const gcpu_bank_logout_t *gbl = &gcl->gcl_data[bankidx];
const char *cpuclass = NULL, *leafclass = NULL;
uint16_t code = MCAX86_ERRCODE(status);
errorq_elem_t *eqep, *scr_eqep;
nvlist_t *ereport, *detector;
char buf[FM_MAX_CLASS];
const char *classfmt;
nv_alloc_t *nva;
if (panicstr) {
if ((eqep = errorq_reserve(ereport_errorq)) == NULL)
return;
ereport = errorq_elem_nvl(ereport_errorq, eqep);
if ((scr_eqep = errorq_reserve(ereport_errorq)) != NULL)
nva = errorq_elem_nva(ereport_errorq, scr_eqep);
else
nva = errorq_elem_nva(ereport_errorq, eqep);
} else {
ereport = fm_nvlist_create(NULL);
nva = NULL;
eqep = NULL;
scr_eqep = NULL;
}
if (ereport == NULL)
return;
cms_ereport_class(hdl, mscookie, &cpuclass, &leafclass);
classfmt = ged ? ged->ged_class_fmt : FM_EREPORT_CPU_GENERIC_UNKNOWN;
gcpu_erpt_clsfmt(classfmt, buf, sizeof (buf), status, cpuclass,
leafclass);
if ((detector = cms_ereport_detector(hdl, bankidx, mscookie,
nva)) == NULL)
detector = gcpu_fmri_create(hdl, nva);
fm_ereport_set(ereport, FM_EREPORT_VERSION, buf,
fm_ena_generate_cpu(gcl->gcl_timestamp,
cmi_hdl_chipid(hdl) << 6 | cmi_hdl_coreid(hdl) << 3 |
cmi_hdl_strandid(hdl), FM_ENA_FMT1), detector, NULL);
if (panicstr) {
fm_nvlist_destroy(detector, FM_NVA_RETAIN);
nv_alloc_reset(nva);
} else {
fm_nvlist_destroy(detector, FM_NVA_FREE);
}
gcpu_ereport_add_logout(ereport, gcl, bankidx, ged, code);
cms_ereport_add_logout(hdl, ereport, nva, bankidx, gbl->gbl_status,
gbl->gbl_addr, gbl->gbl_misc, gcl->gcl_ms_logout, mscookie);
if (gcpu_mca_stack_flag &&
(cms_ereport_includestack(hdl, mscookie) ==
B_TRUE || gcpu_mca_stack_ereport_include)) {
fm_payload_stack_add(ereport, gcl->gcl_stack,
gcl->gcl_stackdepth);
}
if (cmi_inj_tainted() == B_TRUE) {
fm_payload_set(ereport, "__injected", DATA_TYPE_BOOLEAN_VALUE,
B_TRUE, NULL);
}
if (panicstr) {
errorq_commit(ereport_errorq, eqep, ERRORQ_SYNC);
if (scr_eqep)
errorq_cancel(ereport_errorq, scr_eqep);
} else {
(void) fm_ereport_post(ereport, EVCH_TRYHARD);
fm_nvlist_destroy(ereport, FM_NVA_FREE);
}
}
void
gcpu_mca_drain(void *ignored, const void *data, const errorq_elem_t *eqe)
{
const gcpu_logout_t *gcl = data;
const gcpu_bank_logout_t *gbl;
int ismc;
int i;
ismc = gcl->ismc;
for (i = 0, gbl = &gcl->gcl_data[0]; i < gcl->gcl_nbanks; i++, gbl++) {
const gcpu_error_disp_t *gened;
cms_cookie_t mscookie;
if (gbl->gbl_status & MSR_MC_STATUS_VAL &&
!(gbl->gbl_disp & CMI_ERRDISP_INCONSISTENT)) {
uint16_t code = MCAX86_ERRCODE(gbl->gbl_status);
gened = gcpu_disp_match(code);
mscookie = cms_disp_match(gcl->gcl_gcpu->gcpu_hdl, ismc,
i, gbl->gbl_status, gbl->gbl_addr, gbl->gbl_misc,
gcl->gcl_ms_logout);
gcpu_ereport_post(gcl, i, gened, mscookie,
gbl->gbl_status);
} else if (gbl->gbl_status & MSR_MC_STATUS_VAL &&
(gbl->gbl_disp & CMI_ERRDISP_INCONSISTENT)) {
gcpu_ereport_post(gcl, i, &gcpu_unknown, NULL,
gbl->gbl_status);
}
}
}
static size_t gcpu_mca_queue_datasz = 0;
static void
gcpu_errorq_init(size_t datasz)
{
int slots;
mutex_enter(&gcpu_mca_queue_lock);
if (gcpu_mca_queue_datasz >= datasz) {
mutex_exit(&gcpu_mca_queue_lock);
return;
}
membar_producer();
if (gcpu_mca_queue) {
gcpu_mca_queue_datasz = 0;
errorq_destroy(gcpu_mca_queue);
}
slots = MAX(GCPU_MCA_ERRS_PERCPU * max_ncpus, GCPU_MCA_MIN_ERRORS);
slots = MIN(slots, GCPU_MCA_MAX_ERRORS);
gcpu_mca_queue = errorq_create("gcpu_mca_queue", gcpu_mca_drain,
NULL, slots, datasz, 1, ERRORQ_VITAL);
if (gcpu_mca_queue != NULL)
gcpu_mca_queue_datasz = datasz;
mutex_exit(&gcpu_mca_queue_lock);
}
static uint_t global_nbanks;
#ifndef __xpv
int
gcpu_cmci_cpu_setup(cpu_setup_t what, int cpuid, void *arg)
{
switch (what) {
case CPU_ON:
psm_cmci_setup(cpuid, B_TRUE);
break;
case CPU_OFF:
psm_cmci_setup(cpuid, B_FALSE);
break;
default:
break;
}
return (0);
}
void
gcpu_mca_cmci_enable(cmi_hdl_t hdl)
{
gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
gcpu_mca_t *mca = &gcpu->gcpu_mca;
if ((mca->gcpu_mca_flags & GCPU_MCA_F_CMCI_CAPABLE) == 0)
return;
if (psm_cmci_setup == NULL) {
return;
}
mca->gcpu_mca_flags |= GCPU_MCA_F_CMCI_ENABLE;
if (MUTEX_HELD(&cpu_lock)) {
if (!gcpu_mca_cpu_registered) {
register_cpu_setup_func(gcpu_cmci_cpu_setup, NULL);
gcpu_mca_cpu_registered = B_TRUE;
}
} else {
mutex_enter(&cpu_lock);
if (!gcpu_mca_cpu_registered) {
register_cpu_setup_func(gcpu_cmci_cpu_setup, NULL);
gcpu_mca_cpu_registered = B_TRUE;
}
mutex_exit(&cpu_lock);
}
psm_cmci_setup(cmi_hdl_logical_id(hdl), B_TRUE);
}
#endif
void
gcpu_mca_init(cmi_hdl_t hdl)
{
gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
uint64_t cap;
uint_t vendor = cmi_hdl_vendor(hdl);
uint_t family = cmi_hdl_family(hdl);
gcpu_mca_t *mca = &gcpu->gcpu_mca;
int mcg_ctl_present;
uint_t nbanks;
uint32_t ctl_skip_mask = 0;
uint32_t status_skip_mask = 0;
size_t mslsz;
int i;
#ifndef __xpv
int mcg_ctl2_present;
uint32_t cmci_capable = 0;
#endif
if (gcpu == NULL)
return;
if (family >= 0xf)
gcpu_force_addr_in_payload = 1;
if (gcpu_mca_telemetry_retries < 0 || gcpu_mca_telemetry_retries > 100)
gcpu_mca_telemetry_retries = 5;
if (cmi_hdl_rdmsr(hdl, IA32_MSR_MCG_CAP, &cap) != CMI_SUCCESS)
return;
ASSERT(is_x86_feature(x86_featureset, X86FSET_MCA));
mcg_ctl_present = cap & MCG_CAP_CTL_P;
#ifndef __xpv
mcg_ctl2_present = cap & MCG_CAP_CTL2_P;
#endif
mca->gcpu_mca_bioscfg.bios_mcg_cap = cap;
if (mcg_ctl_present)
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MCG_CTL,
&mca->gcpu_mca_bioscfg.bios_mcg_ctl);
mca->gcpu_mca_nbanks = nbanks = cap & MCG_CAP_COUNT_MASK;
if (nbanks != 0 && global_nbanks == 0)
global_nbanks = nbanks;
if (nbanks == 0 || nbanks > global_nbanks)
return;
mca->gcpu_mca_bioscfg.bios_bankcfg = kmem_zalloc(nbanks *
sizeof (struct gcpu_bios_bankcfg), KM_SLEEP);
mslsz = cms_logout_size(hdl);
mca->gcpu_mca_lgsz = sizeof (gcpu_logout_t) +
(nbanks - 1) * sizeof (gcpu_bank_logout_t) + mslsz;
for (i = 0; i < GCPU_MCA_LOGOUT_NUM; i++) {
gcpu_logout_t *gcl;
mca->gcpu_mca_logout[i] = gcl =
kmem_zalloc(mca->gcpu_mca_lgsz, KM_SLEEP);
gcl->gcl_gcpu = gcpu;
gcl->gcl_nbanks = nbanks;
gcl->gcl_ms_logout = (mslsz == 0) ? NULL :
(char *)(&gcl->gcl_data[0]) + nbanks *
sizeof (gcpu_bank_logout_t);
}
#ifdef __xpv
gcpu_xpv_mca_init(nbanks);
#endif
mca->gcpu_mca_nextpoll_idx = GCPU_MCA_LOGOUT_POLLER_1;
#ifndef __xpv
mca->gcpu_bank_cmci = kmem_zalloc(sizeof (gcpu_mca_cmci_t) * nbanks,
KM_SLEEP);
#endif
gcpu_errorq_init(mca->gcpu_mca_lgsz);
mutex_enter(&gcpu->gcpu_shared->gcpus_cfglock);
gcpu_mca_poll_init(hdl);
for (i = 0; i < nbanks; i++) {
boolean_t skipctl = cms_bankctl_skipinit(hdl, i);
boolean_t skipstatus = cms_bankstatus_skipinit(hdl, i);
if (!cms_present(hdl)) {
if (i == 0 && family == 6) {
switch (vendor) {
case X86_VENDOR_AMD:
skipstatus = B_TRUE;
case X86_VENDOR_Intel:
skipctl = B_TRUE;
break;
}
}
}
ctl_skip_mask |= skipctl << i;
status_skip_mask |= skipstatus << i;
if (skipctl && skipstatus)
continue;
atomic_or_32(&gcpu->gcpu_shared->gcpus_actv_banks, 1 << i);
#ifndef __xpv
if (mcg_ctl2_present) {
uint64_t ctl2;
uint32_t cap = 0;
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MC_CTL2(i), &ctl2);
if (ctl2 & MSR_MC_CTL2_EN)
continue;
ctl2 |= MSR_MC_CTL2_EN;
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MC_CTL2(i), ctl2);
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MC_CTL2(i), &ctl2);
mca->gcpu_bank_cmci[i].cmci_cap = cap =
(ctl2 & MSR_MC_CTL2_EN) ? 1 : 0;
if (cap)
cmci_capable ++;
ctl2 = (ctl2 & (~MSR_MC_CTL2_EN)) | 1;
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MC_CTL2(i), ctl2);
mca->gcpu_bank_cmci[i].cmci_enabled = 0;
mca->gcpu_bank_cmci[i].drtcmci = 0;
mca->gcpu_bank_cmci[i].ncmci = 0;
}
#endif
}
#ifndef __xpv
if (cmci_capable) {
mca->gcpu_mca_flags |= GCPU_MCA_F_CMCI_CAPABLE;
gcpu_mca_cmci_enable(hdl);
}
#endif
#ifndef __xpv
if (!gcpu_suppress_log_on_init &&
((vendor == X86_VENDOR_Intel && family >= 0xf) ||
vendor == X86_VENDOR_AMD ||
vendor == X86_VENDOR_HYGON))
gcpu_mca_logout(hdl, NULL, -1ULL, NULL, B_FALSE,
GCPU_MPT_WHAT_POKE_ERR);
for (i = 0; i < nbanks; i++) {
struct gcpu_bios_bankcfg *bcfgp =
mca->gcpu_mca_bioscfg.bios_bankcfg + i;
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MC(i, CTL),
&bcfgp->bios_bank_ctl);
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MC(i, STATUS),
&bcfgp->bios_bank_status);
if ((bcfgp->bios_bank_status & MSR_MC_STATUS_ADDRV) ||
gcpu_force_addr_in_payload) {
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MC(i, ADDR),
&bcfgp->bios_bank_addr);
}
bcfgp->bios_bank_misc = 0;
if (!(ctl_skip_mask & (1 << i))) {
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MC(i, CTL),
cms_bankctl_val(hdl, i, -1ULL));
}
if (!(status_skip_mask & (1 << i))) {
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MC(i, STATUS),
cms_bankstatus_val(hdl, i, 0ULL));
}
}
#endif
cms_mca_init(hdl, nbanks);
#ifndef __xpv
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MCG_STATUS, 0ULL);
membar_producer();
if (mcg_ctl_present)
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MCG_CTL,
cms_mcgctl_val(hdl, nbanks, -1ULL));
#endif
mutex_exit(&gcpu->gcpu_shared->gcpus_cfglock);
#ifndef __xpv
cmi_hdl_enable_mce(hdl);
#endif
}
static uint64_t
gcpu_mca_process(cmi_hdl_t hdl, struct regs *rp, int nerr, gcpu_data_t *gcpu,
gcpu_logout_t *gcl, int ismc, gcpu_mce_status_t *mcesp)
{
int curctxbad = 0, unconstrained = 0, forcefatal = 0;
gcpu_mca_t *mca = &gcpu->gcpu_mca;
int nbanks = mca->gcpu_mca_nbanks;
gcpu_mce_status_t mce;
gcpu_bank_logout_t *gbl;
uint64_t disp = 0;
int i;
if (mcesp == NULL)
mcesp = &mce;
mcesp->mce_nerr = nerr;
mcesp->mce_npcc = mcesp->mce_npcc_ok = mcesp->mce_nuc =
mcesp->mce_nuc_ok = mcesp->mce_nuc_poisoned =
mcesp->mce_forcefatal = mcesp->mce_ignored = 0;
if (ismc && !(gcl->gcl_mcg_status & MCG_STATUS_RIPV))
disp |= CMI_ERRDISP_RIPV_INVALID;
gcl->ismc = ismc;
for (i = 0, gbl = &gcl->gcl_data[0]; i < nbanks; i++, gbl++) {
uint64_t mcistatus = gbl->gbl_status;
uint32_t ms_scope;
int pcc, uc;
int poisoned;
if (!(mcistatus & MSR_MC_STATUS_VAL))
continue;
if (gbl->gbl_disp & CMI_ERRDISP_INCONSISTENT)
continue;
pcc = (mcistatus & MSR_MC_STATUS_PCC) != 0;
uc = (mcistatus & MSR_MC_STATUS_UC) != 0;
mcesp->mce_npcc += pcc;
mcesp->mce_nuc += uc;
ms_scope = cms_error_action(hdl, ismc, i, mcistatus,
gbl->gbl_addr, gbl->gbl_misc, gcl->gcl_ms_logout);
if (pcc && ms_scope & CMS_ERRSCOPE_CURCONTEXT_OK) {
pcc = 0;
mcesp->mce_npcc_ok++;
gbl->gbl_disp |= CMI_ERRDISP_PCC_CLEARED;
}
if (uc && ms_scope & CMS_ERRSCOPE_CLEARED_UC) {
uc = 0;
mcesp->mce_nuc_ok++;
gbl->gbl_disp |= CMI_ERRDISP_UC_CLEARED;
}
if (uc) {
poisoned = (ms_scope & CMS_ERRSCOPE_POISONED) != 0;
if (poisoned) {
mcesp->mce_nuc_poisoned++;
gbl->gbl_disp |= CMI_ERRDISP_POISONED;
}
}
if ((ms_scope & CMS_ERRSCOPE_IGNORE_ERR) == 0) {
if (uc && !poisoned) {
unconstrained++;
gbl->gbl_disp |= disp |
CMI_ERRDISP_UC_UNCONSTRAINED;
}
if (pcc && ismc) {
curctxbad++;
gbl->gbl_disp |= disp |
CMI_ERRDISP_CURCTXBAD;
}
if (ms_scope & CMS_ERRSCOPE_FORCE_FATAL) {
forcefatal++;
mcesp->mce_forcefatal++;
gbl->gbl_disp |= disp |
CMI_ERRDISP_FORCEFATAL;
}
} else {
mcesp->mce_ignored++;
gbl->gbl_disp |= disp | CMI_ERRDISP_IGNORED;
}
}
if (unconstrained > 0)
disp |= CMI_ERRDISP_UC_UNCONSTRAINED;
if (curctxbad > 0)
disp |= CMI_ERRDISP_CURCTXBAD;
if (forcefatal > 0)
disp |= CMI_ERRDISP_FORCEFATAL;
if (gcpu_mca_queue != NULL) {
int how;
if (ismc) {
how = cmi_mce_response(rp, disp) ?
ERRORQ_ASYNC :
ERRORQ_SYNC;
} else {
how = (disp & CMI_ERRDISP_FORCEFATAL &&
cmi_panic_on_ue()) ?
ERRORQ_SYNC :
ERRORQ_ASYNC;
}
errorq_dispatch(gcpu_mca_queue, gcl, mca->gcpu_mca_lgsz, how);
} else if (disp != 0) {
gcpu_bleat(hdl, gcl);
}
mcesp->mce_disp = disp;
return (disp);
}
#define IS_MCE_CANDIDATE(status) (((status) & MSR_MC_STATUS_EN) != 0 && \
((status) & (MSR_MC_STATUS_UC | MSR_MC_STATUS_PCC)) != 0)
#define STATUS_EQV(s1, s2) \
(((s1) & ~MSR_MC_STATUS_OVER) == ((s2) & ~MSR_MC_STATUS_OVER))
static uint32_t gcpu_deferrred_polled_clears;
#ifndef __xpv
static void
gcpu_cmci_logout(cmi_hdl_t hdl, int bank, gcpu_mca_cmci_t *bank_cmci_p,
uint64_t status, int what)
{
uint64_t ctl2;
if (bank_cmci_p->cmci_cap && (what == GCPU_MPT_WHAT_CYC_ERR) &&
(!(status & MSR_MC_STATUS_VAL) || ((status & MSR_MC_STATUS_VAL) &&
!(status & MSR_MC_STATUS_CEC_MASK)))) {
if (!(bank_cmci_p->cmci_enabled)) {
bank_cmci_p->drtcmci ++;
if (bank_cmci_p->drtcmci >=
gcpu_mca_cmci_reenable_threshold) {
(void) cmi_hdl_rdmsr(hdl,
IA32_MSR_MC_CTL2(bank), &ctl2);
ctl2 |= MSR_MC_CTL2_EN;
(void) cmi_hdl_wrmsr(hdl,
IA32_MSR_MC_CTL2(bank), ctl2);
bank_cmci_p->drtcmci = 0;
bank_cmci_p->cmci_enabled = 1;
}
} else {
bank_cmci_p->ncmci = 0;
}
}
}
static void
gcpu_cmci_throttle(cmi_hdl_t hdl, int bank, gcpu_mca_cmci_t *bank_cmci_p,
int what)
{
uint64_t ctl2 = 0;
if (bank_cmci_p->cmci_enabled && what == GCPU_MPT_WHAT_CMCI_ERR) {
bank_cmci_p->ncmci++;
if (bank_cmci_p->ncmci >= gcpu_mca_cmci_throttling_threshold) {
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MC_CTL2(bank),
&ctl2);
ctl2 &= ~MSR_MC_CTL2_EN;
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MC_CTL2(bank),
ctl2);
bank_cmci_p->cmci_enabled = 0;
bank_cmci_p->ncmci = 0;
}
}
}
#endif
static void
clear_mc(int first, int last, int ismc, boolean_t clrstatus,
cmi_hdl_t hdl, gcpu_logout_t *gcl, gcpu_logout_t *pgcl)
{
int i;
gcpu_bank_logout_t *gbl, *pgbl;
uint64_t status;
if (first < 0 || last < 0)
return;
for (i = first, gbl = &gcl->gcl_data[first]; i <= last; i++, gbl++) {
status = gbl->gbl_status;
if (status == 0)
continue;
if (clrstatus == B_FALSE)
goto serialize;
if (isxpv || ismc || !IS_MCE_CANDIDATE(status)) {
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MC(i, STATUS), 0ULL);
goto serialize;
}
pgbl = &pgcl->gcl_data[i];
if (pgbl->gbl_clrdefcnt != 0) {
if (STATUS_EQV(status, pgcl->gcl_data[i].gbl_status) ||
pgbl->gbl_clrdefcnt > 5) {
gbl->gbl_status = 0;
(void) cmi_hdl_wrmsr(hdl,
IA32_MSR_MC(i, STATUS), 0ULL);
} else {
gbl->gbl_clrdefcnt = pgbl->gbl_clrdefcnt + 1;
}
} else {
gbl->gbl_clrdefcnt = 1;
gcpu_deferrred_polled_clears++;
}
serialize:
{
#ifdef __xpv
;
#else
struct cpuid_regs tmp;
(void) __cpuid_insn(&tmp);
#endif
}
}
}
void
gcpu_mca_logout(cmi_hdl_t hdl, struct regs *rp, uint64_t bankmask,
gcpu_mce_status_t *mcesp, boolean_t clrstatus, int what)
{
gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
gcpu_mca_t *mca = &gcpu->gcpu_mca;
int nbanks = mca->gcpu_mca_nbanks;
gcpu_bank_logout_t *gbl, *pgbl;
gcpu_logout_t *gcl, *pgcl;
int ismc = (rp != NULL);
int ispoll = !ismc;
int i, nerr = 0;
cmi_errno_t err;
uint64_t mcg_status;
uint64_t disp;
uint64_t cap;
int first = -1;
int last = -1;
int willpanic = 0;
if (cmi_hdl_rdmsr(hdl, IA32_MSR_MCG_STATUS, &mcg_status) !=
CMI_SUCCESS || cmi_hdl_rdmsr(hdl, IA32_MSR_MCG_CAP, &cap) !=
CMI_SUCCESS) {
if (mcesp != NULL)
mcesp->mce_nerr = mcesp->mce_disp = 0;
return;
}
if (ismc) {
gcl = mca->gcpu_mca_logout[GCPU_MCA_LOGOUT_EXCEPTION];
pgcl = NULL;
} else {
int pidx = mca->gcpu_mca_nextpoll_idx;
int ppidx = (pidx == GCPU_MCA_LOGOUT_POLLER_1) ?
GCPU_MCA_LOGOUT_POLLER_2 : GCPU_MCA_LOGOUT_POLLER_1;
gcl = mca->gcpu_mca_logout[pidx];
pgcl = mca->gcpu_mca_logout[ppidx];
mca->gcpu_mca_nextpoll_idx = ppidx;
}
gcl->gcl_timestamp = gethrtime_waitfree();
gcl->gcl_mcg_status = mcg_status;
gcl->gcl_ip = rp ? rp->r_pc : 0;
gcl->gcl_flags = (rp && USERMODE(rp->r_cs)) ? GCPU_GCL_F_PRIV : 0;
if (cap & MCG_CAP_TES_P)
gcl->gcl_flags |= GCPU_GCL_F_TES_P;
for (i = 0, gbl = &gcl->gcl_data[0]; i < nbanks; i++, gbl++) {
uint64_t status, status2, addr, misc;
int retries = gcpu_mca_telemetry_retries;
gbl->gbl_status = 0;
gbl->gbl_disp = 0;
gbl->gbl_clrdefcnt = 0;
if (!(gcpu->gcpu_shared->gcpus_actv_banks & 1 << i))
continue;
if (rp == NULL && !(bankmask & 1 << i))
continue;
if (cmi_hdl_rdmsr(hdl, IA32_MSR_MC(i, STATUS), &status) !=
CMI_SUCCESS)
continue;
#ifndef __xpv
gcpu_cmci_logout(hdl, i, &mca->gcpu_bank_cmci[i], status, what);
#endif
retry:
if (!(status & MSR_MC_STATUS_VAL))
continue;
if (first < 0)
first = i;
last = i;
addr = -1;
misc = 0;
if ((status & MSR_MC_STATUS_ADDRV) ||
gcpu_force_addr_in_payload)
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MC(i, ADDR), &addr);
if (status & MSR_MC_STATUS_MISCV)
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MC(i, MISC), &misc);
#ifndef __xpv
gcpu_cmci_throttle(hdl, i, &mca->gcpu_bank_cmci[i], what);
#endif
cms_bank_logout(hdl, i, status, addr, misc, gcl->gcl_ms_logout);
if (ispoll && (err = cmi_hdl_rdmsr(hdl, IA32_MSR_MC(i, STATUS),
&status2)) == CMI_SUCCESS) {
if (!STATUS_EQV(status, status2)) {
if (retries-- > 0) {
status = status2;
goto retry;
} else {
gbl->gbl_disp |=
CMI_ERRDISP_INCONSISTENT;
}
}
} else if (ispoll && err != CMI_SUCCESS) {
gbl->gbl_disp |= CMI_ERRDISP_INCONSISTENT;
}
nerr++;
gbl->gbl_status = status;
gbl->gbl_addr = addr;
gbl->gbl_misc = misc;
pgbl = &pgcl->gcl_data[i];
if (!isxpv && ispoll && IS_MCE_CANDIDATE(status) &&
pgbl->gbl_clrdefcnt != 0) {
if (STATUS_EQV(status, pgcl->gcl_data[i].gbl_status)) {
gbl->gbl_status = 0;
(void) cmi_hdl_wrmsr(hdl,
IA32_MSR_MC(i, STATUS), 0ULL);
}
}
}
if (gcpu_mca_stack_flag)
gcl->gcl_stackdepth = getpcstack(gcl->gcl_stack, FM_STK_DEPTH);
else
gcl->gcl_stackdepth = 0;
if (nerr != 0) {
disp = gcpu_mca_process(hdl, rp, nerr, gcpu, gcl, ismc, mcesp);
willpanic = (ismc && cmi_mce_response(rp, disp) == 0);
if (!willpanic)
clear_mc(first, last, ismc, clrstatus, hdl, gcl, pgcl);
} else {
disp = 0;
if (mcesp) {
mcesp->mce_nerr = mcesp->mce_disp = 0;
}
}
if (ismc && mcg_status & MCG_STATUS_MCIP)
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MCG_STATUS, 0);
if (ismc) {
cmi_mc_logout(hdl, 1, willpanic);
}
}
#ifndef __xpv
int gcpu_mca_trap_vomit_summary = 0;
uint64_t
gcpu_mca_trap(cmi_hdl_t hdl, struct regs *rp)
{
gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
kmutex_t *poll_lock = NULL;
gcpu_mce_status_t mce;
uint64_t mcg_status;
int tooklock = 0;
if (cmi_hdl_rdmsr(hdl, IA32_MSR_MCG_STATUS, &mcg_status) !=
CMI_SUCCESS || !(mcg_status & MCG_STATUS_MCIP))
return (0);
if (gcpu->gcpu_shared != NULL)
poll_lock = &gcpu->gcpu_shared->gcpus_poll_lock;
if (poll_lock != NULL && !mutex_owned(poll_lock)) {
while (!mutex_tryenter(poll_lock)) {
while (mutex_owner(poll_lock) != NULL)
;
}
tooklock = 1;
}
gcpu_mca_logout(hdl, rp, 0, &mce, B_TRUE, GCPU_MPT_WHAT_MC_ERR);
if (tooklock)
mutex_exit(poll_lock);
if (mce.mce_nerr != 0 && gcpu_mca_trap_vomit_summary) {
cmn_err(CE_WARN, "MCE: %u errors, disp=0x%llx, "
"%u PCC (%u ok), "
"%u UC (%d ok, %u poisoned), "
"%u forcefatal, %u ignored",
mce.mce_nerr, (u_longlong_t)mce.mce_disp,
mce.mce_npcc, mce.mce_npcc_ok,
mce.mce_nuc, mce.mce_nuc_ok, mce.mce_nuc_poisoned,
mce.mce_forcefatal, mce.mce_ignored);
}
return (mce.mce_disp);
}
#endif
void
gcpu_faulted_enter(cmi_hdl_t hdl)
{
}
void
gcpu_faulted_exit(cmi_hdl_t hdl)
{
gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
gcpu->gcpu_mca.gcpu_mca_flags |= GCPU_MCA_F_UNFAULTING;
}
cmi_errno_t
gcpu_msrinject(cmi_hdl_t hdl, cmi_mca_regs_t *regs, uint_t nregs,
int force)
{
int i, errs = 0;
for (i = 0; i < nregs; i++) {
uint_t msr = regs[i].cmr_msrnum;
uint64_t val = regs[i].cmr_msrval;
if (cms_present(hdl)) {
if (cms_msrinject(hdl, msr, val) != CMS_SUCCESS)
errs++;
} else if (force) {
errs += (cmi_hdl_wrmsr(hdl, msr, val) != CMI_SUCCESS);
} else {
errs++;
}
}
return (errs == 0 ? CMI_SUCCESS : CMIERR_UNKNOWN);
}
void
gcpu_mca_fini(cmi_hdl_t hdl)
{
gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
gcpu_mca_t *mca = &gcpu->gcpu_mca;
int i;
if (!is_x86_feature(x86_featureset, X86FSET_MCA))
return;
#ifndef __xpv
cmi_ntv_hwdisable_mce(hdl);
#endif
mutex_enter(&gcpu->gcpu_shared->gcpus_cfglock);
gcpu_mca_poll_fini(hdl);
mutex_exit(&gcpu->gcpu_shared->gcpus_cfglock);
if (mca->gcpu_bank_cmci != NULL) {
kmem_free(mca->gcpu_bank_cmci, sizeof (gcpu_mca_cmci_t) *
mca->gcpu_mca_nbanks);
}
for (i = 0; i < GCPU_MCA_LOGOUT_NUM; i++) {
if (mca->gcpu_mca_logout[i] != NULL) {
kmem_free(mca->gcpu_mca_logout[i], mca->gcpu_mca_lgsz);
}
}
if (mca->gcpu_mca_bioscfg.bios_bankcfg != NULL) {
kmem_free(mca->gcpu_mca_bioscfg.bios_bankcfg,
sizeof (struct gcpu_bios_bankcfg) * mca->gcpu_mca_nbanks);
}
}