diff options
author | 2025-03-08 22:04:20 +0800 | |
---|---|---|
committer | 2025-03-08 22:04:20 +0800 | |
commit | a07bb8fd1299070229f0e8f3dcb57ffd5ef9870a (patch) | |
tree | 84f21bd0bf7071bc5fc7dd989e77d7ceb5476682 /arch/mips/math-emu/dsemul.c | |
download | ohosKernel-a07bb8fd1299070229f0e8f3dcb57ffd5ef9870a.tar.gz ohosKernel-a07bb8fd1299070229f0e8f3dcb57ffd5ef9870a.zip |
Initial commit: OpenHarmony-v4.0-ReleaseOpenHarmony-v4.0-Release
Diffstat (limited to 'arch/mips/math-emu/dsemul.c')
-rw-r--r-- | arch/mips/math-emu/dsemul.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/arch/mips/math-emu/dsemul.c b/arch/mips/math-emu/dsemul.c new file mode 100644 index 000000000..e2d46cb93 --- /dev/null +++ b/arch/mips/math-emu/dsemul.c | |||
@@ -0,0 +1,308 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | #include <linux/err.h> | ||
3 | #include <linux/slab.h> | ||
4 | #include <linux/mm_types.h> | ||
5 | #include <linux/sched/task.h> | ||
6 | |||
7 | #include <asm/branch.h> | ||
8 | #include <asm/cacheflush.h> | ||
9 | #include <asm/fpu_emulator.h> | ||
10 | #include <asm/inst.h> | ||
11 | #include <asm/mipsregs.h> | ||
12 | #include <linux/uaccess.h> | ||
13 | |||
14 | /** | ||
15 | * struct emuframe - The 'emulation' frame structure | ||
16 | * @emul: The instruction to 'emulate'. | ||
17 | * @badinst: A break instruction to cause a return to the kernel. | ||
18 | * | ||
19 | * This structure defines the frames placed within the delay slot emulation | ||
20 | * page in response to a call to mips_dsemul(). Each thread may be allocated | ||
21 | * only one frame at any given time. The kernel stores within it the | ||
22 | * instruction to be 'emulated' followed by a break instruction, then | ||
23 | * executes the frame in user mode. The break causes a trap to the kernel | ||
24 | * which leads to do_dsemulret() being called unless the instruction in | ||
25 | * @emul causes a trap itself, is a branch, or a signal is delivered to | ||
26 | * the thread. In these cases the allocated frame will either be reused by | ||
27 | * a subsequent delay slot 'emulation', or be freed during signal delivery or | ||
28 | * upon thread exit. | ||
29 | * | ||
30 | * This approach is used because: | ||
31 | * | ||
32 | * - Actually emulating all instructions isn't feasible. We would need to | ||
33 | * be able to handle instructions from all revisions of the MIPS ISA, | ||
34 | * all ASEs & all vendor instruction set extensions. This would be a | ||
35 | * whole lot of work & continual maintenance burden as new instructions | ||
36 | * are introduced, and in the case of some vendor extensions may not | ||
37 | * even be possible. Thus we need to take the approach of actually | ||
38 | * executing the instruction. | ||
39 | * | ||
40 | * - We must execute the instruction within user context. If we were to | ||
41 | * execute the instruction in kernel mode then it would have access to | ||
42 | * kernel resources without very careful checks, leaving us with a | ||
43 | * high potential for security or stability issues to arise. | ||
44 | * | ||
45 | * - We used to place the frame on the users stack, but this requires | ||
46 | * that the stack be executable. This is bad for security so the | ||
47 | * per-process page is now used instead. | ||
48 | * | ||
49 | * - The instruction in @emul may be something entirely invalid for a | ||
50 | * delay slot. The user may (intentionally or otherwise) place a branch | ||
51 | * in a delay slot, or a kernel mode instruction, or something else | ||
52 | * which generates an exception. Thus we can't rely upon the break in | ||
53 | * @badinst always being hit. For this reason we track the index of the | ||
54 | * frame allocated to each thread, allowing us to clean it up at later | ||
55 | * points such as signal delivery or thread exit. | ||
56 | * | ||
57 | * - The user may generate a fake struct emuframe if they wish, invoking | ||
58 | * the BRK_MEMU break instruction themselves. We must therefore not | ||
59 | * trust that BRK_MEMU means there's actually a valid frame allocated | ||
60 | * to the thread, and must not allow the user to do anything they | ||
61 | * couldn't already. | ||
62 | */ | ||
63 | struct emuframe { | ||
64 | mips_instruction emul; | ||
65 | mips_instruction badinst; | ||
66 | }; | ||
67 | |||
68 | static const int emupage_frame_count = PAGE_SIZE / sizeof(struct emuframe); | ||
69 | |||
70 | static inline __user struct emuframe *dsemul_page(void) | ||
71 | { | ||
72 | return (__user struct emuframe *)STACK_TOP; | ||
73 | } | ||
74 | |||
75 | static int alloc_emuframe(void) | ||
76 | { | ||
77 | mm_context_t *mm_ctx = ¤t->mm->context; | ||
78 | int idx; | ||
79 | |||
80 | retry: | ||
81 | spin_lock(&mm_ctx->bd_emupage_lock); | ||
82 | |||
83 | /* Ensure we have an allocation bitmap */ | ||
84 | if (!mm_ctx->bd_emupage_allocmap) { | ||
85 | mm_ctx->bd_emupage_allocmap = | ||
86 | kcalloc(BITS_TO_LONGS(emupage_frame_count), | ||
87 | sizeof(unsigned long), | ||
88 | GFP_ATOMIC); | ||
89 | |||
90 | if (!mm_ctx->bd_emupage_allocmap) { | ||
91 | idx = BD_EMUFRAME_NONE; | ||
92 | goto out_unlock; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | /* Attempt to allocate a single bit/frame */ | ||
97 | idx = bitmap_find_free_region(mm_ctx->bd_emupage_allocmap, | ||
98 | emupage_frame_count, 0); | ||
99 | if (idx < 0) { | ||
100 | /* | ||
101 | * Failed to allocate a frame. We'll wait until one becomes | ||
102 | * available. We unlock the page so that other threads actually | ||
103 | * get the opportunity to free their frames, which means | ||
104 | * technically the result of bitmap_full may be incorrect. | ||
105 | * However the worst case is that we repeat all this and end up | ||
106 | * back here again. | ||
107 | */ | ||
108 | spin_unlock(&mm_ctx->bd_emupage_lock); | ||
109 | if (!wait_event_killable(mm_ctx->bd_emupage_queue, | ||
110 | !bitmap_full(mm_ctx->bd_emupage_allocmap, | ||
111 | emupage_frame_count))) | ||
112 | goto retry; | ||
113 | |||
114 | /* Received a fatal signal - just give in */ | ||
115 | return BD_EMUFRAME_NONE; | ||
116 | } | ||
117 | |||
118 | /* Success! */ | ||
119 | pr_debug("allocate emuframe %d to %d\n", idx, current->pid); | ||
120 | out_unlock: | ||
121 | spin_unlock(&mm_ctx->bd_emupage_lock); | ||
122 | return idx; | ||
123 | } | ||
124 | |||
125 | static void free_emuframe(int idx, struct mm_struct *mm) | ||
126 | { | ||
127 | mm_context_t *mm_ctx = &mm->context; | ||
128 | |||
129 | spin_lock(&mm_ctx->bd_emupage_lock); | ||
130 | |||
131 | pr_debug("free emuframe %d from %d\n", idx, current->pid); | ||
132 | bitmap_clear(mm_ctx->bd_emupage_allocmap, idx, 1); | ||
133 | |||
134 | /* If some thread is waiting for a frame, now's its chance */ | ||
135 | wake_up(&mm_ctx->bd_emupage_queue); | ||
136 | |||
137 | spin_unlock(&mm_ctx->bd_emupage_lock); | ||
138 | } | ||
139 | |||
140 | static bool within_emuframe(struct pt_regs *regs) | ||
141 | { | ||
142 | unsigned long base = (unsigned long)dsemul_page(); | ||
143 | |||
144 | if (regs->cp0_epc < base) | ||
145 | return false; | ||
146 | if (regs->cp0_epc >= (base + PAGE_SIZE)) | ||
147 | return false; | ||
148 | |||
149 | return true; | ||
150 | } | ||
151 | |||
152 | bool dsemul_thread_cleanup(struct task_struct *tsk) | ||
153 | { | ||
154 | int fr_idx; | ||
155 | |||
156 | /* Clear any allocated frame, retrieving its index */ | ||
157 | fr_idx = atomic_xchg(&tsk->thread.bd_emu_frame, BD_EMUFRAME_NONE); | ||
158 | |||
159 | /* If no frame was allocated, we're done */ | ||
160 | if (fr_idx == BD_EMUFRAME_NONE) | ||
161 | return false; | ||
162 | |||
163 | task_lock(tsk); | ||
164 | |||
165 | /* Free the frame that this thread had allocated */ | ||
166 | if (tsk->mm) | ||
167 | free_emuframe(fr_idx, tsk->mm); | ||
168 | |||
169 | task_unlock(tsk); | ||
170 | return true; | ||
171 | } | ||
172 | |||
173 | bool dsemul_thread_rollback(struct pt_regs *regs) | ||
174 | { | ||
175 | struct emuframe __user *fr; | ||
176 | int fr_idx; | ||
177 | |||
178 | /* Do nothing if we're not executing from a frame */ | ||
179 | if (!within_emuframe(regs)) | ||
180 | return false; | ||
181 | |||
182 | /* Find the frame being executed */ | ||
183 | fr_idx = atomic_read(¤t->thread.bd_emu_frame); | ||
184 | if (fr_idx == BD_EMUFRAME_NONE) | ||
185 | return false; | ||
186 | fr = &dsemul_page()[fr_idx]; | ||
187 | |||
188 | /* | ||
189 | * If the PC is at the emul instruction, roll back to the branch. If | ||
190 | * PC is at the badinst (break) instruction, we've already emulated the | ||
191 | * instruction so progress to the continue PC. If it's anything else | ||
192 | * then something is amiss & the user has branched into some other area | ||
193 | * of the emupage - we'll free the allocated frame anyway. | ||
194 | */ | ||
195 | if (msk_isa16_mode(regs->cp0_epc) == (unsigned long)&fr->emul) | ||
196 | regs->cp0_epc = current->thread.bd_emu_branch_pc; | ||
197 | else if (msk_isa16_mode(regs->cp0_epc) == (unsigned long)&fr->badinst) | ||
198 | regs->cp0_epc = current->thread.bd_emu_cont_pc; | ||
199 | |||
200 | atomic_set(¤t->thread.bd_emu_frame, BD_EMUFRAME_NONE); | ||
201 | free_emuframe(fr_idx, current->mm); | ||
202 | return true; | ||
203 | } | ||
204 | |||
205 | void dsemul_mm_cleanup(struct mm_struct *mm) | ||
206 | { | ||
207 | mm_context_t *mm_ctx = &mm->context; | ||
208 | |||
209 | kfree(mm_ctx->bd_emupage_allocmap); | ||
210 | } | ||
211 | |||
212 | int mips_dsemul(struct pt_regs *regs, mips_instruction ir, | ||
213 | unsigned long branch_pc, unsigned long cont_pc) | ||
214 | { | ||
215 | int isa16 = get_isa16_mode(regs->cp0_epc); | ||
216 | mips_instruction break_math; | ||
217 | unsigned long fr_uaddr; | ||
218 | struct emuframe fr; | ||
219 | int fr_idx, ret; | ||
220 | |||
221 | /* NOP is easy */ | ||
222 | if (ir == 0) | ||
223 | return -1; | ||
224 | |||
225 | /* microMIPS instructions */ | ||
226 | if (isa16) { | ||
227 | union mips_instruction insn = { .word = ir }; | ||
228 | |||
229 | /* NOP16 aka MOVE16 $0, $0 */ | ||
230 | if ((ir >> 16) == MM_NOP16) | ||
231 | return -1; | ||
232 | |||
233 | /* ADDIUPC */ | ||
234 | if (insn.mm_a_format.opcode == mm_addiupc_op) { | ||
235 | unsigned int rs; | ||
236 | s32 v; | ||
237 | |||
238 | rs = (((insn.mm_a_format.rs + 0xe) & 0xf) + 2); | ||
239 | v = regs->cp0_epc & ~3; | ||
240 | v += insn.mm_a_format.simmediate << 2; | ||
241 | regs->regs[rs] = (long)v; | ||
242 | return -1; | ||
243 | } | ||
244 | } | ||
245 | |||
246 | pr_debug("dsemul 0x%08lx cont at 0x%08lx\n", regs->cp0_epc, cont_pc); | ||
247 | |||
248 | /* Allocate a frame if we don't already have one */ | ||
249 | fr_idx = atomic_read(¤t->thread.bd_emu_frame); | ||
250 | if (fr_idx == BD_EMUFRAME_NONE) | ||
251 | fr_idx = alloc_emuframe(); | ||
252 | if (fr_idx == BD_EMUFRAME_NONE) | ||
253 | return SIGBUS; | ||
254 | |||
255 | /* Retrieve the appropriately encoded break instruction */ | ||
256 | break_math = BREAK_MATH(isa16); | ||
257 | |||
258 | /* Write the instructions to the frame */ | ||
259 | if (isa16) { | ||
260 | union mips_instruction _emul = { | ||
261 | .halfword = { ir >> 16, ir } | ||
262 | }; | ||
263 | union mips_instruction _badinst = { | ||
264 | .halfword = { break_math >> 16, break_math } | ||
265 | }; | ||
266 | |||
267 | fr.emul = _emul.word; | ||
268 | fr.badinst = _badinst.word; | ||
269 | } else { | ||
270 | fr.emul = ir; | ||
271 | fr.badinst = break_math; | ||
272 | } | ||
273 | |||
274 | /* Write the frame to user memory */ | ||
275 | fr_uaddr = (unsigned long)&dsemul_page()[fr_idx]; | ||
276 | ret = access_process_vm(current, fr_uaddr, &fr, sizeof(fr), | ||
277 | FOLL_FORCE | FOLL_WRITE); | ||
278 | if (unlikely(ret != sizeof(fr))) { | ||
279 | MIPS_FPU_EMU_INC_STATS(errors); | ||
280 | free_emuframe(fr_idx, current->mm); | ||
281 | return SIGBUS; | ||
282 | } | ||
283 | |||
284 | /* Record the PC of the branch, PC to continue from & frame index */ | ||
285 | current->thread.bd_emu_branch_pc = branch_pc; | ||
286 | current->thread.bd_emu_cont_pc = cont_pc; | ||
287 | atomic_set(¤t->thread.bd_emu_frame, fr_idx); | ||
288 | |||
289 | /* Change user register context to execute the frame */ | ||
290 | regs->cp0_epc = fr_uaddr | isa16; | ||
291 | |||
292 | return 0; | ||
293 | } | ||
294 | |||
295 | bool do_dsemulret(struct pt_regs *xcp) | ||
296 | { | ||
297 | /* Cleanup the allocated frame, returning if there wasn't one */ | ||
298 | if (!dsemul_thread_cleanup(current)) { | ||
299 | MIPS_FPU_EMU_INC_STATS(errors); | ||
300 | return false; | ||
301 | } | ||
302 | |||
303 | /* Set EPC to return to post-branch instruction */ | ||
304 | xcp->cp0_epc = current->thread.bd_emu_cont_pc; | ||
305 | pr_debug("dsemulret to 0x%08lx\n", xcp->cp0_epc); | ||
306 | MIPS_FPU_EMU_INC_STATS(ds_emul); | ||
307 | return true; | ||
308 | } | ||