#include <sys/param.h>
#include <sys/systm.h>
#include <sys/syscall.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/user.h>
#include <sys/syscall_mi.h>
#include <uvm/uvm_extern.h>
#include <machine/autoconf.h>
#ifdef DDB
#ifdef TRAPDEBUG
#include <ddb/db_output.h>
#else
#include <machine/db_machdep.h>
#endif
#endif
static __inline int inst_store(u_int ins) {
return (ins & 0xf0000000) == 0x60000000 ||
(ins & 0xf4000200) == 0x24000200 ||
(ins & 0xfc000200) == 0x0c000200 ||
(ins & 0xfc0003c0) == 0x0c0001c0;
}
int pcxs_unaligned(u_int opcode, vaddr_t va);
#ifdef PTRACE
void ss_clear_breakpoints(struct proc *p);
#endif
void ast(struct proc *);
#define SSBREAKPOINT (HPPA_BREAK_KERNEL | (HPPA_BREAK_SS << 13))
const char *trap_type[] = {
"invalid",
"HPMC",
"power failure",
"recovery counter",
"external interrupt",
"LPMC",
"ITLB miss fault",
"instruction protection",
"Illegal instruction",
"break instruction",
"privileged operation",
"privileged register",
"overflow",
"conditional",
"assist exception",
"DTLB miss",
"ITLB non-access miss",
"DTLB non-access miss",
"data protection/rights/alignment",
"data break",
"TLB dirty",
"page reference",
"assist emulation",
"higher-priv transfer",
"lower-priv transfer",
"taken branch",
"data access rights",
"data protection",
"unaligned data ref",
};
int trap_types = sizeof(trap_type)/sizeof(trap_type[0]);
#define frame_regmap(tf,r) (((u_int *)(tf))[hppa_regmap[(r)]])
u_char hppa_regmap[32] = {
offsetof(struct trapframe, tf_pad[0]) / 4,
offsetof(struct trapframe, tf_r1) / 4,
offsetof(struct trapframe, tf_rp) / 4,
offsetof(struct trapframe, tf_r3) / 4,
offsetof(struct trapframe, tf_r4) / 4,
offsetof(struct trapframe, tf_r5) / 4,
offsetof(struct trapframe, tf_r6) / 4,
offsetof(struct trapframe, tf_r7) / 4,
offsetof(struct trapframe, tf_r8) / 4,
offsetof(struct trapframe, tf_r9) / 4,
offsetof(struct trapframe, tf_r10) / 4,
offsetof(struct trapframe, tf_r11) / 4,
offsetof(struct trapframe, tf_r12) / 4,
offsetof(struct trapframe, tf_r13) / 4,
offsetof(struct trapframe, tf_r14) / 4,
offsetof(struct trapframe, tf_r15) / 4,
offsetof(struct trapframe, tf_r16) / 4,
offsetof(struct trapframe, tf_r17) / 4,
offsetof(struct trapframe, tf_r18) / 4,
offsetof(struct trapframe, tf_t4) / 4,
offsetof(struct trapframe, tf_t3) / 4,
offsetof(struct trapframe, tf_t2) / 4,
offsetof(struct trapframe, tf_t1) / 4,
offsetof(struct trapframe, tf_arg3) / 4,
offsetof(struct trapframe, tf_arg2) / 4,
offsetof(struct trapframe, tf_arg1) / 4,
offsetof(struct trapframe, tf_arg0) / 4,
offsetof(struct trapframe, tf_dp) / 4,
offsetof(struct trapframe, tf_ret0) / 4,
offsetof(struct trapframe, tf_ret1) / 4,
offsetof(struct trapframe, tf_sp) / 4,
offsetof(struct trapframe, tf_r31) / 4,
};
void
ast(struct proc *p)
{
if (p->p_md.md_astpending) {
p->p_md.md_astpending = 0;
atomic_inc_int(&uvmexp.softs);
mi_ast(p, curcpu()->ci_want_resched);
}
}
void
trap(int type, struct trapframe *frame)
{
struct proc *p = curproc;
vaddr_t va;
struct vm_map *map;
struct vmspace *vm;
register vm_prot_t access_type;
register pa_space_t space;
union sigval sv;
u_int opcode;
int ret, trapnum;
const char *tts;
#ifdef DIAGNOSTIC
int oldcpl = curcpu()->ci_cpl;
#endif
trapnum = type & ~T_USER;
opcode = frame->tf_iir;
if (trapnum <= T_EXCEPTION || trapnum == T_HIGHERPL ||
trapnum == T_LOWERPL || trapnum == T_TAKENBR ||
trapnum == T_IDEBUG || trapnum == T_PERFMON) {
va = frame->tf_iioq_head;
space = frame->tf_iisq_head;
access_type = PROT_EXEC;
} else {
va = frame->tf_ior;
space = frame->tf_isr;
if (va == frame->tf_iioq_head)
access_type = PROT_EXEC;
else if (inst_store(opcode))
access_type = PROT_WRITE;
else
access_type = PROT_READ;
}
if (frame->tf_flags & TFF_LAST)
p->p_md.md_regs = frame;
if (trapnum > trap_types)
tts = "reserved";
else
tts = trap_type[trapnum];
#ifdef TRAPDEBUG
if (trapnum != T_INTERRUPT && trapnum != T_IBREAK)
db_printf("trap: %x, %s for %x:%x at %x:%x, fl=%x, fp=%p\n",
type, tts, space, va, frame->tf_iisq_head,
frame->tf_iioq_head, frame->tf_flags, frame);
else if (trapnum == T_IBREAK)
db_printf("trap: break instruction %x:%x at %x:%x, fp=%p\n",
break5(opcode), break13(opcode),
frame->tf_iisq_head, frame->tf_iioq_head, frame);
{
extern int etext;
if (frame < (struct trapframe *)&etext) {
printf("trap: bogus frame ptr %p\n", frame);
goto dead_end;
}
}
#endif
if (trapnum != T_INTERRUPT) {
atomic_inc_int(&uvmexp.traps);
mtctl(frame->tf_eiem, CR_EIEM);
}
if (type & T_USER)
refreshcreds(p);
switch (type) {
case T_NONEXIST:
case T_NONEXIST | T_USER:
printf("trap: elvis has just left the building!\n");
goto dead_end;
case T_RECOVERY:
case T_RECOVERY | T_USER:
printf("trap: handicapped");
goto dead_end;
#ifdef DIAGNOSTIC
case T_EXCEPTION:
panic("FPU/SFU emulation botch");
case T_PRIV_OP:
case T_PRIV_REG:
case T_HPMC:
case T_HPMC | T_USER:
#endif
case T_IBREAK:
case T_DATALIGN:
case T_DBREAK:
dead_end:
#ifdef DDB
if (db_ktrap(type, va, frame)) {
if (type == T_IBREAK) {
frame->tf_iioq_head = frame->tf_iioq_tail;
frame->tf_iioq_tail += 4;
}
return;
}
#else
if (type == T_DATALIGN || type == T_DPROT)
panic ("trap: %s at 0x%lx", tts, va);
else
panic ("trap: no debugger for \"%s\" (%d)", tts, type);
#endif
break;
case T_IBREAK | T_USER:
case T_DBREAK | T_USER: {
int code = TRAP_BRKPT;
#ifdef PTRACE
KERNEL_LOCK();
ss_clear_breakpoints(p);
if (opcode == SSBREAKPOINT)
code = TRAP_TRACE;
KERNEL_UNLOCK();
#endif
sv.sival_int = va;
trapsignal(p, SIGTRAP, type & ~T_USER, code, sv);
}
break;
#ifdef PTRACE
case T_TAKENBR | T_USER:
KERNEL_LOCK();
ss_clear_breakpoints(p);
KERNEL_UNLOCK();
sv.sival_int = va;
trapsignal(p, SIGTRAP, type & ~T_USER, TRAP_TRACE, sv);
break;
#endif
case T_EXCEPTION | T_USER: {
struct hppa_fpstate *hfp;
u_int64_t *fpp;
u_int32_t *pex;
int i, flt;
hfp = (struct hppa_fpstate *)frame->tf_cr30;
fpp = (u_int64_t *)&hfp->hfp_regs;
pex = (u_int32_t *)&fpp[0];
for (i = 0, pex++; i < 7 && !*pex; i++, pex++)
;
flt = 0;
if (i < 7) {
u_int32_t stat = HPPA_FPU_OP(*pex);
if (stat & HPPA_FPU_UNMPL)
flt = FPE_FLTINV;
else if (stat & (HPPA_FPU_V << 1))
flt = FPE_FLTINV;
else if (stat & (HPPA_FPU_Z << 1))
flt = FPE_FLTDIV;
else if (stat & (HPPA_FPU_I << 1))
flt = FPE_FLTRES;
else if (stat & (HPPA_FPU_O << 1))
flt = FPE_FLTOVF;
else if (stat & (HPPA_FPU_U << 1))
flt = FPE_FLTUND;
while (i++ < 7)
*pex++ = 0;
}
fpp[0] &= ~(((u_int64_t)HPPA_FPU_T) << 32);
sv.sival_int = va;
trapsignal(p, SIGFPE, type & ~T_USER, flt, sv);
}
break;
case T_EMULATION:
panic("trap: emulation trap in the kernel");
break;
case T_EMULATION | T_USER:
sv.sival_int = va;
trapsignal(p, SIGILL, type & ~T_USER, ILL_COPROC, sv);
break;
case T_OVERFLOW | T_USER:
sv.sival_int = va;
trapsignal(p, SIGFPE, type & ~T_USER, FPE_INTOVF, sv);
break;
case T_CONDITION | T_USER:
sv.sival_int = va;
trapsignal(p, SIGFPE, type & ~T_USER, FPE_INTDIV, sv);
break;
case T_PRIV_OP | T_USER:
sv.sival_int = va;
trapsignal(p, SIGILL, type & ~T_USER, ILL_PRVOPC, sv);
break;
case T_PRIV_REG | T_USER:
if (cpu_type == hpcxs &&
(opcode & (0xfc1fffe0 | (0x1e << 21))) ==
(0x000008a0 | (0x1a << 21))) {
register_t cr;
if (((opcode >> 21) & 0x1f) == 27)
cr = frame->tf_cr27;
else
cr = 0;
frame_regmap(frame, opcode & 0x1f) = cr;
frame->tf_ipsw |= PSL_N;
} else {
sv.sival_int = va;
trapsignal(p, SIGILL, type & ~T_USER, ILL_PRVREG, sv);
}
break;
case T_HIGHERPL | T_USER:
case T_LOWERPL | T_USER:
case T_DATAPID | T_USER:
sv.sival_int = va;
trapsignal(p, SIGSEGV, access_type, SEGV_ACCERR, sv);
break;
case T_DPROT | T_USER:
if (cpu_type == hpcxs) {
if (pcxs_unaligned(opcode, va))
goto datalign_user;
else
goto datacc;
}
sv.sival_int = va;
trapsignal(p, SIGSEGV, access_type, SEGV_ACCERR, sv);
break;
case T_ITLBMISSNA:
case T_ITLBMISSNA | T_USER:
case T_DTLBMISSNA:
case T_DTLBMISSNA | T_USER:
if (space == HPPA_SID_KERNEL)
map = kernel_map;
else {
vm = p->p_vmspace;
map = &vm->vm_map;
}
if ((opcode & 0xfc003fc0) == 0x04001340) {
frame_regmap(frame, opcode & 0x1f) = 0;
frame->tf_ipsw |= PSL_N;
} else if ((opcode & 0xfc001f80) == 0x04001180) {
int pl;
if (opcode & 0x2000)
pl = (opcode >> 16) & 3;
else
pl = frame_regmap(frame,
(opcode >> 16) & 0x1f) & 3;
KERNEL_LOCK();
if ((type & T_USER && space == HPPA_SID_KERNEL) ||
(frame->tf_iioq_head & 3) != pl ||
(type & T_USER && va >= VM_MAXUSER_ADDRESS) ||
uvm_fault(map, trunc_page(va), 0,
opcode & 0x40? PROT_WRITE : PROT_READ)) {
frame_regmap(frame, opcode & 0x1f) = 0;
frame->tf_ipsw |= PSL_N;
}
KERNEL_UNLOCK();
} else if (type & T_USER) {
sv.sival_int = va;
trapsignal(p, SIGILL, type & ~T_USER, ILL_ILLTRP, sv);
} else
panic("trap: %s @ 0x%lx:0x%lx for 0x%x:0x%lx irr 0x%08x",
tts, frame->tf_iisq_head, frame->tf_iioq_head,
space, va, opcode);
break;
case T_IPROT | T_USER:
case T_TLB_DIRTY:
case T_TLB_DIRTY | T_USER:
case T_DATACC:
case T_DATACC | T_USER:
datacc:
case T_ITLBMISS:
case T_ITLBMISS | T_USER:
case T_DTLBMISS:
case T_DTLBMISS | T_USER:
if (type & T_USER) {
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;
}
if (space == HPPA_SID_KERNEL)
map = kernel_map;
else {
vm = p->p_vmspace;
map = &vm->vm_map;
}
if ((type & T_USER && va >= VM_MAXUSER_ADDRESS) ||
(type & T_USER && map->pmap->pm_space != space)) {
sv.sival_int = va;
trapsignal(p, SIGSEGV, access_type, SEGV_MAPERR, sv);
break;
}
KERNEL_LOCK();
ret = uvm_fault(map, trunc_page(va), 0, access_type);
KERNEL_UNLOCK();
if (ret == 0 && space != HPPA_SID_KERNEL)
uvm_grow(p, va);
if (ret != 0) {
if (type & T_USER) {
int signal, sicode;
signal = SIGSEGV;
sicode = SEGV_MAPERR;
if (ret == EACCES)
sicode = SEGV_ACCERR;
if (ret == EIO) {
signal = SIGBUS;
sicode = BUS_OBJERR;
}
sv.sival_int = va;
trapsignal(p, signal, access_type, sicode, sv);
} else {
if (p && p->p_addr->u_pcb.pcb_onfault) {
frame->tf_iioq_tail = 4 +
(frame->tf_iioq_head =
p->p_addr->u_pcb.pcb_onfault);
#ifdef DDB
frame->tf_iir = 0;
#endif
} else {
panic("trap: "
"uvm_fault(%p, %lx, 0, %d): %d",
map, va, access_type, ret);
}
}
}
break;
case T_DATAPID:
if (p && p->p_addr->u_pcb.pcb_onfault) {
frame->tf_iioq_tail = 4 +
(frame->tf_iioq_head =
p->p_addr->u_pcb.pcb_onfault);
#ifdef DDB
frame->tf_iir = 0;
#endif
} else
goto dead_end;
break;
case T_DATALIGN | T_USER:
datalign_user:
sv.sival_int = va;
trapsignal(p, SIGBUS, access_type, BUS_ADRALN, sv);
break;
case T_INTERRUPT:
case T_INTERRUPT | T_USER:
cpu_intr(frame);
break;
case T_CONDITION:
panic("trap: divide by zero in the kernel");
break;
case T_ILLEGAL:
case T_ILLEGAL | T_USER:
if ((opcode & 0xfffffe00) == 0x10000200) {
frame_regmap(frame, opcode & 0x1f) = 0;
frame->tf_ipsw |= PSL_N;
break;
}
if (type & T_USER) {
sv.sival_int = va;
trapsignal(p, SIGILL, type & ~T_USER, ILL_ILLOPC, sv);
break;
}
case T_DPROT:
if (cpu_type == hpcxs) {
if (pcxs_unaligned(opcode, va))
goto dead_end;
else
goto datacc;
}
case T_LOWERPL:
case T_IPROT:
case T_OVERFLOW:
case T_HIGHERPL:
case T_TAKENBR:
case T_POWERFAIL:
case T_LPMC:
case T_PAGEREF:
default:
#ifdef TRAPDEBUG
if (db_ktrap(type, va, frame))
return;
#endif
panic("trap: unimplemented \'%s\' (%d)", tts, trapnum);
}
#ifdef DIAGNOSTIC
if (curcpu()->ci_cpl != oldcpl)
printf("WARNING: SPL (%d) NOT LOWERED ON "
"TRAP (%d) EXIT\n", curcpu()->ci_cpl, trapnum);
#endif
if (trapnum != T_INTERRUPT)
splx(curcpu()->ci_cpl);
if ((type & T_USER) && !(frame->tf_iisq_head == HPPA_SID_KERNEL &&
(frame->tf_iioq_head & ~PAGE_MASK) == SYSCALLGATE)) {
ast(p);
out:
userret(p);
}
}
void
child_return(void *arg)
{
struct proc *p = (struct proc *)arg;
struct trapframe *tf = p->p_md.md_regs;
tf->tf_ret0 = 0;
tf->tf_t1 = 0;
KERNEL_UNLOCK();
ast(p);
mi_child_return(p);
}
#ifdef PTRACE
#include <sys/ptrace.h>
int ss_get_value(struct proc *p, vaddr_t addr, u_int *value);
int ss_put_value(struct proc *p, vaddr_t addr, u_int value);
int
ss_get_value(struct proc *p, vaddr_t addr, u_int *value)
{
struct uio uio;
struct iovec iov;
iov.iov_base = (caddr_t)value;
iov.iov_len = sizeof(u_int);
uio.uio_iov = &iov;
uio.uio_iovcnt = 1;
uio.uio_offset = (off_t)addr;
uio.uio_resid = sizeof(u_int);
uio.uio_segflg = UIO_SYSSPACE;
uio.uio_rw = UIO_READ;
uio.uio_procp = curproc;
return (process_domem(curproc, p->p_p, &uio, PT_READ_I));
}
int
ss_put_value(struct proc *p, vaddr_t addr, u_int value)
{
struct uio uio;
struct iovec iov;
iov.iov_base = (caddr_t)&value;
iov.iov_len = sizeof(u_int);
uio.uio_iov = &iov;
uio.uio_iovcnt = 1;
uio.uio_offset = (off_t)addr;
uio.uio_resid = sizeof(u_int);
uio.uio_segflg = UIO_SYSSPACE;
uio.uio_rw = UIO_WRITE;
uio.uio_procp = curproc;
return (process_domem(curproc, p->p_p, &uio, PT_WRITE_I));
}
void
ss_clear_breakpoints(struct proc *p)
{
if (p->p_md.md_bpva != 0) {
ss_put_value(p, p->p_md.md_bpva, p->p_md.md_bpsave[0]);
ss_put_value(p, p->p_md.md_bpva + 4, p->p_md.md_bpsave[1]);
p->p_md.md_bpva = 0;
}
}
int
process_sstep(struct proc *p, int sstep)
{
int error;
ss_clear_breakpoints(p);
if (sstep == 0) {
p->p_md.md_regs->tf_ipsw &= ~PSL_T;
return (0);
}
if ((p->p_md.md_regs->tf_iioq_tail & ~PAGE_MASK) == SYSCALLGATE)
p->p_md.md_bpva = p->p_md.md_regs->tf_r31 & ~HPPA_PC_PRIV_MASK;
else
p->p_md.md_bpva = p->p_md.md_regs->tf_iioq_tail & ~HPPA_PC_PRIV_MASK;
error = ss_get_value(p, p->p_md.md_bpva, &p->p_md.md_bpsave[0]);
if (error)
return (error);
error = ss_get_value(p, p->p_md.md_bpva + 4, &p->p_md.md_bpsave[1]);
if (error)
return (error);
error = ss_put_value(p, p->p_md.md_bpva, SSBREAKPOINT);
if (error)
return (error);
error = ss_put_value(p, p->p_md.md_bpva + 4, SSBREAKPOINT);
if (error)
return (error);
if ((p->p_md.md_regs->tf_iioq_tail & ~PAGE_MASK) != SYSCALLGATE)
p->p_md.md_regs->tf_ipsw |= PSL_T;
else
p->p_md.md_regs->tf_ipsw &= ~PSL_T;
return (0);
}
#endif
void syscall(struct trapframe *frame);
void
syscall(struct trapframe *frame)
{
struct proc *p = curproc;
const struct sysent *callp = sysent;
int code, argsize, argoff, error;
register_t args[8], rval[2];
#ifdef DIAGNOSTIC
int oldcpl = curcpu()->ci_cpl;
#endif
atomic_inc_int(&uvmexp.syscalls);
if (!USERMODE(frame->tf_iioq_head))
panic("syscall");
p->p_md.md_regs = frame;
argoff = 4;
code = frame->tf_t1;
args[0] = frame->tf_arg0;
args[1] = frame->tf_arg1;
args[2] = frame->tf_arg2;
args[3] = frame->tf_arg3;
if (code > 0 && code < SYS_MAXSYSCALL)
callp += code;
if ((argsize = callp->sy_argsize)) {
register_t *s, *e, t;
int i;
argsize -= argoff * 4;
if (argsize > 0) {
i = argsize / 4;
if ((error = copyin((void *)(frame->tf_sp +
HPPA_FRAME_ARG(4 + i - 1)), args + argoff,
argsize)))
goto bad;
s = args + argoff;
e = s + i - 1;
while (s < e) {
t = *s;
*s = *e;
*e = t;
s++, e--;
}
}
i = 0;
switch (code) {
case SYS_lseek:
case SYS_truncate:
case SYS_ftruncate: i = 2; break;
case SYS_preadv:
case SYS_pwritev:
case SYS_pread:
case SYS_pwrite: i = 4; break;
case SYS_mquery:
case SYS_mmap: i = 6; break;
}
if (i) {
t = args[i];
args[i] = args[i + 1];
args[i + 1] = t;
}
}
rval[0] = 0;
rval[1] = frame->tf_ret1;
error = mi_syscall(p, code, callp, args, rval);
switch (error) {
case 0:
frame->tf_ret0 = rval[0];
frame->tf_ret1 = rval[1];
frame->tf_t1 = 0;
break;
case ERESTART:
frame->tf_iioq_head -= 12;
frame->tf_iioq_tail -= 12;
case EJUSTRETURN:
break;
default:
bad:
frame->tf_t1 = error;
frame->tf_ret0 = error;
frame->tf_ret1 = 0;
break;
}
ast(p);
mi_syscall_return(p, code, error, rval);
#ifdef DIAGNOSTIC
if (curcpu()->ci_cpl != oldcpl) {
printf("WARNING: SPL (0x%x) NOT LOWERED ON "
"syscall(0x%x, 0x%lx, 0x%lx, 0x%lx...) EXIT, PID %d\n",
curcpu()->ci_cpl, code, args[0], args[1], args[2],
p->p_p->ps_pid);
curcpu()->ci_cpl = oldcpl;
}
#endif
splx(curcpu()->ci_cpl);
}
int
pcxs_unaligned(u_int opcode, vaddr_t va)
{
u_int mbz_bits;
if ((va & 0x0f) == 0)
return 0;
mbz_bits = 0;
if ((opcode & 0xd0000000) == 0x40000000) {
switch ((opcode >> 26) & 0x03) {
case 0x00:
mbz_bits = 0x00;
break;
case 0x01:
mbz_bits = 0x01;
break;
case 0x02:
case 0x03:
mbz_bits = 0x03;
break;
}
} else
if ((opcode & 0xfc000000) == 0x0c000000) {
switch ((opcode >> 6) & 0x0f) {
case 0x01:
mbz_bits = 0x01;
break;
case 0x02:
mbz_bits = 0x03;
break;
case 0x07:
mbz_bits = 0x0f;
break;
case 0x09:
if ((opcode & (1 << 12)) != 0)
mbz_bits = 0x01;
break;
case 0x0a:
if ((opcode & (1 << 12)) != 0)
mbz_bits = 0x03;
break;
}
} else
if ((opcode & 0xf4000000) == 0x24000000) {
if ((opcode & (1 << 27)) != 0) {
mbz_bits = 0x03;
} else {
mbz_bits = 0x07;
}
}
return (va & mbz_bits);
}