#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: exception.c,v 1.75 2023/10/05 19:41:05 ad Exp $");
#include "opt_ddb.h"
#include "opt_kgdb.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/signal.h>
#include <sys/intr.h>
#ifdef DDB
#include <sh3/db_machdep.h>
#endif
#ifdef KGDB
#include <sys/kgdb.h>
#endif
#include <uvm/uvm_extern.h>
#include <sh3/cpu.h>
#include <sh3/mmu.h>
#include <sh3/pcb.h>
#include <sh3/exception.h>
#include <sh3/userret.h>
const char * const exp_type[] = {
"--",
"--",
"TLB miss/invalid (load)",
"TLB miss/invalid (store)",
"initial page write",
"TLB protection violation (load)",
"TLB protection violation (store)",
"address error (load)",
"address error (store)",
"FPU",
"--",
"unconditional trap (TRAPA)",
"reserved instruction code exception",
"illegal slot instruction exception",
"--",
"user break point trap",
};
const int exp_types = __arraycount(exp_type);
void general_exception(struct lwp *, struct trapframe *, uint32_t);
void tlb_exception(struct lwp *, struct trapframe *, uint32_t);
void ast(struct lwp *, struct trapframe *);
void
general_exception(struct lwp *l, struct trapframe *tf, uint32_t va)
{
int expevt = tf->tf_expevt;
bool usermode = !KERNELMODE(tf->tf_ssr);
struct pcb *pcb;
ksiginfo_t ksi;
uint32_t trapcode;
#ifdef DDB
uint32_t code;
#endif
curcpu()->ci_data.cpu_ntrap++;
trapcode = _reg_read_4(SH_(TRA)) >> 2;
splx(tf->tf_ssr & PSL_IMASK);
if (l == NULL)
goto do_panic;
if (usermode) {
KDASSERT(l->l_md.md_regs == tf);
expevt |= EXP_USER;
}
switch (expevt) {
case EXPEVT_TRAPA | EXP_USER:
if (trapcode == _SH_TRA_BREAK) {
tf->tf_spc -= 2;
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = SIGTRAP;
ksi.ksi_code = TRAP_BRKPT;
ksi.ksi_addr = (void *)tf->tf_spc;
goto trapsignal;
} else {
(*l->l_proc->p_md.md_syscall)(l, tf);
return;
}
break;
case EXPEVT_BREAK | EXP_USER:
l->l_md.md_flags &= ~MDL_SSTEP;
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = SIGTRAP;
ksi.ksi_code = TRAP_TRACE;
ksi.ksi_addr = (void *)tf->tf_spc;
goto trapsignal;
case EXPEVT_ADDR_ERR_LD:
case EXPEVT_ADDR_ERR_ST:
pcb = lwp_getpcb(l);
if (__predict_false(pcb->pcb_onfault == NULL))
goto do_panic;
tf->tf_spc = (int)pcb->pcb_onfault;
tf->tf_r0 = EFAULT;
break;
case EXPEVT_ADDR_ERR_LD | EXP_USER:
case EXPEVT_ADDR_ERR_ST | EXP_USER:
KSI_INIT_TRAP(&ksi);
if (((int)va) < 0) {
ksi.ksi_signo = SIGSEGV;
ksi.ksi_code = SEGV_ACCERR;
} else {
ksi.ksi_signo = SIGBUS;
ksi.ksi_code = BUS_ADRALN;
}
ksi.ksi_addr = (void *)va;
goto trapsignal;
case EXPEVT_RES_INST | EXP_USER:
case EXPEVT_SLOT_INST | EXP_USER:
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = SIGILL;
ksi.ksi_code = ILL_ILLOPC;
ksi.ksi_addr = (void *)tf->tf_spc;
goto trapsignal;
default:
goto do_panic;
}
if (usermode)
userret(l);
return;
trapsignal:
KASSERT(usermode);
ksi.ksi_trap = tf->tf_expevt;
trapsignal(l, &ksi);
userret(l);
return;
do_panic:
#ifdef DDB
switch (expevt & ~EXP_USER) {
case EXPEVT_TRAPA:
code = trapcode;
break;
default:
code = 0;
break;
}
if (kdb_trap(expevt, code, tf))
return;
#endif
#ifdef KGDB
if (kgdb_trap(EXPEVT_BREAK, tf))
return;
#endif
if (expevt >> 5 < exp_types)
printf("fatal %s", exp_type[expevt >> 5]);
else
printf("EXPEVT 0x%03x", expevt);
printf(" in %s mode\n", usermode ? "user" : "kernel");
printf(" spc %x ssr %x \n", tf->tf_spc, tf->tf_ssr);
panic("general_exception");
}
void
tlb_exception(struct lwp *l, struct trapframe *tf, uint32_t va)
{
struct vm_map *map;
struct pcb *pcb;
pmap_t pmap;
void *onfault;
ksiginfo_t ksi;
bool usermode;
int err, track, ftype;
const char *panic_msg;
pcb = lwp_getpcb(l);
onfault = pcb->pcb_onfault;
#define TLB_ASSERT(assert, msg) \
do { \
if (!(assert)) { \
panic_msg = msg; \
goto tlb_panic; \
} \
} while(0)
usermode = !KERNELMODE(tf->tf_ssr);
if (usermode) {
KDASSERT(l->l_md.md_regs == tf);
} else {
#if 0
KDASSERT(l == NULL ||
l == &lwp0 ||
l->l_md.md_regs != tf);
#endif
}
switch (tf->tf_expevt) {
case EXPEVT_TLB_MISS_LD:
track = PVH_REFERENCED;
ftype = VM_PROT_READ;
break;
case EXPEVT_TLB_MISS_ST:
track = PVH_REFERENCED;
ftype = VM_PROT_WRITE;
break;
case EXPEVT_TLB_MOD:
track = PVH_REFERENCED | PVH_MODIFIED;
ftype = VM_PROT_WRITE;
break;
case EXPEVT_TLB_PROT_LD:
TLB_ASSERT((int)va > 0,
"kernel virtual protection fault (load)");
if (usermode) {
KSI_INIT_TRAP(&ksi);
ksi.ksi_signo = SIGSEGV;
ksi.ksi_code = SEGV_ACCERR;
ksi.ksi_addr = (void *)va;
splx(tf->tf_ssr & PSL_IMASK);
goto user_fault;
} else {
TLB_ASSERT(l && onfault != NULL,
"no copyin/out fault handler (load protection)");
tf->tf_spc = (int)onfault;
tf->tf_r0 = EFAULT;
}
return;
case EXPEVT_TLB_PROT_ST:
track = 0;
ftype = VM_PROT_WRITE;
break;
default:
TLB_ASSERT(0, "impossible expevt");
}
if (usermode) {
TLB_ASSERT(l != NULL, "no curlwp");
map = &l->l_proc->p_vmspace->vm_map;
pmap = map->pmap;
} else {
if ((int)va < 0) {
map = kernel_map;
pmap = pmap_kernel();
} else {
TLB_ASSERT(l != NULL && onfault != NULL,
"invalid user-space access from kernel mode");
if (va == 0) {
tf->tf_spc = (int)onfault;
tf->tf_r0 = EFAULT;
return;
}
map = &l->l_proc->p_vmspace->vm_map;
pmap = map->pmap;
}
}
if (track && __pmap_pte_load(pmap, va, track)) {
return;
}
splx(tf->tf_ssr & PSL_IMASK);
pcb->pcb_onfault = NULL;
err = uvm_fault(map, va, ftype);
pcb->pcb_onfault = onfault;
if (map != kernel_map &&
(va >= (vaddr_t)l->l_proc->p_vmspace->vm_maxsaddr) &&
(va < (vaddr_t)l->l_proc->p_vmspace->vm_minsaddr)) {
if (err == 0) {
struct vmspace *vm = l->l_proc->p_vmspace;
uint32_t nss;
nss = btoc((vaddr_t)vm->vm_minsaddr - va);
if (nss > vm->vm_ssize)
vm->vm_ssize = nss;
} else if (err == EACCES) {
err = EFAULT;
}
}
if (err == 0) {
bool loaded;
if (usermode)
userret(l);
loaded = __pmap_pte_load(pmap, va, track);
#if 0
TLB_ASSERT(loaded, "page table entry not found");
#else
__USE(loaded);
#endif
return;
}
if (usermode) {
KSI_INIT_TRAP(&ksi);
ksi.ksi_addr = (void *)va;
switch (err) {
case ENOMEM:
ksi.ksi_signo = SIGKILL;
break;
case EINVAL:
ksi.ksi_signo = SIGBUS;
ksi.ksi_code = BUS_ADRERR;
break;
case EACCES:
ksi.ksi_signo = SIGSEGV;
ksi.ksi_code = SEGV_ACCERR;
break;
default:
ksi.ksi_signo = SIGSEGV;
ksi.ksi_code = SEGV_MAPERR;
break;
}
goto user_fault;
} else {
TLB_ASSERT(onfault,
"no copyin/out fault handler (page not found)");
tf->tf_spc = (int)onfault;
tf->tf_r0 = err;
}
return;
user_fault:
ksi.ksi_trap = tf->tf_expevt;
trapsignal(l, &ksi);
userret(l);
return;
tlb_panic:
panic("tlb_exception: %s\n"
"expevt=%x va=%08x ssr=%08x spc=%08x lwp=%p onfault=%p",
panic_msg, tf->tf_expevt, va, tf->tf_ssr, tf->tf_spc,
l, pcb->pcb_onfault);
#undef TLB_ASSERT
}
void
ast(struct lwp *l, struct trapframe *tf)
{
int s;
if (__predict_true(l->l_md.md_astpending == 0)) {
return;
}
if (__predict_false(KERNELMODE(tf->tf_ssr))) {
return;
}
KDASSERT(l != NULL);
KDASSERT(l->l_md.md_regs == tf);
s = tf->tf_ssr & PSL_IMASK;
do {
splx(s);
userret(l);
s = splhigh();
} while (__predict_false(l->l_md.md_astpending));
}