#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/signal.h>
#include <sys/signalvar.h>
#include <sys/sysctl.h>
#include <sys/sysent.h>
#include <machine/atomic.h>
#include <machine/frame.h>
#define _MD_WANT_SWAPWORD
#include <machine/md_var.h>
#include <machine/pcb.h>
#include <machine/undefined.h>
#include <machine/vmparam.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
#define INSN_COND(insn) ((insn >> 28) & ~0x1)
#define INSN_COND_INVERTED(insn) ((insn >> 28) & 0x1)
#define INSN_COND_EQ 0x00
#define INSN_COND_CS 0x02
#define INSN_COND_MI 0x04
#define INSN_COND_VS 0x06
#define INSN_COND_HI 0x08
#define INSN_COND_GE 0x0a
#define INSN_COND_GT 0x0c
#define INSN_COND_AL 0x0e
MALLOC_DEFINE(M_UNDEF, "undefhandler", "Undefined instruction handler data");
#ifdef COMPAT_FREEBSD32
#ifndef EMUL_SWP
#define EMUL_SWP 0
#endif
SYSCTL_DECL(_compat_arm);
static bool compat32_emul_swp = EMUL_SWP;
SYSCTL_BOOL(_compat_arm, OID_AUTO, emul_swp,
CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &compat32_emul_swp, 0,
"Enable SWP/SWPB emulation");
#endif
struct undef_handler {
LIST_ENTRY(undef_handler) uh_link;
undef_handler_t uh_handler;
};
struct sys_handler {
LIST_ENTRY(sys_handler) sys_link;
undef_sys_handler_t sys_handler;
};
LIST_HEAD(, sys_handler) sys_handlers = LIST_HEAD_INITIALIZER(sys_handler);
LIST_HEAD(, undef_handler) undef_handlers =
LIST_HEAD_INITIALIZER(undef_handlers);
#ifdef COMPAT_FREEBSD32
LIST_HEAD(, undef_handler) undef32_handlers =
LIST_HEAD_INITIALIZER(undef32_handlers);
#endif
static bool
arm_cond_match(uint32_t insn, struct trapframe *frame)
{
uint64_t spsr;
uint32_t cond;
bool invert;
bool match;
spsr = frame->tf_spsr;
cond = INSN_COND(insn);
invert = INSN_COND_INVERTED(insn);
switch (cond) {
case INSN_COND_EQ:
match = (spsr & PSR_Z) != 0;
break;
case INSN_COND_CS:
match = (spsr & PSR_C) != 0;
break;
case INSN_COND_MI:
match = (spsr & PSR_N) != 0;
break;
case INSN_COND_VS:
match = (spsr & PSR_V) != 0;
break;
case INSN_COND_HI:
match = (spsr & (PSR_C | PSR_Z)) == PSR_C;
break;
case INSN_COND_GE:
match = (!(spsr & PSR_N) == !(spsr & PSR_V));
break;
case INSN_COND_GT:
match = !(spsr & PSR_Z) && (!(spsr & PSR_N) == !(spsr & PSR_V));
break;
case INSN_COND_AL:
match = true;
break;
default:
__assert_unreachable();
}
return (match != invert);
}
#ifdef COMPAT_FREEBSD32
#define GDB_BREAKPOINT 0xe6000011
#define GDB5_BREAKPOINT 0xe7ffdefe
static int
gdb_trapper(vm_offset_t va, uint32_t insn, struct trapframe *frame,
uint32_t esr)
{
struct thread *td = curthread;
if (insn == GDB_BREAKPOINT || insn == GDB5_BREAKPOINT) {
if (va < VM_MAXUSER_ADDRESS) {
ksiginfo_t ksi;
ksiginfo_init_trap(&ksi);
ksi.ksi_signo = SIGTRAP;
ksi.ksi_code = TRAP_BRKPT;
ksi.ksi_addr = (void *)va;
trapsignal(td, &ksi);
return 1;
}
}
return 0;
}
static int
swp_emulate(vm_offset_t va, uint32_t insn, struct trapframe *frame,
uint32_t esr)
{
ksiginfo_t ksi;
struct thread *td;
vm_offset_t vaddr;
uint64_t *regs;
uint32_t val;
int attempts, error, Rn, Rd, Rm;
bool is_swpb;
td = curthread;
if (!compat32_emul_swp || (frame->tf_spsr & PSR_T) != 0)
return (0);
else if ((insn & 0x0fb00ff0) != 0x01000090)
return (0);
else if (!arm_cond_match(insn, frame))
goto next;
Rn = (insn & 0xf0000) >> 16;
Rd = (insn & 0xf000) >> 12;
Rm = (insn & 0xf);
regs = frame->tf_x;
vaddr = regs[Rn] & 0xffffffff;
val = regs[Rm];
is_swpb = (insn & 0x00400000) != 0;
if (!is_swpb && (vaddr & 3) != 0)
goto fault;
attempts = 0;
do {
if (is_swpb) {
uint8_t bval;
bval = val;
error = swapueword8((void *)vaddr, &bval);
val = bval;
} else {
error = swapueword32((void *)vaddr, &val);
}
if (error == -1)
goto fault;
if (error != 0 && (++attempts % 5) == 0)
maybe_yield();
} while (error != 0);
regs[Rd] = val;
next:
frame->tf_elr += 4;
return (1);
fault:
ksiginfo_init_trap(&ksi);
ksi.ksi_signo = SIGSEGV;
ksi.ksi_code = SEGV_MAPERR;
ksi.ksi_addr = (void *)va;
trapsignal(td, &ksi);
return (1);
}
#endif
void
undef_init(void)
{
#ifdef COMPAT_FREEBSD32
install_undef32_handler(gdb_trapper);
install_undef32_handler(swp_emulate);
#endif
}
void *
install_undef_handler(undef_handler_t func)
{
struct undef_handler *uh;
uh = malloc(sizeof(*uh), M_UNDEF, M_WAITOK);
uh->uh_handler = func;
LIST_INSERT_HEAD(&undef_handlers, uh, uh_link);
return (uh);
}
#ifdef COMPAT_FREEBSD32
void *
install_undef32_handler(undef_handler_t func)
{
struct undef_handler *uh;
uh = malloc(sizeof(*uh), M_UNDEF, M_WAITOK);
uh->uh_handler = func;
LIST_INSERT_HEAD(&undef32_handlers, uh, uh_link);
return (uh);
}
#endif
void
remove_undef_handler(void *handle)
{
struct undef_handler *uh;
uh = handle;
LIST_REMOVE(uh, uh_link);
free(handle, M_UNDEF);
}
void
install_sys_handler(undef_sys_handler_t func)
{
struct sys_handler *sysh;
sysh = malloc(sizeof(*sysh), M_UNDEF, M_WAITOK);
sysh->sys_handler = func;
LIST_INSERT_HEAD(&sys_handlers, sysh, sys_link);
}
bool
undef_sys(uint64_t esr, struct trapframe *frame)
{
struct sys_handler *sysh;
LIST_FOREACH(sysh, &sys_handlers, sys_link) {
if (sysh->sys_handler(esr, frame))
return (true);
}
return (false);
}
static bool
undef_sys_insn(struct trapframe *frame, uint32_t insn)
{
uint64_t esr;
bool read;
#define MRS_MASK 0xfff00000
#define MRS_VALUE 0xd5300000
#define MSR_REG_VALUE 0xd5100000
#define MSR_IMM_VALUE 0xd5000000
#define MRS_REGISTER(insn) ((insn) & 0x0000001f)
#define MRS_Op0_SHIFT 19
#define MRS_Op0_MASK 0x00180000
#define MRS_Op1_SHIFT 16
#define MRS_Op1_MASK 0x00070000
#define MRS_CRn_SHIFT 12
#define MRS_CRn_MASK 0x0000f000
#define MRS_CRm_SHIFT 8
#define MRS_CRm_MASK 0x00000f00
#define MRS_Op2_SHIFT 5
#define MRS_Op2_MASK 0x000000e0
read = false;
switch (insn & MRS_MASK) {
case MRS_VALUE:
read = true;
break;
case MSR_REG_VALUE:
break;
case MSR_IMM_VALUE:
if (MRS_REGISTER(insn) != 31)
return (false);
if ((insn & MRS_CRn_MASK) >> MRS_CRn_SHIFT != 4)
return (false);
if ((insn & MRS_Op0_MASK) >> MRS_Op0_SHIFT != 0)
return (false);
break;
default:
return (false);
}
esr = EXCP_MSR << ESR_ELx_EC_SHIFT;
esr |= ESR_ELx_IL;
esr |= __ISS_MSR_REG(
(insn & MRS_Op0_MASK) >> MRS_Op0_SHIFT,
(insn & MRS_Op1_MASK) >> MRS_Op1_SHIFT,
(insn & MRS_CRn_MASK) >> MRS_CRn_SHIFT,
(insn & MRS_CRm_MASK) >> MRS_CRm_SHIFT,
(insn & MRS_Op2_MASK) >> MRS_Op2_SHIFT);
esr |= MRS_REGISTER(insn) << ISS_MSR_Rt_SHIFT;
if (read)
esr |= ISS_MSR_DIR;
#undef MRS_MASK
#undef MRS_VALUE
#undef MSR_REG_VALUE
#undef MSR_IMM_VALUE
#undef MRS_REGISTER
#undef MRS_Op0_SHIFT
#undef MRS_Op0_MASK
#undef MRS_Op1_SHIFT
#undef MRS_Op1_MASK
#undef MRS_CRn_SHIFT
#undef MRS_CRn_MASK
#undef MRS_CRm_SHIFT
#undef MRS_CRm_MASK
#undef MRS_Op2_SHIFT
#undef MRS_Op2_MASK
return (undef_sys(esr, frame));
}
int
undef_insn(struct trapframe *frame)
{
struct undef_handler *uh;
uint32_t insn;
int ret;
ret = fueword32((uint32_t *)frame->tf_elr, &insn);
if (ret != 0)
return (0);
#ifdef COMPAT_FREEBSD32
if (SV_PROC_FLAG(curthread->td_proc, SV_ILP32)) {
LIST_FOREACH(uh, &undef32_handlers, uh_link) {
ret = uh->uh_handler(frame->tf_elr, insn, frame,
frame->tf_esr);
if (ret)
return (1);
}
return (0);
}
#endif
if (undef_sys_insn(frame, insn))
return (1);
LIST_FOREACH(uh, &undef_handlers, uh_link) {
ret = uh->uh_handler(frame->tf_elr, insn, frame, frame->tf_esr);
if (ret)
return (1);
}
return (0);
}