From cc2317978a88717198b5190124add3cc6e4936d9 Mon Sep 17 00:00:00 2001 From: Vladimir Murzin Date: Sat, 22 Sep 2012 15:49:23 +0400 Subject: arm: add SDIV/UDIV emulation for ARMv7 processors The Virtualization Extensions introduce the requirement for an ARMv7-A implementation to include SDIV and UDIV. Any implementation of the Virtualization Extensions must include the SDIV and UDIV instructions in the Thumb and ARM instruction sets. In an ARMv7-A implementation that does not include the Virtualization Extensions, it is IMPLEMENTATION DEFINED whether: * SDIV and UDIV are not implemented * SDIV and UDIV are implemented only in the Thumb instruction set * SDIV and UDIV are implemented in the Thumb and ARM instruction sets. This patch adds a handler to trap and emulate unimplemented SDIV and UDIV instructions in ARM and Thumb modes. Also some basic statistic is exported via /proc/cpu/idiv_emulation Change-Id: I8e721ecac62a05fab42ed7db7951b4c837a59bc7 Signed-off-by: Vladimir Murzin --- arch/arm/kernel/idiv_emulate.c | 195 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 arch/arm/kernel/idiv_emulate.c (limited to 'arch/arm/kernel/idiv_emulate.c') diff --git a/arch/arm/kernel/idiv_emulate.c b/arch/arm/kernel/idiv_emulate.c new file mode 100644 index 0000000..e5818db --- /dev/null +++ b/arch/arm/kernel/idiv_emulate.c @@ -0,0 +1,195 @@ +/* + * linux/arch/arm/kernel/idiv_emulate.c + * + * This code is based on swp_emulate.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Implements emulation of the SDIV/UDIV instructions. They are defined for + * ARMv7-R and ARMv7-M profiles (in Thumb state only) and are UNDEFINED in the + * ARMv7-A profile. SDIV/UDIV are present by default on Cortex-A15. + * + * This emulation allow using integer divide instructions in case hardware is + * not presented + * + * Syntax of SDIV/UDIV instructions: SDIV/UDIV ,, + * Where: Rd = the destination register. + * Rn = the register that contains the dividend. + * Rm = the register that contains the divisor. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * Macros/defines for extracting register numbers from instruction. + */ +#define EXTRACT_REG_NUM(instruction, offset) \ + (((instruction) & (0xf << (offset))) >> (offset)) + +/* + * Offsets for ARM mode + */ +#define RD_OFFSET 16 +#define RN_OFFSET 0 +#define RM_OFFSET 8 + +/* + * Offsets for Thumb mode + */ +#define RD_T_OFFSET 8 +#define RN_T_OFFSET 16 +#define RM_T_OFFSET 0 + +#define TYPE_OF_DIV (1 << 21) + +static unsigned long sdivcounter; +static unsigned long udivcounter; +static pid_t previous_pid; + +#ifdef CONFIG_PROC_FS +static int proc_read_status(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + char *p = page; + int len; + + p += sprintf(p, "Emulated UDIV:\t\t%lu\n", udivcounter); + p += sprintf(p, "Emulated SDIV:\t\t%lu\n", sdivcounter); + if (previous_pid != 0) + p += sprintf(p, "Last process:\t\t%d\n", previous_pid); + + len = (p - page) - off; + if (len < 0) + len = 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return len; +} +#endif + +static u32 emulate_udiv(u32 n, u32 base) +{ + udivcounter++; + + return n/base; +} + +static s32 emulate_sdiv(s32 n, s32 base) +{ + sdivcounter++; + + return n/base; +} + +static int idiv_handler(struct pt_regs *regs, unsigned int instr) +{ + long dividend, divisor, dest, res; + + perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, 0, regs, regs->ARM_pc); + + res = arm_check_condition(instr, regs->ARM_cpsr); + + switch (res) { + case ARM_OPCODE_CONDTEST_PASS: + break; + case ARM_OPCODE_CONDTEST_FAIL: + /* Condition failed - return to next instruction */ + regs->ARM_pc += 4; + return 0; + case ARM_OPCODE_CONDTEST_UNCOND: + if (!thumb_mode(regs)) + return -EFAULT; + break; + default: + return -EINVAL; + } + + if (current->pid != previous_pid) { + pr_debug("\"%s\" (%ld) uses idiv instruction\n", + current->comm, (unsigned long)current->pid); + previous_pid = current->pid; + } + + if (!thumb_mode(regs)) { + dividend = regs->uregs[EXTRACT_REG_NUM(instr, RN_OFFSET)]; + divisor = regs->uregs[EXTRACT_REG_NUM(instr, RM_OFFSET)]; + dest = EXTRACT_REG_NUM(instr, RD_OFFSET); + } else { + dividend = regs->uregs[EXTRACT_REG_NUM(instr, RN_T_OFFSET)]; + divisor = regs->uregs[EXTRACT_REG_NUM(instr, RM_T_OFFSET)]; + dest = EXTRACT_REG_NUM(instr, RD_T_OFFSET); + } + +/* + * In an ARMv7-A profile implementation that supports the SDIV and UDIV + * instructions, divide-by-zero always returns a zero result. + * In fact, integer division emulation provided by gcc lib has already handle + * division by zero case sending the signal to the caused process. Emulate this + * behavior here as well. + */ + if (!divisor) { + siginfo_t info; + + info.si_code = FPE_INTDIV; + info.si_signo = SIGFPE; + info.si_errno = 0; + + arm_notify_die("Division by zero", regs, &info, 0, 0); + + goto out; + } + + if (instr & TYPE_OF_DIV) + res = emulate_udiv((u32)dividend, (u32)divisor); + else + res = emulate_sdiv((s32)dividend, (s32)divisor); + + regs->ARM_pc += 4; + regs->uregs[dest] = res; + +out: + return 0; +} + +static struct undef_hook idiv_hook = { + .instr_mask = 0x0310f010, + .instr_val = 0x0310f010, + .cpsr_mask = MODE_MASK, + .cpsr_val = USR_MODE, + .fn = idiv_handler +}; + +static int __init idiv_emulation_init(void) +{ +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *res; + + res = create_proc_entry("cpu/idiv_emulation", S_IRUGO, NULL); + + if (!res) + return -ENOMEM; + + res->read_proc = proc_read_status; +#endif /* CONFIG_PROC_FS */ + + pr_notice("Registering SDIV/UDIV emulation handler\n"); + + register_undef_hook(&idiv_hook); + + return 0; +} + +late_initcall(idiv_emulation_init); -- cgit v1.1