#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/modctl.h>
#include <sys/cpu_module.h>
#include <sys/mca_x86.h>
#include <sys/pci_cfgspace.h>
#include <sys/x86_archext.h>
#include <sys/mc_amd.h>
#include <sys/fm/protocol.h>
#include <sys/fm/cpu/GENAMD.h>
#include <sys/fm/smb/fmsmb.h>
#include <sys/fm/util.h>
#include <sys/nvpair.h>
#include <sys/controlregs.h>
#include <sys/pghw.h>
#include <sys/sunddi.h>
#include <sys/sysmacros.h>
#include <sys/cpu_module_ms_impl.h>
#include "authamd.h"
extern int x86gentopo_legacy;
int authamd_ms_support_disable = 0;
#define AUTHAMD_F_REVS_BCDE \
(X86_CHIPREV_AMD_LEGACY_F_REV_B | X86_CHIPREV_AMD_LEGACY_F_REV_C0 | \
X86_CHIPREV_AMD_LEGACY_F_REV_CG | X86_CHIPREV_AMD_LEGACY_F_REV_D | \
X86_CHIPREV_AMD_LEGACY_F_REV_E)
#define AUTHAMD_F_REVS_FG \
(X86_CHIPREV_AMD_LEGACY_F_REV_F | X86_CHIPREV_AMD_LEGACY_F_REV_G)
#define AUTHAMD_10_REVS_AB \
(X86_CHIPREV_AMD_LEGACY_10_REV_A | X86_CHIPREV_AMD_LEGACY_10_REV_B)
#define AUTHAMD_NBONCHIP(rev) \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \
chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A))
#define AUTHAMD_MEMECC_RECOGNISED(rev) \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \
chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A))
#define AUTHAMD_HAS_ONLINESPARECTL(rev) \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_F) || \
chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A))
#define AUTHAMD_DO_NBMCACFG(rev) \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \
chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A))
#define AUTHAMD_HAS_CHIPSCRUB(rev) \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \
chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A))
#define AUTHAMD_NBMISC_NUM(rev) \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_F)? 1 : \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A) ? 3 : 0))
#define AUTHAMD_NOGARTTBLWLK_MC(rev) \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \
chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A))
#define AUTHAMD_L3CAPABLE(rev) \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A))
#define AUTHAMD_SUPPORTS_X8ECC(rev) \
(chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_D0))
#define AUTHAMD_IS_MEMECCERR(bank, status) \
((bank) == AMD_MCA_BANK_NB && \
MCAX86_ERRCODE_ISBUS_INTERCONNECT(MCAX86_ERRCODE(status)) && \
MCAX86_ERRCODE_LL(MCAX86_ERRCODE(status)) == MCAX86_ERRCODE_LL_LG && \
MCAX86_ERRCODE_II(MCAX86_ERRCODE(status)) == MCAX86_ERRCODE_II_MEM && \
((status) & (AMD_BANK_STAT_CECC | AMD_BANK_STAT_UECC)))
static authamd_error_disp_t authamd_memce_disp = {
FM_EREPORT_CPU_GENAMD,
FM_EREPORT_CPU_GENAMD_MEM_CE,
FM_EREPORT_GENAMD_PAYLOAD_FLAGS_MEM_CE
};
static authamd_error_disp_t authamd_memue_disp = {
FM_EREPORT_CPU_GENAMD,
FM_EREPORT_CPU_GENAMD_MEM_UE,
FM_EREPORT_GENAMD_PAYLOAD_FLAGS_MEM_UE
};
static authamd_error_disp_t authamd_ckmemce_disp = {
FM_EREPORT_CPU_GENAMD,
FM_EREPORT_CPU_GENAMD_CKMEM_CE,
FM_EREPORT_GENAMD_PAYLOAD_FLAGS_CKMEM_CE
};
static authamd_error_disp_t authamd_ckmemue_disp = {
FM_EREPORT_CPU_GENAMD,
FM_EREPORT_CPU_GENAMD_CKMEM_UE,
FM_EREPORT_GENAMD_PAYLOAD_FLAGS_CKMEM_UE
};
#define AUTHAMD_IS_GARTERR(bank, status) \
((bank) == AMD_MCA_BANK_NB && \
MCAX86_ERRCODE_ISTLB(MCAX86_ERRCODE(status)) && \
MCAX86_ERRCODE_LL(MCAX86_ERRCODE(status)) == MCAX86_ERRCODE_LL_LG && \
MCAX86_ERRCODE_TT(MCAX86_ERRCODE(status)) == MCAX86_ERRCODE_TT_GEN && \
(status) & MSR_MC_STATUS_UC)
static authamd_error_disp_t authamd_gart_disp = {
FM_EREPORT_CPU_GENAMD,
FM_EREPORT_CPU_GENADM_GARTTBLWLK,
0
};
static struct authamd_nodeshared *authamd_shared[AUTHAMD_MAX_NODES];
static int
authamd_chip_once(authamd_data_t *authamd, enum authamd_cfgonce_bitnum what)
{
return (atomic_set_long_excl(&authamd->amd_shared->ans_cfgonce,
what) == 0 ? B_TRUE : B_FALSE);
}
static void
authamd_pcicfg_write(uint_t procnodeid, uint_t func, uint_t reg, uint32_t val)
{
ASSERT(procnodeid + 24 <= 31);
ASSERT((func & 7) == func);
ASSERT((reg & 3) == 0 && reg < 4096);
cmi_pci_putl(0, procnodeid + 24, func, reg, 0, val);
}
static uint32_t
authamd_pcicfg_read(uint_t procnodeid, uint_t func, uint_t reg)
{
ASSERT(procnodeid + 24 <= 31);
ASSERT((func & 7) == func);
ASSERT((reg & 3) == 0 && reg < 4096);
return (cmi_pci_getl(0, procnodeid + 24, func, reg, 0, 0));
}
void
authamd_bankstatus_prewrite(cmi_hdl_t hdl, authamd_data_t *authamd)
{
uint64_t hwcr;
if (cmi_hdl_rdmsr(hdl, MSR_AMD_HWCR, &hwcr) != CMI_SUCCESS)
return;
authamd->amd_hwcr = hwcr;
if (!(hwcr & AMD_HWCR_MCI_STATUS_WREN)) {
hwcr |= AMD_HWCR_MCI_STATUS_WREN;
(void) cmi_hdl_wrmsr(hdl, MSR_AMD_HWCR, hwcr);
}
}
void
authamd_bankstatus_postwrite(cmi_hdl_t hdl, authamd_data_t *authamd)
{
uint64_t hwcr = authamd->amd_hwcr;
if (!(hwcr & AMD_HWCR_MCI_STATUS_WREN)) {
hwcr &= ~AMD_HWCR_MCI_STATUS_WREN;
(void) cmi_hdl_wrmsr(hdl, MSR_AMD_HWCR, hwcr);
}
}
static int
authamd_read_ecccnt(authamd_data_t *authamd, struct authamd_logout *msl)
{
union mcreg_sparectl sparectl;
uint_t procnodeid = authamd->amd_shared->ans_procnodeid;
uint_t family = authamd->amd_shared->ans_family;
x86_chiprev_t rev = authamd->amd_shared->ans_rev;
int chan, cs;
if (!AUTHAMD_HAS_ONLINESPARECTL(rev)) {
bzero(&msl->aal_eccerrcnt, sizeof (msl->aal_eccerrcnt));
return (0);
}
MCREG_VAL32(&sparectl) =
authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_SPARECTL);
switch (family) {
case AUTHAMD_FAMILY_F:
MCREG_FIELD_F_revFG(&sparectl, EccErrCntWrEn) = 0;
break;
case AUTHAMD_FAMILY_10:
MCREG_FIELD_10_revAB(&sparectl, EccErrCntWrEn) = 0;
break;
}
for (chan = 0; chan < AUTHAMD_DRAM_NCHANNEL; chan++) {
switch (family) {
case AUTHAMD_FAMILY_F:
MCREG_FIELD_F_revFG(&sparectl, EccErrCntDramChan) =
chan;
break;
case AUTHAMD_FAMILY_10:
MCREG_FIELD_10_revAB(&sparectl, EccErrCntDramChan) =
chan;
break;
}
for (cs = 0; cs < AUTHAMD_DRAM_NCS; cs++) {
switch (family) {
case AUTHAMD_FAMILY_F:
MCREG_FIELD_F_revFG(&sparectl,
EccErrCntDramCs) = cs;
break;
case AUTHAMD_FAMILY_10:
MCREG_FIELD_10_revAB(&sparectl,
EccErrCntDramCs) = cs;
break;
}
authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_SPARECTL, MCREG_VAL32(&sparectl));
MCREG_VAL32(&sparectl) = authamd_pcicfg_read(procnodeid,
MC_FUNC_MISCCTL, MC_CTL_REG_SPARECTL);
switch (family) {
case AUTHAMD_FAMILY_F:
msl->aal_eccerrcnt[chan][cs] =
MCREG_FIELD_F_revFG(&sparectl, EccErrCnt);
break;
case AUTHAMD_FAMILY_10:
msl->aal_eccerrcnt[chan][cs] =
MCREG_FIELD_10_revAB(&sparectl, EccErrCnt);
break;
}
}
}
return (1);
}
static void
authamd_clear_ecccnt(authamd_data_t *authamd, boolean_t clrint)
{
union mcreg_sparectl sparectl;
uint_t procnodeid = authamd->amd_shared->ans_procnodeid;
uint_t family = authamd->amd_shared->ans_family;
x86_chiprev_t rev = authamd->amd_shared->ans_rev;
int chan, cs;
if (!AUTHAMD_HAS_ONLINESPARECTL(rev))
return;
MCREG_VAL32(&sparectl) =
authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_SPARECTL);
switch (family) {
case AUTHAMD_FAMILY_F:
MCREG_FIELD_F_revFG(&sparectl, EccErrCntWrEn) = 1;
if (clrint) {
MCREG_FIELD_F_revFG(&sparectl, EccErrInt) = 0;
MCREG_FIELD_F_revFG(&sparectl, SwapDoneInt) = 0;
}
break;
case AUTHAMD_FAMILY_10:
MCREG_FIELD_10_revAB(&sparectl, EccErrCntWrEn) = 1;
if (clrint) {
MCREG_FIELD_10_revAB(&sparectl, EccErrInt) = 0;
MCREG_FIELD_10_revAB(&sparectl, SwapDoneInt) = 0;
}
break;
}
authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_SPARECTL, MCREG_VAL32(&sparectl));
for (chan = 0; chan < AUTHAMD_DRAM_NCHANNEL; chan++) {
switch (family) {
case AUTHAMD_FAMILY_F:
MCREG_FIELD_F_revFG(&sparectl, EccErrCntDramChan) =
chan;
break;
case AUTHAMD_FAMILY_10:
MCREG_FIELD_10_revAB(&sparectl, EccErrCntDramChan) =
chan;
break;
}
for (cs = 0; cs < AUTHAMD_DRAM_NCS; cs++) {
switch (family) {
case AUTHAMD_FAMILY_F:
MCREG_FIELD_F_revFG(&sparectl,
EccErrCntDramCs) = cs;
MCREG_FIELD_F_revFG(&sparectl,
EccErrCnt) = 0;
break;
case AUTHAMD_FAMILY_10:
MCREG_FIELD_10_revAB(&sparectl,
EccErrCntDramCs) = cs;
MCREG_FIELD_10_revAB(&sparectl,
EccErrCnt) = 0;
break;
}
authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_SPARECTL, MCREG_VAL32(&sparectl));
}
}
}
static int
authamd_supported(cmi_hdl_t hdl)
{
uint_t family = cmi_hdl_family(hdl);
switch (family) {
case AUTHAMD_FAMILY_6:
case AUTHAMD_FAMILY_F:
case AUTHAMD_FAMILY_10:
return (1);
default:
return (0);
}
}
int
authamd_init(cmi_hdl_t hdl, void **datap)
{
uint_t chipid = cmi_hdl_chipid(hdl);
uint_t procnodeid = cmi_hdl_procnodeid(hdl);
struct authamd_nodeshared *sp, *osp;
uint_t family = cmi_hdl_family(hdl);
x86_chiprev_t rev = cmi_hdl_chiprev(hdl);
authamd_data_t *authamd;
uint64_t cap;
if (authamd_ms_support_disable ||
!authamd_supported(hdl))
return (ENOTSUP);
if (!is_x86_feature(x86_featureset, X86FSET_MCA))
return (ENOTSUP);
if (cmi_hdl_rdmsr(hdl, IA32_MSR_MCG_CAP, &cap) != CMI_SUCCESS)
return (ENOTSUP);
if (!(cap & MCG_CAP_CTL_P))
return (ENOTSUP);
authamd = *datap = kmem_zalloc(sizeof (authamd_data_t), KM_SLEEP);
cmi_hdl_hold(hdl);
authamd->amd_hdl = hdl;
if ((sp = authamd_shared[procnodeid]) == NULL) {
sp = kmem_zalloc(sizeof (struct authamd_nodeshared), KM_SLEEP);
sp->ans_chipid = chipid;
sp->ans_procnodeid = procnodeid;
sp->ans_family = family;
sp->ans_rev = rev;
membar_producer();
osp = atomic_cas_ptr(&authamd_shared[procnodeid], NULL, sp);
if (osp != NULL) {
kmem_free(sp, sizeof (struct authamd_nodeshared));
sp = osp;
}
}
authamd->amd_shared = sp;
return (0);
}
size_t
authamd_logout_size(cmi_hdl_t hdl)
{
return (sizeof (struct authamd_logout));
}
uint64_t
authamd_mcgctl_val(cmi_hdl_t hdl, int nbanks, uint64_t proposed)
{
return (nbanks < 64 ? (1ULL << nbanks) - 1 : proposed);
}
boolean_t
authamd_bankctl_skipinit(cmi_hdl_t hdl, int bank)
{
authamd_data_t *authamd = cms_hdl_getcmsdata(hdl);
x86_chiprev_t rev = authamd->amd_shared->ans_rev;
if (authamd->amd_shared->ans_family == AUTHAMD_FAMILY_6)
return (bank == 0 ? B_TRUE : B_FALSE);
if (AUTHAMD_NBONCHIP(rev) && bank == AMD_MCA_BANK_NB) {
return (authamd_chip_once(authamd, AUTHAMD_CFGONCE_NBMCA) ==
B_TRUE ? B_FALSE : B_TRUE);
}
return (B_FALSE);
}
uint64_t
authamd_bankctl_val(cmi_hdl_t hdl, int bank, uint64_t proposed)
{
authamd_data_t *authamd = cms_hdl_getcmsdata(hdl);
x86_chiprev_t rev = authamd->amd_shared->ans_rev;
uint64_t val = proposed;
if (bank == AMD_MCA_BANK_NB && AUTHAMD_NOGARTTBLWLK_MC(rev))
val &= ~AMD_NB_EN_GARTTBLWK;
return (val);
}
uint32_t authamd_nb_mcacfg_add = AMD_NB_CFG_ADD_CMN;
uint32_t authamd_nb_mcacfg_remove = AMD_NB_CFG_REMOVE_CMN;
enum {
AUTHAMD_NB_WDOG_LEAVEALONE,
AUTHAMD_NB_WDOG_DISABLE,
AUTHAMD_NB_WDOG_ENABLE_IF_DISABLED,
AUTHAMD_NB_WDOG_ENABLE_FORCE_RATE
} authamd_nb_watchdog_policy = AUTHAMD_NB_WDOG_ENABLE_IF_DISABLED;
uint32_t authamd_nb_mcacfg_wdog = AMD_NB_CFG_WDOGTMRCNTSEL_4095 |
AMD_NB_CFG_WDOGTMRBASESEL_1MS;
enum {
AUTHAMD_SCRUB_BIOSDEFAULT,
AUTHAMD_SCRUB_FIXED,
AUTHAMD_SCRUB_MAX
} authamd_scrub_policy = AUTHAMD_SCRUB_MAX;
uint32_t authamd_scrub_rate_dcache = 0xf;
uint32_t authamd_scrub_rate_l2cache = 0xe;
uint32_t authamd_scrub_rate_l3cache = 0xd;
static uint32_t
authamd_scrubrate(uint32_t osrate, uint32_t biosrate, const char *varnm)
{
uint32_t rate;
if (osrate > AMD_NB_SCRUBCTL_RATE_MAX) {
cmn_err(CE_WARN, "%s is too large, resetting to 0x%x\n",
varnm, AMD_NB_SCRUBCTL_RATE_MAX);
osrate = AMD_NB_SCRUBCTL_RATE_MAX;
}
switch (authamd_scrub_policy) {
case AUTHAMD_SCRUB_FIXED:
rate = osrate;
break;
default:
cmn_err(CE_WARN, "Unknown authamd_scrub_policy %d - "
"using default policy of AUTHAMD_SCRUB_MAX",
authamd_scrub_policy);
case AUTHAMD_SCRUB_MAX:
if (osrate != 0 && biosrate != 0)
rate = MIN(osrate, biosrate);
else
rate = osrate ? osrate : biosrate;
}
return (rate);
}
void
authamd_mca_init(cmi_hdl_t hdl, int nbanks)
{
authamd_data_t *authamd = cms_hdl_getcmsdata(hdl);
x86_chiprev_t rev = authamd->amd_shared->ans_rev;
uint_t procnodeid = authamd->amd_shared->ans_procnodeid;
if (AUTHAMD_HAS_ONLINESPARECTL(rev) &&
authamd_chip_once(authamd, AUTHAMD_CFGONCE_ONLNSPRCFG)) {
authamd_clear_ecccnt(authamd, B_TRUE);
}
if (AUTHAMD_NBMISC_NUM(rev) &&
authamd_chip_once(authamd, AUTHAMD_CFGONCE_NBTHRESH)) {
union mcmsr_nbmisc nbm;
int i;
authamd_bankstatus_prewrite(hdl, authamd);
for (i = 0; i < AUTHAMD_NBMISC_NUM(rev); i++) {
if (cmi_hdl_rdmsr(hdl, MC_MSR_NB_MISC(i),
(uint64_t *)&nbm) != CMI_SUCCESS)
continue;
if (chiprev_at_least(rev,
X86_CHIPREV_AMD_LEGACY_F_REV_F) &&
MCMSR_FIELD_F_revFG(&nbm, mcmisc_Valid) &&
MCMSR_FIELD_F_revFG(&nbm, mcmisc_CntP)) {
MCMSR_FIELD_F_revFG(&nbm, mcmisc_IntType) = 0;
} else if (chiprev_at_least(rev,
X86_CHIPREV_AMD_LEGACY_10_REV_A) &&
MCMSR_FIELD_10_revAB(&nbm, mcmisc_Valid) &&
MCMSR_FIELD_10_revAB(&nbm, mcmisc_CntP)) {
MCMSR_FIELD_10_revAB(&nbm, mcmisc_IntType) = 0;
}
(void) cmi_hdl_wrmsr(hdl, MC_MSR_NB_MISC(i),
MCMSR_VAL(&nbm));
}
authamd_bankstatus_postwrite(hdl, authamd);
}
if (AUTHAMD_DO_NBMCACFG(rev) &&
authamd_chip_once(authamd, AUTHAMD_CFGONCE_NBMCACFG)) {
uint32_t val = authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_NBCFG);
switch (authamd_nb_watchdog_policy) {
case AUTHAMD_NB_WDOG_LEAVEALONE:
break;
case AUTHAMD_NB_WDOG_DISABLE:
val &= ~(AMD_NB_CFG_WDOGTMRBASESEL_MASK |
AMD_NB_CFG_WDOGTMRCNTSEL_MASK);
val |= AMD_NB_CFG_WDOGTMRDIS;
break;
default:
cmn_err(CE_NOTE, "authamd_nb_watchdog_policy=%d "
"unrecognised, using default policy",
authamd_nb_watchdog_policy);
case AUTHAMD_NB_WDOG_ENABLE_IF_DISABLED:
if (!(val & AMD_NB_CFG_WDOGTMRDIS))
break;
case AUTHAMD_NB_WDOG_ENABLE_FORCE_RATE:
val &= ~(AMD_NB_CFG_WDOGTMRBASESEL_MASK |
AMD_NB_CFG_WDOGTMRCNTSEL_MASK |
AMD_NB_CFG_WDOGTMRDIS);
val |= authamd_nb_mcacfg_wdog;
break;
}
if (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A))
authamd_nb_mcacfg_add &= ~AMD_NB_CFG_CPUECCERREN;
val &= ~authamd_nb_mcacfg_remove;
val |= authamd_nb_mcacfg_add;
authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_NBCFG, val);
}
if (AUTHAMD_HAS_CHIPSCRUB(rev) &&
authamd_scrub_policy != AUTHAMD_SCRUB_BIOSDEFAULT &&
authamd_chip_once(authamd, AUTHAMD_CFGONCE_CACHESCRUB)) {
uint32_t val = authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_SCRUBCTL);
int l3cap = 0;
if (AUTHAMD_L3CAPABLE(rev)) {
l3cap = (authamd_pcicfg_read(procnodeid,
MC_FUNC_MISCCTL, MC_CTL_REG_NBCAP) &
MC_NBCAP_L3CAPABLE) != 0;
}
authamd_scrub_rate_dcache =
authamd_scrubrate(authamd_scrub_rate_dcache,
(val & AMD_NB_SCRUBCTL_DC_MASK) >> AMD_NB_SCRUBCTL_DC_SHIFT,
"authamd_scrub_rate_dcache");
authamd_scrub_rate_l2cache =
authamd_scrubrate(authamd_scrub_rate_l2cache,
(val & AMD_NB_SCRUBCTL_L2_MASK) >> AMD_NB_SCRUBCTL_L2_SHIFT,
"authamd_scrub_rate_l2cache");
authamd_scrub_rate_l3cache = l3cap ?
authamd_scrubrate(authamd_scrub_rate_l3cache,
(val & AMD_NB_SCRUBCTL_L3_MASK) >> AMD_NB_SCRUBCTL_L3_SHIFT,
"authamd_scrub_rate_l3cache") : 0;
val = AMD_NB_MKSCRUBCTL(authamd_scrub_rate_l3cache,
authamd_scrub_rate_dcache, authamd_scrub_rate_l2cache,
val & AMD_NB_SCRUBCTL_DRAM_MASK);
authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_SCRUBCTL, val);
}
if (authamd_chip_once(authamd, AUTHAMD_CFGONCE_ECCSYMSZ)) {
authamd->amd_shared->ans_eccsymsz = "C4";
if (AUTHAMD_SUPPORTS_X8ECC(rev) &&
(authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL,
MC_CTL_REG_EXTNBCFG) & MC_EXTNBCFG_ECCSYMSZ))
authamd->amd_shared->ans_eccsymsz = "C8";
}
}
uint64_t
authamd_poll_ownermask(cmi_hdl_t hdl, hrtime_t pintvl)
{
authamd_data_t *authamd = cms_hdl_getcmsdata(hdl);
struct authamd_nodeshared *ansp = authamd->amd_shared;
hrtime_t now = gethrtime_waitfree();
hrtime_t last = ansp->ans_poll_timestamp;
int dopoll = 0;
if (now - last > 2 * pintvl || last == 0) {
ansp->ans_pollowner = hdl;
dopoll = 1;
} else if (ansp->ans_pollowner == hdl) {
dopoll = 1;
}
if (dopoll)
ansp->ans_poll_timestamp = now;
return (dopoll ? -1ULL : ~(1 << AMD_MCA_BANK_NB));
}
void
authamd_bank_logout(cmi_hdl_t hdl, int bank, uint64_t status,
uint64_t addr, uint64_t misc, void *mslogout)
{
authamd_data_t *authamd = cms_hdl_getcmsdata(hdl);
struct authamd_logout *msl = mslogout;
x86_chiprev_t rev = authamd->amd_shared->ans_rev;
if (msl == NULL)
return;
if (AUTHAMD_MEMECC_RECOGNISED(rev) &&
AUTHAMD_IS_MEMECCERR(bank, status) &&
AUTHAMD_HAS_ONLINESPARECTL(rev)) {
if (authamd_read_ecccnt(authamd, msl))
authamd_clear_ecccnt(authamd, B_FALSE);
}
}
int authamd_forgive_uc = 0;
int authamd_forgive_pcc = 0;
int authamd_fake_poison = 0;
uint32_t
authamd_error_action(cmi_hdl_t hdl, int ismc, int bank,
uint64_t status, uint64_t addr, uint64_t misc, void *mslogout)
{
authamd_error_disp_t *disp;
uint32_t rv = 0;
if (authamd_forgive_uc)
rv |= CMS_ERRSCOPE_CLEARED_UC;
if (authamd_forgive_pcc)
rv |= CMS_ERRSCOPE_CURCONTEXT_OK;
if (authamd_fake_poison && status & MSR_MC_STATUS_UC)
rv |= CMS_ERRSCOPE_POISONED;
if (rv)
return (rv);
disp = authamd_disp_match(hdl, ismc, bank, status, addr, misc,
mslogout);
if (disp == &authamd_gart_disp) {
return (CMS_ERRSCOPE_IGNORE_ERR);
}
return (rv);
}
cms_cookie_t
authamd_disp_match(cmi_hdl_t hdl, int ismc, int bank, uint64_t status,
uint64_t addr, uint64_t misc, void *mslogout)
{
authamd_data_t *authamd = cms_hdl_getcmsdata(hdl);
uint16_t exterrcode = AMD_EXT_ERRCODE(status);
x86_chiprev_t rev = authamd->amd_shared->ans_rev;
if (AUTHAMD_MEMECC_RECOGNISED(rev) &&
AUTHAMD_IS_MEMECCERR(bank, status)) {
if (status & AMD_BANK_STAT_CECC) {
return (exterrcode == 0 ? &authamd_memce_disp :
&authamd_ckmemce_disp);
} else if (status & AMD_BANK_STAT_UECC) {
return (exterrcode == 0 ? &authamd_memue_disp :
&authamd_ckmemue_disp);
}
}
if (AUTHAMD_NOGARTTBLWLK_MC(rev) && AUTHAMD_IS_GARTERR(bank, status))
return (&authamd_gart_disp);
return (NULL);
}
void
authamd_ereport_class(cmi_hdl_t hdl, cms_cookie_t mscookie,
const char **cpuclsp, const char **leafclsp)
{
const authamd_error_disp_t *aed = mscookie;
if (aed == NULL)
return;
if (aed->aad_subclass != NULL)
*cpuclsp = aed->aad_subclass;
if (aed->aad_leafclass != NULL)
*leafclsp = aed->aad_leafclass;
}
static void
authamd_ereport_add_resource(cmi_hdl_t hdl, authamd_data_t *authamd,
nvlist_t *ereport, nv_alloc_t *nva, void *mslogout)
{
nvlist_t *elems[AUTHAMD_DRAM_NCHANNEL * AUTHAMD_DRAM_NCS];
uint8_t counts[AUTHAMD_DRAM_NCHANNEL * AUTHAMD_DRAM_NCS];
authamd_logout_t *msl;
nvlist_t *nvl;
int nelems = 0;
int i, chan, cs, mc;
nvlist_t *board_list = NULL;
if ((msl = mslogout) == NULL)
return;
mc = authamd->amd_shared->ans_procnodeid %
cpuid_get_procnodes_per_pkg(CPU);
for (chan = 0; chan < AUTHAMD_DRAM_NCHANNEL; chan++) {
for (cs = 0; cs < AUTHAMD_DRAM_NCS; cs++) {
if (msl->aal_eccerrcnt[chan][cs] == 0)
continue;
if ((nvl = fm_nvlist_create(nva)) == NULL)
continue;
elems[nelems] = nvl;
counts[nelems++] = msl->aal_eccerrcnt[chan][cs];
if (!x86gentopo_legacy) {
board_list = cmi_hdl_smb_bboard(hdl);
if (board_list == NULL)
continue;
fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION,
NULL, NULL, board_list, 4,
"chip", cmi_hdl_smb_chipid(hdl),
"memory-controller", 0,
"dram-channel", chan,
"chip-select", cs);
} else {
fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION,
NULL, NULL, 5,
"motherboard", 0,
"chip", authamd->amd_shared->ans_chipid,
"memory-controller", mc,
"dram-channel", chan,
"chip-select", cs);
}
}
}
if (nelems == 0)
return;
fm_payload_set(ereport, FM_EREPORT_GENAMD_PAYLOAD_NAME_RESOURCE,
DATA_TYPE_NVLIST_ARRAY, nelems, elems,
NULL);
fm_payload_set(ereport, FM_EREPORT_GENAMD_PAYLOAD_NAME_RESOURCECNT,
DATA_TYPE_UINT8_ARRAY, nelems, &counts[0],
NULL);
for (i = 0; i < nelems; i++)
fm_nvlist_destroy(elems[i], nva ? FM_NVA_RETAIN : FM_NVA_FREE);
}
void
authamd_ereport_add_logout(cmi_hdl_t hdl, nvlist_t *ereport, nv_alloc_t *nva,
int bank, uint64_t status, uint64_t addr, uint64_t misc,
void *mslogout, cms_cookie_t mscookie)
{
authamd_data_t *authamd = cms_hdl_getcmsdata(hdl);
const authamd_error_disp_t *aed = mscookie;
uint64_t members;
if (aed == NULL)
return;
members = aed->aad_ereport_members;
if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_SYND) {
fm_payload_set(ereport, FM_EREPORT_GENAMD_PAYLOAD_NAME_SYND,
DATA_TYPE_UINT16, (uint16_t)AMD_BANK_SYND(status),
NULL);
if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_SYNDTYPE) {
fm_payload_set(ereport,
FM_EREPORT_GENAMD_PAYLOAD_NAME_SYNDTYPE,
DATA_TYPE_STRING, "E",
NULL);
}
}
if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_CKSYND) {
fm_payload_set(ereport, FM_EREPORT_GENAMD_PAYLOAD_NAME_CKSYND,
DATA_TYPE_UINT16, (uint16_t)AMD_NB_STAT_CKSYND(status),
NULL);
if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_SYNDTYPE) {
fm_payload_set(ereport,
FM_EREPORT_GENAMD_PAYLOAD_NAME_SYNDTYPE,
DATA_TYPE_STRING, authamd->amd_shared->ans_eccsymsz,
NULL);
}
}
if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_RESOURCE &&
status & MSR_MC_STATUS_ADDRV) {
authamd_ereport_add_resource(hdl, authamd, ereport, nva,
mslogout);
}
}
cms_errno_t
authamd_msrinject(cmi_hdl_t hdl, uint_t msr, uint64_t val)
{
authamd_data_t *authamd = cms_hdl_getcmsdata(hdl);
cms_errno_t rv = CMSERR_BADMSRWRITE;
authamd_bankstatus_prewrite(hdl, authamd);
if (cmi_hdl_wrmsr(hdl, msr, val) == CMI_SUCCESS)
rv = CMS_SUCCESS;
authamd_bankstatus_postwrite(hdl, authamd);
return (rv);
}
cms_api_ver_t _cms_api_version = CMS_API_VERSION_2;
const cms_ops_t _cms_ops = {
authamd_init,
NULL,
NULL,
authamd_logout_size,
authamd_mcgctl_val,
authamd_bankctl_skipinit,
authamd_bankctl_val,
NULL,
NULL,
authamd_mca_init,
authamd_poll_ownermask,
authamd_bank_logout,
authamd_error_action,
authamd_disp_match,
authamd_ereport_class,
NULL,
NULL,
authamd_ereport_add_logout,
authamd_msrinject,
NULL,
};
static struct modlcpu modlcpu = {
&mod_cpuops,
"Generic AMD model-specific MCA"
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modlcpu,
NULL
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}