162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci#include <linux/err.h> 362306a36Sopenharmony_ci#include <linux/slab.h> 462306a36Sopenharmony_ci#include <linux/mm_types.h> 562306a36Sopenharmony_ci#include <linux/sched/task.h> 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <asm/branch.h> 862306a36Sopenharmony_ci#include <asm/cacheflush.h> 962306a36Sopenharmony_ci#include <asm/fpu_emulator.h> 1062306a36Sopenharmony_ci#include <asm/inst.h> 1162306a36Sopenharmony_ci#include <asm/mipsregs.h> 1262306a36Sopenharmony_ci#include <linux/uaccess.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci/** 1562306a36Sopenharmony_ci * struct emuframe - The 'emulation' frame structure 1662306a36Sopenharmony_ci * @emul: The instruction to 'emulate'. 1762306a36Sopenharmony_ci * @badinst: A break instruction to cause a return to the kernel. 1862306a36Sopenharmony_ci * 1962306a36Sopenharmony_ci * This structure defines the frames placed within the delay slot emulation 2062306a36Sopenharmony_ci * page in response to a call to mips_dsemul(). Each thread may be allocated 2162306a36Sopenharmony_ci * only one frame at any given time. The kernel stores within it the 2262306a36Sopenharmony_ci * instruction to be 'emulated' followed by a break instruction, then 2362306a36Sopenharmony_ci * executes the frame in user mode. The break causes a trap to the kernel 2462306a36Sopenharmony_ci * which leads to do_dsemulret() being called unless the instruction in 2562306a36Sopenharmony_ci * @emul causes a trap itself, is a branch, or a signal is delivered to 2662306a36Sopenharmony_ci * the thread. In these cases the allocated frame will either be reused by 2762306a36Sopenharmony_ci * a subsequent delay slot 'emulation', or be freed during signal delivery or 2862306a36Sopenharmony_ci * upon thread exit. 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * This approach is used because: 3162306a36Sopenharmony_ci * 3262306a36Sopenharmony_ci * - Actually emulating all instructions isn't feasible. We would need to 3362306a36Sopenharmony_ci * be able to handle instructions from all revisions of the MIPS ISA, 3462306a36Sopenharmony_ci * all ASEs & all vendor instruction set extensions. This would be a 3562306a36Sopenharmony_ci * whole lot of work & continual maintenance burden as new instructions 3662306a36Sopenharmony_ci * are introduced, and in the case of some vendor extensions may not 3762306a36Sopenharmony_ci * even be possible. Thus we need to take the approach of actually 3862306a36Sopenharmony_ci * executing the instruction. 3962306a36Sopenharmony_ci * 4062306a36Sopenharmony_ci * - We must execute the instruction within user context. If we were to 4162306a36Sopenharmony_ci * execute the instruction in kernel mode then it would have access to 4262306a36Sopenharmony_ci * kernel resources without very careful checks, leaving us with a 4362306a36Sopenharmony_ci * high potential for security or stability issues to arise. 4462306a36Sopenharmony_ci * 4562306a36Sopenharmony_ci * - We used to place the frame on the users stack, but this requires 4662306a36Sopenharmony_ci * that the stack be executable. This is bad for security so the 4762306a36Sopenharmony_ci * per-process page is now used instead. 4862306a36Sopenharmony_ci * 4962306a36Sopenharmony_ci * - The instruction in @emul may be something entirely invalid for a 5062306a36Sopenharmony_ci * delay slot. The user may (intentionally or otherwise) place a branch 5162306a36Sopenharmony_ci * in a delay slot, or a kernel mode instruction, or something else 5262306a36Sopenharmony_ci * which generates an exception. Thus we can't rely upon the break in 5362306a36Sopenharmony_ci * @badinst always being hit. For this reason we track the index of the 5462306a36Sopenharmony_ci * frame allocated to each thread, allowing us to clean it up at later 5562306a36Sopenharmony_ci * points such as signal delivery or thread exit. 5662306a36Sopenharmony_ci * 5762306a36Sopenharmony_ci * - The user may generate a fake struct emuframe if they wish, invoking 5862306a36Sopenharmony_ci * the BRK_MEMU break instruction themselves. We must therefore not 5962306a36Sopenharmony_ci * trust that BRK_MEMU means there's actually a valid frame allocated 6062306a36Sopenharmony_ci * to the thread, and must not allow the user to do anything they 6162306a36Sopenharmony_ci * couldn't already. 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_cistruct emuframe { 6462306a36Sopenharmony_ci mips_instruction emul; 6562306a36Sopenharmony_ci mips_instruction badinst; 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic const int emupage_frame_count = PAGE_SIZE / sizeof(struct emuframe); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic inline __user struct emuframe *dsemul_page(void) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci return (__user struct emuframe *)STACK_TOP; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic int alloc_emuframe(void) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci mm_context_t *mm_ctx = ¤t->mm->context; 7862306a36Sopenharmony_ci int idx; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ciretry: 8162306a36Sopenharmony_ci spin_lock(&mm_ctx->bd_emupage_lock); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci /* Ensure we have an allocation bitmap */ 8462306a36Sopenharmony_ci if (!mm_ctx->bd_emupage_allocmap) { 8562306a36Sopenharmony_ci mm_ctx->bd_emupage_allocmap = bitmap_zalloc(emupage_frame_count, 8662306a36Sopenharmony_ci GFP_ATOMIC); 8762306a36Sopenharmony_ci if (!mm_ctx->bd_emupage_allocmap) { 8862306a36Sopenharmony_ci idx = BD_EMUFRAME_NONE; 8962306a36Sopenharmony_ci goto out_unlock; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci /* Attempt to allocate a single bit/frame */ 9462306a36Sopenharmony_ci idx = bitmap_find_free_region(mm_ctx->bd_emupage_allocmap, 9562306a36Sopenharmony_ci emupage_frame_count, 0); 9662306a36Sopenharmony_ci if (idx < 0) { 9762306a36Sopenharmony_ci /* 9862306a36Sopenharmony_ci * Failed to allocate a frame. We'll wait until one becomes 9962306a36Sopenharmony_ci * available. We unlock the page so that other threads actually 10062306a36Sopenharmony_ci * get the opportunity to free their frames, which means 10162306a36Sopenharmony_ci * technically the result of bitmap_full may be incorrect. 10262306a36Sopenharmony_ci * However the worst case is that we repeat all this and end up 10362306a36Sopenharmony_ci * back here again. 10462306a36Sopenharmony_ci */ 10562306a36Sopenharmony_ci spin_unlock(&mm_ctx->bd_emupage_lock); 10662306a36Sopenharmony_ci if (!wait_event_killable(mm_ctx->bd_emupage_queue, 10762306a36Sopenharmony_ci !bitmap_full(mm_ctx->bd_emupage_allocmap, 10862306a36Sopenharmony_ci emupage_frame_count))) 10962306a36Sopenharmony_ci goto retry; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci /* Received a fatal signal - just give in */ 11262306a36Sopenharmony_ci return BD_EMUFRAME_NONE; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci /* Success! */ 11662306a36Sopenharmony_ci pr_debug("allocate emuframe %d to %d\n", idx, current->pid); 11762306a36Sopenharmony_ciout_unlock: 11862306a36Sopenharmony_ci spin_unlock(&mm_ctx->bd_emupage_lock); 11962306a36Sopenharmony_ci return idx; 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic void free_emuframe(int idx, struct mm_struct *mm) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci mm_context_t *mm_ctx = &mm->context; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci spin_lock(&mm_ctx->bd_emupage_lock); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci pr_debug("free emuframe %d from %d\n", idx, current->pid); 12962306a36Sopenharmony_ci bitmap_clear(mm_ctx->bd_emupage_allocmap, idx, 1); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* If some thread is waiting for a frame, now's its chance */ 13262306a36Sopenharmony_ci wake_up(&mm_ctx->bd_emupage_queue); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci spin_unlock(&mm_ctx->bd_emupage_lock); 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic bool within_emuframe(struct pt_regs *regs) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci unsigned long base = (unsigned long)dsemul_page(); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (regs->cp0_epc < base) 14262306a36Sopenharmony_ci return false; 14362306a36Sopenharmony_ci if (regs->cp0_epc >= (base + PAGE_SIZE)) 14462306a36Sopenharmony_ci return false; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci return true; 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cibool dsemul_thread_cleanup(struct task_struct *tsk) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci int fr_idx; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci /* Clear any allocated frame, retrieving its index */ 15462306a36Sopenharmony_ci fr_idx = atomic_xchg(&tsk->thread.bd_emu_frame, BD_EMUFRAME_NONE); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* If no frame was allocated, we're done */ 15762306a36Sopenharmony_ci if (fr_idx == BD_EMUFRAME_NONE) 15862306a36Sopenharmony_ci return false; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci task_lock(tsk); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* Free the frame that this thread had allocated */ 16362306a36Sopenharmony_ci if (tsk->mm) 16462306a36Sopenharmony_ci free_emuframe(fr_idx, tsk->mm); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci task_unlock(tsk); 16762306a36Sopenharmony_ci return true; 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cibool dsemul_thread_rollback(struct pt_regs *regs) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci struct emuframe __user *fr; 17362306a36Sopenharmony_ci int fr_idx; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* Do nothing if we're not executing from a frame */ 17662306a36Sopenharmony_ci if (!within_emuframe(regs)) 17762306a36Sopenharmony_ci return false; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* Find the frame being executed */ 18062306a36Sopenharmony_ci fr_idx = atomic_read(¤t->thread.bd_emu_frame); 18162306a36Sopenharmony_ci if (fr_idx == BD_EMUFRAME_NONE) 18262306a36Sopenharmony_ci return false; 18362306a36Sopenharmony_ci fr = &dsemul_page()[fr_idx]; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* 18662306a36Sopenharmony_ci * If the PC is at the emul instruction, roll back to the branch. If 18762306a36Sopenharmony_ci * PC is at the badinst (break) instruction, we've already emulated the 18862306a36Sopenharmony_ci * instruction so progress to the continue PC. If it's anything else 18962306a36Sopenharmony_ci * then something is amiss & the user has branched into some other area 19062306a36Sopenharmony_ci * of the emupage - we'll free the allocated frame anyway. 19162306a36Sopenharmony_ci */ 19262306a36Sopenharmony_ci if (msk_isa16_mode(regs->cp0_epc) == (unsigned long)&fr->emul) 19362306a36Sopenharmony_ci regs->cp0_epc = current->thread.bd_emu_branch_pc; 19462306a36Sopenharmony_ci else if (msk_isa16_mode(regs->cp0_epc) == (unsigned long)&fr->badinst) 19562306a36Sopenharmony_ci regs->cp0_epc = current->thread.bd_emu_cont_pc; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci atomic_set(¤t->thread.bd_emu_frame, BD_EMUFRAME_NONE); 19862306a36Sopenharmony_ci free_emuframe(fr_idx, current->mm); 19962306a36Sopenharmony_ci return true; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_civoid dsemul_mm_cleanup(struct mm_struct *mm) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci mm_context_t *mm_ctx = &mm->context; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci bitmap_free(mm_ctx->bd_emupage_allocmap); 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ciint mips_dsemul(struct pt_regs *regs, mips_instruction ir, 21062306a36Sopenharmony_ci unsigned long branch_pc, unsigned long cont_pc) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci int isa16 = get_isa16_mode(regs->cp0_epc); 21362306a36Sopenharmony_ci mips_instruction break_math; 21462306a36Sopenharmony_ci unsigned long fr_uaddr; 21562306a36Sopenharmony_ci struct emuframe fr; 21662306a36Sopenharmony_ci int fr_idx, ret; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci /* NOP is easy */ 21962306a36Sopenharmony_ci if (ir == 0) 22062306a36Sopenharmony_ci return -1; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci /* microMIPS instructions */ 22362306a36Sopenharmony_ci if (isa16) { 22462306a36Sopenharmony_ci union mips_instruction insn = { .word = ir }; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci /* NOP16 aka MOVE16 $0, $0 */ 22762306a36Sopenharmony_ci if ((ir >> 16) == MM_NOP16) 22862306a36Sopenharmony_ci return -1; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* ADDIUPC */ 23162306a36Sopenharmony_ci if (insn.mm_a_format.opcode == mm_addiupc_op) { 23262306a36Sopenharmony_ci unsigned int rs; 23362306a36Sopenharmony_ci s32 v; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci rs = (((insn.mm_a_format.rs + 0xe) & 0xf) + 2); 23662306a36Sopenharmony_ci v = regs->cp0_epc & ~3; 23762306a36Sopenharmony_ci v += insn.mm_a_format.simmediate << 2; 23862306a36Sopenharmony_ci regs->regs[rs] = (long)v; 23962306a36Sopenharmony_ci return -1; 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci pr_debug("dsemul 0x%08lx cont at 0x%08lx\n", regs->cp0_epc, cont_pc); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci /* Allocate a frame if we don't already have one */ 24662306a36Sopenharmony_ci fr_idx = atomic_read(¤t->thread.bd_emu_frame); 24762306a36Sopenharmony_ci if (fr_idx == BD_EMUFRAME_NONE) 24862306a36Sopenharmony_ci fr_idx = alloc_emuframe(); 24962306a36Sopenharmony_ci if (fr_idx == BD_EMUFRAME_NONE) 25062306a36Sopenharmony_ci return SIGBUS; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci /* Retrieve the appropriately encoded break instruction */ 25362306a36Sopenharmony_ci break_math = BREAK_MATH(isa16); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci /* Write the instructions to the frame */ 25662306a36Sopenharmony_ci if (isa16) { 25762306a36Sopenharmony_ci union mips_instruction _emul = { 25862306a36Sopenharmony_ci .halfword = { ir >> 16, ir } 25962306a36Sopenharmony_ci }; 26062306a36Sopenharmony_ci union mips_instruction _badinst = { 26162306a36Sopenharmony_ci .halfword = { break_math >> 16, break_math } 26262306a36Sopenharmony_ci }; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci fr.emul = _emul.word; 26562306a36Sopenharmony_ci fr.badinst = _badinst.word; 26662306a36Sopenharmony_ci } else { 26762306a36Sopenharmony_ci fr.emul = ir; 26862306a36Sopenharmony_ci fr.badinst = break_math; 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci /* Write the frame to user memory */ 27262306a36Sopenharmony_ci fr_uaddr = (unsigned long)&dsemul_page()[fr_idx]; 27362306a36Sopenharmony_ci ret = access_process_vm(current, fr_uaddr, &fr, sizeof(fr), 27462306a36Sopenharmony_ci FOLL_FORCE | FOLL_WRITE); 27562306a36Sopenharmony_ci if (unlikely(ret != sizeof(fr))) { 27662306a36Sopenharmony_ci MIPS_FPU_EMU_INC_STATS(errors); 27762306a36Sopenharmony_ci free_emuframe(fr_idx, current->mm); 27862306a36Sopenharmony_ci return SIGBUS; 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci /* Record the PC of the branch, PC to continue from & frame index */ 28262306a36Sopenharmony_ci current->thread.bd_emu_branch_pc = branch_pc; 28362306a36Sopenharmony_ci current->thread.bd_emu_cont_pc = cont_pc; 28462306a36Sopenharmony_ci atomic_set(¤t->thread.bd_emu_frame, fr_idx); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci /* Change user register context to execute the frame */ 28762306a36Sopenharmony_ci regs->cp0_epc = fr_uaddr | isa16; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci return 0; 29062306a36Sopenharmony_ci} 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_cibool do_dsemulret(struct pt_regs *xcp) 29362306a36Sopenharmony_ci{ 29462306a36Sopenharmony_ci /* Cleanup the allocated frame, returning if there wasn't one */ 29562306a36Sopenharmony_ci if (!dsemul_thread_cleanup(current)) { 29662306a36Sopenharmony_ci MIPS_FPU_EMU_INC_STATS(errors); 29762306a36Sopenharmony_ci return false; 29862306a36Sopenharmony_ci } 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci /* Set EPC to return to post-branch instruction */ 30162306a36Sopenharmony_ci xcp->cp0_epc = current->thread.bd_emu_cont_pc; 30262306a36Sopenharmony_ci pr_debug("dsemulret to 0x%08lx\n", xcp->cp0_epc); 30362306a36Sopenharmony_ci MIPS_FPU_EMU_INC_STATS(ds_emul); 30462306a36Sopenharmony_ci return true; 30562306a36Sopenharmony_ci} 306