Commit | Line | Data |
---|---|---|
1da177e4 | 1 | #include <asm/branch.h> |
1da177e4 | 2 | #include <asm/cacheflush.h> |
1da177e4 | 3 | #include <asm/fpu_emulator.h> |
cd8ee345 RB |
4 | #include <asm/inst.h> |
5 | #include <asm/mipsregs.h> | |
6 | #include <asm/uaccess.h> | |
1da177e4 LT |
7 | |
8 | #include "ieee754.h" | |
1da177e4 | 9 | |
1da177e4 LT |
10 | /* |
11 | * Emulate the arbritrary instruction ir at xcp->cp0_epc. Required when | |
12 | * we have to emulate the instruction in a COP1 branch delay slot. Do | |
13 | * not change cp0_epc due to the instruction | |
14 | * | |
15 | * According to the spec: | |
25985edc | 16 | * 1) it shouldn't be a branch :-) |
1da177e4 LT |
17 | * 2) it can be a COP instruction :-( |
18 | * 3) if we are tring to run a protected memory space we must take | |
19 | * special care on memory access instructions :-( | |
20 | */ | |
21 | ||
22 | /* | |
23 | * "Trampoline" return routine to catch exception following | |
24 | * execution of delay-slot instruction execution. | |
25 | */ | |
26 | ||
27 | struct emuframe { | |
28 | mips_instruction emul; | |
29 | mips_instruction badinst; | |
30 | mips_instruction cookie; | |
333d1f67 | 31 | unsigned long epc; |
1da177e4 LT |
32 | }; |
33 | ||
333d1f67 | 34 | int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc) |
1da177e4 LT |
35 | { |
36 | extern asmlinkage void handle_dsemulret(void); | |
5e0373b8 | 37 | struct emuframe __user *fr; |
1da177e4 LT |
38 | int err; |
39 | ||
102cedc3 LY |
40 | if ((get_isa16_mode(regs->cp0_epc) && ((ir >> 16) == MM_NOP16)) || |
41 | (ir == 0)) { | |
42 | /* NOP is easy */ | |
1da177e4 | 43 | regs->cp0_epc = cpc; |
e7e9cae5 | 44 | clear_delay_slot(regs); |
1da177e4 LT |
45 | return 0; |
46 | } | |
1da177e4 | 47 | |
92df0f8b | 48 | pr_debug("dsemul %lx %lx\n", regs->cp0_epc, cpc); |
1da177e4 LT |
49 | |
50 | /* | |
51 | * The strategy is to push the instruction onto the user stack | |
52 | * and put a trap after it which we can catch and jump to | |
53 | * the required address any alternative apart from full | |
54 | * instruction emulation!!. | |
55 | * | |
56 | * Algorithmics used a system call instruction, and | |
57 | * borrowed that vector. MIPS/Linux version is a bit | |
58 | * more heavyweight in the interests of portability and | |
59 | * multiprocessor support. For Linux we generate a | |
60 | * an unaligned access and force an address error exception. | |
61 | * | |
62 | * For embedded systems (stand-alone) we prefer to use a | |
63 | * non-existing CP1 instruction. This prevents us from emulating | |
64 | * branches, but gives us a cleaner interface to the exception | |
65 | * handler (single entry point). | |
66 | */ | |
67 | ||
68 | /* Ensure that the two instructions are in the same cache line */ | |
5e0373b8 AN |
69 | fr = (struct emuframe __user *) |
70 | ((regs->regs[29] - sizeof(struct emuframe)) & ~0x7); | |
1da177e4 LT |
71 | |
72 | /* Verify that the stack pointer is not competely insane */ | |
73 | if (unlikely(!access_ok(VERIFY_WRITE, fr, sizeof(struct emuframe)))) | |
74 | return SIGBUS; | |
75 | ||
102cedc3 LY |
76 | if (get_isa16_mode(regs->cp0_epc)) { |
77 | err = __put_user(ir >> 16, (u16 __user *)(&fr->emul)); | |
78 | err |= __put_user(ir & 0xffff, (u16 __user *)((long)(&fr->emul) + 2)); | |
79 | err |= __put_user(BREAK_MATH >> 16, (u16 __user *)(&fr->badinst)); | |
80 | err |= __put_user(BREAK_MATH & 0xffff, (u16 __user *)((long)(&fr->badinst) + 2)); | |
81 | } else { | |
82 | err = __put_user(ir, &fr->emul); | |
83 | err |= __put_user((mips_instruction)BREAK_MATH, &fr->badinst); | |
84 | } | |
85 | ||
1da177e4 LT |
86 | err |= __put_user((mips_instruction)BD_COOKIE, &fr->cookie); |
87 | err |= __put_user(cpc, &fr->epc); | |
88 | ||
89 | if (unlikely(err)) { | |
b6ee75ed | 90 | MIPS_FPU_EMU_INC_STATS(errors); |
1da177e4 LT |
91 | return SIGBUS; |
92 | } | |
93 | ||
102cedc3 LY |
94 | regs->cp0_epc = ((unsigned long) &fr->emul) | |
95 | get_isa16_mode(regs->cp0_epc); | |
1da177e4 | 96 | |
7737b20b | 97 | flush_cache_sigtramp((unsigned long)&fr->emul); |
1da177e4 | 98 | |
9ab4471c | 99 | return 0; |
1da177e4 LT |
100 | } |
101 | ||
102 | int do_dsemulret(struct pt_regs *xcp) | |
103 | { | |
5e0373b8 | 104 | struct emuframe __user *fr; |
333d1f67 | 105 | unsigned long epc; |
1da177e4 LT |
106 | u32 insn, cookie; |
107 | int err = 0; | |
102cedc3 | 108 | u16 instr[2]; |
1da177e4 | 109 | |
5e0373b8 | 110 | fr = (struct emuframe __user *) |
102cedc3 | 111 | (msk_isa16_mode(xcp->cp0_epc) - sizeof(mips_instruction)); |
1da177e4 LT |
112 | |
113 | /* | |
114 | * If we can't even access the area, something is very wrong, but we'll | |
115 | * leave that to the default handling | |
116 | */ | |
117 | if (!access_ok(VERIFY_READ, fr, sizeof(struct emuframe))) | |
118 | return 0; | |
119 | ||
120 | /* | |
121 | * Do some sanity checking on the stackframe: | |
122 | * | |
ba3049ed | 123 | * - Is the instruction pointed to by the EPC an BREAK_MATH? |
1da177e4 LT |
124 | * - Is the following memory word the BD_COOKIE? |
125 | */ | |
102cedc3 LY |
126 | if (get_isa16_mode(xcp->cp0_epc)) { |
127 | err = __get_user(instr[0], (u16 __user *)(&fr->badinst)); | |
128 | err |= __get_user(instr[1], (u16 __user *)((long)(&fr->badinst) + 2)); | |
129 | insn = (instr[0] << 16) | instr[1]; | |
130 | } else { | |
131 | err = __get_user(insn, &fr->badinst); | |
132 | } | |
1da177e4 LT |
133 | err |= __get_user(cookie, &fr->cookie); |
134 | ||
ba3049ed | 135 | if (unlikely(err || (insn != BREAK_MATH) || (cookie != BD_COOKIE))) { |
b6ee75ed | 136 | MIPS_FPU_EMU_INC_STATS(errors); |
1da177e4 LT |
137 | return 0; |
138 | } | |
139 | ||
140 | /* | |
141 | * At this point, we are satisfied that it's a BD emulation trap. Yes, | |
142 | * a user might have deliberately put two malformed and useless | |
143 | * instructions in a row in his program, in which case he's in for a | |
144 | * nasty surprise - the next instruction will be treated as a | |
145 | * continuation address! Alas, this seems to be the only way that we | |
146 | * can handle signals, recursion, and longjmps() in the context of | |
147 | * emulating the branch delay instruction. | |
148 | */ | |
149 | ||
92df0f8b RB |
150 | pr_debug("dsemulret\n"); |
151 | ||
1da177e4 LT |
152 | if (__get_user(epc, &fr->epc)) { /* Saved EPC */ |
153 | /* This is not a good situation to be in */ | |
154 | force_sig(SIGBUS, current); | |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
159 | /* Set EPC to return to post-branch instruction */ | |
160 | xcp->cp0_epc = epc; | |
2707cd29 | 161 | MIPS_FPU_EMU_INC_STATS(ds_emul); |
1da177e4 LT |
162 | return 1; |
163 | } |