#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fix_unaligned.c,v 1.2 2022/06/02 00:32:14 rin Exp $");
#include "opt_ddb.h"
#include "opt_ppcarch.h"
#include <sys/param.h>
#include <sys/types.h>
#include <sys/evcnt.h>
#include <sys/siginfo.h>
#include <sys/systm.h>
#include <powerpc/frame.h>
#include <powerpc/instr.h>
#include <powerpc/trap.h>
#define UA_EVCNT_ATTACH(name) \
static struct evcnt unaligned_ev_##name = \
EVCNT_INITIALIZER(EVCNT_TYPE_TRAP, NULL, "unaligned", #name); \
EVCNT_ATTACH_STATIC(unaligned_ev_##name)
#define UA_EVCNT_INCR(name) unaligned_ev_##name.ev_count++
UA_EVCNT_ATTACH(lwz);
UA_EVCNT_ATTACH(lwzu);
UA_EVCNT_ATTACH(stw);
UA_EVCNT_ATTACH(stwu);
UA_EVCNT_ATTACH(lhz);
UA_EVCNT_ATTACH(lhzu);
UA_EVCNT_ATTACH(lha);
UA_EVCNT_ATTACH(lhau);
UA_EVCNT_ATTACH(sth);
UA_EVCNT_ATTACH(sthu);
UA_EVCNT_ATTACH(lwzx);
UA_EVCNT_ATTACH(lwzux);
UA_EVCNT_ATTACH(stwx);
UA_EVCNT_ATTACH(stwux);
UA_EVCNT_ATTACH(lhzx);
UA_EVCNT_ATTACH(lhzux);
UA_EVCNT_ATTACH(lhax);
UA_EVCNT_ATTACH(lhaux);
UA_EVCNT_ATTACH(sthx);
UA_EVCNT_ATTACH(sthux);
UA_EVCNT_ATTACH(lwbrx);
UA_EVCNT_ATTACH(stwbrx);
UA_EVCNT_ATTACH(lhbrx);
UA_EVCNT_ATTACH(sthbrx);
UA_EVCNT_ATTACH(lmw);
UA_EVCNT_ATTACH(stmw);
UA_EVCNT_ATTACH(isi);
UA_EVCNT_ATTACH(dsi);
UA_EVCNT_ATTACH(unknown);
UA_EVCNT_ATTACH(invalid);
#if 0
#define UNALIGNED_DEBUG 1
#define FIX_UNALIGNED_LSTMW 1
#endif
#if defined(UNALIGNED_DEBUG)
int unaligned_debug = 1;
#elif defined(DEBUG)
int unaligned_debug = 0;
#endif
#if defined(UNALIGNED_DEBUG) || defined(DEBUG)
#define DPRINTF(fmt, args...) \
do { \
if (unaligned_debug) \
printf("%s: " fmt, __func__, ##args); \
} while (0)
#else
#define DPRINTF(fmt, args...) __nothing
#endif
#if defined(DDB) && (defined(UNALIGNED_DEBUG) || defined(DEBUG))
extern vaddr_t opc_disasm(vaddr_t, int);
#define DISASM(tf, insn) \
do { \
if (unaligned_debug) \
opc_disasm((tf)->tf_srr0, (insn)->i_int); \
} while (0)
#else
#define DISASM(tf, insn) __nothing
#endif
static bool emul_unaligned(struct trapframe *, ksiginfo_t *,
const union instr *);
static bool do_lst(struct trapframe *, const union instr *, int);
#ifdef FIX_UNALIGNED_LSTMW
static bool do_lstmw(struct trapframe *, const union instr *, int);
#endif
bool
fix_unaligned(struct trapframe *tf, ksiginfo_t *ksi)
{
union instr insn;
int ret;
KSI_INIT_TRAP(ksi);
ret = ufetch_32((uint32_t *)tf->tf_srr0, (uint32_t *)&insn.i_int);
if (ret) {
UA_EVCNT_INCR(isi);
DPRINTF("EXC_ISI: ret: %d, srr0: 0x%08lx dear: 0x%08lx\n",
ret, tf->tf_srr0, tf->tf_dear);
ksi->ksi_signo = SIGSEGV;
ksi->ksi_trap = EXC_ISI;
ksi->ksi_code = SEGV_MAPERR;
ksi->ksi_addr = (void *)tf->tf_srr0;
return true;
}
if (emul_unaligned(tf, ksi, &insn))
return true;
CTASSERT(sizeof(insn) == 4);
tf->tf_srr0 += sizeof(insn);
return false;
}
#define UAF_STORE 0
#define UAF_LOAD __BIT(0)
#define UAF_HALF __BIT(1)
#define UAF_ALGEBRA __BIT(2)
#define UAF_REVERSE __BIT(3)
#define UAF_UPDATE __BIT(4)
static bool
emul_unaligned(struct trapframe *tf, ksiginfo_t *ksi, const union instr *insn)
{
int flags;
switch (insn->i_any.i_opcd) {
case OPC_LWZ:
UA_EVCNT_INCR(lwz);
flags = UAF_LOAD;
break;
case OPC_LWZU:
UA_EVCNT_INCR(lwzu);
flags = UAF_LOAD | UAF_UPDATE;
break;
case OPC_STW:
UA_EVCNT_INCR(stw);
flags = UAF_STORE;
break;
case OPC_STWU:
UA_EVCNT_INCR(stwu);
flags = UAF_STORE | UAF_UPDATE;
break;
case OPC_LHZ:
UA_EVCNT_INCR(lhz);
flags = UAF_LOAD | UAF_HALF;
break;
case OPC_LHZU:
UA_EVCNT_INCR(lhzu);
flags = UAF_LOAD | UAF_HALF | UAF_UPDATE;
break;
case OPC_LHA:
UA_EVCNT_INCR(lha);
flags = UAF_LOAD | UAF_HALF | UAF_ALGEBRA;
break;
case OPC_LHAU:
UA_EVCNT_INCR(lhau);
flags = UAF_LOAD | UAF_HALF | UAF_ALGEBRA | UAF_UPDATE;
break;
case OPC_STH:
UA_EVCNT_INCR(sth);
flags = UAF_STORE | UAF_HALF;
break;
case OPC_STHU:
UA_EVCNT_INCR(sthu);
flags = UAF_STORE | UAF_HALF | UAF_UPDATE;
break;
case OPC_integer_31:
switch (insn->i_x.i_xo) {
case OPC31_LWZX:
UA_EVCNT_INCR(lwzx);
flags = UAF_LOAD;
break;
case OPC31_LWZUX:
UA_EVCNT_INCR(lwzux);
flags = UAF_LOAD | UAF_UPDATE;
break;
case OPC31_STWX:
UA_EVCNT_INCR(stwx);
flags = UAF_STORE;
break;
case OPC31_STWUX:
UA_EVCNT_INCR(stwux);
flags = UAF_STORE | UAF_UPDATE;
break;
case OPC31_LHZX:
UA_EVCNT_INCR(lhzx);
flags = UAF_LOAD | UAF_HALF;
break;
case OPC31_LHZUX:
UA_EVCNT_INCR(lhzux);
flags = UAF_LOAD | UAF_HALF | UAF_UPDATE;
break;
case OPC31_LHAX:
UA_EVCNT_INCR(lhax);
flags = UAF_LOAD | UAF_HALF | UAF_ALGEBRA;
break;
case OPC31_LHAUX:
UA_EVCNT_INCR(lhaux);
flags = UAF_LOAD | UAF_HALF | UAF_ALGEBRA | UAF_UPDATE;
break;
case OPC31_STHX:
UA_EVCNT_INCR(sthx);
flags = UAF_STORE | UAF_HALF;
break;
case OPC31_STHUX:
UA_EVCNT_INCR(sthux);
flags = UAF_STORE | UAF_HALF | UAF_UPDATE;
break;
case OPC31_LWBRX:
UA_EVCNT_INCR(lwbrx);
flags = UAF_LOAD | UAF_REVERSE;
break;
case OPC31_STWBRX:
UA_EVCNT_INCR(stwbrx);
flags = UAF_STORE | UAF_REVERSE;
break;
case OPC31_LHBRX:
UA_EVCNT_INCR(lhbrx);
flags = UAF_LOAD | UAF_HALF | UAF_REVERSE;
break;
case OPC31_STHBRX:
UA_EVCNT_INCR(sthbrx);
flags = UAF_STORE | UAF_HALF | UAF_REVERSE;
break;
default:
UA_EVCNT_INCR(unknown);
goto unknown;
}
break;
case OPC_LMW:
UA_EVCNT_INCR(lmw);
#ifdef FIX_UNALIGNED_LSTMW
flags = UAF_LOAD;
if (do_lstmw(tf, insn, flags))
goto fault;
return false;
#else
goto unknown;
#endif
case OPC_STMW:
UA_EVCNT_INCR(stmw);
#ifdef FIX_UNALIGNED_LSTMW
flags = UAF_STORE;
if (do_lstmw(tf, insn, flags))
goto fault;
return false;
#else
goto unknown;
#endif
default:
UA_EVCNT_INCR(unknown);
unknown:
DPRINTF("unknown: srr0: 0x%08lx dear: 0x%08lx "
"insn: 0x%08x (opcd: 0x%02x, xo: 0x%03x) ",
tf->tf_srr0, tf->tf_dear,
insn->i_int, insn->i_any.i_opcd, insn->i_x.i_xo);
DISASM(tf, insn);
ksi->ksi_signo = SIGBUS;
ksi->ksi_trap = EXC_ALI;
ksi->ksi_addr = (void *)tf->tf_dear;
return true;
}
if (do_lst(tf, insn, flags)) {
#ifdef FIX_UNALIGNED_LSTMW
fault:
#endif
UA_EVCNT_INCR(dsi);
ksi->ksi_signo = SIGSEGV;
ksi->ksi_trap = EXC_DSI;
ksi->ksi_code = SEGV_MAPERR;
ksi->ksi_addr = (void *)tf->tf_dear;
return true;
}
return false;
}
#define SIGN_EXT(u16, algebra) \
((u16) | (((algebra) && (u16) >= 0x8000) ? 0xffff0000 : 0))
static bool
do_lst(struct trapframe *tf, const union instr *insn, int flags)
{
const bool load = flags & UAF_LOAD,
half = flags & UAF_HALF,
algebra = flags & UAF_ALGEBRA,
reverse = flags & UAF_REVERSE,
update = flags & UAF_UPDATE;
uint8_t * const dear = (uint8_t *)tf->tf_dear;
uint32_t u32;
uint16_t u16;
int rs, ra, ret;
rs = insn->i_d.i_rs;
if (load) {
if (half)
ret = copyin(dear, &u16, sizeof(u16));
else
ret = copyin(dear, &u32, sizeof(u32));
} else {
if (half) {
u16 = (uint16_t)tf->tf_fixreg[rs];
if (reverse)
u16 = bswap16(u16);
ret = copyout(&u16, dear, sizeof(u16));
} else {
u32 = tf->tf_fixreg[rs];
if (reverse)
u32 = bswap32(u32);
ret = copyout(&u32, dear, sizeof(u32));
}
}
if (ret)
goto fault;
if (load) {
if (half)
tf->tf_fixreg[rs] = reverse ?
bswap16(u16) : SIGN_EXT(u16, algebra);
else
tf->tf_fixreg[rs] = reverse ?
bswap32(u32) : u32;
}
if (update) {
ra = insn->i_d.i_ra;
if (ra == 0 || (load && ra == rs)) {
UA_EVCNT_INCR(invalid);
DPRINTF("invalid: rs: %d ra: %d "
"srr0: 0x%08lx dear: 0x%08x "
"insn: 0x%08x (opcd: 0x%02x xo: 0x%03x) ",
rs, ra, tf->tf_srr0, (uint32_t)dear,
insn->i_int, insn->i_any.i_opcd, insn->i_x.i_xo);
DISASM(tf, insn);
} else
tf->tf_fixreg[ra] = (__register_t)dear;
}
return false;
fault:
DPRINTF("fault: ret: %d srr0: 0x%08lx dear: 0x%08x "
"insn: 0x%08x (opcd: 0x%02x xo: 0x%03x) ",
ret, tf->tf_srr0, (uint32_t)dear,
insn->i_int, insn->i_any.i_opcd, insn->i_x.i_xo);
DISASM(tf, insn);
return true;
}
#ifdef FIX_UNALIGNED_LSTMW
static bool
do_lstmw(struct trapframe *tf, const union instr *insn, int flags)
{
const bool load = flags & UAF_LOAD;
const size_t size = sizeof(tf->tf_fixreg[0]);
uint8_t *ea;
uint32_t u32;
int rs, ra, r, ret;
rs = insn->i_d.i_rs;
ra = insn->i_d.i_ra;
ea = (uint8_t *)(insn->i_d.i_d + (ra ? tf->tf_fixreg[ra] : 0));
for (r = rs; r < 32; r++) {
if (load)
ret = copyin(ea, &u32, size);
else
ret = copyout(&tf->tf_fixreg[r], ea, size);
if (ret)
goto fault;
if (load) {
if (r == ra) {
UA_EVCNT_INCR(invalid);
DPRINTF("invalid: rs: %d ra: %d r: %d "
"srr0: 0x%08lx dear: 0x%08lx (ea: 0x%08x) "
"insn: 0x%08x (opcd: 0x%02x xo: 0x%03x) ",
rs, ra, r,
tf->tf_srr0, tf->tf_dear, (uint32_t)ea,
insn->i_int, insn->i_any.i_opcd,
insn->i_x.i_xo);
DISASM(tf, insn);
if (r == 0) {
tf->tf_fixreg[r] = u32;
} else {
}
} else
tf->tf_fixreg[r] = u32;
}
ea += size;
}
return false;
fault:
DPRINTF("fault: ret: %d rs: %d r: %d "
"srr0: 0x%08lx dear: 0x%08lx (ea: 0x%08x) "
"insn: 0x%08x (opcd: 0x%02x xo: 0x%03x) ",
ret, rs, r, tf->tf_srr0, tf->tf_dear, (uint32_t)ea,
insn->i_int, insn->i_any.i_opcd, insn->i_x.i_xo);
DISASM(tf, insn);
return true;
}
#endif