#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: intr.c,v 1.127 2021/01/24 07:36:54 mrg Exp $");
#include "opt_multiprocessor.h"
#include "opt_sparc_arch.h"
#include "sx.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/cpu.h>
#include <sys/intr.h>
#include <sys/atomic.h>
#include <uvm/uvm_extern.h>
#include <dev/cons.h>
#include <machine/ctlreg.h>
#include <machine/instr.h>
#include <machine/trap.h>
#include <machine/promlib.h>
#include <machine/locore.h>
#include <sparc/sparc/asm.h>
#include <sparc/sparc/cpuvar.h>
#if defined(MULTIPROCESSOR) && defined(DDB)
#include <machine/db_machdep.h>
#endif
#if NSX > 0
#include <sys/bus.h>
#include <sparc/dev/sxvar.h>
#endif
#if defined(MULTIPROCESSOR)
static int intr_biglock_wrapper(void *);
void *xcall_cookie;
#endif
void strayintr(struct clockframe *);
#ifdef DIAGNOSTIC
void bogusintr(struct clockframe *);
#endif
void
strayintr(struct clockframe *fp)
{
static int straytime, nstray;
char bits[64];
int timesince;
#if defined(MULTIPROCESSOR)
#define ZS_INTR_IPL 12
if (fp->ipl == ZS_INTR_IPL)
return;
#endif
snprintb(bits, sizeof(bits), PSR_BITS, fp->psr);
printf("stray interrupt cpu%d ipl 0x%x pc=0x%x npc=0x%x psr=%s\n",
cpu_number(), fp->ipl, fp->pc, fp->npc, bits);
timesince = time_uptime - straytime;
if (timesince <= 10) {
if (++nstray > 10)
panic("crazy interrupts");
} else {
straytime = time_uptime;
nstray = 1;
}
}
#ifdef DIAGNOSTIC
void
bogusintr(struct clockframe *fp)
{
char bits[64];
#if defined(MULTIPROCESSOR)
if (fp->ipl == ZS_INTR_IPL)
return;
#endif
snprintb(bits, sizeof(bits), PSR_BITS, fp->psr);
printf("cpu%d: bogus interrupt ipl 0x%x pc=0x%x npc=0x%x psr=%s\n",
cpu_number(), fp->ipl, fp->pc, fp->npc, bits);
}
#endif
u_int
getitr(void)
{
#if defined(MULTIPROCESSOR)
u_int v;
if (!CPU_ISSUN4M || sparc_ncpus <= 1)
return (0);
v = *((u_int *)ICR_ITR);
return (v + 8);
#else
return (0);
#endif
}
u_int
setitr(u_int mid)
{
#if defined(MULTIPROCESSOR)
u_int v;
if (!CPU_ISSUN4M || sparc_ncpus <= 1)
return (0);
v = *((u_int *)ICR_ITR);
*((u_int *)ICR_ITR) = CPU_MID2CPUNO(mid);
return (v + 8);
#else
return (0);
#endif
}
#if (defined(SUN4M) && !defined(MSIIEP)) || defined(SUN4D)
void nmi_hard(void);
void nmi_soft(struct trapframe *);
int (*memerr_handler)(void);
int (*sbuserr_handler)(void);
int (*vmeerr_handler)(void);
int (*moduleerr_handler)(void);
#if defined(MULTIPROCESSOR)
static volatile u_int nmi_hard_wait = 0;
int drop_into_rom_on_fatal = 1;
#endif
void
nmi_hard(void)
{
int fatal = 0;
uint32_t si;
char bits[64];
u_int afsr, afva;
cpuinfo.ci_intrcnt[15].ev_count++;
cpuinfo.ci_data.cpu_nintr++;
afsr = afva = 0;
if ((*cpuinfo.get_asyncflt)(&afsr, &afva) == 0) {
snprintb(bits, sizeof(bits), AFSR_BITS, afsr);
printf("Async registers (mid %d): afsr=%s; afva=0x%x%x\n",
cpuinfo.mid, bits,
(afsr & AFSR_AFA) >> AFSR_AFA_RSHIFT, afva);
}
#if defined(MULTIPROCESSOR)
atomic_inc_uint(&nmi_hard_wait);
if (cpuinfo.master == 0) {
while (nmi_hard_wait)
;
return;
} else {
int n = 100000;
while (nmi_hard_wait < sparc_ncpus) {
DELAY(1);
if (n-- > 0)
continue;
printf("nmi_hard: SMP botch.\n");
break;
}
}
#endif
si = *((uint32_t *)ICR_SI_PEND);
snprintb(bits, sizeof(bits), SINTR_BITS, si);
printf("cpu%d: NMI: system interrupts: %s\n", cpu_number(), bits);
#if NSX > 0
sx_dump();
#endif
if ((si & SINTR_M) != 0) {
if (memerr_handler != NULL)
fatal |= (*memerr_handler)();
}
if ((si & SINTR_I) != 0) {
if (sbuserr_handler != NULL)
fatal |= (*sbuserr_handler)();
}
if ((si & SINTR_V) != 0) {
if (vmeerr_handler != NULL)
fatal |= (*vmeerr_handler)();
}
if ((si & SINTR_ME) != 0) {
if (moduleerr_handler != NULL)
fatal |= (*moduleerr_handler)();
}
#if defined(MULTIPROCESSOR)
nmi_hard_wait = 0;
if (fatal && drop_into_rom_on_fatal) {
prom_abort();
return;
}
#endif
if (fatal)
panic("nmi");
}
void
nmi_soft(struct trapframe *tf)
{
cpuinfo.ci_sintrcnt[15].ev_count++;
cpuinfo.ci_data.cpu_nintr++;
if (cpuinfo.mailbox) {
uint8_t msg = *(uint8_t *)cpuinfo.mailbox;
switch (msg) {
case OPENPROM_MBX_STOP:
case OPENPROM_MBX_WD:
spl0();
#ifdef MULTIPROCESSOR
cpu_ready_mask &= ~(1 << cpu_number());
#endif
prom_cpustop(0);
break;
case OPENPROM_MBX_ABORT:
case OPENPROM_MBX_BPT:
prom_cpuidle(0);
return;
default:
break;
}
}
#if defined(MULTIPROCESSOR)
switch (cpuinfo.msg_lev15.tag) {
case XPMSG15_PAUSECPU:
cpuinfo.flags |= CPUFLG_PAUSED;
#if defined(DDB)
__asm("ta 0x8b");
#else
while (cpuinfo.flags & CPUFLG_PAUSED)
;
#endif
}
cpuinfo.msg_lev15.tag = 0;
#endif
}
#if defined(MULTIPROCESSOR)
void
xcallintr(void *v)
{
kpreempt_disable();
if (v != xcallintr)
cpuinfo.ci_sintrcnt[13].ev_count++;
if (mutex_owned(&xpmsg_mutex) == 0) {
cpuinfo.ci_xpmsg_mutex_not_held.ev_count++;
#ifdef DEBUG
printf("%s: cpu%d mutex not held\n", __func__, cpu_number());
#endif
cpuinfo.msg.complete = 1;
kpreempt_enable();
return;
}
if (cpuinfo.msg.complete != 0) {
cpuinfo.ci_xpmsg_bogus.ev_count++;
#ifdef DEBUG
volatile struct xpmsg_func *p = &cpuinfo.msg.u.xpmsg_func;
printf("%s: bogus message %08x %08x %08x %08x\n", __func__,
cpuinfo.msg.tag, (uint32_t)p->func, p->arg0, p->arg1);
#endif
kpreempt_enable();
return;
}
switch (cpuinfo.msg.tag) {
case XPMSG_FUNC:
{
volatile struct xpmsg_func *p = &cpuinfo.msg.u.xpmsg_func;
if (p->func)
(*p->func)(p->arg0, p->arg1, p->arg2);
break;
}
}
cpuinfo.msg.tag = 0;
cpuinfo.msg.complete = 1;
kpreempt_enable();
}
#endif
#endif
#ifdef MSIIEP
#include <sparc/sparc/msiiepreg.h>
void nmi_hard_msiiep(void);
void nmi_soft_msiiep(void);
void
nmi_hard_msiiep(void)
{
uint32_t si;
char bits[128];
int fatal = 0;
si = mspcic_read_4(pcic_sys_ipr);
snprintb(bits, sizeof(bits), MSIIEP_SYS_IPR_BITS, si);
printf("NMI: system interrupts: %s\n", bits);
if (si & MSIIEP_SYS_IPR_MEM_FAULT) {
uint32_t afsr, afar, mfsr, mfar;
afar = *(volatile uint32_t *)MSIIEP_AFAR;
afsr = *(volatile uint32_t *)MSIIEP_AFSR;
mfar = *(volatile uint32_t *)MSIIEP_MFAR;
mfsr = *(volatile uint32_t *)MSIIEP_MFSR;
if (afsr & MSIIEP_AFSR_ERR) {
snprintb(bits, sizeof(bits), MSIIEP_AFSR_BITS, afsr);
printf("async fault: afsr=%s; afar=%08x\n", bits, afar);
}
if (mfsr & MSIIEP_MFSR_ERR) {
snprintb(bits, sizeof(bits), MSIIEP_MFSR_BITS, mfsr);
printf("mem fault: mfsr=%s; mfar=%08x\n", bits, mfar);
}
fatal = 0;
}
if (si & MSIIEP_SYS_IPR_SERR) {
printf("serr#\n");
fatal = 0;
}
if (si & MSIIEP_SYS_IPR_DMA_ERR) {
printf("dma: %08x\n",
mspcic_read_stream_4(pcic_iotlb_err_addr));
fatal = 0;
}
if (si & MSIIEP_SYS_IPR_PIO_ERR) {
printf("pio: addr=%08x, cmd=%x stat=%04x\n",
mspcic_read_stream_4(pcic_pio_err_addr),
mspcic_read_stream_1(pcic_pio_err_cmd),
mspcic_read_stream_2(pcic_stat));
fatal = 0;
}
if (fatal)
panic("nmi");
mspcic_write_1(pcic_sys_ipr_clr, MSIIEP_SYS_IPR_CLR_ALL);
}
void
nmi_soft_msiiep(void)
{
panic("soft nmi");
}
#endif
struct intrhand *intrhand[15] = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
struct intrhand *sintrhand[15] = { NULL };
static void
ih_insert(struct intrhand **head, struct intrhand *ih)
{
struct intrhand **p, *q;
for (p = head; (q = *p) != NULL; p = &q->ih_next)
continue;
*p = ih;
ih->ih_next = NULL;
}
static void
ih_remove(struct intrhand **head, struct intrhand *ih)
{
struct intrhand **p, *q;
for (p = head; (q = *p) != ih; p = &q->ih_next)
continue;
if (q == NULL)
panic("intr_remove: intrhand %p fun %p arg %p",
ih, ih->ih_fun, ih->ih_arg);
*p = q->ih_next;
q->ih_next = NULL;
}
static int fastvec;
#ifdef DIAGNOSTIC
static void
check_tv(int level)
{
struct trapvec *tv;
int displ;
tv = &trapbase[T_L1INT - 1 + level];
displ = (CPU_ISSUN4M || CPU_ISSUN4D)
? &sparc_interrupt4m[0] - &tv->tv_instr[1]
: &sparc_interrupt44c[0] - &tv->tv_instr[1];
if (tv->tv_instr[0] != I_MOVi(I_L3, level) ||
tv->tv_instr[1] != I_BA(0, displ) ||
tv->tv_instr[2] != I_RDPSR(I_L0))
panic("intr_establish(%d)\n0x%x 0x%x 0x%x != 0x%x 0x%x 0x%x",
level,
tv->tv_instr[0], tv->tv_instr[1], tv->tv_instr[2],
I_MOVi(I_L3, level), I_BA(0, displ), I_RDPSR(I_L0));
}
#endif
static void
inst_fasttrap(int level, void (*vec)(void))
{
struct trapvec *tv;
u_long hi22, lo10;
int s;
if (CPU_ISSUN4 || CPU_ISSUN4C) {
if (level == 1 || level == 4 || level == 6)
return;
}
#ifdef DIAGNOSTIC
check_tv(level);
#endif
tv = &trapbase[T_L1INT - 1 + level];
hi22 = ((u_long)vec) >> 10;
lo10 = ((u_long)vec) & 0x3ff;
s = splhigh();
pmap_kprotect((vaddr_t)tv & -PAGE_SIZE, PAGE_SIZE,
VM_PROT_READ|VM_PROT_WRITE);
cpuinfo.cache_flush_all();
tv->tv_instr[0] = I_SETHI(I_L3, hi22);
tv->tv_instr[1] = I_JMPLri(I_G0, I_L3, lo10);
tv->tv_instr[2] = I_RDPSR(I_L0);
pmap_kprotect((vaddr_t)tv & -PAGE_SIZE, PAGE_SIZE, VM_PROT_READ);
cpuinfo.cache_flush_all();
fastvec |= 1 << level;
splx(s);
}
static void
uninst_fasttrap(int level)
{
struct trapvec *tv;
int displ;
int s;
tv = &trapbase[T_L1INT - 1 + level];
s = splhigh();
displ = (CPU_ISSUN4M || CPU_ISSUN4D)
? &sparc_interrupt4m[0] - &tv->tv_instr[1]
: &sparc_interrupt44c[0] - &tv->tv_instr[1];
pmap_kprotect((vaddr_t)tv & -PAGE_SIZE, PAGE_SIZE,
VM_PROT_READ|VM_PROT_WRITE);
cpuinfo.cache_flush_all();
tv->tv_instr[0] = I_MOVi(I_L3, level);
tv->tv_instr[1] = I_BA(0, displ);
tv->tv_instr[2] = I_RDPSR(I_L0);
pmap_kprotect((vaddr_t)tv & -PAGE_SIZE, PAGE_SIZE, VM_PROT_READ);
cpuinfo.cache_flush_all();
fastvec &= ~(1 << level);
splx(s);
}
void
intr_establish(int level, int classipl,
struct intrhand *ih, void (*vec)(void),
bool maybe_mpsafe)
{
int s = splhigh();
#ifdef MULTIPROCESSOR
bool mpsafe;
#endif
if (classipl == 0)
classipl = level;
#ifdef MULTIPROCESSOR
mpsafe = (classipl != IPL_VM) || maybe_mpsafe;
#endif
#ifdef DIAGNOSTIC
if (CPU_ISSUN4C) {
if (level == 1 || level == 4 || level == 6)
panic("intr_establish: reserved softintr level");
}
#endif
if (fastvec & (1 << level)) {
printf("intr_establish: untie fast vector at level %d\n",
level);
uninst_fasttrap(level);
} else if (vec != NULL &&
intrhand[level] == NULL && sintrhand[level] == NULL) {
inst_fasttrap(level, vec);
}
if (classipl < level)
panic("intr_establish: class lvl (%d) < pil (%d)\n",
classipl, level);
ih->ih_classipl = (classipl << 8) & PSR_PIL;
#ifdef MULTIPROCESSOR
if (!mpsafe) {
ih->ih_realfun = ih->ih_fun;
ih->ih_realarg = ih->ih_arg;
ih->ih_fun = intr_biglock_wrapper;
ih->ih_arg = ih;
}
#endif
ih_insert(&intrhand[level], ih);
splx(s);
}
void
intr_disestablish(int level, struct intrhand *ih)
{
ih_remove(&intrhand[level], ih);
}
struct softintr_cookie {
int sic_pilreq;
int sic_pil;
struct intrhand sic_hand;
};
void
sparc_softintr_init(void)
{
#if defined(MULTIPROCESSOR) && (defined(SUN4M) || defined(SUN4D))
xcall_cookie = sparc_softintr_establish(13, xcallintr, NULL);
#endif
}
void *
sparc_softintr_establish(int level, void (*fun)(void *), void *arg)
{
struct softintr_cookie *sic;
struct intrhand *ih;
int pilreq;
int pil;
#ifdef MULTIPROCESSOR
bool mpsafe = (level != IPL_VM);
#endif
pil = pilreq = level;
if (CPU_ISSUN4 || CPU_ISSUN4C) {
if (level >= 1 && level < 4) {
pil = 1;
pilreq = IE_L1;
} else if (level >= 4 && level < 6) {
pil = 4;
pilreq = IE_L4;
} else {
pil = 6;
pilreq = IE_L6;
}
}
sic = kmem_alloc(sizeof(*sic), KM_SLEEP);
sic->sic_pil = pil;
sic->sic_pilreq = pilreq;
ih = &sic->sic_hand;
#ifdef MULTIPROCESSOR
if (!mpsafe) {
ih->ih_realfun = (int (*)(void *))fun;
ih->ih_realarg = arg;
ih->ih_fun = intr_biglock_wrapper;
ih->ih_arg = ih;
} else
#endif
{
ih->ih_fun = (int (*)(void *))fun;
ih->ih_arg = arg;
}
ih->ih_classipl = (level << 8) & PSR_PIL;
if (fastvec & (1 << pil)) {
printf("softintr_establish: untie fast vector at level %d\n",
pil);
uninst_fasttrap(level);
}
ih_insert(&sintrhand[pil], ih);
return (void *)sic;
}
void
sparc_softintr_disestablish(void *cookie)
{
struct softintr_cookie *sic = cookie;
ih_remove(&sintrhand[sic->sic_pil], &sic->sic_hand);
kmem_free(sic, sizeof(*sic));
}
#if 0
void
sparc_softintr_schedule(void *cookie)
{
struct softintr_cookie *sic = cookie;
if (CPU_ISSUN4M || CPU_ISSUN4D) {
#if defined(SUN4M) || defined(SUN4D)
raise(0, sic->sic_pilreq);
#endif
} else {
#if defined(SUN4) || defined(SUN4C)
ienab_bis(sic->sic_pilreq);
#endif
}
}
#endif
#ifdef MULTIPROCESSOR
static int
intr_biglock_wrapper(void *vp)
{
struct intrhand *ih = vp;
int ret;
KERNEL_LOCK(1, NULL);
ret = (*ih->ih_realfun)(ih->ih_realarg);
KERNEL_UNLOCK_ONE(NULL);
return ret;
}
#endif
bool
cpu_intr_p(void)
{
return cpuinfo.ci_idepth != 0;
}