ao_mca.c revision fb2f18f820d90b001aea4fb27dd654bc1263c440
/*
* 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
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/privregs.h>
#include <sys/pci_impl.h>
#include <sys/x86_archext.h>
#include <sys/sysmacros.h>
#include <sys/cpu_module_impl.h>
#include <sys/pci_cfgspace_impl.h>
#include <sys/sysevent.h>
#include "ao.h"
#include "ao_mca_disp.h"
int ao_mca_stack_flag = 0; /* record stack trace in ereports */
};
struct ao_ctl_init {
};
/*
* Additional NB MCA ctl initialization for revs F and G
*/
static const struct ao_ctl_init ao_nb_ctl_init[] = {
{ X86_CHIPREV_UNKNOWN, 0 }
};
typedef struct ao_bank_cfg {
static const ao_bank_cfg_t ao_bank_cfgs[] = {
};
static const ao_error_disp_t ao_disp_unknown = {
};
/*
* This is quite awful but necessary to work around x86 system vendor's view of
* the world. Other operating systems (you know who you are) don't understand
* Opteron-specific error handling, so BIOS and system vendors often hide these
* conditions from them by using SMI polling to copy out any errors from the
* machine-check registers. When Solaris runs on a system with this feature,
* we want to disable the SMI polling so we can use FMA instead. Sadly, there
* isn't even a standard self-describing way to express the whole situation,
* so we have to resort to hard-coded values. This should all be changed to
* be a self-describing vendor-specific SMBIOS structure in the future.
*/
static const struct ao_smi_disable {
const char *asd_sys_vendor; /* SMB_TYPE_SYSTEM vendor prefix */
const char *asd_sys_product; /* SMB_TYPE_SYSTEM product prefix */
const char *asd_bios_vendor; /* SMB_TYPE_BIOS vendor prefix */
} ao_smi_disable[] = {
{ "Sun Microsystems", "Galaxy12",
"American Megatrends", 0x59 },
{ "Sun Microsystems", "Sun Fire X4100 Server",
"American Megatrends", 0x59 },
{ "Sun Microsystems", "Sun Fire X4200 Server",
"American Megatrends", 0x59 },
};
static int
{
AO_MCA_R4_BIT_GEN, /* AMD_ERRCODE_R4_GEN */
AO_MCA_R4_BIT_RD, /* AMD_ERRCODE_R4_RD */
AO_MCA_R4_BIT_WR, /* AMD_ERRCODE_R4_WR */
AO_MCA_R4_BIT_DRD, /* AMD_ERRCODE_R4_DRD */
AO_MCA_R4_BIT_DWR, /* AMD_ERRCODE_R4_DWR */
AO_MCA_R4_BIT_IRD, /* AMD_ERRCODE_R4_IRD */
AO_MCA_R4_BIT_PREFETCH, /* AMD_ERRCODE_R4_PREFETCH */
AO_MCA_R4_BIT_EVICT, /* AMD_ERRCODE_R4_EVICT */
AO_MCA_R4_BIT_SNOOP /* AMD_ERRCODE_R4_SNOOP */
};
}
static int
{
AO_MCA_PP_BIT_SRC, /* AMD_ERRCODE_PP_SRC */
AO_MCA_PP_BIT_RSP, /* AMD_ERRCODE_PP_RSP */
AO_MCA_PP_BIT_OBS, /* AMD_ERRCODE_PP_OBS */
AO_MCA_PP_BIT_GEN /* AMD_ERRCODE_PP_GEN */
};
}
static int
{
AO_MCA_II_BIT_MEM, /* AMD_ERRCODE_II_MEM */
0,
AO_MCA_II_BIT_IO, /* AMD_ERRCODE_II_IO */
AO_MCA_II_BIT_GEN /* AMD_ERRCODE_II_GEN */
};
}
static uint8_t
{
return (val);
}
static int
int bankno)
{
/*
* If the bank's status register indicates overflow, then we can no
* longer rely on the value of CECC: our experience with actual fault
* injection has shown that multiple CE's overwriting each other shows
* AMD_BANK_STAT_CECC and AMD_BANK_STAT_UECC both set to zero. This
* should be clarified in a future BKDG or by the Revision Guide.
* This behaviour is fixed in revision F.
*/
if (bankno == AMD_MCA_BANK_NB &&
status & AMD_BANK_STAT_OVER) {
}
return (0);
/*
* r4 and pp bits are stored separately, so we mask off and compare them
* for the code types that use them. Once we've taken the r4 and pp
* bits out of the equation, we can directly compare the resulting code
* with the one stored in the ao_error_disp_t.
*/
if (AMD_ERRCODE_ISMEM(code)) {
return (0);
} else if (AMD_ERRCODE_ISBUS(code)) {
return (0);
}
}
static const ao_error_disp_t *
{
const ao_error_disp_t *aed;
return (aed);
}
return (&ao_disp_unknown);
}
void
{
}
{
}
/*
* ao_chip_once returns 1 if the caller should perform the operation for
* this chip, or 0 if some other core has already performed the operation.
*/
int
{
1 : 0);
}
/*
* Setup individual bank detectors after stashing their bios settings.
* The 'donb' argument indicates whether this core should configured
* the shared NorthBridhe MSRs.
*/
static void
{
const struct ao_ctl_init *extrap;
int i;
for (i = 0; i < AMD_MCA_BANK_COUNT; i++, bankcfg++) {
if (i == AMD_MCA_BANK_NB && donb == 0) {
continue;
} else if (i == AMD_MCA_BANK_NB) {
} else {
}
/* Initialize MCi_CTL register for this bank */
extrap++;
}
}
/* Initialize the MCi_MISC register for this bank */
}
}
/*
* This knob exists in case any platform has a problem with our default
* policy of disabling any interrupt registered in the NB MC4_MISC
* register. Setting this may cause Solaris and external entities
* who also have an interest in this register to argue over available
* telemetry (so setting it is generally not recommended).
*/
int ao_nb_cfg_mc4misc_noseize = 0;
/*
* The BIOS may have setup to receive SMI on counter overflow. It may also
* have locked various fields or made them read-only. We will clear any
* SMI request and leave the register locked. We will also clear the
* counter and enable counting - while we don't use the counter it is nice
* to have it enabled for verification and debug work.
*/
static void
{
int locked;
return;
return; /* stash BIOS value, but no changes */
/*
* The Valid bit tells us whether the CtrP bit is defined; if it
* is the CtrP bit tells us whether an ErrCount field is present.
* If not then there is nothing for us to do.
*/
return;
if (locked) {
}
if (locked)
}
/*
* NorthBridge (NB) Configuration.
*
* We add and remove bits from the BIOS-configured value, rather than
* writing an absolute value. The variables ao_nb_cfg_{add,remove}_cmn and
* ap_nb_cfg_{add,remove}_revFG are available for modification via kmdb
* after the common changes, and one write is made to the config register.
* These are not intended for watchdog configuration via these variables -
* use the watchdog policy below.
*/
/*
* Bits to be added to the NB configuration register - all revs.
*/
/*
* Bits to be cleared from the NB configuration register - all revs.
*/
/*
* Bits to be added to the NB configuration register - revs F and G.
*/
/*
* Bits to be cleared from the NB configuration register - revs F and G.
*/
struct ao_nb_cfg {
};
static const struct ao_nb_cfg ao_cfg_extra[] = {
};
/*
* Bits to be used if we configure the NorthBridge (NB) Watchdog. The watchdog
* triggers a machine check exception when no response to an NB system access
* occurs within a specified time interval.
*/
/*
* The default watchdog policy is to enable it (at the above rate) if it
* is disabled; if it is enabled then we leave it enabled at the rate
* chosen by the BIOS.
*/
enum {
AO_NB_WDOG_LEAVEALONE, /* Don't touch watchdog config */
AO_NB_WDOG_DISABLE, /* Always disable watchdog */
AO_NB_WDOG_ENABLE_IF_DISABLED, /* If disabled, enable at our rate */
AO_NB_WDOG_ENABLE_FORCE_RATE /* Enable and set our rate */
static void
{
/*
* Read the NorthBridge (NB) configuration register in PCI space,
* modify the settings accordingly, and store the new value back.
*/
switch (ao_nb_watchdog_policy) {
case AO_NB_WDOG_LEAVEALONE:
break;
case AO_NB_WDOG_DISABLE:
break;
default:
"using default policy", ao_nb_watchdog_policy);
/*FALLTHRU*/
if (val & AMD_NB_CFG_WDOGTMRDIS)
break; /* if enabled leave rate intact */
/*FALLTHRU*/
val &= ~AMD_NB_CFG_WDOGTMRDIS;
val |= ao_nb_cfg_wdog;
break;
}
/*
* Now apply bit adds and removes, first those common to all revs
* and then the revision-specific ones.
*/
val &= ~ao_nb_cfg_remove_cmn;
val |= ao_nb_cfg_add_cmn;
}
nbcp++;
}
}
/*
* This knob exists in case any platform has a problem with our default
* policy of disabling any interrupt registered in the online spare
* control register. Setting this may cause Solaris and external entities
* who also have an interest in this register to argue over available
* telemetry (so setting it is generally not recommended).
*/
int ao_nb_cfg_sparectl_noseize = 0;
/*
* Setup the online spare control register (revs F and G). We disable
* any interrupt registered by the BIOS and zero all error counts.
*/
static void
{
union mcreg_sparectl sparectl;
return; /* stash BIOS value, but no changes */
/*
* If the BIOS has requested SMI interrupt type for ECC count
* overflow for a chip-select or channel force those off.
*/
/* Enable writing to the EccErrCnt field */
/* First write, preparing for writes to EccErrCnt */
MCREG_VAL32(&sparectl));
/*
*/
}
}
}
/*
* Capture the machine-check exception state into our per-CPU logout area, and
* dispatch a copy of the logout area to our error queue for ereport creation.
* If 'rp' is non-NULL, we're being called from trap context; otherwise we're
* being polled or poked by the injector. We return the number of errors
* found through 'np', and a boolean indicating whether the error is fatal.
* The caller is expected to call fm_panic() if we return fatal (non-zero).
*/
int
{
int i, fatal = 0, n = 0;
/*
* Iterate over the banks of machine-check registers, read the address
* and status registers into the logout area, and clear status as we go.
* Also read the MCi_MISC register if MCi_STATUS.MISCV indicates that
* there is valid info there (as it will in revisions F and G for
* NorthBridge ECC errors).
*/
for (i = 0; i < AMD_MCA_BANK_COUNT; i++) {
if (i == AMD_MCA_BANK_NB && skipnb) {
abl->abl_status = 0;
continue;
}
else
}
if (ao_mca_stack_flag)
else
acl->acl_stackdepth = 0;
/*
* Clear MCG_STATUS, indicating that machine-check trap processing is
* complete. Once we do this, another machine-check trap can occur
* (if another occurs up to this point then the system will reset).
*/
if (mcg_status & MCG_STATUS_MCIP)
wrmsr(IA32_MSR_MCG_STATUS, 0);
/*
* If we took a machine-check trap, then the error is fatal if the
* return instruction pointer is not valid in the global register.
*/
fatal++;
/*
* Now iterate over the saved logout area, determining whether the
* error that we saw is fatal or not based upon our dispositions
* and the hardware's indicators of whether or not we can resume.
*/
for (i = 0; i < AMD_MCA_BANK_COUNT; i++) {
const ao_error_disp_t *aed;
continue;
if ((when & AO_AED_PANIC_ALWAYS) ||
fatal++;
}
}
/*
* If we are taking a machine-check exception and our context
* is corrupt, then we must die.
*/
fatal++;
/*
* The overflow bit is set if the bank detects an error but
* the valid bit of its status register is already set
* (software has not yet read and cleared it). Enabled
* (for mc# reporting) errors overwrite disabled errors,
* uncorrectable errors overwrite correctable errors,
* uncorrectable errors are not overwritten.
*
* For the NB detector bank the overflow bit will not be
* set for repeated correctable errors on revisions D and
* earlier; it will be set on revisions E and later.
* On revision E, however, the CorrECC bit does appear
* to clear in these circumstances. Since we can enable
* machine-check exception on NB correctables we need to
* be careful here; we never enable mc# for correctable from
* other banks.
*
* Our solution will be to declare a machine-check exception
* fatal if the overflow bit is set except in the case of
* revision F on the NB detector bank for which CorrECC
* is indicated. Machine-check exception for NB correctables
* on rev E is explicitly not supported.
*/
!(i == AMD_MCA_BANK_NB &&
fatal++;
/*
* If we are taking a machine-check exception and we don't
* recognize the error case at all, then assume it's fatal.
* This will need to change if we eventually use the Opteron
* Rev E exception mechanism for detecting correctable errors.
*/
fatal++;
n++;
}
if (n > 0) {
}
*np = n; /* return number of errors found to caller */
return (fatal != 0);
}
static uint_t
int is_nb)
{
if (is_nb) {
} else {
}
} else {
}
}
static void
{
unump->unum_offset);
}
static void
{
int nelems = 0;
int i;
for (i = 0; i < MC_UNUM_NDIMM; i++) {
break;
unump, i);
}
for (i = 0; i < nelems; i++)
}
static void
{
if (members & FM_EREPORT_PAYLOAD_FLAG_BANK_STAT) {
}
if (members & FM_EREPORT_PAYLOAD_FLAG_BANK_NUM) {
}
if (members & FM_EREPORT_PAYLOAD_FLAG_ADDR) {
}
}
if (members & FM_EREPORT_PAYLOAD_FLAG_BANK_MISC) {
}
if (members & FM_EREPORT_PAYLOAD_FLAG_SYND) {
}
if (members & FM_EREPORT_PAYLOAD_FLAG_SYND_TYPE) {
}
if (members & FM_EREPORT_PAYLOAD_FLAG_IP) {
}
if (members & FM_EREPORT_PAYLOAD_FLAG_PRIV) {
}
if (members & FM_EREPORT_PAYLOAD_FLAG_RESOURCE) {
int addrvalid = 0;
}
}
}
}
static void
{
char buf[FM_MAX_CLASS];
if (panicstr) {
return;
/*
* Now try to allocate another element for scratch space and
* use that for further scratch space (eg for constructing
* nvlists to add the main ereport). If we can't reserve
* a scratch element just fallback to working within the
* element we already have, and hope for the best. All this
* is necessary because the fixed buffer nv allocator does
* not reclaim freed space and nvlist construction is
* expensive.
*/
else
} else {
}
/*
* Create the "hc" scheme detector FMRI identifying this cpu
*/
/*
* Encode all the common data into the ereport.
*/
/*
* We're done with 'detector' so reclaim the scratch space.
*/
if (panicstr) {
} else {
}
/*
* Encode the error-specific data that was saved in the logout area.
*/
if (panicstr) {
if (scr_eqep)
} else {
}
}
/*ARGSUSED*/
void
{
int i;
for (i = 0; i < AMD_MCA_BANK_COUNT; i++) {
const ao_error_disp_t *aed;
}
}
}
/*
* Machine check interrupt handler - we jump here from mcetrap.
*
* A sibling core may attempt to poll the NorthBridge during the
* time we are performing the logout. So we coordinate NB access
* of all cores of the same chip via a per-chip lock. If the lock
* is held on a sibling core then we spin for it here; if the
* lock is held by the thread we have interrupted then we do
* not acquire the lock but can proceed safe in the knowledge that
* the lock owner can't actually perform any NB accesses. This
* requires that threads that take the aos_nb_poll_lock do not
* block and that they disable preemption while they hold the lock.
* It also requires that the lock be adaptive since mutex_owner does
* not work for spin locks.
*/
static int ao_mca_path1, ao_mca_path2;
int
{
int tooklock = 0;
int rv;
/*
* The mutex is not owned by the thread we have interrupted
* (since the holder may not block or be preempted once the
* lock is acquired). We will spin for this adaptive lock.
*/
++ao_mca_path1;
while (!mutex_tryenter(nblock)) {
;
}
tooklock = 1;
} else {
++ao_mca_path2;
}
if (tooklock)
return (rv);
}
/*ARGSUSED*/
int
{
int i;
for (i = 0; i < nregs; i++)
return (0);
}
void
ao_mca_init(void *data)
{
int donb;
int i;
/*
* If the hardware's bank count is different than what we expect, then
* we're running on some Opteron variant that we don't understand yet.
*/
return;
}
/*
* Configure the logout areas. We preset every logout area's acl_ao
* pointer to refer back to our per-CPU state for errorq drain usage.
*/
for (i = 0; i < AO_MCA_LOGOUT_NUM; i++)
/* LINTED: logical expression always true */
/*
* Must this core perform NB MCA configuration? This must be done
* by just one core.
*/
/*
* Initialize poller data, but don't start polling yet.
*/
/*
* Configure the bank MCi_CTL register to nominate which error
* types for each bank will produce a machine-check (we'll poll
* for others). Correctable error types mentioned in these MCi_CTL
* settings won't actually produce an exception unless an additional
* (and undocumented) bit is set elsewhere - the poller must still
* handle these.
*/
/*
* Modify the MCA NB Configuration Register.
*/
if (donb)
/*
* Setup the Online Spare Control Register
*/
}
/*
* Enable all error reporting banks (icache, dcache, ...). This
* enables error detection, as opposed to error reporting above.
*/
/*
* Throw away all existing bank state. We do this because some BIOSes,
* perhaps during POST, do things to the machine that cause MCA state
* to be updated. If we interpret this state as an actual error, we
* may end up indicting something that's not actually broken.
*/
for (i = 0; i < AMD_MCA_BANK_COUNT; i++) {
if (!donb)
continue;
}
}
/*
* Note that although this cpu module is loaded before the PSMs are
* loaded (and hence before acpica is loaded), this function is
* called from post_startup(), after PSMs are initialized and acpica
* is loaded.
*/
static int
ao_acpi_find_smicmd(int *asd_port)
{
/*
* AcpiGetFirmwareTable works even if ACPI is disabled, so a failure
* here means we weren't able to retreive a pointer to the FADT.
*/
return (-1);
return (0);
}
/*ARGSUSED*/
void
ao_mca_post_init(void *data)
{
const struct ao_smi_disable *asd;
/*
* Fetch the System and BIOS vendor strings from SMBIOS and see if they
* match a value in our table. If so, disable SMI error polling. This
* is grotesque and should be replaced by self-describing vendor-
* specific SMBIOS data or a specification enhancement instead.
*/
continue;
/*
* Look for the SMI_CMD port in the ACPI FADT,
* if the port is 0, this platform doesn't support
* SMM, so there is no SMI error polling to disable.
*/
asd_port != 0) {
"favor of Solaris Fault Management for "
"AMD Processors\n");
} else if (rv < 0) {
"for AMD Processors could not disable SMI "
"polling because an error occurred while "
"trying to determine the SMI command port "
"from the ACPI FADT table\n");
}
break;
}
}
}
/*
* Called after a CPU has been marked with CPU_FAULTED. Not called on the
* faulted CPU. cpu_lock is held.
*/
/*ARGSUSED*/
void
ao_faulted_enter(void *data)
{
/*
* Nothing to do here. We'd like to turn off the faulted CPU's
* correctable error detectors, but that can only be done by the
* faulted CPU itself. cpu_get_state() will now return P_FAULTED,
* allowing the poller to skip this CPU until it is re-enabled.
*/
}
/*
* Called after the CPU_FAULTED bit has been cleared from a previously-faulted
* CPU. Not called on the faulted CPU. cpu_lock is held.
*/
void
ao_faulted_exit(void *data)
{
/*
* We'd like to clear the faulted CPU's MCi_STATUS registers so as to
* avoid generating ereports for errors which occurred while the CPU was
* officially faulted. Unfortunately, those registers can only be
* cleared by the CPU itself, so we can't do it here.
*
* We're going to set the UNFAULTING bit on the formerly-faulted CPU's
* MCA state. This will tell the poller that the MCi_STATUS registers
* can't yet be trusted. The poller, which is the first thing we
* control that'll execute on that CPU, will clear the registers, and
* will then clear the bit.
*/
}