aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/kernel/idiv_emulate.c
blob: e5818db420673e6edf223fdaaa866448979b6e3b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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<c> <Rd>,<Rn>,<Rm>
 *  Where: Rd  = the destination register.
 *	   Rn  = the register that contains the dividend.
 *	   Rm  = the register that contains the divisor.
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/syscalls.h>
#include <linux/perf_event.h>

#include <asm/opcodes.h>
#include <asm/traps.h>
#include <asm/uaccess.h>

/*
 * 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);