#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/pool.h>
#include <sys/kernel.h>
#include <sys/signal.h>
#include <sys/resourcevar.h>
#include <sys/signalvar.h>
#include <uvm/uvm_extern.h>
#include <sys/syscall.h>
#include <sys/syscall_mi.h>
#include <sh/cache.h>
#include <sh/cpu.h>
#include <sh/mmu.h>
#include <sh/pcb.h>
#include <sh/trap.h>
#ifdef SH4
#include <sh/fpu.h>
#endif
#ifdef DDB
#include <machine/db_machdep.h>
#endif
const char * const exp_type[] = {
NULL,
NULL,
"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",
NULL,
"unconditional trap (TRAPA)",
"reserved instruction code exception",
"illegal slot instruction exception",
NULL,
"user break point trap",
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
"FPU disabled",
"slot FPU disabled"
};
const int exp_types = sizeof exp_type / sizeof exp_type[0];
void general_exception(struct proc *, struct trapframe *, uint32_t);
void tlb_exception(struct proc *, struct trapframe *, uint32_t);
void ast(struct proc *, struct trapframe *);
void syscall(struct proc *, struct trapframe *);
void cachectl(struct proc *, struct trapframe *);
void
general_exception(struct proc *p, struct trapframe *tf, uint32_t va)
{
int expevt = tf->tf_expevt;
int tra = _reg_read_4(SH_(TRA));
int usermode = !KERNELMODE(tf->tf_ssr);
union sigval sv;
atomic_inc_int(&uvmexp.traps);
splx(tf->tf_ssr & PSL_IMASK);
if (usermode) {
if (p == NULL)
goto do_panic;
KDASSERT(p->p_md.md_regs == tf);
expevt |= EXP_USER;
refreshcreds(p);
}
switch (expevt) {
case EXPEVT_BREAK:
#ifdef DDB
if (db_ktrap(EXPEVT_BREAK, 0, tf))
return;
else
#endif
goto do_panic;
break;
case EXPEVT_TRAPA:
#ifdef DDB
if (tra == (_SH_TRA_BREAK << 2) &&
db_ktrap(expevt, tra, tf))
return;
else
#endif
goto do_panic;
break;
case EXPEVT_TRAPA | EXP_USER:
switch (tra) {
case _SH_TRA_BREAK << 2:
tf->tf_spc -= 2;
sv.sival_ptr = (void *)tf->tf_spc;
trapsignal(p, SIGTRAP, expevt & ~EXP_USER, TRAP_BRKPT,
sv);
goto out;
case _SH_TRA_SYSCALL << 2:
syscall(p, tf);
return;
case _SH_TRA_CACHECTL << 2:
cachectl(p, tf);
return;
default:
sv.sival_ptr = (void *)tf->tf_spc;
trapsignal(p, SIGILL, expevt & ~EXP_USER, ILL_ILLTRP,
sv);
goto out;
}
break;
case EXPEVT_ADDR_ERR_LD:
case EXPEVT_ADDR_ERR_ST:
KDASSERT(p && p->p_md.md_pcb->pcb_onfault != NULL);
if (p == NULL || p->p_md.md_pcb->pcb_onfault == 0)
goto do_panic;
tf->tf_spc = (int)p->p_md.md_pcb->pcb_onfault;
break;
case EXPEVT_ADDR_ERR_LD | EXP_USER:
case EXPEVT_ADDR_ERR_ST | EXP_USER:
sv.sival_ptr = (void *)va;
if (((int)va) < 0)
trapsignal(p, SIGSEGV, expevt & ~EXP_USER, SEGV_ACCERR,
sv);
else
trapsignal(p, SIGBUS, expevt & ~EXP_USER, BUS_ADRALN,
sv);
goto out;
case EXPEVT_RES_INST | EXP_USER:
case EXPEVT_SLOT_INST | EXP_USER:
sv.sival_ptr = (void *)tf->tf_spc;
trapsignal(p, SIGILL, expevt & ~EXP_USER, ILL_ILLOPC, sv);
goto out;
case EXPEVT_BREAK | EXP_USER:
sv.sival_ptr = (void *)tf->tf_spc;
trapsignal(p, SIGTRAP, expevt & ~EXP_USER, TRAP_TRACE, sv);
goto out;
#ifdef SH4
case EXPEVT_FPU_DISABLE | EXP_USER:
case EXPEVT_FPU_SLOT_DISABLE | EXP_USER:
sv.sival_ptr = (void *)tf->tf_spc;
trapsignal(p, SIGILL, expevt & ~EXP_USER, ILL_COPROC, sv);
goto out;
case EXPEVT_FPU | EXP_USER:
{
int fpscr, sigi;
__asm__ volatile ("sts fpscr, %0" : "=r" (fpscr));
fpscr = (fpscr & FPSCR_CAUSE_MASK) >> FPSCR_CAUSE_SHIFT;
if (fpscr & FPEXC_E)
sigi = FPE_FLTINV;
else if (fpscr & FPEXC_V)
sigi = FPE_FLTINV;
else if (fpscr & FPEXC_Z)
sigi = FPE_FLTDIV;
else if (fpscr & FPEXC_O)
sigi = FPE_FLTOVF;
else if (fpscr & FPEXC_U)
sigi = FPE_FLTUND;
else if (fpscr & FPEXC_I)
sigi = FPE_FLTRES;
else
sigi = 0;
sv.sival_ptr = (void *)tf->tf_spc;
trapsignal(p, SIGFPE, expevt & ~EXP_USER, sigi, sv);
}
goto out;
#endif
default:
goto do_panic;
}
if (!usermode)
return;
out:
userret(p);
return;
do_panic:
if ((expevt >> 5) < exp_types && exp_type[expevt >> 5] != NULL)
printf("fatal %s", exp_type[expevt >> 5]);
else
printf("EXPEVT 0x%03x", expevt);
printf(" in %s mode\n", expevt & EXP_USER ? "user" : "kernel");
printf("va 0x%x spc 0x%x ssr 0x%x pr 0x%x \n",
va, tf->tf_spc, tf->tf_ssr, tf->tf_pr);
panic("general_exception");
}
void
tlb_exception(struct proc *p, struct trapframe *tf, uint32_t va)
{
struct vm_map *map;
pmap_t pmap;
union sigval sv;
int usermode;
int err, track, access_type;
const char *panic_msg;
#define TLB_ASSERT(assert, msg) \
do { \
if (!(assert)) { \
panic_msg = msg; \
goto tlb_panic; \
} \
} while(0)
splx(tf->tf_ssr & PSL_IMASK);
usermode = !KERNELMODE(tf->tf_ssr);
if (usermode) {
KDASSERT(p->p_md.md_regs == tf);
refreshcreds(p);
} else {
KDASSERT(p == NULL ||
p == &proc0 ||
p->p_md.md_regs != tf);
}
switch (tf->tf_expevt) {
case EXPEVT_TLB_MISS_LD:
track = PG_PMAP_REF;
access_type = PROT_READ;
break;
case EXPEVT_TLB_MISS_ST:
track = PG_PMAP_REF;
access_type = PROT_WRITE;
break;
case EXPEVT_TLB_MOD:
track = PG_PMAP_REF | PG_PMAP_MOD;
access_type = PROT_WRITE;
break;
case EXPEVT_TLB_PROT_LD:
TLB_ASSERT((int)va > 0,
"kernel virtual protection fault (load)");
if (usermode) {
sv.sival_ptr = (void *)va;
trapsignal(p, SIGSEGV, tf->tf_expevt, SEGV_ACCERR, sv);
goto out;
} else {
TLB_ASSERT(p->p_md.md_pcb->pcb_onfault != NULL,
"no copyin/out fault handler (load protection)");
tf->tf_spc = (int)p->p_md.md_pcb->pcb_onfault;
}
return;
case EXPEVT_TLB_PROT_ST:
track = 0;
access_type = PROT_WRITE;
break;
default:
TLB_ASSERT(0, "impossible expevt");
}
if (usermode) {
if (!uvm_map_inentry(p, &p->p_spinentry, PROC_STACK(p),
"[%s]%d/%d sp=%lx inside %lx-%lx: not MAP_STACK\n",
uvm_map_inentry_sp, p->p_vmspace->vm_map.sserial))
goto out;
TLB_ASSERT(p != NULL, "no curproc");
map = &p->p_vmspace->vm_map;
pmap = map->pmap;
} else {
if ((int)va < 0) {
map = kernel_map;
pmap = pmap_kernel();
} else {
TLB_ASSERT(p != NULL &&
p->p_md.md_pcb->pcb_onfault != NULL,
"invalid user-space access from kernel mode");
if (va == 0) {
tf->tf_spc = (int)p->p_md.md_pcb->pcb_onfault;
return;
}
map = &p->p_vmspace->vm_map;
pmap = map->pmap;
}
}
if (track && __pmap_pte_load(pmap, va, track)) {
if (usermode)
userret(p);
return;
}
err = uvm_fault(map, va, 0, access_type);
if (usermode && access_type == PROT_READ && err == EACCES) {
access_type = PROT_EXEC;
err = uvm_fault(map, va, 0, access_type);
}
if (err == 0 && map != kernel_map)
uvm_grow(p, va);
if (err == 0) {
int loaded = __pmap_pte_load(pmap, va, track);
TLB_ASSERT(loaded, "page table entry not found");
if (usermode)
userret(p);
return;
}
if (usermode) {
sv.sival_ptr = (void *)va;
if (err == ENOMEM) {
printf("UVM: pid %d (%s), uid %d killed: out of swap\n",
p->p_p->ps_pid, p->p_p->ps_comm,
p->p_ucred ? (int)p->p_ucred->cr_uid : -1);
trapsignal(p, SIGKILL, tf->tf_expevt, SEGV_MAPERR, sv);
} else
trapsignal(p, SIGSEGV, tf->tf_expevt, SEGV_MAPERR, sv);
goto out;
} else {
TLB_ASSERT(p->p_md.md_pcb->pcb_onfault,
"no copyin/out fault handler (page not found)");
tf->tf_spc = (int)p->p_md.md_pcb->pcb_onfault;
}
return;
out:
userret(p);
ast(p, tf);
return;
tlb_panic:
panic("tlb_exception: %s\n"
"expevt=%x va=%08x ssr=%08x spc=%08x proc=%p onfault=%p",
panic_msg, tf->tf_expevt, va, tf->tf_ssr, tf->tf_spc,
p, p ? p->p_md.md_pcb->pcb_onfault : NULL);
#undef TLB_ASSERT
}
void
ast(struct proc *p, struct trapframe *tf)
{
if (KERNELMODE(tf->tf_ssr))
return;
KDASSERT(p != NULL);
KDASSERT(p->p_md.md_regs == tf);
while (p->p_md.md_astpending) {
p->p_md.md_astpending = 0;
refreshcreds(p);
atomic_inc_int(&uvmexp.softs);
mi_ast(p, curcpu()->ci_want_resched);
userret(p);
}
}
void
cachectl(struct proc *p, struct trapframe *tf)
{
vaddr_t va;
vsize_t len;
if (!SH_HAS_UNIFIED_CACHE) {
va = (vaddr_t)tf->tf_r4;
len = (vsize_t)tf->tf_r5;
if (va < VM_MIN_ADDRESS || va >= VM_MAXUSER_ADDRESS ||
va + len <= va || va + len >= VM_MAXUSER_ADDRESS)
len = 0;
if (len != 0)
sh_icache_sync_range_index(va, len);
}
userret(p);
}
void
syscall(struct proc *p, struct trapframe *tf)
{
caddr_t params;
const struct sysent *callp = sysent;
int error, opc;
int argsize;
register_t code, args[8], rval[2];
atomic_inc_int(&uvmexp.syscalls);
opc = tf->tf_spc;
params = (caddr_t)tf->tf_r15;
code = tf->tf_r0;
if (code > 0 && code < SYS_MAXSYSCALL)
callp += code;
argsize = callp->sy_argsize;
if (argsize) {
register_t *ap;
int off_t_arg;
switch (code) {
default: off_t_arg = 0; break;
case SYS_lseek:
case SYS_truncate:
case SYS_ftruncate: off_t_arg = 1; break;
case SYS_preadv:
case SYS_pwritev:
case SYS_pread:
case SYS_pwrite: off_t_arg = 3; break;
}
ap = args;
*ap++ = tf->tf_r4; argsize -= sizeof(int);
*ap++ = tf->tf_r5; argsize -= sizeof(int);
*ap++ = tf->tf_r6; argsize -= sizeof(int);
if (off_t_arg != 3) {
*ap++ = tf->tf_r7;
argsize -= sizeof(int);
}
if (argsize > 0) {
if ((error = copyin(params, ap, argsize)))
goto bad;
}
}
rval[0] = 0;
rval[1] = tf->tf_r1;
error = mi_syscall(p, code, callp, args, rval);
switch (error) {
case 0:
tf->tf_r0 = rval[0];
tf->tf_r1 = rval[1];
tf->tf_ssr |= PSL_TBIT;
break;
case ERESTART:
tf->tf_spc = opc - 2;
break;
case EJUSTRETURN:
break;
default:
bad:
tf->tf_r0 = error;
tf->tf_ssr &= ~PSL_TBIT;
break;
}
mi_syscall_return(p, code, error, rval);
}
void
child_return(void *arg)
{
struct proc *p = arg;
struct trapframe *tf = p->p_md.md_regs;
tf->tf_r0 = 0;
tf->tf_ssr |= PSL_TBIT;
mi_child_return(p);
}