#include <sys/types.h>
#include <sys/atomic.h>
#include <sys/cyclic.h>
#include <sys/x86_archext.h>
#include <sys/mca_x86.h>
#include "gcpu.h"
hrtime_t gcpu_mca_poll_interval = NANOSEC * 10ULL;
static cyclic_id_t gcpu_mca_poll_cycid;
static volatile uint_t gcpu_mca_poll_inits;
extern int gcpu_poll_trace_always;
extern uint_t gcpu_poll_trace_nent;
static kmutex_t mch_poll_lock;
static hrtime_t mch_poll_timestamp;
static cmi_hdl_t mch_poll_owner;
static int
mch_pollowner(cmi_hdl_t hdl)
{
hrtime_t now = gethrtime_waitfree();
int dopoll = 0;
mutex_enter(&mch_poll_lock);
if (now - mch_poll_timestamp > 2 * gcpu_mca_poll_interval ||
mch_poll_timestamp == 0) {
mch_poll_owner = hdl;
dopoll = 1;
} else if (mch_poll_owner == hdl) {
dopoll = 1;
}
if (dopoll)
mch_poll_timestamp = now;
mutex_exit(&mch_poll_lock);
return (dopoll);
}
static void
gcpu_ntv_mca_poll(cmi_hdl_t hdl, int what)
{
gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
gcpu_mca_t *mca = &gcpu->gcpu_mca;
gcpu_mce_status_t mce;
int willpanic;
uint64_t bankmask;
ASSERT(MUTEX_HELD(&gcpu->gcpu_shared->gcpus_poll_lock));
if ((mca->gcpu_mca_flags & GCPU_MCA_F_CMCI_ENABLE) != 0 &&
(!mca->gcpu_mca_first_poll_cmci_enabled)) {
int i;
uint64_t ctl2;
for (i = 0; i < mca->gcpu_mca_nbanks; i++) {
if (mca->gcpu_bank_cmci[i].cmci_cap) {
(void) cmi_hdl_rdmsr(hdl, IA32_MSR_MC_CTL2(i),
&ctl2);
ctl2 |= MSR_MC_CTL2_EN;
(void) cmi_hdl_wrmsr(hdl, IA32_MSR_MC_CTL2(i),
ctl2);
mca->gcpu_bank_cmci[i].cmci_enabled = 1;
}
}
mca->gcpu_mca_first_poll_cmci_enabled = 1;
}
if (mca->gcpu_mca_flags & GCPU_MCA_F_UNFAULTING) {
int i;
mca->gcpu_mca_flags &= ~GCPU_MCA_F_UNFAULTING;
gcpu_poll_trace(&gcpu->gcpu_mca.gcpu_polltrace,
GCPU_MPT_WHAT_UNFAULTING, 0);
if (what == GCPU_MPT_WHAT_CYC_ERR) {
for (i = 0; i < mca->gcpu_mca_nbanks; i++) {
(void) cmi_hdl_wrmsr(hdl,
IA32_MSR_MC(i, STATUS), 0ULL);
}
return;
}
}
if (what == GCPU_MPT_WHAT_CMCI_ERR) {
bankmask = -1ULL;
} else {
bankmask = cms_poll_ownermask(hdl, gcpu_mca_poll_interval);
}
gcpu_mca_logout(hdl, NULL, bankmask, &mce, B_TRUE, what);
if (mce.mce_nerr != 0)
gcpu_poll_trace(&gcpu->gcpu_mca.gcpu_polltrace, what,
mce.mce_nerr);
mca->gcpu_mca_lastpoll = gethrtime_waitfree();
willpanic = mce.mce_disp & CMI_ERRDISP_FORCEFATAL && cmi_panic_on_ue();
if (what != GCPU_MPT_WHAT_CMCI_ERR) {
if (mch_pollowner(hdl))
cmi_mc_logout(hdl, 0, willpanic);
}
if (willpanic) {
#ifdef DEBUG
cmn_err(CE_WARN, "MCA Poll: %u errors, disp=0x%llx, "
"%u PCC (%u ok), "
"%u UC (%u 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);
#endif
fm_panic("Unrecoverable Machine-Check Exception (Polled)");
}
}
static void
gcpu_ntv_mca_poll_wrapper(cmi_hdl_t hdl, int what)
{
gcpu_data_t *gcpu;
if (hdl == NULL || (gcpu = cmi_hdl_getcmidata(hdl)) == NULL ||
gcpu->gcpu_mca.gcpu_mca_lgsz == 0)
return;
kpreempt_disable();
mutex_enter(&gcpu->gcpu_shared->gcpus_poll_lock);
gcpu_ntv_mca_poll(hdl, what);
mutex_exit(&gcpu->gcpu_shared->gcpus_poll_lock);
kpreempt_enable();
}
static void
gcpu_ntv_mca_poll_cyclic(void *arg)
{
gcpu_ntv_mca_poll_wrapper((cmi_hdl_t)arg, GCPU_MPT_WHAT_CYC_ERR);
}
static void
gcpu_ntv_mca_poll_online(void *arg, cpu_t *cp, cyc_handler_t *cyh,
cyc_time_t *cyt)
{
cmi_hdl_t hdl;
hdl = cmi_hdl_lookup(CMI_HDL_NATIVE, cmi_ntv_hwchipid(cp),
cmi_ntv_hwcoreid(cp), cmi_ntv_hwstrandid(cp));
cyt->cyt_when = 0;
cyt->cyt_interval = gcpu_mca_poll_interval;
cyh->cyh_func = gcpu_ntv_mca_poll_cyclic;
cyh->cyh_arg = (void *)hdl;
cyh->cyh_level = CY_LOW_LEVEL;
}
static void
gcpu_ntv_mca_poll_offline(void *arg, cpu_t *cpu, void *cyh_arg)
{
cmi_hdl_t hdl = (cmi_hdl_t)cyh_arg;
if (hdl != NULL)
cmi_hdl_rele(hdl);
}
static void
gcpu_ntv_mca_poll_start(void)
{
cyc_omni_handler_t cyo;
if (gcpu_mca_poll_interval == 0 || gcpu_mca_poll_inits == 0)
return;
cyo.cyo_online = gcpu_ntv_mca_poll_online;
cyo.cyo_offline = gcpu_ntv_mca_poll_offline;
cyo.cyo_arg = NULL;
mutex_enter(&cpu_lock);
gcpu_mca_poll_cycid = cyclic_add_omni(&cyo);
mutex_exit(&cpu_lock);
}
void
gcpu_mca_poll_init(cmi_hdl_t hdl)
{
gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
gcpu_poll_trace_ctl_t *ptc = &gcpu->gcpu_mca.gcpu_polltrace;
ASSERT(cmi_hdl_class(hdl) == CMI_HDL_NATIVE);
gcpu_poll_trace_init(ptc);
atomic_inc_uint(&gcpu_mca_poll_inits);
}
void
gcpu_mca_poll_fini(cmi_hdl_t hdl)
{
gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
gcpu_poll_trace_ctl_t *ptc = &gcpu->gcpu_mca.gcpu_polltrace;
ASSERT(cmi_hdl_class(hdl) == CMI_HDL_NATIVE);
if (gcpu_poll_trace_always && (ptc->mptc_tbufs != NULL)) {
kmem_free(ptc->mptc_tbufs, sizeof (gcpu_poll_trace_t) *
gcpu_poll_trace_nent);
}
atomic_dec_uint(&gcpu_mca_poll_inits);
}
void
gcpu_mca_poll_start(cmi_hdl_t hdl)
{
ASSERT(cmi_hdl_class(hdl) == CMI_HDL_NATIVE);
gcpu_ntv_mca_poll_start();
}
void
gcpu_hdl_poke(cmi_hdl_t hdl)
{
ASSERT(cmi_hdl_class(hdl) == CMI_HDL_NATIVE);
gcpu_ntv_mca_poll_wrapper(hdl, GCPU_MPT_WHAT_POKE_ERR);
}
void
gcpu_cmci_trap(cmi_hdl_t hdl)
{
gcpu_ntv_mca_poll_wrapper(hdl, GCPU_MPT_WHAT_CMCI_ERR);
}