openrisc: add l.lwa/l.swa emulation

This adds an emulation layer for implementations
that lack the l.lwa and l.swa instructions.
It handles these instructions both in kernel space and
user space.

Signed-off-by: Stefan Kristiansson <stefan.kristiansson@saunalahti.fi>
[shorne@gmail.com: Added delay slot pc adjust logic]
Signed-off-by: Stafford Horne <shorne@gmail.com>
This commit is contained in:
Stefan Kristiansson
2014-11-03 14:28:14 +02:00
committed by Stafford Horne
parent 8c9b7db0de
commit 63104c06a9
3 changed files with 206 additions and 2 deletions

View File

@@ -40,6 +40,8 @@
extern char _etext, _stext;
int kstack_depth_to_print = 0x180;
int lwa_flag;
unsigned long __user *lwa_addr;
static inline int valid_stack_ptr(struct thread_info *tinfo, void *p)
{
@@ -334,10 +336,191 @@ asmlinkage void do_bus_fault(struct pt_regs *regs, unsigned long address)
}
}
static inline int in_delay_slot(struct pt_regs *regs)
{
#ifdef CONFIG_OPENRISC_NO_SPR_SR_DSX
/* No delay slot flag, do the old way */
unsigned int op, insn;
insn = *((unsigned int *)regs->pc);
op = insn >> 26;
switch (op) {
case 0x00: /* l.j */
case 0x01: /* l.jal */
case 0x03: /* l.bnf */
case 0x04: /* l.bf */
case 0x11: /* l.jr */
case 0x12: /* l.jalr */
return 1;
default:
return 0;
}
#else
return regs->sr & SPR_SR_DSX;
#endif
}
static inline void adjust_pc(struct pt_regs *regs, unsigned long address)
{
int displacement;
unsigned int rb, op, jmp;
if (unlikely(in_delay_slot(regs))) {
/* In delay slot, instruction at pc is a branch, simulate it */
jmp = *((unsigned int *)regs->pc);
displacement = sign_extend32(((jmp) & 0x3ffffff) << 2, 27);
rb = (jmp & 0x0000ffff) >> 11;
op = jmp >> 26;
switch (op) {
case 0x00: /* l.j */
regs->pc += displacement;
return;
case 0x01: /* l.jal */
regs->pc += displacement;
regs->gpr[9] = regs->pc + 8;
return;
case 0x03: /* l.bnf */
if (regs->sr & SPR_SR_F)
regs->pc += 8;
else
regs->pc += displacement;
return;
case 0x04: /* l.bf */
if (regs->sr & SPR_SR_F)
regs->pc += displacement;
else
regs->pc += 8;
return;
case 0x11: /* l.jr */
regs->pc = regs->gpr[rb];
return;
case 0x12: /* l.jalr */
regs->pc = regs->gpr[rb];
regs->gpr[9] = regs->pc + 8;
return;
default:
break;
}
} else {
regs->pc += 4;
}
}
static inline void simulate_lwa(struct pt_regs *regs, unsigned long address,
unsigned int insn)
{
unsigned int ra, rd;
unsigned long value;
unsigned long orig_pc;
long imm;
const struct exception_table_entry *entry;
orig_pc = regs->pc;
adjust_pc(regs, address);
ra = (insn >> 16) & 0x1f;
rd = (insn >> 21) & 0x1f;
imm = (short)insn;
lwa_addr = (unsigned long __user *)(regs->gpr[ra] + imm);
if ((unsigned long)lwa_addr & 0x3) {
do_unaligned_access(regs, address);
return;
}
if (get_user(value, lwa_addr)) {
if (user_mode(regs)) {
force_sig(SIGSEGV, current);
return;
}
if ((entry = search_exception_tables(orig_pc))) {
regs->pc = entry->fixup;
return;
}
/* kernel access in kernel space, load it directly */
value = *((unsigned long *)lwa_addr);
}
lwa_flag = 1;
regs->gpr[rd] = value;
}
static inline void simulate_swa(struct pt_regs *regs, unsigned long address,
unsigned int insn)
{
unsigned long __user *vaddr;
unsigned long orig_pc;
unsigned int ra, rb;
long imm;
const struct exception_table_entry *entry;
orig_pc = regs->pc;
adjust_pc(regs, address);
ra = (insn >> 16) & 0x1f;
rb = (insn >> 11) & 0x1f;
imm = (short)(((insn & 0x2200000) >> 10) | (insn & 0x7ff));
vaddr = (unsigned long __user *)(regs->gpr[ra] + imm);
if (!lwa_flag || vaddr != lwa_addr) {
regs->sr &= ~SPR_SR_F;
return;
}
if ((unsigned long)vaddr & 0x3) {
do_unaligned_access(regs, address);
return;
}
if (put_user(regs->gpr[rb], vaddr)) {
if (user_mode(regs)) {
force_sig(SIGSEGV, current);
return;
}
if ((entry = search_exception_tables(orig_pc))) {
regs->pc = entry->fixup;
return;
}
/* kernel access in kernel space, store it directly */
*((unsigned long *)vaddr) = regs->gpr[rb];
}
lwa_flag = 0;
regs->sr |= SPR_SR_F;
}
#define INSN_LWA 0x1b
#define INSN_SWA 0x33
asmlinkage void do_illegal_instruction(struct pt_regs *regs,
unsigned long address)
{
siginfo_t info;
unsigned int op;
unsigned int insn = *((unsigned int *)address);
op = insn >> 26;
switch (op) {
case INSN_LWA:
simulate_lwa(regs, address, insn);
return;
case INSN_SWA:
simulate_swa(regs, address, insn);
return;
default:
break;
}
if (user_mode(regs)) {
/* Send a SIGILL */