aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Burton <paul.burton@imgtec.com>2016-07-08 06:06:19 -0400
committerRalf Baechle <ralf@linux-mips.org>2016-08-02 03:28:53 -0400
commit432c6bacbd0c16ec210c43da411ccc3855c4c010 (patch)
treed71032862a6ee0c04c021fb57e1a127b8d3a0466
parent33799a6d1aeb892862d5f69ee87195becabf8d0c (diff)
MIPS: Use per-mm page to execute branch delay slot instructions
In some cases the kernel needs to execute an instruction from the delay slot of an emulated branch instruction. These cases include: - Emulated floating point branch instructions (bc1[ft]l?) for systems which don't include an FPU, or upon which the kernel is run with the "nofpu" parameter. - MIPSr6 systems running binaries targeting older revisions of the architecture, which may include branch instructions whose encodings are no longer valid in MIPSr6. Executing instructions from such delay slots is done by writing the instruction to memory followed by a trap, as part of an "emuframe", and executing it. This avoids the requirement of an emulator for the entire MIPS instruction set. Prior to this patch such emuframes are written to the user stack and executed from there. This patch moves FP branch delay emuframes off of the user stack and into a per-mm page. Allocating a page per-mm leaves userland with access to only what it had access to previously, and compared to other solutions is relatively simple. When a thread requires a delay slot emulation, it is allocated a frame. A thread may only have one frame allocated at any one time, since it may only ever be executing one instruction at any one time. In order to ensure that we can free up allocated frame later, its index is recorded in struct thread_struct. In the typical case, after executing the delay slot instruction we'll execute a break instruction with the BRK_MEMU code. This traps back to the kernel & leads to a call to do_dsemulret which frees the allocated frame & moves the user PC back to the instruction that would have executed following the emulated branch. In some cases the delay slot instruction may be invalid, such as a branch, or may trigger an exception. In these cases the BRK_MEMU break instruction will not be hit. In order to ensure that frames are freed this patch introduces dsemul_thread_cleanup() and calls it to free any allocated frame upon thread exit. If the instruction generated an exception & leads to a signal being delivered to the thread, or indeed if a signal simply happens to be delivered to the thread whilst it is executing from the struct emuframe, then we need to take care to exit the frame appropriately. This is done by either rolling back the user PC to the branch or advancing it to the continuation PC prior to signal delivery, using dsemul_thread_rollback(). If this were not done then a sigreturn would return to the struct emuframe, and if that frame had meanwhile been used in response to an emulated branch instruction within the signal handler then we would execute the wrong user code. Whilst a user could theoretically place something like a compact branch to self in a delay slot and cause their thread to become stuck in an infinite loop with the frame never being deallocated, this would: - Only affect the users single process. - Be architecturally invalid since there would be a branch in the delay slot, which is forbidden. - Be extremely unlikely to happen by mistake, and provide a program with no more ability to harm the system than a simple infinite loop would. If a thread requires a delay slot emulation & no frame is available to it (ie. the process has enough other threads that all frames are currently in use) then the thread joins a waitqueue. It will sleep until a frame is freed by another thread in the process. Since we now know whether a thread has an allocated frame due to our tracking of its index, the cookie field of struct emuframe is removed as we can be more certain whether we have a valid frame. Since a thread may only ever have a single frame at any given time, the epc field of struct emuframe is also removed & the PC to continue from is instead stored in struct thread_struct. Together these changes simplify & shrink struct emuframe somewhat, allowing twice as many frames to fit into the page allocated for them. The primary benefit of this patch is that we are now free to mark the user stack non-executable where that is possible. Signed-off-by: Paul Burton <paul.burton@imgtec.com> Cc: Leonid Yegoshin <leonid.yegoshin@imgtec.com> Cc: Maciej Rozycki <maciej.rozycki@imgtec.com> Cc: Faraz Shahbazker <faraz.shahbazker@imgtec.com> Cc: Raghu Gandham <raghu.gandham@imgtec.com> Cc: Matthew Fortune <matthew.fortune@imgtec.com> Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/13764/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
-rw-r--r--arch/mips/Kconfig1
-rw-r--r--arch/mips/include/asm/dsemul.h92
-rw-r--r--arch/mips/include/asm/fpu_emulator.h17
-rw-r--r--arch/mips/include/asm/mmu.h9
-rw-r--r--arch/mips/include/asm/mmu_context.h6
-rw-r--r--arch/mips/include/asm/processor.h18
-rw-r--r--arch/mips/kernel/mips-r2-to-r6-emul.c8
-rw-r--r--arch/mips/kernel/process.c14
-rw-r--r--arch/mips/kernel/signal.c8
-rw-r--r--arch/mips/kernel/vdso.c10
-rw-r--r--arch/mips/math-emu/cp1emu.c8
-rw-r--r--arch/mips/math-emu/dsemul.c333
12 files changed, 391 insertions, 133 deletions
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 286893da4855..3953bb14795e 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -64,6 +64,7 @@ config MIPS
64 select GENERIC_TIME_VSYSCALL 64 select GENERIC_TIME_VSYSCALL
65 select ARCH_CLOCKSOURCE_DATA 65 select ARCH_CLOCKSOURCE_DATA
66 select HANDLE_DOMAIN_IRQ 66 select HANDLE_DOMAIN_IRQ
67 select HAVE_EXIT_THREAD
67 68
68menu "Machine selection" 69menu "Machine selection"
69 70
diff --git a/arch/mips/include/asm/dsemul.h b/arch/mips/include/asm/dsemul.h
new file mode 100644
index 000000000000..a6e067801f23
--- /dev/null
+++ b/arch/mips/include/asm/dsemul.h
@@ -0,0 +1,92 @@
1/*
2 * Copyright (C) 2016 Imagination Technologies
3 * Author: Paul Burton <paul.burton@imgtec.com>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 */
10
11#ifndef __MIPS_ASM_DSEMUL_H__
12#define __MIPS_ASM_DSEMUL_H__
13
14#include <asm/break.h>
15#include <asm/inst.h>
16
17/* Break instruction with special math emu break code set */
18#define BREAK_MATH(micromips) (((micromips) ? 0x7 : 0xd) | (BRK_MEMU << 16))
19
20/* When used as a frame index, indicates the lack of a frame */
21#define BD_EMUFRAME_NONE ((int)BIT(31))
22
23struct mm_struct;
24struct pt_regs;
25struct task_struct;
26
27/**
28 * mips_dsemul() - 'Emulate' an instruction from a branch delay slot
29 * @regs: User thread register context.
30 * @ir: The instruction to be 'emulated'.
31 * @branch_pc: The PC of the branch instruction.
32 * @cont_pc: The PC to continue at following 'emulation'.
33 *
34 * Emulate or execute an arbitrary MIPS instruction within the context of
35 * the current user thread. This is used primarily to handle instructions
36 * in the delay slots of emulated branch instructions, for example FP
37 * branch instructions on systems without an FPU.
38 *
39 * Return: Zero on success, negative if ir is a NOP, signal number on failure.
40 */
41extern int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
42 unsigned long branch_pc, unsigned long cont_pc);
43
44/**
45 * do_dsemulret() - Return from a delay slot 'emulation' frame
46 * @xcp: User thread register context.
47 *
48 * Call in response to the BRK_MEMU break instruction used to return to
49 * the kernel from branch delay slot 'emulation' frames following a call
50 * to mips_dsemul(). Restores the user thread PC to the value that was
51 * passed as the cpc parameter to mips_dsemul().
52 *
53 * Return: True if an emulation frame was returned from, else false.
54 */
55extern bool do_dsemulret(struct pt_regs *xcp);
56
57/**
58 * dsemul_thread_cleanup() - Cleanup thread 'emulation' frame
59 * @tsk: The task structure associated with the thread
60 *
61 * If the thread @tsk has a branch delay slot 'emulation' frame
62 * allocated to it then free that frame.
63 *
64 * Return: True if a frame was freed, else false.
65 */
66extern bool dsemul_thread_cleanup(struct task_struct *tsk);
67
68/**
69 * dsemul_thread_rollback() - Rollback from an 'emulation' frame
70 * @regs: User thread register context.
71 *
72 * If the current thread, whose register context is represented by @regs,
73 * is executing within a delay slot 'emulation' frame then exit that
74 * frame. The PC will be rolled back to the branch if the instruction
75 * that was being 'emulated' has not yet executed, or advanced to the
76 * continuation PC if it has.
77 *
78 * Return: True if a frame was exited, else false.
79 */
80extern bool dsemul_thread_rollback(struct pt_regs *regs);
81
82/**
83 * dsemul_mm_cleanup() - Cleanup per-mm delay slot 'emulation' state
84 * @mm: The struct mm_struct to cleanup state for.
85 *
86 * Cleanup state for the given @mm, ensuring that any memory allocated
87 * for delay slot 'emulation' book-keeping is freed. This is to be called
88 * before @mm is freed in order to avoid memory leaks.
89 */
90extern void dsemul_mm_cleanup(struct mm_struct *mm);
91
92#endif /* __MIPS_ASM_DSEMUL_H__ */
diff --git a/arch/mips/include/asm/fpu_emulator.h b/arch/mips/include/asm/fpu_emulator.h
index 3225c3c0724b..355dc25172e7 100644
--- a/arch/mips/include/asm/fpu_emulator.h
+++ b/arch/mips/include/asm/fpu_emulator.h
@@ -24,7 +24,7 @@
24#define _ASM_FPU_EMULATOR_H 24#define _ASM_FPU_EMULATOR_H
25 25
26#include <linux/sched.h> 26#include <linux/sched.h>
27#include <asm/break.h> 27#include <asm/dsemul.h>
28#include <asm/thread_info.h> 28#include <asm/thread_info.h>
29#include <asm/inst.h> 29#include <asm/inst.h>
30#include <asm/local.h> 30#include <asm/local.h>
@@ -60,27 +60,16 @@ do { \
60#define MIPS_FPU_EMU_INC_STATS(M) do { } while (0) 60#define MIPS_FPU_EMU_INC_STATS(M) do { } while (0)
61#endif /* CONFIG_DEBUG_FS */ 61#endif /* CONFIG_DEBUG_FS */
62 62
63extern int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
64 unsigned long cpc);
65extern int do_dsemulret(struct pt_regs *xcp);
66extern int fpu_emulator_cop1Handler(struct pt_regs *xcp, 63extern int fpu_emulator_cop1Handler(struct pt_regs *xcp,
67 struct mips_fpu_struct *ctx, int has_fpu, 64 struct mips_fpu_struct *ctx, int has_fpu,
68 void *__user *fault_addr); 65 void *__user *fault_addr);
69int process_fpemu_return(int sig, void __user *fault_addr, 66int process_fpemu_return(int sig, void __user *fault_addr,
70 unsigned long fcr31); 67 unsigned long fcr31);
68int isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn,
69 unsigned long *contpc);
71int mm_isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn, 70int mm_isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn,
72 unsigned long *contpc); 71 unsigned long *contpc);
73 72
74/*
75 * Instruction inserted following the badinst to further tag the sequence
76 */
77#define BD_COOKIE 0x0000bd36 /* tne $0, $0 with baggage */
78
79/*
80 * Break instruction with special math emu break code set
81 */
82#define BREAK_MATH(micromips) (((micromips) ? 0x7 : 0xd) | (BRK_MEMU << 16))
83
84#define SIGNALLING_NAN 0x7ff800007ff80000LL 73#define SIGNALLING_NAN 0x7ff800007ff80000LL
85 74
86static inline void fpu_emulator_init_fpu(void) 75static inline void fpu_emulator_init_fpu(void)
diff --git a/arch/mips/include/asm/mmu.h b/arch/mips/include/asm/mmu.h
index 1afa1f986df8..f6ba08d77931 100644
--- a/arch/mips/include/asm/mmu.h
+++ b/arch/mips/include/asm/mmu.h
@@ -2,11 +2,20 @@
2#define __ASM_MMU_H 2#define __ASM_MMU_H
3 3
4#include <linux/atomic.h> 4#include <linux/atomic.h>
5#include <linux/spinlock.h>
6#include <linux/wait.h>
5 7
6typedef struct { 8typedef struct {
7 unsigned long asid[NR_CPUS]; 9 unsigned long asid[NR_CPUS];
8 void *vdso; 10 void *vdso;
9 atomic_t fp_mode_switching; 11 atomic_t fp_mode_switching;
12
13 /* lock to be held whilst modifying fp_bd_emupage_allocmap */
14 spinlock_t bd_emupage_lock;
15 /* bitmap tracking allocation of fp_bd_emupage */
16 unsigned long *bd_emupage_allocmap;
17 /* wait queue for threads requiring an emuframe */
18 wait_queue_head_t bd_emupage_queue;
10} mm_context_t; 19} mm_context_t;
11 20
12#endif /* __ASM_MMU_H */ 21#endif /* __ASM_MMU_H */
diff --git a/arch/mips/include/asm/mmu_context.h b/arch/mips/include/asm/mmu_context.h
index fc57e135cb0a..ddd57ade1aa8 100644
--- a/arch/mips/include/asm/mmu_context.h
+++ b/arch/mips/include/asm/mmu_context.h
@@ -16,6 +16,7 @@
16#include <linux/smp.h> 16#include <linux/smp.h>
17#include <linux/slab.h> 17#include <linux/slab.h>
18#include <asm/cacheflush.h> 18#include <asm/cacheflush.h>
19#include <asm/dsemul.h>
19#include <asm/hazards.h> 20#include <asm/hazards.h>
20#include <asm/tlbflush.h> 21#include <asm/tlbflush.h>
21#include <asm-generic/mm_hooks.h> 22#include <asm-generic/mm_hooks.h>
@@ -128,6 +129,10 @@ init_new_context(struct task_struct *tsk, struct mm_struct *mm)
128 129
129 atomic_set(&mm->context.fp_mode_switching, 0); 130 atomic_set(&mm->context.fp_mode_switching, 0);
130 131
132 mm->context.bd_emupage_allocmap = NULL;
133 spin_lock_init(&mm->context.bd_emupage_lock);
134 init_waitqueue_head(&mm->context.bd_emupage_queue);
135
131 return 0; 136 return 0;
132} 137}
133 138
@@ -162,6 +167,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,
162 */ 167 */
163static inline void destroy_context(struct mm_struct *mm) 168static inline void destroy_context(struct mm_struct *mm)
164{ 169{
170 dsemul_mm_cleanup(mm);
165} 171}
166 172
167#define deactivate_mm(tsk, mm) do { } while (0) 173#define deactivate_mm(tsk, mm) do { } while (0)
diff --git a/arch/mips/include/asm/processor.h b/arch/mips/include/asm/processor.h
index 7e78b6208d7d..0d36c87acbe2 100644
--- a/arch/mips/include/asm/processor.h
+++ b/arch/mips/include/asm/processor.h
@@ -11,12 +11,14 @@
11#ifndef _ASM_PROCESSOR_H 11#ifndef _ASM_PROCESSOR_H
12#define _ASM_PROCESSOR_H 12#define _ASM_PROCESSOR_H
13 13
14#include <linux/atomic.h>
14#include <linux/cpumask.h> 15#include <linux/cpumask.h>
15#include <linux/threads.h> 16#include <linux/threads.h>
16 17
17#include <asm/cachectl.h> 18#include <asm/cachectl.h>
18#include <asm/cpu.h> 19#include <asm/cpu.h>
19#include <asm/cpu-info.h> 20#include <asm/cpu-info.h>
21#include <asm/dsemul.h>
20#include <asm/mipsregs.h> 22#include <asm/mipsregs.h>
21#include <asm/prefetch.h> 23#include <asm/prefetch.h>
22 24
@@ -78,7 +80,11 @@ extern unsigned int vced_count, vcei_count;
78 80
79#endif 81#endif
80 82
81#define STACK_TOP (TASK_SIZE & PAGE_MASK) 83/*
84 * One page above the stack is used for branch delay slot "emulation".
85 * See dsemul.c for details.
86 */
87#define STACK_TOP ((TASK_SIZE & PAGE_MASK) - PAGE_SIZE)
82 88
83/* 89/*
84 * This decides where the kernel will search for a free chunk of vm 90 * This decides where the kernel will search for a free chunk of vm
@@ -256,6 +262,12 @@ struct thread_struct {
256 262
257 /* Saved fpu/fpu emulator stuff. */ 263 /* Saved fpu/fpu emulator stuff. */
258 struct mips_fpu_struct fpu FPU_ALIGN; 264 struct mips_fpu_struct fpu FPU_ALIGN;
265 /* Assigned branch delay slot 'emulation' frame */
266 atomic_t bd_emu_frame;
267 /* PC of the branch from a branch delay slot 'emulation' */
268 unsigned long bd_emu_branch_pc;
269 /* PC to continue from following a branch delay slot 'emulation' */
270 unsigned long bd_emu_cont_pc;
259#ifdef CONFIG_MIPS_MT_FPAFF 271#ifdef CONFIG_MIPS_MT_FPAFF
260 /* Emulated instruction count */ 272 /* Emulated instruction count */
261 unsigned long emulated_fp; 273 unsigned long emulated_fp;
@@ -323,6 +335,10 @@ struct thread_struct {
323 * FPU affinity state (null if not FPAFF) \ 335 * FPU affinity state (null if not FPAFF) \
324 */ \ 336 */ \
325 FPAFF_INIT \ 337 FPAFF_INIT \
338 /* Delay slot emulation */ \
339 .bd_emu_frame = ATOMIC_INIT(BD_EMUFRAME_NONE), \
340 .bd_emu_branch_pc = 0, \
341 .bd_emu_cont_pc = 0, \
326 /* \ 342 /* \
327 * Saved DSP stuff \ 343 * Saved DSP stuff \
328 */ \ 344 */ \
diff --git a/arch/mips/kernel/mips-r2-to-r6-emul.c b/arch/mips/kernel/mips-r2-to-r6-emul.c
index 7ff2a557f4aa..ef23c61c1e28 100644
--- a/arch/mips/kernel/mips-r2-to-r6-emul.c
+++ b/arch/mips/kernel/mips-r2-to-r6-emul.c
@@ -283,7 +283,7 @@ static int jr_func(struct pt_regs *regs, u32 ir)
283 err = mipsr6_emul(regs, nir); 283 err = mipsr6_emul(regs, nir);
284 if (err > 0) { 284 if (err > 0) {
285 regs->cp0_epc = nepc; 285 regs->cp0_epc = nepc;
286 err = mips_dsemul(regs, nir, cepc); 286 err = mips_dsemul(regs, nir, epc, cepc);
287 if (err == SIGILL) 287 if (err == SIGILL)
288 err = SIGEMT; 288 err = SIGEMT;
289 MIPS_R2_STATS(dsemul); 289 MIPS_R2_STATS(dsemul);
@@ -1033,7 +1033,7 @@ repeat:
1033 if (nir) { 1033 if (nir) {
1034 err = mipsr6_emul(regs, nir); 1034 err = mipsr6_emul(regs, nir);
1035 if (err > 0) { 1035 if (err > 0) {
1036 err = mips_dsemul(regs, nir, cpc); 1036 err = mips_dsemul(regs, nir, epc, cpc);
1037 if (err == SIGILL) 1037 if (err == SIGILL)
1038 err = SIGEMT; 1038 err = SIGEMT;
1039 MIPS_R2_STATS(dsemul); 1039 MIPS_R2_STATS(dsemul);
@@ -1082,7 +1082,7 @@ repeat:
1082 if (nir) { 1082 if (nir) {
1083 err = mipsr6_emul(regs, nir); 1083 err = mipsr6_emul(regs, nir);
1084 if (err > 0) { 1084 if (err > 0) {
1085 err = mips_dsemul(regs, nir, cpc); 1085 err = mips_dsemul(regs, nir, epc, cpc);
1086 if (err == SIGILL) 1086 if (err == SIGILL)
1087 err = SIGEMT; 1087 err = SIGEMT;
1088 MIPS_R2_STATS(dsemul); 1088 MIPS_R2_STATS(dsemul);
@@ -1149,7 +1149,7 @@ repeat:
1149 if (nir) { 1149 if (nir) {
1150 err = mipsr6_emul(regs, nir); 1150 err = mipsr6_emul(regs, nir);
1151 if (err > 0) { 1151 if (err > 0) {
1152 err = mips_dsemul(regs, nir, cpc); 1152 err = mips_dsemul(regs, nir, epc, cpc);
1153 if (err == SIGILL) 1153 if (err == SIGILL)
1154 err = SIGEMT; 1154 err = SIGEMT;
1155 MIPS_R2_STATS(dsemul); 1155 MIPS_R2_STATS(dsemul);
diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c
index 813ed7829c61..7429ad09fbe3 100644
--- a/arch/mips/kernel/process.c
+++ b/arch/mips/kernel/process.c
@@ -30,6 +30,7 @@
30#include <asm/asm.h> 30#include <asm/asm.h>
31#include <asm/bootinfo.h> 31#include <asm/bootinfo.h>
32#include <asm/cpu.h> 32#include <asm/cpu.h>
33#include <asm/dsemul.h>
33#include <asm/dsp.h> 34#include <asm/dsp.h>
34#include <asm/fpu.h> 35#include <asm/fpu.h>
35#include <asm/msa.h> 36#include <asm/msa.h>
@@ -68,11 +69,22 @@ void start_thread(struct pt_regs * regs, unsigned long pc, unsigned long sp)
68 lose_fpu(0); 69 lose_fpu(0);
69 clear_thread_flag(TIF_MSA_CTX_LIVE); 70 clear_thread_flag(TIF_MSA_CTX_LIVE);
70 clear_used_math(); 71 clear_used_math();
72 atomic_set(&current->thread.bd_emu_frame, BD_EMUFRAME_NONE);
71 init_dsp(); 73 init_dsp();
72 regs->cp0_epc = pc; 74 regs->cp0_epc = pc;
73 regs->regs[29] = sp; 75 regs->regs[29] = sp;
74} 76}
75 77
78void exit_thread(struct task_struct *tsk)
79{
80 /*
81 * User threads may have allocated a delay slot emulation frame.
82 * If so, clean up that allocation.
83 */
84 if (!(current->flags & PF_KTHREAD))
85 dsemul_thread_cleanup(tsk);
86}
87
76int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src) 88int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
77{ 89{
78 /* 90 /*
@@ -159,6 +171,8 @@ int copy_thread(unsigned long clone_flags, unsigned long usp,
159 clear_tsk_thread_flag(p, TIF_FPUBOUND); 171 clear_tsk_thread_flag(p, TIF_FPUBOUND);
160#endif /* CONFIG_MIPS_MT_FPAFF */ 172#endif /* CONFIG_MIPS_MT_FPAFF */
161 173
174 atomic_set(&p->thread.bd_emu_frame, BD_EMUFRAME_NONE);
175
162 if (clone_flags & CLONE_SETTLS) 176 if (clone_flags & CLONE_SETTLS)
163 ti->tp_value = regs->regs[7]; 177 ti->tp_value = regs->regs[7];
164 178
diff --git a/arch/mips/kernel/signal.c b/arch/mips/kernel/signal.c
index ae4231452115..938363554c24 100644
--- a/arch/mips/kernel/signal.c
+++ b/arch/mips/kernel/signal.c
@@ -772,6 +772,14 @@ static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
772 struct mips_abi *abi = current->thread.abi; 772 struct mips_abi *abi = current->thread.abi;
773 void *vdso = current->mm->context.vdso; 773 void *vdso = current->mm->context.vdso;
774 774
775 /*
776 * If we were emulating a delay slot instruction, exit that frame such
777 * that addresses in the sigframe are as expected for userland and we
778 * don't have a problem if we reuse the thread's frame for an
779 * instruction within the signal handler.
780 */
781 dsemul_thread_rollback(regs);
782
775 if (regs->regs[0]) { 783 if (regs->regs[0]) {
776 switch(regs->regs[2]) { 784 switch(regs->regs[2]) {
777 case ERESTART_RESTARTBLOCK: 785 case ERESTART_RESTARTBLOCK:
diff --git a/arch/mips/kernel/vdso.c b/arch/mips/kernel/vdso.c
index 54e1663ce639..9abe447a4b48 100644
--- a/arch/mips/kernel/vdso.c
+++ b/arch/mips/kernel/vdso.c
@@ -107,6 +107,16 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp)
107 if (down_write_killable(&mm->mmap_sem)) 107 if (down_write_killable(&mm->mmap_sem))
108 return -EINTR; 108 return -EINTR;
109 109
110 /* Map delay slot emulation page */
111 base = mmap_region(NULL, STACK_TOP, PAGE_SIZE,
112 VM_READ|VM_WRITE|VM_EXEC|
113 VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,
114 0);
115 if (IS_ERR_VALUE(base)) {
116 ret = base;
117 goto out;
118 }
119
110 /* 120 /*
111 * Determine total area size. This includes the VDSO data itself, the 121 * Determine total area size. This includes the VDSO data itself, the
112 * data page, and the GIC user page if present. Always create a mapping 122 * data page, and the GIC user page if present. Always create a mapping
diff --git a/arch/mips/math-emu/cp1emu.c b/arch/mips/math-emu/cp1emu.c
index d96e912b9d44..8afa090f9536 100644
--- a/arch/mips/math-emu/cp1emu.c
+++ b/arch/mips/math-emu/cp1emu.c
@@ -434,8 +434,8 @@ static int microMIPS32_to_MIPS32(union mips_instruction *insn_ptr)
434 * a single subroutine should be used across both 434 * a single subroutine should be used across both
435 * modules. 435 * modules.
436 */ 436 */
437static int isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn, 437int isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn,
438 unsigned long *contpc) 438 unsigned long *contpc)
439{ 439{
440 union mips_instruction insn = (union mips_instruction)dec_insn.insn; 440 union mips_instruction insn = (union mips_instruction)dec_insn.insn;
441 unsigned int fcr31; 441 unsigned int fcr31;
@@ -1268,7 +1268,7 @@ branch_common:
1268 * instruction in the dslot. 1268 * instruction in the dslot.
1269 */ 1269 */
1270 sig = mips_dsemul(xcp, ir, 1270 sig = mips_dsemul(xcp, ir,
1271 contpc); 1271 bcpc, contpc);
1272 if (sig < 0) 1272 if (sig < 0)
1273 break; 1273 break;
1274 if (sig) 1274 if (sig)
@@ -1323,7 +1323,7 @@ branch_common:
1323 * Single step the non-cp1 1323 * Single step the non-cp1
1324 * instruction in the dslot 1324 * instruction in the dslot
1325 */ 1325 */
1326 sig = mips_dsemul(xcp, ir, contpc); 1326 sig = mips_dsemul(xcp, ir, bcpc, contpc);
1327 if (sig < 0) 1327 if (sig < 0)
1328 break; 1328 break;
1329 if (sig) 1329 if (sig)
diff --git a/arch/mips/math-emu/dsemul.c b/arch/mips/math-emu/dsemul.c
index 47074887e64c..72a4642eee2c 100644
--- a/arch/mips/math-emu/dsemul.c
+++ b/arch/mips/math-emu/dsemul.c
@@ -1,3 +1,6 @@
1#include <linux/err.h>
2#include <linux/slab.h>
3
1#include <asm/branch.h> 4#include <asm/branch.h>
2#include <asm/cacheflush.h> 5#include <asm/cacheflush.h>
3#include <asm/fpu_emulator.h> 6#include <asm/fpu_emulator.h>
@@ -5,43 +8,211 @@
5#include <asm/mipsregs.h> 8#include <asm/mipsregs.h>
6#include <asm/uaccess.h> 9#include <asm/uaccess.h>
7 10
8#include "ieee754.h" 11/**
9 12 * struct emuframe - The 'emulation' frame structure
10/* 13 * @emul: The instruction to 'emulate'.
11 * Emulate the arbitrary instruction ir at xcp->cp0_epc. Required when 14 * @badinst: A break instruction to cause a return to the kernel.
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 *
15 * According to the spec: 16 * This structure defines the frames placed within the delay slot emulation
16 * 1) it shouldn't be a branch :-) 17 * page in response to a call to mips_dsemul(). Each thread may be allocated
17 * 2) it can be a COP instruction :-( 18 * only one frame at any given time. The kernel stores within it the
18 * 3) if we are tring to run a protected memory space we must take 19 * instruction to be 'emulated' followed by a break instruction, then
19 * special care on memory access instructions :-( 20 * executes the frame in user mode. The break causes a trap to the kernel
20 */ 21 * which leads to do_dsemulret() being called unless the instruction in
21 22 * @emul causes a trap itself, is a branch, or a signal is delivered to
22/* 23 * the thread. In these cases the allocated frame will either be reused by
23 * "Trampoline" return routine to catch exception following 24 * a subsequent delay slot 'emulation', or be freed during signal delivery or
24 * execution of delay-slot instruction execution. 25 * upon thread exit.
26 *
27 * This approach is used because:
28 *
29 * - Actually emulating all instructions isn't feasible. We would need to
30 * be able to handle instructions from all revisions of the MIPS ISA,
31 * all ASEs & all vendor instruction set extensions. This would be a
32 * whole lot of work & continual maintenance burden as new instructions
33 * are introduced, and in the case of some vendor extensions may not
34 * even be possible. Thus we need to take the approach of actually
35 * executing the instruction.
36 *
37 * - We must execute the instruction within user context. If we were to
38 * execute the instruction in kernel mode then it would have access to
39 * kernel resources without very careful checks, leaving us with a
40 * high potential for security or stability issues to arise.
41 *
42 * - We used to place the frame on the users stack, but this requires
43 * that the stack be executable. This is bad for security so the
44 * per-process page is now used instead.
45 *
46 * - The instruction in @emul may be something entirely invalid for a
47 * delay slot. The user may (intentionally or otherwise) place a branch
48 * in a delay slot, or a kernel mode instruction, or something else
49 * which generates an exception. Thus we can't rely upon the break in
50 * @badinst always being hit. For this reason we track the index of the
51 * frame allocated to each thread, allowing us to clean it up at later
52 * points such as signal delivery or thread exit.
53 *
54 * - The user may generate a fake struct emuframe if they wish, invoking
55 * the BRK_MEMU break instruction themselves. We must therefore not
56 * trust that BRK_MEMU means there's actually a valid frame allocated
57 * to the thread, and must not allow the user to do anything they
58 * couldn't already.
25 */ 59 */
26
27struct emuframe { 60struct emuframe {
28 mips_instruction emul; 61 mips_instruction emul;
29 mips_instruction badinst; 62 mips_instruction badinst;
30 mips_instruction cookie;
31 unsigned long epc;
32}; 63};
33 64
34/* 65static const int emupage_frame_count = PAGE_SIZE / sizeof(struct emuframe);
35 * Set up an emulation frame for instruction IR, from a delay slot of 66
36 * a branch jumping to CPC. Return 0 if successful, -1 if no emulation 67static inline __user struct emuframe *dsemul_page(void)
37 * required, otherwise a signal number causing a frame setup failure. 68{
38 */ 69 return (__user struct emuframe *)STACK_TOP;
39int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc) 70}
71
72static int alloc_emuframe(void)
73{
74 mm_context_t *mm_ctx = &current->mm->context;
75 int idx;
76
77retry:
78 spin_lock(&mm_ctx->bd_emupage_lock);
79
80 /* Ensure we have an allocation bitmap */
81 if (!mm_ctx->bd_emupage_allocmap) {
82 mm_ctx->bd_emupage_allocmap =
83 kcalloc(BITS_TO_LONGS(emupage_frame_count),
84 sizeof(unsigned long),
85 GFP_ATOMIC);
86
87 if (!mm_ctx->bd_emupage_allocmap) {
88 idx = BD_EMUFRAME_NONE;
89 goto out_unlock;
90 }
91 }
92
93 /* Attempt to allocate a single bit/frame */
94 idx = bitmap_find_free_region(mm_ctx->bd_emupage_allocmap,
95 emupage_frame_count, 0);
96 if (idx < 0) {
97 /*
98 * Failed to allocate a frame. We'll wait until one becomes
99 * available. We unlock the page so that other threads actually
100 * get the opportunity to free their frames, which means
101 * technically the result of bitmap_full may be incorrect.
102 * However the worst case is that we repeat all this and end up
103 * back here again.
104 */
105 spin_unlock(&mm_ctx->bd_emupage_lock);
106 if (!wait_event_killable(mm_ctx->bd_emupage_queue,
107 !bitmap_full(mm_ctx->bd_emupage_allocmap,
108 emupage_frame_count)))
109 goto retry;
110
111 /* Received a fatal signal - just give in */
112 return BD_EMUFRAME_NONE;
113 }
114
115 /* Success! */
116 pr_debug("allocate emuframe %d to %d\n", idx, current->pid);
117out_unlock:
118 spin_unlock(&mm_ctx->bd_emupage_lock);
119 return idx;
120}
121
122static void free_emuframe(int idx, struct mm_struct *mm)
123{
124 mm_context_t *mm_ctx = &mm->context;
125
126 spin_lock(&mm_ctx->bd_emupage_lock);
127
128 pr_debug("free emuframe %d from %d\n", idx, current->pid);
129 bitmap_clear(mm_ctx->bd_emupage_allocmap, idx, 1);
130
131 /* If some thread is waiting for a frame, now's its chance */
132 wake_up(&mm_ctx->bd_emupage_queue);
133
134 spin_unlock(&mm_ctx->bd_emupage_lock);
135}
136
137static bool within_emuframe(struct pt_regs *regs)
138{
139 unsigned long base = (unsigned long)dsemul_page();
140
141 if (regs->cp0_epc < base)
142 return false;
143 if (regs->cp0_epc >= (base + PAGE_SIZE))
144 return false;
145
146 return true;
147}
148
149bool dsemul_thread_cleanup(struct task_struct *tsk)
150{
151 int fr_idx;
152
153 /* Clear any allocated frame, retrieving its index */
154 fr_idx = atomic_xchg(&tsk->thread.bd_emu_frame, BD_EMUFRAME_NONE);
155
156 /* If no frame was allocated, we're done */
157 if (fr_idx == BD_EMUFRAME_NONE)
158 return false;
159
160 task_lock(tsk);
161
162 /* Free the frame that this thread had allocated */
163 if (tsk->mm)
164 free_emuframe(fr_idx, tsk->mm);
165
166 task_unlock(tsk);
167 return true;
168}
169
170bool dsemul_thread_rollback(struct pt_regs *regs)
171{
172 struct emuframe __user *fr;
173 int fr_idx;
174
175 /* Do nothing if we're not executing from a frame */
176 if (!within_emuframe(regs))
177 return false;
178
179 /* Find the frame being executed */
180 fr_idx = atomic_read(&current->thread.bd_emu_frame);
181 if (fr_idx == BD_EMUFRAME_NONE)
182 return false;
183 fr = &dsemul_page()[fr_idx];
184
185 /*
186 * If the PC is at the emul instruction, roll back to the branch. If
187 * PC is at the badinst (break) instruction, we've already emulated the
188 * instruction so progress to the continue PC. If it's anything else
189 * then something is amiss & the user has branched into some other area
190 * of the emupage - we'll free the allocated frame anyway.
191 */
192 if (msk_isa16_mode(regs->cp0_epc) == (unsigned long)&fr->emul)
193 regs->cp0_epc = current->thread.bd_emu_branch_pc;
194 else if (msk_isa16_mode(regs->cp0_epc) == (unsigned long)&fr->badinst)
195 regs->cp0_epc = current->thread.bd_emu_cont_pc;
196
197 atomic_set(&current->thread.bd_emu_frame, BD_EMUFRAME_NONE);
198 free_emuframe(fr_idx, current->mm);
199 return true;
200}
201
202void dsemul_mm_cleanup(struct mm_struct *mm)
203{
204 mm_context_t *mm_ctx = &mm->context;
205
206 kfree(mm_ctx->bd_emupage_allocmap);
207}
208
209int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
210 unsigned long branch_pc, unsigned long cont_pc)
40{ 211{
41 int isa16 = get_isa16_mode(regs->cp0_epc); 212 int isa16 = get_isa16_mode(regs->cp0_epc);
42 mips_instruction break_math; 213 mips_instruction break_math;
43 struct emuframe __user *fr; 214 struct emuframe __user *fr;
44 int err; 215 int err, fr_idx;
45 216
46 /* NOP is easy */ 217 /* NOP is easy */
47 if (ir == 0) 218 if (ir == 0)
@@ -68,30 +239,20 @@ int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc)
68 } 239 }
69 } 240 }
70 241
71 pr_debug("dsemul %lx %lx\n", regs->cp0_epc, cpc); 242 pr_debug("dsemul 0x%08lx cont at 0x%08lx\n", regs->cp0_epc, cont_pc);
72 243
73 /* 244 /* Allocate a frame if we don't already have one */
74 * The strategy is to push the instruction onto the user stack 245 fr_idx = atomic_read(&current->thread.bd_emu_frame);
75 * and put a trap after it which we can catch and jump to 246 if (fr_idx == BD_EMUFRAME_NONE)
76 * the required address any alternative apart from full 247 fr_idx = alloc_emuframe();
77 * instruction emulation!!. 248 if (fr_idx == BD_EMUFRAME_NONE)
78 *
79 * Algorithmics used a system call instruction, and
80 * borrowed that vector. MIPS/Linux version is a bit
81 * more heavyweight in the interests of portability and
82 * multiprocessor support. For Linux we use a BREAK 514
83 * instruction causing a breakpoint exception.
84 */
85 break_math = BREAK_MATH(isa16);
86
87 /* Ensure that the two instructions are in the same cache line */
88 fr = (struct emuframe __user *)
89 ((regs->regs[29] - sizeof(struct emuframe)) & ~0x7);
90
91 /* Verify that the stack pointer is not completely insane */
92 if (unlikely(!access_ok(VERIFY_WRITE, fr, sizeof(struct emuframe))))
93 return SIGBUS; 249 return SIGBUS;
250 fr = &dsemul_page()[fr_idx];
251
252 /* Retrieve the appropriately encoded break instruction */
253 break_math = BREAK_MATH(isa16);
94 254
255 /* Write the instructions to the frame */
95 if (isa16) { 256 if (isa16) {
96 err = __put_user(ir >> 16, 257 err = __put_user(ir >> 16,
97 (u16 __user *)(&fr->emul)); 258 (u16 __user *)(&fr->emul));
@@ -106,84 +267,36 @@ int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc)
106 err |= __put_user(break_math, &fr->badinst); 267 err |= __put_user(break_math, &fr->badinst);
107 } 268 }
108 269
109 err |= __put_user((mips_instruction)BD_COOKIE, &fr->cookie);
110 err |= __put_user(cpc, &fr->epc);
111
112 if (unlikely(err)) { 270 if (unlikely(err)) {
113 MIPS_FPU_EMU_INC_STATS(errors); 271 MIPS_FPU_EMU_INC_STATS(errors);
272 free_emuframe(fr_idx, current->mm);
114 return SIGBUS; 273 return SIGBUS;
115 } 274 }
116 275
276 /* Record the PC of the branch, PC to continue from & frame index */
277 current->thread.bd_emu_branch_pc = branch_pc;
278 current->thread.bd_emu_cont_pc = cont_pc;
279 atomic_set(&current->thread.bd_emu_frame, fr_idx);
280
281 /* Change user register context to execute the frame */
117 regs->cp0_epc = (unsigned long)&fr->emul | isa16; 282 regs->cp0_epc = (unsigned long)&fr->emul | isa16;
118 283
284 /* Ensure the icache observes our newly written frame */
119 flush_cache_sigtramp((unsigned long)&fr->emul); 285 flush_cache_sigtramp((unsigned long)&fr->emul);
120 286
121 return 0; 287 return 0;
122} 288}
123 289
124int do_dsemulret(struct pt_regs *xcp) 290bool do_dsemulret(struct pt_regs *xcp)
125{ 291{
126 int isa16 = get_isa16_mode(xcp->cp0_epc); 292 /* Cleanup the allocated frame, returning if there wasn't one */
127 struct emuframe __user *fr; 293 if (!dsemul_thread_cleanup(current)) {
128 unsigned long epc;
129 u32 insn, cookie;
130 int err = 0;
131 u16 instr[2];
132
133 fr = (struct emuframe __user *)
134 (msk_isa16_mode(xcp->cp0_epc) - sizeof(mips_instruction));
135
136 /*
137 * If we can't even access the area, something is very wrong, but we'll
138 * leave that to the default handling
139 */
140 if (!access_ok(VERIFY_READ, fr, sizeof(struct emuframe)))
141 return 0;
142
143 /*
144 * Do some sanity checking on the stackframe:
145 *
146 * - Is the instruction pointed to by the EPC an BREAK_MATH?
147 * - Is the following memory word the BD_COOKIE?
148 */
149 if (isa16) {
150 err = __get_user(instr[0],
151 (u16 __user *)(&fr->badinst));
152 err |= __get_user(instr[1],
153 (u16 __user *)((long)(&fr->badinst) + 2));
154 insn = (instr[0] << 16) | instr[1];
155 } else {
156 err = __get_user(insn, &fr->badinst);
157 }
158 err |= __get_user(cookie, &fr->cookie);
159
160 if (unlikely(err ||
161 insn != BREAK_MATH(isa16) || cookie != BD_COOKIE)) {
162 MIPS_FPU_EMU_INC_STATS(errors); 294 MIPS_FPU_EMU_INC_STATS(errors);
163 return 0; 295 return false;
164 }
165
166 /*
167 * At this point, we are satisfied that it's a BD emulation trap. Yes,
168 * a user might have deliberately put two malformed and useless
169 * instructions in a row in his program, in which case he's in for a
170 * nasty surprise - the next instruction will be treated as a
171 * continuation address! Alas, this seems to be the only way that we
172 * can handle signals, recursion, and longjmps() in the context of
173 * emulating the branch delay instruction.
174 */
175
176 pr_debug("dsemulret\n");
177
178 if (__get_user(epc, &fr->epc)) { /* Saved EPC */
179 /* This is not a good situation to be in */
180 force_sig(SIGBUS, current);
181
182 return 0;
183 } 296 }
184 297
185 /* Set EPC to return to post-branch instruction */ 298 /* Set EPC to return to post-branch instruction */
186 xcp->cp0_epc = epc; 299 xcp->cp0_epc = current->thread.bd_emu_cont_pc;
187 MIPS_FPU_EMU_INC_STATS(ds_emul); 300 pr_debug("dsemulret to 0x%08lx\n", xcp->cp0_epc);
188 return 1; 301 return true;
189} 302}