/* * Handle unaligned accesses by emulation. * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 1996, 1998, 1999, 2002 by Ralf Baechle * Copyright (C) 1999 Silicon Graphics, Inc. * Copyright (C) 2014 Imagination Technologies Ltd. * * This file contains exception handler for address error exception with the * special capability to execute faulting instructions in software. The * handler does not try to handle the case when the program counter points * to an address not aligned to a word boundary. * * Putting data to unaligned addresses is a bad practice even on Intel where * only the performance is affected. Much worse is that such code is non- * portable. Due to several programs that die on MIPS due to alignment * problems I decided to implement this handler anyway though I originally * didn't intend to do this at all for user code. * * For now I enable fixing of address errors by default to make life easier. * I however intend to disable this somewhen in the future when the alignment * problems with user programs have been fixed. For programmers this is the * right way to go. * * Fixing address errors is a per process option. The option is inherited * across fork(2) and execve(2) calls. If you really want to use the * option in your user programs - I discourage the use of the software * emulation strongly - use the following code in your userland stuff: * * #include <sys/sysmips.h> * * ... * sysmips(MIPS_FIXADE, x); * ... * * The argument x is 0 for disabling software emulation, enabled otherwise. * * Below a little program to play around with this feature. * * #include <stdio.h> * #include <sys/sysmips.h> * * struct foo { * unsigned char bar[8]; * }; * * main(int argc, char *argv[]) * { * struct foo x = {0, 1, 2, 3, 4, 5, 6, 7}; * unsigned int *p = (unsigned int *) (x.bar + 3); * int i; * * if (argc > 1) * sysmips(MIPS_FIXADE, atoi(argv[1])); * * printf("*p = %08lx\n", *p); * * *p = 0xdeadface; * * for(i = 0; i <= 7; i++) * printf("%02x ", x.bar[i]); * printf("\n"); * } * * Coprocessor loads are not supported; I think this case is unimportant * in the practice. * * TODO: Handle ndc (attempted store to doubleword in uncached memory) * exception for the R6000. * A store crossing a page boundary might be executed only partially. * Undo the partial store in this case.
*/ #include <linux/context_tracking.h> #include <linux/mm.h> #include <linux/signal.h> #include <linux/smp.h> #include <linux/sched.h> #include <linux/debugfs.h> #include <linux/perf_event.h>
/* * This load never faults.
*/
__get_inst32(&insn.word, pc, user);
switch (insn.i_format.opcode) { /* * These are instructions that a compiler doesn't generate. We * can assume therefore that the code is MIPS-aware and * really buggy. Emulating these instructions would break the * semantics anyway.
*/ case ll_op: case lld_op: case sc_op: case scd_op:
/* * For these instructions the only way to create an address * error is an attempted access to kernel/supervisor address * space.
*/ case ldl_op: case ldr_op: case lwl_op: case lwr_op: case sdl_op: case sdr_op: case swl_op: case swr_op: case lb_op: case lbu_op: case sb_op: goto sigbus;
/* * The remaining opcodes are the ones that are really of * interest.
*/ #ifdef CONFIG_MACH_INGENIC case spec2_op: if (insn.mxu_lx_format.func != mxu_lx_op) goto sigbus; /* other MXU instructions we don't care */
switch (insn.mxu_lx_format.op) { case mxu_lxw_op: if (user && !access_ok(addr, 4)) goto sigbus;
LoadW(addr, value, res); if (res) goto fault;
compute_return_epc(regs);
regs->regs[insn.mxu_lx_format.rd] = value; break; case mxu_lxh_op: if (user && !access_ok(addr, 2)) goto sigbus;
LoadHW(addr, value, res); if (res) goto fault;
compute_return_epc(regs);
regs->regs[insn.dsp_format.rd] = value; break; case mxu_lxhu_op: if (user && !access_ok(addr, 2)) goto sigbus;
LoadHWU(addr, value, res); if (res) goto fault;
compute_return_epc(regs);
regs->regs[insn.dsp_format.rd] = value; break; case mxu_lxb_op: case mxu_lxbu_op: goto sigbus; default: goto sigill;
} break; #endif case spec3_op: if (insn.dsp_format.func == lx_op) { switch (insn.dsp_format.op) { case lwx_op: if (user && !access_ok(addr, 4)) goto sigbus;
LoadW(addr, value, res); if (res) goto fault;
compute_return_epc(regs);
regs->regs[insn.dsp_format.rd] = value; break; case lhx_op: if (user && !access_ok(addr, 2)) goto sigbus;
LoadHW(addr, value, res); if (res) goto fault;
compute_return_epc(regs);
regs->regs[insn.dsp_format.rd] = value; break; default: goto sigill;
}
} #ifdef CONFIG_EVA else { /* * we can land here only from kernel accessing user * memory, so we need to "switch" the address limit to * user space, so that address check can work properly.
*/ switch (insn.spec3_format.func) { case lhe_op: if (!access_ok(addr, 2)) goto sigbus;
LoadHWE(addr, value, res); if (res) goto fault;
compute_return_epc(regs);
regs->regs[insn.spec3_format.rt] = value; break; case lwe_op: if (!access_ok(addr, 4)) goto sigbus;
LoadWE(addr, value, res); if (res) goto fault;
compute_return_epc(regs);
regs->regs[insn.spec3_format.rt] = value; break; case lhue_op: if (!access_ok(addr, 2)) goto sigbus;
LoadHWUE(addr, value, res); if (res) goto fault;
compute_return_epc(regs);
regs->regs[insn.spec3_format.rt] = value; break; case she_op: if (!access_ok(addr, 2)) goto sigbus;
compute_return_epc(regs);
value = regs->regs[insn.spec3_format.rt];
StoreHWE(addr, value, res); if (res) goto fault; break; case swe_op: if (!access_ok(addr, 4)) goto sigbus;
compute_return_epc(regs);
value = regs->regs[insn.spec3_format.rt];
StoreWE(addr, value, res); if (res) goto fault; break; default: goto sigill;
}
} #endif break; case lh_op: if (user && !access_ok(addr, 2)) goto sigbus;
if (res) goto fault;
compute_return_epc(regs);
regs->regs[insn.i_format.rt] = value; break;
case lwu_op: #ifdef CONFIG_64BIT /* * A 32-bit kernel might be running on a 64-bit processor. But * if we're on a 32-bit processor and an i-cache incoherency * or race makes us see a 64-bit instruction here the sdl/sdr * would blow up, so for now we don't handle unaligned 64-bit * instructions on 32-bit kernels.
*/ if (user && !access_ok(addr, 4)) goto sigbus;
case ld_op: #ifdef CONFIG_64BIT /* * A 32-bit kernel might be running on a 64-bit processor. But * if we're on a 32-bit processor and an i-cache incoherency * or race makes us see a 64-bit instruction here the sdl/sdr * would blow up, so for now we don't handle unaligned 64-bit * instructions on 32-bit kernels.
*/ if (user && !access_ok(addr, 8)) goto sigbus;
case sd_op: #ifdef CONFIG_64BIT /* * A 32-bit kernel might be running on a 64-bit processor. But * if we're on a 32-bit processor and an i-cache incoherency * or race makes us see a 64-bit instruction here the sdl/sdr * would blow up, so for now we don't handle unaligned 64-bit * instructions on 32-bit kernels.
*/ if (user && !access_ok(addr, 8)) goto sigbus;
compute_return_epc(regs);
value = regs->regs[insn.i_format.rt];
StoreDW(addr, value, res); if (res) goto fault; break; #endif/* CONFIG_64BIT */
/* Signal if something went wrong. */
process_fpemu_return(res, fault_addr, 0);
if (res == 0) break; return;
} #endif/* CONFIG_MIPS_FP_SUPPORT */
#ifdef CONFIG_CPU_HAS_MSA
case msa_op: { unsignedint wd, preempted; enum msa_2b_fmt df; union fpureg *fpr;
if (!cpu_has_msa) goto sigill;
/* * If we've reached this point then userland should have taken * the MSA disabled exception & initialised vector context at * some point in the past.
*/
BUG_ON(!thread_msa_context_live());
switch (insn.msa_mi10_format.func) { case msa_ld_op: if (!access_ok(addr, sizeof(*fpr))) goto sigbus;
do { /* * If we have live MSA context keep track of * whether we get preempted in order to avoid * the register context we load being clobbered * by the live context as it's saved during * preemption. If we don't have live context * then it can't be saved to clobber the value * we load.
*/
preempted = test_thread_flag(TIF_USEDMSA);
res = __copy_from_user_inatomic(fpr, addr, sizeof(*fpr)); if (res) goto fault;
/* * Update the hardware register if it is in use * by the task in this quantum, in order to * avoid having to save & restore the whole * vector context.
*/
preempt_disable(); if (test_thread_flag(TIF_USEDMSA)) {
write_msa_wr(wd, fpr, df);
preempted = 0;
}
preempt_enable();
} while (preempted); break;
case msa_st_op: if (!access_ok(addr, sizeof(*fpr))) goto sigbus;
/* * Update from the hardware register if it is in use by * the task in this quantum, in order to avoid having to * save & restore the whole vector context.
*/
preempt_disable(); if (test_thread_flag(TIF_USEDMSA))
read_msa_wr(wd, fpr, df);
preempt_enable();
res = __copy_to_user_inatomic(addr, fpr, sizeof(*fpr)); if (res) goto fault; break;
#ifndef CONFIG_CPU_MIPSR6 /* * COP2 is available to implementor for application specific use. * It's up to applications to register a notifier chain and do * whatever they have to do, including possible sending of signals. * * This instruction has been reallocated in Release 6
*/ case lwc2_op:
cu2_notifier_call_chain(CU2_LWC2_OP, regs); break;
case ldc2_op:
cu2_notifier_call_chain(CU2_LDC2_OP, regs); break;
case swc2_op:
cu2_notifier_call_chain(CU2_SWC2_OP, regs); break;
case sdc2_op:
cu2_notifier_call_chain(CU2_SDC2_OP, regs); break; #endif default: /* * Pheeee... We encountered an yet unknown instruction or * cache coherence problem. Die sucker, die ...
*/ goto sigill;
}
fault: /* roll back jump/branch */
regs->cp0_epc = origpc;
regs->regs[31] = orig31; /* Did we have an exception handler installed? */ if (fixup_exception(regs)) return;
case mm_sdp_func: #ifdef CONFIG_64BIT
reg = insn.mm_m_format.rd; if (reg == 31) goto sigbus;
if (user && !access_ok(addr, 16)) goto sigbus;
value = regs->regs[reg];
StoreDW(addr, value, res); if (res) goto fault;
addr += 8;
value = regs->regs[reg + 1];
StoreDW(addr, value, res); if (res) goto fault; goto success; #endif/* CONFIG_64BIT */
goto sigill;
case mm_lwm32_func:
reg = insn.mm_m_format.rd;
rvar = reg & 0xf; if ((rvar > 9) || !reg) goto sigill; if (reg & 0x10) { if (user && !access_ok(addr, 4 * (rvar + 1))) goto sigbus;
} else { if (user && !access_ok(addr, 4 * rvar)) goto sigbus;
} if (rvar == 9)
rvar = 8; for (i = 16; rvar; rvar--, i++) {
LoadW(addr, value, res); if (res) goto fault;
addr += 4;
regs->regs[i] = value;
} if ((reg & 0xf) == 9) {
LoadW(addr, value, res); if (res) goto fault;
addr += 4;
regs->regs[30] = value;
} if (reg & 0x10) {
LoadW(addr, value, res); if (res) goto fault;
regs->regs[31] = value;
} goto success;
case mm_swm32_func:
reg = insn.mm_m_format.rd;
rvar = reg & 0xf; if ((rvar > 9) || !reg) goto sigill; if (reg & 0x10) { if (user && !access_ok(addr, 4 * (rvar + 1))) goto sigbus;
} else { if (user && !access_ok(addr, 4 * rvar)) goto sigbus;
} if (rvar == 9)
rvar = 8; for (i = 16; rvar; rvar--, i++) {
value = regs->regs[i];
StoreW(addr, value, res); if (res) goto fault;
addr += 4;
} if ((reg & 0xf) == 9) {
value = regs->regs[30];
StoreW(addr, value, res); if (res) goto fault;
addr += 4;
} if (reg & 0x10) {
value = regs->regs[31];
StoreW(addr, value, res); if (res) goto fault;
} goto success;
case mm_ldm_func: #ifdef CONFIG_64BIT
reg = insn.mm_m_format.rd;
rvar = reg & 0xf; if ((rvar > 9) || !reg) goto sigill; if (reg & 0x10) { if (user && !access_ok(addr, 8 * (rvar + 1))) goto sigbus;
} else { if (user && !access_ok(addr, 8 * rvar)) goto sigbus;
} if (rvar == 9)
rvar = 8;
for (i = 16; rvar; rvar--, i++) {
LoadDW(addr, value, res); if (res) goto fault;
addr += 4;
regs->regs[i] = value;
} if ((reg & 0xf) == 9) {
LoadDW(addr, value, res); if (res) goto fault;
addr += 8;
regs->regs[30] = value;
} if (reg & 0x10) {
LoadDW(addr, value, res); if (res) goto fault;
regs->regs[31] = value;
} goto success; #endif/* CONFIG_64BIT */
goto sigill;
case mm_sdm_func: #ifdef CONFIG_64BIT
reg = insn.mm_m_format.rd;
rvar = reg & 0xf; if ((rvar > 9) || !reg) goto sigill; if (reg & 0x10) { if (user && !access_ok(addr, 8 * (rvar + 1))) goto sigbus;
} else { if (user && !access_ok(addr, 8 * rvar)) goto sigbus;
} if (rvar == 9)
rvar = 8;
for (i = 16; rvar; rvar--, i++) {
value = regs->regs[i];
StoreDW(addr, value, res); if (res) goto fault;
addr += 8;
} if ((reg & 0xf) == 9) {
value = regs->regs[30];
StoreDW(addr, value, res); if (res) goto fault;
addr += 8;
} if (reg & 0x10) {
value = regs->regs[31];
StoreDW(addr, value, res); if (res) goto fault;
} goto success; #endif/* CONFIG_64BIT */
goto sigill;
/* LWC2, SWC2, LDC2, SDC2 are not serviced */
}
goto sigbus;
case mm_pool32c_op: switch (insn.mm_m_format.func) { case mm_lwu_func:
reg = insn.mm_m_format.rd; goto loadWU;
}
/* LL,SC,LLD,SCD are not serviced */ goto sigbus;
#ifdef CONFIG_MIPS_FP_SUPPORT case mm_pool32f_op: switch (insn.mm_x_format.func) { case mm_lwxc1_func: case mm_swxc1_func: case mm_ldxc1_func: case mm_sdxc1_func: goto fpu_emul;
}
goto sigbus;
case mm_ldc132_op: case mm_sdc132_op: case mm_lwc132_op: case mm_swc132_op: { void __user *fault_addr = NULL;
fpu_emul: /* roll back jump/branch */
regs->cp0_epc = origpc;
regs->regs[31] = orig31;
die_if_kernel("Unaligned FP access in kernel code", regs);
BUG_ON(!used_math());
BUG_ON(!is_fpu_owner());
res = fpu_emulator_cop1Handler(regs, ¤t->thread.fpu, 1,
&fault_addr);
own_fpu(1); /* restore FPU state */
/* If something went wrong, signal */
process_fpemu_return(res, fault_addr, 0);
loadWU: #ifdef CONFIG_64BIT /* * A 32-bit kernel might be running on a 64-bit processor. But * if we're on a 32-bit processor and an i-cache incoherency * or race makes us see a 64-bit instruction here the sdl/sdr * would blow up, so for now we don't handle unaligned 64-bit * instructions on 32-bit kernels.
*/ if (user && !access_ok(addr, 4)) goto sigbus;
loadDW: #ifdef CONFIG_64BIT /* * A 32-bit kernel might be running on a 64-bit processor. But * if we're on a 32-bit processor and an i-cache incoherency * or race makes us see a 64-bit instruction here the sdl/sdr * would blow up, so for now we don't handle unaligned 64-bit * instructions on 32-bit kernels.
*/ if (user && !access_ok(addr, 8)) goto sigbus;
storeHW: if (user && !access_ok(addr, 2)) goto sigbus;
value = regs->regs[reg];
StoreHW(addr, value, res); if (res) goto fault; goto success;
storeW: if (user && !access_ok(addr, 4)) goto sigbus;
value = regs->regs[reg];
StoreW(addr, value, res); if (res) goto fault; goto success;
storeDW: #ifdef CONFIG_64BIT /* * A 32-bit kernel might be running on a 64-bit processor. But * if we're on a 32-bit processor and an i-cache incoherency * or race makes us see a 64-bit instruction here the sdl/sdr * would blow up, so for now we don't handle unaligned 64-bit * instructions on 32-bit kernels.
*/ if (user && !access_ok(addr, 8)) goto sigbus;
value = regs->regs[reg];
StoreDW(addr, value, res); if (res) goto fault; goto success; #endif/* CONFIG_64BIT */
fault: /* roll back jump/branch */
regs->cp0_epc = origpc;
regs->regs[31] = orig31; /* Did we have an exception handler installed? */ if (fixup_exception(regs)) return;
/* skip EXTEND instruction */ if (mips16inst.ri.opcode == MIPS16e_extend_op) {
extended = 1;
pc16++;
__get_user(mips16inst.full, pc16);
} elseif (delay_slot(regs)) { /* skip jump instructions */ /* JAL/JALX are 32 bits but have OPCODE in first short int */ if (mips16inst.ri.opcode == MIPS16e_jal_op)
pc16++;
pc16++; if (get_user(mips16inst.full, pc16)) goto sigbus;
}
opcode = mips16inst.ri.opcode; switch (opcode) { case MIPS16e_i64_op: /* I64 or RI64 instruction */ switch (mips16inst.i64.func) { /* I64/RI64 func field check */ case MIPS16e_ldpc_func: case MIPS16e_ldsp_func:
reg = reg16to32[mips16inst.ri64.ry]; goto loadDW;
case MIPS16e_sdsp_func:
reg = reg16to32[mips16inst.ri64.ry]; goto writeDW;
case MIPS16e_lwu_op: #ifdef CONFIG_64BIT /* * A 32-bit kernel might be running on a 64-bit processor. But * if we're on a 32-bit processor and an i-cache incoherency * or race makes us see a 64-bit instruction here the sdl/sdr * would blow up, so for now we don't handle unaligned 64-bit * instructions on 32-bit kernels.
*/ if (user && !access_ok(addr, 4)) goto sigbus;
case MIPS16e_ld_op:
loadDW: #ifdef CONFIG_64BIT /* * A 32-bit kernel might be running on a 64-bit processor. But * if we're on a 32-bit processor and an i-cache incoherency * or race makes us see a 64-bit instruction here the sdl/sdr * would blow up, so for now we don't handle unaligned 64-bit * instructions on 32-bit kernels.
*/ if (user && !access_ok(addr, 8)) goto sigbus;
case MIPS16e_sh_op: if (user && !access_ok(addr, 2)) goto sigbus;
MIPS16e_compute_return_epc(regs, &oldinst);
value = regs->regs[reg];
StoreHW(addr, value, res); if (res) goto fault; break;
case MIPS16e_sw_op: case MIPS16e_swsp_op: case MIPS16e_i8_op: /* actually - MIPS16e_swrasp_func */ if (user && !access_ok(addr, 4)) goto sigbus;
MIPS16e_compute_return_epc(regs, &oldinst);
value = regs->regs[reg];
StoreW(addr, value, res); if (res) goto fault; break;
case MIPS16e_sd_op:
writeDW: #ifdef CONFIG_64BIT /* * A 32-bit kernel might be running on a 64-bit processor. But * if we're on a 32-bit processor and an i-cache incoherency * or race makes us see a 64-bit instruction here the sdl/sdr * would blow up, so for now we don't handle unaligned 64-bit * instructions on 32-bit kernels.
*/ if (user && !access_ok(addr, 8)) goto sigbus;
MIPS16e_compute_return_epc(regs, &oldinst);
value = regs->regs[reg];
StoreDW(addr, value, res); if (res) goto fault; break; #endif/* CONFIG_64BIT */
fault: /* roll back jump/branch */
regs->cp0_epc = origpc;
regs->regs[31] = orig31; /* Did we have an exception handler installed? */ if (fixup_exception(regs)) return;
#ifdef CONFIG_64BIT /* * check, if we are hitting space between CPU implemented maximum * virtual user address and 64bit maximum virtual user address * and do exception handling to get EFAULTs for get_user/put_user
*/ if ((regs->cp0_badvaddr >= (1UL << cpu_vmbits)) &&
(regs->cp0_badvaddr < XKSSEG)) { if (fixup_exception(regs)) {
current->thread.cp0_baduaddr = regs->cp0_badvaddr; return;
} goto sigbus;
} #endif
/* * Did we catch a fault trying to load an instruction?
*/ if (regs->cp0_badvaddr == regs->cp0_epc) goto sigbus;
if (user_mode(regs) && !test_thread_flag(TIF_FIXADE)) goto sigbus; if (unaligned_action == UNALIGNED_ACTION_SIGNAL) goto sigbus;
/* * Do branch emulation only if we didn't forward the exception. * This is all so but ugly ...
*/
/* * Are we running in microMIPS mode?
*/ if (get_isa16_mode(regs->cp0_epc)) { /* * Did we catch a fault trying to load an instruction in * 16-bit mode?
*/ if (regs->cp0_badvaddr == msk_isa16_mode(regs->cp0_epc)) goto sigbus; if (unaligned_action == UNALIGNED_ACTION_SHOW)
show_registers(regs);
if (cpu_has_mmips) {
emulate_load_store_microMIPS(regs,
(void __user *)regs->cp0_badvaddr); return;
}
if (cpu_has_mips16) {
emulate_load_store_MIPS16e(regs,
(void __user *)regs->cp0_badvaddr); return;
}
goto sigbus;
}
if (unaligned_action == UNALIGNED_ACTION_SHOW)
show_registers(regs);
pc = (unsignedint *)exception_epc(regs);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.