dsemul.c 4.8 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3
#include <asm/branch.h>
#include <asm/cacheflush.h>
#include <asm/fpu_emulator.h>
4 5 6
#include <asm/inst.h>
#include <asm/mipsregs.h>
#include <asm/uaccess.h>
L
Linus Torvalds 已提交
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

#include "ieee754.h"

/* Strap kernel emulator for full MIPS IV emulation */

#ifdef __mips
#undef __mips
#endif
#define __mips 4

/*
 * Emulate the arbritrary instruction ir at xcp->cp0_epc.  Required when
 * we have to emulate the instruction in a COP1 branch delay slot.  Do
 * not change cp0_epc due to the instruction
 *
 * According to the spec:
L
Lucas De Marchi 已提交
23
 * 1) it shouldn't be a branch :-)
L
Linus Torvalds 已提交
24 25 26 27 28 29 30 31 32 33 34 35 36 37
 * 2) it can be a COP instruction :-(
 * 3) if we are tring to run a protected memory space we must take
 *    special care on memory access instructions :-(
 */

/*
 * "Trampoline" return routine to catch exception following
 *  execution of delay-slot instruction execution.
 */

struct emuframe {
	mips_instruction	emul;
	mips_instruction	badinst;
	mips_instruction	cookie;
38
	unsigned long		epc;
L
Linus Torvalds 已提交
39 40
};

41
int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc)
L
Linus Torvalds 已提交
42 43
{
	extern asmlinkage void handle_dsemulret(void);
A
Atsushi Nemoto 已提交
44
	struct emuframe __user *fr;
L
Linus Torvalds 已提交
45 46
	int err;

47 48 49
	if ((get_isa16_mode(regs->cp0_epc) && ((ir >> 16) == MM_NOP16)) ||
		(ir == 0)) {
		/* NOP is easy */
L
Linus Torvalds 已提交
50
		regs->cp0_epc = cpc;
51
		clear_delay_slot(regs);
L
Linus Torvalds 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
		return 0;
	}
#ifdef DSEMUL_TRACE
	printk("dsemul %lx %lx\n", regs->cp0_epc, cpc);

#endif

	/*
	 * The strategy is to push the instruction onto the user stack
	 * and put a trap after it which we can catch and jump to
	 * the required address any alternative apart from full
	 * instruction emulation!!.
	 *
	 * Algorithmics used a system call instruction, and
	 * borrowed that vector.  MIPS/Linux version is a bit
	 * more heavyweight in the interests of portability and
	 * multiprocessor support.  For Linux we generate a
	 * an unaligned access and force an address error exception.
	 *
	 * For embedded systems (stand-alone) we prefer to use a
	 * non-existing CP1 instruction. This prevents us from emulating
	 * branches, but gives us a cleaner interface to the exception
	 * handler (single entry point).
	 */

	/* Ensure that the two instructions are in the same cache line */
A
Atsushi Nemoto 已提交
78 79
	fr = (struct emuframe __user *)
		((regs->regs[29] - sizeof(struct emuframe)) & ~0x7);
L
Linus Torvalds 已提交
80 81 82 83 84

	/* Verify that the stack pointer is not competely insane */
	if (unlikely(!access_ok(VERIFY_WRITE, fr, sizeof(struct emuframe))))
		return SIGBUS;

85 86 87 88 89 90 91 92 93 94
	if (get_isa16_mode(regs->cp0_epc)) {
		err = __put_user(ir >> 16, (u16 __user *)(&fr->emul));
		err |= __put_user(ir & 0xffff, (u16 __user *)((long)(&fr->emul) + 2));
		err |= __put_user(BREAK_MATH >> 16, (u16 __user *)(&fr->badinst));
		err |= __put_user(BREAK_MATH & 0xffff, (u16 __user *)((long)(&fr->badinst) + 2));
	} else {
		err = __put_user(ir, &fr->emul);
		err |= __put_user((mips_instruction)BREAK_MATH, &fr->badinst);
	}

L
Linus Torvalds 已提交
95 96 97 98
	err |= __put_user((mips_instruction)BD_COOKIE, &fr->cookie);
	err |= __put_user(cpc, &fr->epc);

	if (unlikely(err)) {
99
		MIPS_FPU_EMU_INC_STATS(errors);
L
Linus Torvalds 已提交
100 101 102
		return SIGBUS;
	}

103 104
	regs->cp0_epc = ((unsigned long) &fr->emul) |
		get_isa16_mode(regs->cp0_epc);
L
Linus Torvalds 已提交
105 106 107 108 109 110 111 112

	flush_cache_sigtramp((unsigned long)&fr->badinst);

	return SIGILL;		/* force out of emulation loop */
}

int do_dsemulret(struct pt_regs *xcp)
{
A
Atsushi Nemoto 已提交
113
	struct emuframe __user *fr;
114
	unsigned long epc;
L
Linus Torvalds 已提交
115 116
	u32 insn, cookie;
	int err = 0;
117
	u16 instr[2];
L
Linus Torvalds 已提交
118

A
Atsushi Nemoto 已提交
119
	fr = (struct emuframe __user *)
120
		(msk_isa16_mode(xcp->cp0_epc) - sizeof(mips_instruction));
L
Linus Torvalds 已提交
121 122 123 124 125 126 127 128 129 130 131

	/*
	 * If we can't even access the area, something is very wrong, but we'll
	 * leave that to the default handling
	 */
	if (!access_ok(VERIFY_READ, fr, sizeof(struct emuframe)))
		return 0;

	/*
	 * Do some sanity checking on the stackframe:
	 *
132
	 *  - Is the instruction pointed to by the EPC an BREAK_MATH?
L
Linus Torvalds 已提交
133 134
	 *  - Is the following memory word the BD_COOKIE?
	 */
135 136 137 138 139 140 141
	if (get_isa16_mode(xcp->cp0_epc)) {
		err = __get_user(instr[0], (u16 __user *)(&fr->badinst));
		err |= __get_user(instr[1], (u16 __user *)((long)(&fr->badinst) + 2));
		insn = (instr[0] << 16) | instr[1];
	} else {
		err = __get_user(insn, &fr->badinst);
	}
L
Linus Torvalds 已提交
142 143
	err |= __get_user(cookie, &fr->cookie);

144
	if (unlikely(err || (insn != BREAK_MATH) || (cookie != BD_COOKIE))) {
145
		MIPS_FPU_EMU_INC_STATS(errors);
L
Linus Torvalds 已提交
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
		return 0;
	}

	/*
	 * At this point, we are satisfied that it's a BD emulation trap.  Yes,
	 * a user might have deliberately put two malformed and useless
	 * instructions in a row in his program, in which case he's in for a
	 * nasty surprise - the next instruction will be treated as a
	 * continuation address!  Alas, this seems to be the only way that we
	 * can handle signals, recursion, and longjmps() in the context of
	 * emulating the branch delay instruction.
	 */

#ifdef DSEMUL_TRACE
	printk("dsemulret\n");
#endif
	if (__get_user(epc, &fr->epc)) {		/* Saved EPC */
		/* This is not a good situation to be in */
		force_sig(SIGBUS, current);

		return 0;
	}

	/* Set EPC to return to post-branch instruction */
	xcp->cp0_epc = epc;

	return 1;
}