/*
* 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.
*/
/*
* Intel model-specific support. Right now all this conists of is
* to modify the ereport subclass to produce different ereport classes
* so that we can have different diagnosis rules and corresponding faults.
*/
#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/modctl.h>
#include <sys/mca_x86.h>
#include <sys/cpu_module_ms_impl.h>
#include <sys/mc_intel.h>
#include <sys/pci_cfgspace.h>
#include <sys/fm/protocol.h>
#include <sys/fm/util.h>
#include <sys/fm/smb/fmsmb.h>
extern int x86gentopo_legacy;
int gintel_ms_support_disable = 0;
int gintel_error_action_return = 0;
int gintel_ms_unconstrained = 0;
int quickpath;
int max_bus_number = 0xff;
#define ERR_COUNTER_INDEX 2
#define MAX_CPU_NODES 2
#define N_MC_COR_ECC_CNT 6
uint32_t err_counter_array[MAX_CPU_NODES][ERR_COUNTER_INDEX][N_MC_COR_ECC_CNT];
uint8_t err_counter_index[MAX_CPU_NODES];
#define MAX_BUS_NUMBER max_bus_number
#define SOCKET_BUS(cpu) (MAX_BUS_NUMBER - (cpu))
#define MC_COR_ECC_CNT(chipid, reg) (*pci_getl_func)(SOCKET_BUS(chipid), \
NEHALEM_EP_MEMORY_CONTROLLER_DEV, NEHALEM_EP_MEMORY_CONTROLLER_FUNC, \
0x80 + (reg) * 4)
#define MSCOD_MEM_ECC_READ 0x1
#define MSCOD_MEM_ECC_SCRUB 0x2
#define MSCOD_MEM_WR_PARITY 0x4
#define MSCOD_MEM_REDUNDANT_MEM 0x8
#define MSCOD_MEM_SPARE_MEM 0x10
#define MSCOD_MEM_ILLEGAL_ADDR 0x20
#define MSCOD_MEM_BAD_ID 0x40
#define MSCOD_MEM_ADDR_PARITY 0x80
#define MSCOD_MEM_BYTE_PARITY 0x100
#define GINTEL_ERROR_MEM 0x1000
#define GINTEL_ERROR_QUICKPATH 0x2000
#define GINTEL_ERROR_UNKNOWN 0x4000
#define GINTEL_ERR_SPARE_MEM (GINTEL_ERROR_MEM | 1)
#define GINTEL_ERR_MEM_UE (GINTEL_ERROR_MEM | 2)
#define GINTEL_ERR_MEM_CE (GINTEL_ERROR_MEM | 3)
#define GINTEL_ERR_MEM_PARITY (GINTEL_ERROR_MEM | 4)
#define GINTEL_ERR_MEM_ADDR_PARITY (GINTEL_ERROR_MEM | 5)
#define GINTEL_ERR_MEM_REDUNDANT (GINTEL_ERROR_MEM | 6)
#define GINTEL_ERR_MEM_BAD_ADDR (GINTEL_ERROR_MEM | 7)
#define GINTEL_ERR_MEM_BAD_ID (GINTEL_ERROR_MEM | 8)
#define GINTEL_ERR_MEM_UNKNOWN (GINTEL_ERROR_MEM | 0xfff)
#define MSR_MC_MISC_MEM_CHANNEL_MASK 0x00000000000c0000ULL
#define MSR_MC_MISC_MEM_CHANNEL_SHIFT 18
#define MSR_MC_MISC_MEM_DIMM_MASK 0x0000000000030000ULL
#define MSR_MC_MISC_MEM_DIMM_SHIFT 16
#define MSR_MC_MISC_MEM_SYNDROME_MASK 0xffffffff00000000ULL
#define MSR_MC_MISC_MEM_SYNDROME_SHIFT 32
#define CPU_GENERATION_DONT_CARE 0
#define CPU_GENERATION_NEHALEM_EP 1
#define INTEL_CPU_6_ID 0x6
#define INTEL_NEHALEM_CPU_FAMILY_ID 0x6
#define INTEL_NEHALEM_CPU_MODEL_ID 0x1A
#define NEHALEM_EP_MEMORY_CONTROLLER_DEV 0x3
#define NEHALEM_EP_MEMORY_CONTROLLER_FUNC 0x2
/*ARGSUSED*/
int
gintel_init(cmi_hdl_t hdl, void **datap)
{
uint32_t nb_chipset;
if (gintel_ms_support_disable)
return (ENOTSUP);
if (!is_x86_feature(x86_featureset, X86FSET_MCA))
return (ENOTSUP);
nb_chipset = (*pci_getl_func)(0, 0, 0, 0x0);
switch (nb_chipset) {
case INTEL_NB_7300:
case INTEL_NB_5000P:
case INTEL_NB_5000X:
case INTEL_NB_5000V:
case INTEL_NB_5000Z:
case INTEL_NB_5400:
case INTEL_NB_5400A:
case INTEL_NB_5400B:
if (!gintel_ms_unconstrained)
gintel_error_action_return |= CMS_ERRSCOPE_POISONED;
break;
case INTEL_QP_IO:
case INTEL_QP_WP:
case INTEL_QP_36D:
case INTEL_QP_24D:
case INTEL_QP_U1:
case INTEL_QP_U2:
case INTEL_QP_U3:
case INTEL_QP_U4:
case INTEL_QP_JF:
case INTEL_QP_JF0:
case INTEL_QP_JF1:
case INTEL_QP_JF2:
case INTEL_QP_JF3:
case INTEL_QP_JF4:
case INTEL_QP_JF5:
case INTEL_QP_JF6:
case INTEL_QP_JF7:
case INTEL_QP_JF8:
case INTEL_QP_JF9:
case INTEL_QP_JFa:
case INTEL_QP_JFb:
case INTEL_QP_JFc:
case INTEL_QP_JFd:
case INTEL_QP_JFe:
case INTEL_QP_JFf:
quickpath = 1;
break;
default:
break;
}
return (0);
}
/*ARGSUSED*/
uint32_t
gintel_error_action(cmi_hdl_t hdl, int ismc, int bank,
uint64_t status, uint64_t addr, uint64_t misc, void *mslogout)
{
uint32_t rc;
if (ismc == 0 && bank == 0 &&
cmi_hdl_family(hdl) == INTEL_CPU_6_ID &&
cmi_hdl_model(hdl) < INTEL_NEHALEM_CPU_MODEL_ID &&
MCAX86_ERRCODE_ISBUS_INTERCONNECT(MCAX86_ERRCODE(status)) &&
MCAX86_MSERRCODE(status) == 0) {
rc = CMS_ERRSCOPE_CURCONTEXT_OK | CMS_ERRSCOPE_CLEARED_UC;
} else if ((status & MSR_MC_STATUS_PCC) == 0) {
rc = gintel_error_action_return;
} else {
rc = gintel_error_action_return & ~CMS_ERRSCOPE_POISONED;
}
return (rc);
}
/*ARGSUSED*/
cms_cookie_t
gintel_disp_match(cmi_hdl_t hdl, int ismc, int bank, uint64_t status,
uint64_t addr, uint64_t misc, void *mslogout)
{
cms_cookie_t rt = (cms_cookie_t)NULL;
uint16_t mcacode = MCAX86_ERRCODE(status);
uint16_t mscode = MCAX86_MSERRCODE(status);
if (MCAX86_ERRCODE_ISMEMORY_CONTROLLER(mcacode)) {
/*
* memory controller errors
*/
if (mscode & MSCOD_MEM_SPARE_MEM) {
rt = (cms_cookie_t)GINTEL_ERR_SPARE_MEM;
} else if (mscode & (MSCOD_MEM_ECC_READ |
MSCOD_MEM_ECC_SCRUB)) {
if (status & MSR_MC_STATUS_UC)
rt = (cms_cookie_t)GINTEL_ERR_MEM_UE;
else
rt = (cms_cookie_t)GINTEL_ERR_MEM_CE;
} else if (mscode & (MSCOD_MEM_WR_PARITY |
MSCOD_MEM_BYTE_PARITY)) {
rt = (cms_cookie_t)GINTEL_ERR_MEM_PARITY;
} else if (mscode & MSCOD_MEM_ADDR_PARITY) {
rt = (cms_cookie_t)GINTEL_ERR_MEM_ADDR_PARITY;
} else if (mscode & MSCOD_MEM_REDUNDANT_MEM) {
rt = (cms_cookie_t)GINTEL_ERR_MEM_REDUNDANT;
} else if (mscode & MSCOD_MEM_ILLEGAL_ADDR) {
rt = (cms_cookie_t)GINTEL_ERR_MEM_BAD_ADDR;
} else if (mscode & MSCOD_MEM_BAD_ID) {
rt = (cms_cookie_t)GINTEL_ERR_MEM_BAD_ID;
} else {
rt = (cms_cookie_t)GINTEL_ERR_MEM_UNKNOWN;
}
} else if (quickpath &&
MCAX86_ERRCODE_ISBUS_INTERCONNECT(MCAX86_ERRCODE(status))) {
rt = (cms_cookie_t)GINTEL_ERROR_QUICKPATH;
} else if (ismc == 0 && bank == 0 &&
cmi_hdl_family(hdl) == INTEL_CPU_6_ID &&
cmi_hdl_model(hdl) < INTEL_NEHALEM_CPU_MODEL_ID &&
MCAX86_ERRCODE_ISBUS_INTERCONNECT(MCAX86_ERRCODE(status)) &&
MCAX86_MSERRCODE(status) == 0) {
rt = (cms_cookie_t)GINTEL_ERROR_UNKNOWN;
}
return (rt);
}
/*ARGSUSED*/
void
gintel_ereport_class(cmi_hdl_t hdl, cms_cookie_t mscookie,
const char **cpuclsp, const char **leafclsp)
{
*cpuclsp = FM_EREPORT_CPU_INTEL;
switch ((uintptr_t)mscookie) {
case GINTEL_ERROR_QUICKPATH:
*leafclsp = "quickpath.interconnect";
break;
case GINTEL_ERR_SPARE_MEM:
*leafclsp = "quickpath.mem_spare";
break;
case GINTEL_ERR_MEM_UE:
*leafclsp = "quickpath.mem_ue";
break;
case GINTEL_ERR_MEM_CE:
*leafclsp = "quickpath.mem_ce";
break;
case GINTEL_ERR_MEM_PARITY:
*leafclsp = "quickpath.mem_parity";
break;
case GINTEL_ERR_MEM_ADDR_PARITY:
*leafclsp = "quickpath.mem_addr_parity";
break;
case GINTEL_ERR_MEM_REDUNDANT:
*leafclsp = "quickpath.mem_redundant";
break;
case GINTEL_ERR_MEM_BAD_ADDR:
*leafclsp = "quickpath.mem_bad_addr";
break;
case GINTEL_ERR_MEM_BAD_ID:
*leafclsp = "quickpath.mem_bad_id";
break;
case GINTEL_ERR_MEM_UNKNOWN:
*leafclsp = "quickpath.mem_unknown";
break;
case GINTEL_ERROR_UNKNOWN:
*leafclsp = "unknown";
break;
}
}
static nvlist_t *
gintel_gentopo_ereport_detector(cmi_hdl_t hdl, cms_cookie_t mscookie,
nv_alloc_t *nva)
{
nvlist_t *nvl = (nvlist_t *)NULL;
nvlist_t *board_list = (nvlist_t *)NULL;
if (mscookie) {
board_list = cmi_hdl_smb_bboard(hdl);
if (board_list == NULL)
return (NULL);
if ((nvl = fm_nvlist_create(nva)) == NULL)
return (NULL);
if ((uintptr_t)mscookie & GINTEL_ERROR_QUICKPATH) {
fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION,
NULL, NULL, board_list, 1,
"chip", cmi_hdl_smb_chipid(hdl));
} else {
fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION,
NULL, NULL, board_list, 2,
"chip", cmi_hdl_smb_chipid(hdl),
"memory-controller", 0);
}
}
return (nvl);
}
/*ARGSUSED*/
nvlist_t *
gintel_ereport_detector(cmi_hdl_t hdl, int bankno, cms_cookie_t mscookie,
nv_alloc_t *nva)
{
nvlist_t *nvl = (nvlist_t *)NULL;
if (!x86gentopo_legacy) {
nvl = gintel_gentopo_ereport_detector(hdl, mscookie, nva);
return (nvl);
}
if (mscookie) {
if ((nvl = fm_nvlist_create(nva)) == NULL)
return (NULL);
if (((uintptr_t)mscookie & GINTEL_ERROR_QUICKPATH) ||
((uintptr_t)mscookie & GINTEL_ERROR_UNKNOWN)) {
fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, NULL, NULL, 2,
"motherboard", 0,
"chip", cmi_hdl_chipid(hdl));
} else {
fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, NULL, NULL, 3,
"motherboard", 0,
"chip", cmi_hdl_chipid(hdl),
"memory-controller", 0);
}
}
return (nvl);
}
static nvlist_t *
gintel_gentopo_ereport_create_resource_elem(cmi_hdl_t hdl, nv_alloc_t *nva,
mc_unum_t *unump)
{
nvlist_t *nvl, *snvl;
nvlist_t *board_list = NULL;
board_list = cmi_hdl_smb_bboard(hdl);
if (board_list == NULL) {
return (NULL);
}
if ((nvl = fm_nvlist_create(nva)) == NULL) /* freed by caller */
return (NULL);
if ((snvl = fm_nvlist_create(nva)) == NULL) {
fm_nvlist_destroy(nvl, nva ? FM_NVA_RETAIN : FM_NVA_FREE);
return (NULL);
}
(void) nvlist_add_uint64(snvl, FM_FMRI_HC_SPECIFIC_OFFSET,
unump->unum_offset);
if (unump->unum_chan == -1) {
fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION, NULL, snvl,
board_list, 2,
"chip", cmi_hdl_smb_chipid(hdl),
"memory-controller", unump->unum_mc);
} else if (unump->unum_cs == -1) {
fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION, NULL, snvl,
board_list, 3,
"chip", cmi_hdl_smb_chipid(hdl),
"memory-controller", unump->unum_mc,
"dram-channel", unump->unum_chan);
} else if (unump->unum_rank == -1) {
fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION, NULL, snvl,
board_list, 4,
"chip", cmi_hdl_smb_chipid(hdl),
"memory-controller", unump->unum_mc,
"dram-channel", unump->unum_chan,
"dimm", unump->unum_cs);
} else {
fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION, NULL, snvl,
board_list, 5,
"chip", cmi_hdl_smb_chipid(hdl),
"memory-controller", unump->unum_mc,
"dram-channel", unump->unum_chan,
"dimm", unump->unum_cs,
"rank", unump->unum_rank);
}
fm_nvlist_destroy(snvl, nva ? FM_NVA_RETAIN : FM_NVA_FREE);
return (nvl);
}
static nvlist_t *
gintel_ereport_create_resource_elem(nv_alloc_t *nva, mc_unum_t *unump)
{
nvlist_t *nvl, *snvl;
if ((nvl = fm_nvlist_create(nva)) == NULL) /* freed by caller */
return (NULL);
if ((snvl = fm_nvlist_create(nva)) == NULL) {
fm_nvlist_destroy(nvl, nva ? FM_NVA_RETAIN : FM_NVA_FREE);
return (NULL);
}
(void) nvlist_add_uint64(snvl, FM_FMRI_HC_SPECIFIC_OFFSET,
unump->unum_offset);
if (unump->unum_chan == -1) {
fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, NULL, snvl, 3,
"motherboard", unump->unum_board,
"chip", unump->unum_chip,
"memory-controller", unump->unum_mc);
} else if (unump->unum_cs == -1) {
fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, NULL, snvl, 4,
"motherboard", unump->unum_board,
"chip", unump->unum_chip,
"memory-controller", unump->unum_mc,
"dram-channel", unump->unum_chan);
} else if (unump->unum_rank == -1) {
fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, NULL, snvl, 5,
"motherboard", unump->unum_board,
"chip", unump->unum_chip,
"memory-controller", unump->unum_mc,
"dram-channel", unump->unum_chan,
"dimm", unump->unum_cs);
} else {
fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, NULL, snvl, 6,
"motherboard", unump->unum_board,
"chip", unump->unum_chip,
"memory-controller", unump->unum_mc,
"dram-channel", unump->unum_chan,
"dimm", unump->unum_cs,
"rank", unump->unum_rank);
}
fm_nvlist_destroy(snvl, nva ? FM_NVA_RETAIN : FM_NVA_FREE);
return (nvl);
}
static void
nehalem_ep_ereport_add_memory_error_counter(uint_t chipid,
uint32_t *this_err_counter_array)
{
int index;
for (index = 0; index < N_MC_COR_ECC_CNT; index ++)
this_err_counter_array[index] = MC_COR_ECC_CNT(chipid, index);
}
static int
gintel_cpu_generation(cmi_hdl_t hdl)
{
int cpu_generation = CPU_GENERATION_DONT_CARE;
if ((cmi_hdl_family(hdl) == INTEL_NEHALEM_CPU_FAMILY_ID) &&
(cmi_hdl_model(hdl) == INTEL_NEHALEM_CPU_MODEL_ID))
cpu_generation = CPU_GENERATION_NEHALEM_EP;
return (cpu_generation);
}
/*ARGSUSED*/
void
gintel_ereport_add_logout(cmi_hdl_t hdl, nvlist_t *ereport,
nv_alloc_t *nva, int banknum, uint64_t status, uint64_t addr,
uint64_t misc, void *mslogout, cms_cookie_t mscookie)
{
mc_unum_t unum;
nvlist_t *resource;
uint32_t synd = 0;
int chan = MCAX86_ERRCODE_CCCC(status);
uint8_t last_index, this_index;
int chipid;
if (chan == 0xf)
chan = -1;
if ((uintptr_t)mscookie & GINTEL_ERROR_MEM) {
unum.unum_board = 0;
unum.unum_chip = cmi_hdl_chipid(hdl);
unum.unum_mc = 0;
unum.unum_chan = chan;
unum.unum_cs = -1;
unum.unum_rank = -1;
unum.unum_offset = -1ULL;
if (status & MSR_MC_STATUS_MISCV) {
unum.unum_chan =
(misc & MSR_MC_MISC_MEM_CHANNEL_MASK) >>
MSR_MC_MISC_MEM_CHANNEL_SHIFT;
unum.unum_cs =
(misc & MSR_MC_MISC_MEM_DIMM_MASK) >>
MSR_MC_MISC_MEM_DIMM_SHIFT;
synd = (misc & MSR_MC_MISC_MEM_SYNDROME_MASK) >>
MSR_MC_MISC_MEM_SYNDROME_SHIFT;
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_ECC_SYND,
DATA_TYPE_UINT32, synd, 0);
}
if (status & MSR_MC_STATUS_ADDRV) {
fm_payload_set(ereport, FM_FMRI_MEM_PHYSADDR,
DATA_TYPE_UINT64, addr, NULL);
(void) cmi_mc_patounum(addr, 0, 0, synd, 0, &unum);
if (unum.unum_offset != -1ULL &&
(unum.unum_offset & OFFSET_ROW_BANK_COL) != 0) {
fm_payload_set(ereport,
FM_EREPORT_PAYLOAD_NAME_BANK,
DATA_TYPE_INT32,
TCODE_OFFSET_BANK(unum.unum_offset), NULL);
fm_payload_set(ereport,
FM_EREPORT_PAYLOAD_NAME_CAS,
DATA_TYPE_INT32,
TCODE_OFFSET_CAS(unum.unum_offset), NULL);
fm_payload_set(ereport,
FM_EREPORT_PAYLOAD_NAME_RAS,
DATA_TYPE_INT32,
TCODE_OFFSET_RAS(unum.unum_offset), NULL);
}
}
if (!x86gentopo_legacy) {
resource = gintel_gentopo_ereport_create_resource_elem(
hdl, nva, &unum);
} else {
resource = gintel_ereport_create_resource_elem(nva,
&unum);
}
fm_payload_set(ereport, FM_EREPORT_PAYLOAD_NAME_RESOURCE,
DATA_TYPE_NVLIST_ARRAY, 1, &resource, NULL);
fm_nvlist_destroy(resource, nva ? FM_NVA_RETAIN:FM_NVA_FREE);
if (gintel_cpu_generation(hdl) == CPU_GENERATION_NEHALEM_EP) {
chipid = unum.unum_chip;
if (chipid < MAX_CPU_NODES) {
last_index = err_counter_index[chipid];
this_index =
(last_index + 1) % ERR_COUNTER_INDEX;
err_counter_index[chipid] = this_index;
nehalem_ep_ereport_add_memory_error_counter(
chipid,
err_counter_array[chipid][this_index]);
fm_payload_set(ereport,
FM_EREPORT_PAYLOAD_MEM_ECC_COUNTER_THIS,
DATA_TYPE_UINT32_ARRAY, N_MC_COR_ECC_CNT,
err_counter_array[chipid][this_index],
NULL);
fm_payload_set(ereport,
FM_EREPORT_PAYLOAD_MEM_ECC_COUNTER_LAST,
DATA_TYPE_UINT32_ARRAY, N_MC_COR_ECC_CNT,
err_counter_array[chipid][last_index],
NULL);
}
}
}
}
boolean_t
gintel_bankctl_skipinit(cmi_hdl_t hdl, int banknum)
{
/*
* On Intel family 6 before QuickPath we must not enable machine check
* from bank 0 detectors. bank 0 is reserved for the platform
*/
if (banknum == 0 &&
cmi_hdl_family(hdl) == INTEL_NEHALEM_CPU_FAMILY_ID &&
cmi_hdl_model(hdl) < INTEL_NEHALEM_CPU_MODEL_ID)
return (1);
else
return (0);
}
cms_api_ver_t _cms_api_version = CMS_API_VERSION_2;
const cms_ops_t _cms_ops = {
gintel_init, /* cms_init */
NULL, /* cms_post_startup */
NULL, /* cms_post_mpstartup */
NULL, /* cms_logout_size */
NULL, /* cms_mcgctl_val */
gintel_bankctl_skipinit, /* cms_bankctl_skipinit */
NULL, /* cms_bankctl_val */
NULL, /* cms_bankstatus_skipinit */
NULL, /* cms_bankstatus_val */
NULL, /* cms_mca_init */
NULL, /* cms_poll_ownermask */
NULL, /* cms_bank_logout */
gintel_error_action, /* cms_error_action */
gintel_disp_match, /* cms_disp_match */
gintel_ereport_class, /* cms_ereport_class */
gintel_ereport_detector, /* cms_ereport_detector */
NULL, /* cms_ereport_includestack */
gintel_ereport_add_logout, /* cms_ereport_add_logout */
NULL, /* cms_msrinject */
NULL, /* cms_fini */
};
static struct modlcpu modlcpu = {
&mod_cpuops,
"Generic Intel 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));
}