#include <sys/types.h>
#include <sys/machsystm.h>
#include <sys/sysmacros.h>
#include <sys/cpuvar.h>
#include <sys/async.h>
#include <sys/ontrap.h>
#include <sys/ddifm.h>
#include <sys/hypervisor_api.h>
#include <sys/errorq.h>
#include <sys/promif.h>
#include <sys/prom_plat.h>
#include <sys/x_call.h>
#include <sys/error.h>
#include <sys/fm/util.h>
#include <sys/ivintr.h>
#include <sys/archsystm.h>
#define MAX_CE_FLTS 10
#define MAX_ASYNC_FLTS 6
errorq_t *ue_queue;
errorq_t *ce_queue;
errorq_t *errh_queue;
int ce_verbose_memory = 1;
int ce_verbose_other = 1;
int ce_show_data = 0;
int ce_debug = 0;
int ue_debug = 0;
int reset_debug = 0;
int aft_verbose = 0;
int aft_panic = 0;
int aft_testfatal = 0;
int err_shutdown_triggered = 0;
uint64_t err_shutdown_inum = 0;
int printerrh = 0;
static void errh_er_print(errh_er_t *, const char *);
kmutex_t errh_print_lock;
extern kmutex_t bfd_lock;
static uint32_t rq_overflow_count = 0;
static void cpu_queue_one_event(errh_async_flt_t *);
static uint32_t count_entries_on_queue(uint64_t, uint64_t, uint32_t);
static void errh_page_retire(errh_async_flt_t *, uchar_t);
static int errh_error_protected(struct regs *, struct async_flt *, int *);
static void errh_rq_full(struct async_flt *);
static void ue_drain(void *, struct async_flt *, errorq_elem_t *);
static void ce_drain(void *, struct async_flt *, errorq_elem_t *);
static void errh_drain(void *, errh_er_t *, errorq_elem_t *);
static void errh_handle_attr(errh_async_flt_t *);
static void errh_handle_asr(errh_async_flt_t *);
static void errh_handle_sp(errh_er_t *);
static void sp_ereport_post(uint8_t);
void
process_resumable_error(struct regs *rp, uint32_t head_offset,
uint32_t tail_offset)
{
struct machcpu *mcpup;
struct async_flt *aflt;
errh_async_flt_t errh_flt;
errh_er_t *head_va;
mcpup = &(CPU->cpu_m);
while (head_offset != tail_offset) {
head_va = (errh_er_t *)(mcpup->cpu_rq_va + head_offset +
CPU_RQ_SIZE);
bzero(&errh_flt, sizeof (errh_async_flt_t));
bcopy((char *)head_va, &(errh_flt.errh_er),
sizeof (errh_er_t));
mcpup->cpu_rq_lastre = head_va;
if (printerrh)
errh_er_print(&errh_flt.errh_er, "RQ");
head_offset += Q_ENTRY_SIZE;
head_offset &= (CPU_RQ_SIZE - 1);
head_va->ehdl = 0;
switch (errh_flt.errh_er.desc) {
case ERRH_DESC_UCOR_RE:
errh_handle_attr(&errh_flt);
break;
case ERRH_DESC_WARN_RE:
if (!err_shutdown_triggered) {
setsoftint(err_shutdown_inum);
++err_shutdown_triggered;
}
continue;
case ERRH_DESC_SP:
errorq_dispatch(errh_queue, &errh_flt.errh_er,
sizeof (errh_er_t), ERRORQ_ASYNC);
continue;
default:
cmn_err(CE_WARN, "Error Descriptor 0x%llx "
" invalid in resumable error handler",
(long long) errh_flt.errh_er.desc);
continue;
}
aflt = (struct async_flt *)&(errh_flt.cmn_asyncflt);
aflt->flt_id = gethrtime();
aflt->flt_bus_id = getprocessorid();
aflt->flt_class = CPU_FAULT;
aflt->flt_prot = AFLT_PROT_NONE;
aflt->flt_priv = (((errh_flt.errh_er.attr & ERRH_MODE_MASK)
>> ERRH_MODE_SHIFT) == ERRH_MODE_PRIV);
if (errh_flt.errh_er.attr & ERRH_ATTR_CPU)
aflt->flt_panic = 1;
else
aflt->flt_panic = 0;
if (errh_flt.errh_er.attr & ERRH_ATTR_RQF) {
(void) errh_rq_full(aflt);
}
(void) cpu_queue_one_event(&errh_flt);
if (aflt->flt_panic) {
fm_panic("Unrecoverable error on another CPU");
}
}
}
void
process_nonresumable_error(struct regs *rp, uint64_t flags,
uint32_t head_offset, uint32_t tail_offset)
{
struct machcpu *mcpup;
struct async_flt *aflt;
errh_async_flt_t errh_flt;
errh_er_t *head_va;
int trampolined = 0;
int expected = DDI_FM_ERR_UNEXPECTED;
uint64_t exec_mode;
uint8_t u_spill_fill;
mcpup = &(CPU->cpu_m);
while (head_offset != tail_offset) {
head_va = (errh_er_t *)(mcpup->cpu_nrq_va + head_offset +
CPU_NRQ_SIZE);
bzero(&errh_flt, sizeof (errh_async_flt_t));
bcopy((char *)head_va, &(errh_flt.errh_er),
sizeof (errh_er_t));
mcpup->cpu_nrq_lastnre = head_va;
if (printerrh)
errh_er_print(&errh_flt.errh_er, "NRQ");
head_offset += Q_ENTRY_SIZE;
head_offset &= (CPU_NRQ_SIZE - 1);
head_va->ehdl = 0;
aflt = (struct async_flt *)&(errh_flt.cmn_asyncflt);
trampolined = 0;
if (errh_flt.errh_er.attr & ERRH_ATTR_PIO)
aflt->flt_class = BUS_FAULT;
else
aflt->flt_class = CPU_FAULT;
aflt->flt_id = gethrtime();
aflt->flt_bus_id = getprocessorid();
aflt->flt_pc = (caddr_t)rp->r_pc;
exec_mode = (errh_flt.errh_er.attr & ERRH_MODE_MASK)
>> ERRH_MODE_SHIFT;
aflt->flt_priv = (exec_mode == ERRH_MODE_PRIV ||
exec_mode == ERRH_MODE_UNKNOWN);
aflt->flt_prot = AFLT_PROT_NONE;
aflt->flt_tl = (uchar_t)(flags & ERRH_TL_MASK);
aflt->flt_panic = ((aflt->flt_tl != 0) ||
(aft_testfatal != 0));
if (flags & ERRH_U_SPILL_FILL) {
u_spill_fill = 1;
flags = (uint64_t)aflt->flt_tl;
} else
u_spill_fill = 0;
switch (errh_flt.errh_er.desc) {
case ERRH_DESC_PR_NRE:
if (u_spill_fill) {
aflt->flt_panic = 0;
break;
}
case ERRH_DESC_DEF_NRE:
if (aflt->flt_priv == 1 && aflt->flt_tl == 0 &&
((errh_flt.errh_er.attr & ERRH_ATTR_PIO) ||
(errh_flt.errh_er.attr & ERRH_ATTR_MEM))) {
trampolined =
errh_error_protected(rp, aflt, &expected);
}
if (!aflt->flt_priv || aflt->flt_prot ==
AFLT_PROT_COPY) {
aflt->flt_panic |= aft_panic;
} else if (!trampolined &&
(aflt->flt_class != BUS_FAULT)) {
aflt->flt_panic = 1;
}
errh_handle_attr(&errh_flt);
if (aflt->flt_class == BUS_FAULT) {
aflt->flt_addr = errh_flt.errh_er.ra;
errh_cpu_run_bus_error_handlers(aflt,
expected);
}
break;
case ERRH_DESC_USER_DCORE:
panic("Panic - Generated at user request");
break;
default:
cmn_err(CE_WARN, "Panic - Error Descriptor 0x%llx "
" invalid in non-resumable error handler",
(long long) errh_flt.errh_er.desc);
aflt->flt_panic = 1;
break;
}
(void) cpu_queue_one_event(&errh_flt);
if (aflt->flt_panic) {
fm_panic("Unrecoverable hardware error");
}
if (errh_flt.errh_er.attr & ERRH_ATTR_MEM)
errh_page_retire(&errh_flt, PR_UE);
if (!aflt->flt_priv || aflt->flt_prot == AFLT_PROT_COPY ||
u_spill_fill) {
int pcb_flag = 0;
if (aflt->flt_class == CPU_FAULT)
pcb_flag |= ASYNC_HWERR;
else if (aflt->flt_class == BUS_FAULT)
pcb_flag |= ASYNC_BERR;
ttolwp(curthread)->lwp_pcb.pcb_flags |= pcb_flag;
aston(curthread);
}
}
}
void
errh_cpu_run_bus_error_handlers(struct async_flt *aflt, int expected)
{
int status;
ddi_fm_error_t de;
bzero(&de, sizeof (ddi_fm_error_t));
de.fme_version = DDI_FME_VERSION;
de.fme_ena = fm_ena_generate(aflt->flt_id, FM_ENA_FMT1);
de.fme_flag = expected;
de.fme_bus_specific = (void *)aflt->flt_addr;
status = ndi_fm_handler_dispatch(ddi_root_node(), NULL, &de);
if ((aflt->flt_prot == AFLT_PROT_NONE) && (aflt->flt_priv == 1) &&
(status == DDI_FM_FATAL))
aflt->flt_panic = 1;
}
static int
errh_error_protected(struct regs *rp, struct async_flt *aflt, int *expected)
{
int trampolined = 0;
ddi_acc_hdl_t *hp;
if (curthread->t_ontrap != NULL) {
on_trap_data_t *otp = curthread->t_ontrap;
if (otp->ot_prot & OT_DATA_EC) {
aflt->flt_prot = AFLT_PROT_EC;
otp->ot_trap |= OT_DATA_EC;
rp->r_pc = otp->ot_trampoline;
rp->r_npc = rp->r_pc +4;
trampolined = 1;
}
if (otp->ot_prot & OT_DATA_ACCESS) {
aflt->flt_prot = AFLT_PROT_ACCESS;
otp->ot_trap |= OT_DATA_ACCESS;
rp->r_pc = otp->ot_trampoline;
rp->r_npc = rp->r_pc + 4;
trampolined = 1;
hp = (ddi_acc_hdl_t *)otp->ot_handle;
if (!hp)
*expected = DDI_FM_ERR_PEEK;
else if (hp->ah_acc.devacc_attr_access ==
DDI_CAUTIOUS_ACC)
*expected = DDI_FM_ERR_EXPECTED;
}
} else if (curthread->t_lofault) {
aflt->flt_prot = AFLT_PROT_COPY;
rp->r_g1 = EFAULT;
rp->r_pc = curthread->t_lofault;
rp->r_npc = rp->r_pc + 4;
trampolined = 1;
}
return (trampolined);
}
static void
cpu_queue_one_event(errh_async_flt_t *errh_fltp)
{
struct async_flt *aflt = (struct async_flt *)errh_fltp;
errorq_t *eqp;
if (aflt->flt_panic)
eqp = ue_queue;
else
eqp = ce_queue;
errorq_dispatch(eqp, errh_fltp, sizeof (errh_async_flt_t),
aflt->flt_panic);
}
void
cpu_async_log_err(void *flt)
{
errh_async_flt_t *errh_fltp = (errh_async_flt_t *)flt;
errh_er_t *errh_erp = (errh_er_t *)&errh_fltp->errh_er;
switch (errh_erp->desc) {
case ERRH_DESC_UCOR_RE:
if (errh_erp->attr & ERRH_ATTR_MEM) {
errh_page_retire(errh_fltp, PR_UE);
}
break;
case ERRH_DESC_PR_NRE:
case ERRH_DESC_DEF_NRE:
if (errh_erp->attr & ERRH_ATTR_MEM) {
errh_page_retire(errh_fltp, PR_UE);
if (errh_fltp->cmn_asyncflt.flt_panic)
mem_scrub(errh_fltp->errh_er.ra,
errh_fltp->errh_er.sz);
}
break;
default:
break;
}
}
void
cpu_ce_log_err(struct async_flt *aflt)
{
switch (aflt->flt_class) {
case CPU_FAULT:
cpu_async_log_err(aflt);
break;
case BUS_FAULT:
cpu_async_log_err(aflt);
break;
default:
break;
}
}
void
cpu_ue_log_err(struct async_flt *aflt)
{
switch (aflt->flt_class) {
case CPU_FAULT:
cpu_async_log_err(aflt);
break;
case BUS_FAULT:
cpu_async_log_err(aflt);
break;
default:
break;
}
}
static void
errh_page_retire(errh_async_flt_t *errh_fltp, uchar_t flag)
{
uint64_t flt_real_addr_start = errh_fltp->errh_er.ra;
uint64_t flt_real_addr_end = flt_real_addr_start +
errh_fltp->errh_er.sz - 1;
int64_t current_addr;
if (errh_fltp->errh_er.sz == 0)
return;
for (current_addr = flt_real_addr_start;
current_addr < flt_real_addr_end; current_addr += MMU_PAGESIZE) {
(void) page_retire(current_addr, flag);
}
}
void
mem_scrub(uint64_t paddr, uint64_t len)
{
uint64_t pa, length, scrubbed_len;
pa = paddr;
length = len;
scrubbed_len = 0;
while (length > 0) {
if (hv_mem_scrub(pa, length, &scrubbed_len) != H_EOK)
break;
pa += scrubbed_len;
length -= scrubbed_len;
}
}
uint64_t
mem_sync(caddr_t orig_va, size_t orig_len)
{
uint64_t pa, length, flushed;
uint64_t chunk_len = MMU_PAGESIZE;
uint64_t total_flushed = 0;
uint64_t va, len;
if (orig_len == 0)
return (total_flushed);
va = P2ALIGN_TYPED(orig_va, MMU_PAGESIZE, uint64_t);
len = P2ROUNDUP_TYPED(orig_va + orig_len, MMU_PAGESIZE, uint64_t) - va;
while (len > 0) {
pa = va_to_pa((caddr_t)va);
if (pa == (uint64_t)-1)
return (total_flushed);
length = chunk_len;
flushed = 0;
while (length > 0) {
if (hv_mem_sync(pa, length, &flushed) != H_EOK)
return (total_flushed);
pa += flushed;
length -= flushed;
total_flushed += flushed;
}
va += chunk_len;
len -= chunk_len;
}
return (total_flushed);
}
static void
errh_rq_full(struct async_flt *afltp)
{
processorid_t who;
uint64_t cpu_state;
uint64_t retval;
uint64_t current_tick;
current_tick = (uint64_t)gettick();
tickcmpr_set(current_tick);
for (who = 0; who < NCPU; who++)
if (CPU_IN_SET(cpu_ready_set, who)) {
retval = hv_cpu_state(who, &cpu_state);
if (retval != H_EOK || cpu_state == CPU_STATE_ERROR) {
afltp->flt_panic = 1;
break;
}
}
}
int
cpu_aflt_size(void)
{
return (sizeof (errh_async_flt_t));
}
#define SZ_TO_ETRS_SHIFT 6
void
rq_overflow(struct regs *rp, uint64_t head_offset,
uint64_t tail_offset)
{
rq_overflow_count++;
}
static void
ue_drain(void *ignored, struct async_flt *aflt, errorq_elem_t *eqep)
{
cpu_ue_log_err(aflt);
}
static void
ce_drain(void *ignored, struct async_flt *aflt, errorq_elem_t *eqep)
{
cpu_ce_log_err(aflt);
}
static void
errh_drain(void *ignored, errh_er_t *errh_erp, errorq_elem_t *eqep)
{
ASSERT(errh_erp->desc == ERRH_DESC_SP);
errh_handle_sp(errh_erp);
}
static int
err_shutdown_softintr()
{
cmn_err(CE_WARN, "Power-off requested, system will now shutdown.");
do_shutdown();
(void) timeout((void(*)(void *))power_down, NULL, 100 * hz);
return (DDI_INTR_CLAIMED);
}
void
error_init(void)
{
char tmp_name[MAXSYSNAME];
pnode_t node;
size_t size = cpu_aflt_size();
ue_queue = errorq_create("ue_queue", (errorq_func_t)ue_drain, NULL,
MAX_ASYNC_FLTS * (max_ncpus + 1), size, PIL_2, ERRORQ_VITAL);
ce_queue = errorq_create("ce_queue", (errorq_func_t)ce_drain, NULL,
MAX_CE_FLTS * (max_ncpus + 1), size, PIL_1, 0);
errh_queue = errorq_create("errh_queue", (errorq_func_t)errh_drain,
NULL, CPU_RQ_ENTRIES, sizeof (errh_er_t), PIL_1, 0);
if (ue_queue == NULL || ce_queue == NULL || errh_queue == NULL)
panic("failed to create required system error queue");
err_shutdown_inum = add_softintr(PIL_9,
(softintrfunc)err_shutdown_softintr, NULL, SOFTINT_ST);
mutex_init(&bfd_lock, NULL, MUTEX_SPIN, (void *)PIL_15);
mutex_init(&errh_print_lock, NULL, MUTEX_SPIN, (void *)PIL_15);
node = prom_rootnode();
if ((node == OBP_NONODE) || (node == OBP_BADNODE)) {
cmn_err(CE_CONT, "error_init: node 0x%x\n", (uint_t)node);
return;
}
if (((size = prom_getproplen(node, "reset-reason")) != -1) &&
(size <= MAXSYSNAME) &&
(prom_getprop(node, "reset-reason", tmp_name) != -1)) {
if (reset_debug) {
cmn_err(CE_CONT, "System booting after %s\n", tmp_name);
} else if (strncmp(tmp_name, "FATAL", 5) == 0) {
cmn_err(CE_CONT,
"System booting after fatal error %s\n", tmp_name);
}
}
}
void
nrq_overflow(struct regs *rp)
{
fm_panic("Nonresumable queue full");
}
static void
errh_handle_attr(errh_async_flt_t *errh_fltp)
{
switch (errh_fltp->errh_er.attr & ~ERRH_MODE_MASK) {
case ERRH_ATTR_CPU:
case ERRH_ATTR_MEM:
case ERRH_ATTR_PIO:
case ERRH_ATTR_IRF:
case ERRH_ATTR_FRF:
case ERRH_ATTR_SHUT:
break;
case ERRH_ATTR_ASR:
errh_handle_asr(errh_fltp);
break;
case ERRH_ATTR_ASI:
case ERRH_ATTR_PREG:
case ERRH_ATTR_RQF:
break;
default:
break;
}
}
static void
errh_handle_asr(errh_async_flt_t *errh_fltp)
{
uint64_t current_tick;
switch (errh_fltp->errh_er.reg) {
case ASR_REG_VALID | ASR_REG_TICK:
current_tick = (uint64_t)gettick();
tickcmpr_set(current_tick);
errh_fltp->cmn_asyncflt.flt_panic = 0;
break;
default:
break;
}
}
static void
errh_handle_sp(errh_er_t *errh_erp)
{
uint8_t sp_state;
sp_state = (errh_erp->attr & ERRH_SP_MASK) >> ERRH_SP_SHIFT;
sp_ereport_post(sp_state);
}
static void
errh_er_print(errh_er_t *errh_erp, const char *queue)
{
typedef union {
uint64_t w;
uint16_t s[4];
} errhp_t;
errhp_t *p = (errhp_t *)errh_erp;
int i;
mutex_enter(&errh_print_lock);
switch (errh_erp->desc) {
case ERRH_DESC_UCOR_RE:
cmn_err(CE_CONT, "\nResumable Uncorrectable Error ");
break;
case ERRH_DESC_PR_NRE:
cmn_err(CE_CONT, "\nNonresumable Precise Error ");
break;
case ERRH_DESC_DEF_NRE:
cmn_err(CE_CONT, "\nNonresumable Deferred Error ");
break;
default:
cmn_err(CE_CONT, "\nError packet ");
break;
}
cmn_err(CE_CONT, "received on %s\n", queue);
for (i = Q_ENTRY_SIZE; i > 0; i -= 8, ++p) {
cmn_err(CE_CONT, "%016lx: %04x %04x %04x %04x\n", (uint64_t)p,
p->s[0], p->s[1], p->s[2], p->s[3]);
}
mutex_exit(&errh_print_lock);
}
static void
sp_ereport_post(uint8_t sp_state)
{
nvlist_t *ereport, *detector;
char *str = NULL;
switch (sp_state) {
case ERRH_SP_FAULTED:
str = "chassis.sp.unavailable";
break;
case ERRH_SP_NOT_PRESENT:
return;
case ERRH_SP_AVAILABLE:
cmn_err(CE_WARN, "Received unexpected notification "
"that the SP is available.");
return;
default:
cmn_err(CE_WARN, "Invalid SP state 0x%x. No ereport posted.\n",
sp_state);
return;
}
ereport = fm_nvlist_create(NULL);
detector = fm_nvlist_create(NULL);
fm_fmri_hc_set(detector, FM_HC_SCHEME_VERSION, NULL, NULL, 1,
"chassis", 0);
fm_ereport_set(ereport, FM_EREPORT_VERSION, str,
fm_ena_generate(0, FM_ENA_FMT1), detector, NULL);
(void) fm_ereport_post(ereport, EVCH_TRYHARD);
fm_nvlist_destroy(ereport, FM_NVA_FREE);
fm_nvlist_destroy(detector, FM_NVA_FREE);
}