Changeset View
Changeset View
Standalone View
Standalone View
FreeBSD/sys/x86/x86/mca.c
Show First 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | |||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/lock.h> | #include <sys/lock.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/mutex.h> | #include <sys/mutex.h> | ||||
#include <sys/proc.h> | #include <sys/proc.h> | ||||
#include <sys/sched.h> | #include <sys/sched.h> | ||||
#include <sys/smp.h> | #include <sys/smp.h> | ||||
#include <sys/sysctl.h> | #include <sys/sysctl.h> | ||||
#include <sys/syslog.h> | |||||
#include <sys/systm.h> | #include <sys/systm.h> | ||||
#include <sys/taskqueue.h> | #include <sys/taskqueue.h> | ||||
#include <machine/intr_machdep.h> | #include <machine/intr_machdep.h> | ||||
#include <x86/apicvar.h> | #include <x86/apicvar.h> | ||||
#include <machine/cpu.h> | #include <machine/cpu.h> | ||||
#include <machine/cputypes.h> | #include <machine/cputypes.h> | ||||
#include <x86/mca.h> | #include <x86/mca.h> | ||||
#include <machine/md_var.h> | #include <machine/md_var.h> | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | |||||
SYSCTL_INT(_hw_mca, OID_AUTO, intel6h_HSD131, CTLFLAG_RDTUN, &intel6h_HSD131, 0, | SYSCTL_INT(_hw_mca, OID_AUTO, intel6h_HSD131, CTLFLAG_RDTUN, &intel6h_HSD131, 0, | ||||
"Administrative toggle for logging of spurious corrected errors"); | "Administrative toggle for logging of spurious corrected errors"); | ||||
int workaround_erratum383; | int workaround_erratum383; | ||||
SYSCTL_INT(_hw_mca, OID_AUTO, erratum383, CTLFLAG_RDTUN, | SYSCTL_INT(_hw_mca, OID_AUTO, erratum383, CTLFLAG_RDTUN, | ||||
&workaround_erratum383, 0, | &workaround_erratum383, 0, | ||||
"Is the workaround for Erratum 383 on AMD Family 10h processors enabled?"); | "Is the workaround for Erratum 383 on AMD Family 10h processors enabled?"); | ||||
static bool mca_uselog = false; | |||||
SYSCTL_BOOL(_hw_mca, OID_AUTO, uselog, CTLFLAG_RWTUN, &mca_uselog, 0, | |||||
"Should the system send non-fatal machine check errors to the log " | |||||
"(instead of the console)?"); | |||||
static STAILQ_HEAD(, mca_internal) mca_freelist; | static STAILQ_HEAD(, mca_internal) mca_freelist; | ||||
static int mca_freecount; | static int mca_freecount; | ||||
static STAILQ_HEAD(, mca_internal) mca_records; | static STAILQ_HEAD(, mca_internal) mca_records; | ||||
static struct mca_internal *mca_lastrec = NULL; | static struct mca_internal *mca_lastrec = NULL; | ||||
static struct callout mca_timer; | static struct callout mca_timer; | ||||
static int mca_ticks = 3600; /* Check hourly by default. */ | static int mca_ticks = 3600; /* Check hourly by default. */ | ||||
static struct taskqueue *mca_tq; | static struct taskqueue *mca_tq; | ||||
static struct task mca_postscan_task, mca_scan_task; | static struct task mca_postscan_task, mca_scan_task; | ||||
▲ Show 20 Lines • Show All 165 Lines • ▼ Show 20 Lines | if (cpu_vendor_id == CPU_VENDOR_INTEL && | ||||
!intel6h_HSD131) | !intel6h_HSD131) | ||||
return (1); | return (1); | ||||
return (0); | return (0); | ||||
} | } | ||||
/* Dump details about a single machine check. */ | /* Dump details about a single machine check. */ | ||||
static void | static void | ||||
mca_log(const struct mca_record *rec) | mca_log(const struct mca_record *rec, bool fatal) | ||||
{ | { | ||||
char buf[1024], *bufp, *bufend; | |||||
jhb: I'm a bit leery of putting 1k on the stack. I have some old patches to use dedicated stacks… | |||||
uint16_t mca_error; | uint16_t mca_error; | ||||
bool uncor; | |||||
if (mca_mute(rec)) | if (mca_mute(rec)) | ||||
return; | return; | ||||
printf("MCA: Bank %d, Status 0x%016llx\n", rec->mr_bank, | uncor = false; | ||||
bufp = buf; | |||||
bufend = bufp + 1024; | |||||
buf[0] = '\0'; | |||||
#define ADD_OUTPUT(fmt, ...) do { \ | |||||
int rv; \ | |||||
\ | |||||
rv = snprintf(bufp, bufend - bufp, fmt, ##__VA_ARGS__); \ | |||||
if (rv > 0) \ | |||||
bufp += rv; \ | |||||
if (rv < 0 || bufp >= bufend) \ | |||||
goto done; \ | |||||
} while (0) | |||||
ADD_OUTPUT("MCA: Bank %d, Status 0x%016llx\n", rec->mr_bank, | |||||
(long long)rec->mr_status); | (long long)rec->mr_status); | ||||
printf("MCA: Global Cap 0x%016llx, Status 0x%016llx\n", | ADD_OUTPUT("MCA: Global Cap 0x%016llx, Status 0x%016llx\n", | ||||
(long long)rec->mr_mcg_cap, (long long)rec->mr_mcg_status); | (long long)rec->mr_mcg_cap, (long long)rec->mr_mcg_status); | ||||
printf("MCA: Vendor \"%s\", ID 0x%x, APIC ID %d\n", cpu_vendor, | ADD_OUTPUT("MCA: Vendor \"%s\", ID 0x%x, APIC ID %d\n", cpu_vendor, | ||||
rec->mr_cpu_id, rec->mr_apic_id); | rec->mr_cpu_id, rec->mr_apic_id); | ||||
printf("MCA: CPU %d ", rec->mr_cpu); | ADD_OUTPUT("MCA: CPU %d ", rec->mr_cpu); | ||||
if (rec->mr_status & MC_STATUS_UC) | if (rec->mr_status & MC_STATUS_UC) { | ||||
printf("UNCOR "); | uncor = true; | ||||
else { | ADD_OUTPUT("UNCOR "); | ||||
printf("COR "); | } else { | ||||
ADD_OUTPUT("COR "); | |||||
if (rec->mr_mcg_cap & MCG_CAP_CMCI_P) | if (rec->mr_mcg_cap & MCG_CAP_CMCI_P) | ||||
printf("(%lld) ", ((long long)rec->mr_status & | ADD_OUTPUT("(%lld) ", ((long long)rec->mr_status & | ||||
MC_STATUS_COR_COUNT) >> 38); | MC_STATUS_COR_COUNT) >> 38); | ||||
} | } | ||||
if (rec->mr_status & MC_STATUS_PCC) | if (rec->mr_status & MC_STATUS_PCC) | ||||
printf("PCC "); | ADD_OUTPUT("PCC "); | ||||
if (rec->mr_status & MC_STATUS_OVER) | if (rec->mr_status & MC_STATUS_OVER) | ||||
printf("OVER "); | ADD_OUTPUT("OVER "); | ||||
mca_error = rec->mr_status & MC_STATUS_MCA_ERROR; | mca_error = rec->mr_status & MC_STATUS_MCA_ERROR; | ||||
switch (mca_error) { | switch (mca_error) { | ||||
/* Simple error codes. */ | /* Simple error codes. */ | ||||
case 0x0000: | case 0x0000: | ||||
printf("no error"); | ADD_OUTPUT("no error"); | ||||
break; | break; | ||||
case 0x0001: | case 0x0001: | ||||
printf("unclassified error"); | ADD_OUTPUT("unclassified error"); | ||||
break; | break; | ||||
case 0x0002: | case 0x0002: | ||||
printf("ucode ROM parity error"); | ADD_OUTPUT("ucode ROM parity error"); | ||||
break; | break; | ||||
case 0x0003: | case 0x0003: | ||||
printf("external error"); | ADD_OUTPUT("external error"); | ||||
break; | break; | ||||
case 0x0004: | case 0x0004: | ||||
printf("FRC error"); | ADD_OUTPUT("FRC error"); | ||||
break; | break; | ||||
case 0x0005: | case 0x0005: | ||||
printf("internal parity error"); | ADD_OUTPUT("internal parity error"); | ||||
break; | break; | ||||
case 0x0400: | case 0x0400: | ||||
printf("internal timer error"); | ADD_OUTPUT("internal timer error"); | ||||
break; | break; | ||||
default: | default: | ||||
if ((mca_error & 0xfc00) == 0x0400) { | if ((mca_error & 0xfc00) == 0x0400) { | ||||
printf("internal error %x", mca_error & 0x03ff); | ADD_OUTPUT("internal error %x", mca_error & 0x03ff); | ||||
break; | break; | ||||
} | } | ||||
/* Compound error codes. */ | /* Compound error codes. */ | ||||
/* Memory hierarchy error. */ | /* Memory hierarchy error. */ | ||||
if ((mca_error & 0xeffc) == 0x000c) { | if ((mca_error & 0xeffc) == 0x000c) { | ||||
printf("%s memory error", mca_error_level(mca_error)); | ADD_OUTPUT("%s memory error", | ||||
mca_error_level(mca_error)); | |||||
break; | break; | ||||
} | } | ||||
/* TLB error. */ | /* TLB error. */ | ||||
if ((mca_error & 0xeff0) == 0x0010) { | if ((mca_error & 0xeff0) == 0x0010) { | ||||
printf("%sTLB %s error", mca_error_ttype(mca_error), | ADD_OUTPUT("%sTLB %s error", mca_error_ttype(mca_error), | ||||
mca_error_level(mca_error)); | mca_error_level(mca_error)); | ||||
break; | break; | ||||
} | } | ||||
/* Memory controller error. */ | /* Memory controller error. */ | ||||
if ((mca_error & 0xef80) == 0x0080) { | if ((mca_error & 0xef80) == 0x0080) { | ||||
printf("%s channel ", mca_error_mmtype(mca_error)); | ADD_OUTPUT("%s channel ", mca_error_mmtype(mca_error)); | ||||
if ((mca_error & 0x000f) != 0x000f) | if ((mca_error & 0x000f) != 0x000f) | ||||
printf("%d", mca_error & 0x000f); | ADD_OUTPUT("%d", mca_error & 0x000f); | ||||
else | else | ||||
printf("??"); | ADD_OUTPUT("??"); | ||||
printf(" memory error"); | ADD_OUTPUT(" memory error"); | ||||
break; | break; | ||||
} | } | ||||
/* Cache error. */ | /* Cache error. */ | ||||
if ((mca_error & 0xef00) == 0x0100) { | if ((mca_error & 0xef00) == 0x0100) { | ||||
printf("%sCACHE %s %s error", | ADD_OUTPUT("%sCACHE %s %s error", | ||||
mca_error_ttype(mca_error), | mca_error_ttype(mca_error), | ||||
mca_error_level(mca_error), | mca_error_level(mca_error), | ||||
mca_error_request(mca_error)); | mca_error_request(mca_error)); | ||||
break; | break; | ||||
} | } | ||||
/* Bus and/or Interconnect error. */ | /* Bus and/or Interconnect error. */ | ||||
if ((mca_error & 0xe800) == 0x0800) { | if ((mca_error & 0xe800) == 0x0800) { | ||||
printf("BUS%s ", mca_error_level(mca_error)); | ADD_OUTPUT("BUS%s ", mca_error_level(mca_error)); | ||||
switch ((mca_error & 0x0600) >> 9) { | switch ((mca_error & 0x0600) >> 9) { | ||||
case 0: | case 0: | ||||
printf("Source"); | ADD_OUTPUT("Source"); | ||||
break; | break; | ||||
case 1: | case 1: | ||||
printf("Responder"); | ADD_OUTPUT("Responder"); | ||||
break; | break; | ||||
case 2: | case 2: | ||||
printf("Observer"); | ADD_OUTPUT("Observer"); | ||||
break; | break; | ||||
default: | default: | ||||
printf("???"); | ADD_OUTPUT("???"); | ||||
break; | break; | ||||
} | } | ||||
printf(" %s ", mca_error_request(mca_error)); | ADD_OUTPUT(" %s ", mca_error_request(mca_error)); | ||||
switch ((mca_error & 0x000c) >> 2) { | switch ((mca_error & 0x000c) >> 2) { | ||||
case 0: | case 0: | ||||
printf("Memory"); | ADD_OUTPUT("Memory"); | ||||
break; | break; | ||||
case 2: | case 2: | ||||
printf("I/O"); | ADD_OUTPUT("I/O"); | ||||
break; | break; | ||||
case 3: | case 3: | ||||
printf("Other"); | ADD_OUTPUT("Other"); | ||||
break; | break; | ||||
default: | default: | ||||
printf("???"); | ADD_OUTPUT("???"); | ||||
break; | break; | ||||
} | } | ||||
if (mca_error & 0x0100) | if (mca_error & 0x0100) | ||||
printf(" timed out"); | ADD_OUTPUT(" timed out"); | ||||
break; | break; | ||||
} | } | ||||
printf("unknown error %x", mca_error); | ADD_OUTPUT("unknown error %x", mca_error); | ||||
break; | break; | ||||
} | } | ||||
printf("\n"); | |||||
if (rec->mr_status & MC_STATUS_ADDRV) | if (rec->mr_status & MC_STATUS_ADDRV) | ||||
printf("MCA: Address 0x%llx\n", (long long)rec->mr_addr); | ADD_OUTPUT("\nMCA: Address 0x%llx", (long long)rec->mr_addr); | ||||
if (rec->mr_status & MC_STATUS_MISCV) | if (rec->mr_status & MC_STATUS_MISCV) | ||||
printf("MCA: Misc 0x%llx\n", (long long)rec->mr_misc); | ADD_OUTPUT("\nMCA: Misc 0x%llx", (long long)rec->mr_misc); | ||||
#undef ADD_OUTPUT | |||||
done: | |||||
if (fatal || !mca_uselog) | |||||
printf("%s\n", buf); | |||||
else { | |||||
log(uncor ? LOG_CRIT : LOG_ERR, "%s", buf); | |||||
} | } | ||||
} | |||||
static int | static int | ||||
mca_check_status(int bank, struct mca_record *rec) | mca_check_status(int bank, struct mca_record *rec) | ||||
{ | { | ||||
uint64_t status; | uint64_t status; | ||||
u_int p[4]; | u_int p[4]; | ||||
status = rdmsr(MSR_MC_STATUS(bank)); | status = rdmsr(MSR_MC_STATUS(bank)); | ||||
▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | mca_emit_logs(void) | ||||
if (mca_lastrec == NULL) | if (mca_lastrec == NULL) | ||||
mca = STAILQ_FIRST(&mca_records); | mca = STAILQ_FIRST(&mca_records); | ||||
else | else | ||||
mca = STAILQ_NEXT(mca_lastrec, link); | mca = STAILQ_NEXT(mca_lastrec, link); | ||||
if (mca != NULL) { | if (mca != NULL) { | ||||
STAILQ_FOREACH_FROM(mca, &mca_records, link) { | STAILQ_FOREACH_FROM(mca, &mca_records, link) { | ||||
if (!mca->logged) { | if (!mca->logged) { | ||||
mca->logged = 1; | mca->logged = 1; | ||||
mca_log(&mca->rec); | mca_log(&mca->rec, false); | ||||
} | } | ||||
mca_lastrec = mca; | mca_lastrec = mca; | ||||
} | } | ||||
mca_lastrec = STAILQ_LAST(&mca_records, mca_internal, link); | mca_lastrec = STAILQ_LAST(&mca_records, mca_internal, link); | ||||
} | } | ||||
mtx_unlock_spin(&mca_lock); | mtx_unlock_spin(&mca_lock); | ||||
} | } | ||||
/* | /* | ||||
* Refill the free list and log any unlogged messages. This is | * Refill the free list and log any unlogged messages. This is | ||||
* intended to be called from a task queue and to handle work which | * intended to be called from a task queue and to handle work which | ||||
* does not need to be done (or cannot be done) in the hardware | * does not need to be done (or cannot be done) in the hardware | ||||
* interrupt context. | * interrupt context. | ||||
*/ | */ | ||||
static void | static void | ||||
mca_postscan(void *context __unused, int pending __unused) | mca_postscan(void *context __unused, int pending __unused) | ||||
{ | { | ||||
mca_fill_freelist(); | mca_fill_freelist(); | ||||
mca_emit_logs(); | mca_emit_logs(); | ||||
} | } | ||||
static void | static void | ||||
mca_record_entry(enum scan_mode mode, const struct mca_record *record) | mca_record_entry(enum scan_mode mode, const struct mca_record *record, | ||||
bool logged) | |||||
{ | { | ||||
struct mca_internal *rec; | struct mca_internal *rec; | ||||
if (mode == POLLED) { | if (mode == POLLED) { | ||||
rec = malloc(sizeof(*rec), M_MCA, M_WAITOK); | rec = malloc(sizeof(*rec), M_MCA, M_WAITOK); | ||||
mtx_lock_spin(&mca_lock); | mtx_lock_spin(&mca_lock); | ||||
} else { | } else { | ||||
mtx_lock_spin(&mca_lock); | mtx_lock_spin(&mca_lock); | ||||
rec = STAILQ_FIRST(&mca_freelist); | rec = STAILQ_FIRST(&mca_freelist); | ||||
if (rec == NULL) { | if (rec == NULL && !logged) { | ||||
printf("MCA: Unable to allocate space for an event.\n"); | printf("MCA: Unable to allocate space for an event.\n"); | ||||
mca_log(record); | mca_log(record, false); | ||||
mtx_unlock_spin(&mca_lock); | mtx_unlock_spin(&mca_lock); | ||||
return; | return; | ||||
} | } | ||||
STAILQ_REMOVE_HEAD(&mca_freelist, link); | STAILQ_REMOVE_HEAD(&mca_freelist, link); | ||||
mca_freecount--; | mca_freecount--; | ||||
} | } | ||||
rec->rec = *record; | rec->rec = *record; | ||||
rec->logged = 0; | rec->logged = logged ? 1: 0; | ||||
STAILQ_INSERT_TAIL(&mca_records, rec, link); | STAILQ_INSERT_TAIL(&mca_records, rec, link); | ||||
mca_count++; | mca_count++; | ||||
mtx_unlock_spin(&mca_lock); | mtx_unlock_spin(&mca_lock); | ||||
} | } | ||||
#ifdef DEV_APIC | #ifdef DEV_APIC | ||||
/* | /* | ||||
* Update the interrupt threshold for a CMCI. The strategy is to use | * Update the interrupt threshold for a CMCI. The strategy is to use | ||||
▲ Show 20 Lines • Show All 149 Lines • ▼ Show 20 Lines | |||||
#endif | #endif | ||||
valid = mca_check_status(i, &rec); | valid = mca_check_status(i, &rec); | ||||
if (valid) { | if (valid) { | ||||
count++; | count++; | ||||
if (rec.mr_status & ucmask) { | if (rec.mr_status & ucmask) { | ||||
recoverable = 0; | recoverable = 0; | ||||
mtx_lock_spin(&mca_lock); | mtx_lock_spin(&mca_lock); | ||||
mca_log(&rec); | mca_log(&rec, (mode == MCE)); | ||||
mtx_unlock_spin(&mca_lock); | mtx_unlock_spin(&mca_lock); | ||||
} | mca_record_entry(mode, &rec, true); | ||||
mca_record_entry(mode, &rec); | } else | ||||
mca_record_entry(mode, &rec, false); | |||||
} | } | ||||
#ifdef DEV_APIC | #ifdef DEV_APIC | ||||
/* | /* | ||||
* If this is a bank this CPU monitors via CMCI, | * If this is a bank this CPU monitors via CMCI, | ||||
* update the threshold. | * update the threshold. | ||||
*/ | */ | ||||
if (PCPU_GET(cmci_mask) & 1 << i) { | if (PCPU_GET(cmci_mask) & 1 << i) { | ||||
▲ Show 20 Lines • Show All 516 Lines • Show Last 20 Lines |
I'm a bit leery of putting 1k on the stack. I have some old patches to use dedicated stacks for MC# (similar to what we do for NMI) which we might consider at some point and which would perhaps alleviate that worry.