1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/arch/arm/kernel/swp_emulate.c |
4 | * |
5 | * Copyright (C) 2009 ARM Limited |
6 | * __user_* functions adapted from include/asm/uaccess.h |
7 | * |
8 | * Implements emulation of the SWP/SWPB instructions using load-exclusive and |
9 | * store-exclusive for processors that have them disabled (or future ones that |
10 | * might not implement them). |
11 | * |
12 | * Syntax of SWP{B} instruction: SWP{B}<c> <Rt>, <Rt2>, [<Rn>] |
13 | * Where: Rt = destination |
14 | * Rt2 = source |
15 | * Rn = address |
16 | */ |
17 | |
18 | #include <linux/init.h> |
19 | #include <linux/kernel.h> |
20 | #include <linux/proc_fs.h> |
21 | #include <linux/seq_file.h> |
22 | #include <linux/sched.h> |
23 | #include <linux/sched/mm.h> |
24 | #include <linux/syscalls.h> |
25 | #include <linux/perf_event.h> |
26 | |
27 | #include <asm/opcodes.h> |
28 | #include <asm/system_info.h> |
29 | #include <asm/traps.h> |
30 | #include <linux/uaccess.h> |
31 | |
32 | /* |
33 | * Error-checking SWP macros implemented using ldrex{b}/strex{b} |
34 | */ |
35 | #define __user_swpX_asm(data, addr, res, temp, B) \ |
36 | __asm__ __volatile__( \ |
37 | ".arch armv7-a\n" \ |
38 | "0: ldrex"B" %2, [%3]\n" \ |
39 | "1: strex"B" %0, %1, [%3]\n" \ |
40 | " cmp %0, #0\n" \ |
41 | " moveq %1, %2\n" \ |
42 | " movne %0, %4\n" \ |
43 | "2:\n" \ |
44 | " .section .text.fixup,\"ax\"\n" \ |
45 | " .align 2\n" \ |
46 | "3: mov %0, %5\n" \ |
47 | " b 2b\n" \ |
48 | " .previous\n" \ |
49 | " .section __ex_table,\"a\"\n" \ |
50 | " .align 3\n" \ |
51 | " .long 0b, 3b\n" \ |
52 | " .long 1b, 3b\n" \ |
53 | " .previous" \ |
54 | : "=&r" (res), "+r" (data), "=&r" (temp) \ |
55 | : "r" (addr), "i" (-EAGAIN), "i" (-EFAULT) \ |
56 | : "cc", "memory") |
57 | |
58 | #define __user_swp_asm(data, addr, res, temp) \ |
59 | __user_swpX_asm(data, addr, res, temp, "") |
60 | #define __user_swpb_asm(data, addr, res, temp) \ |
61 | __user_swpX_asm(data, addr, res, temp, "b") |
62 | |
63 | /* |
64 | * Macros/defines for extracting register numbers from instruction. |
65 | */ |
66 | #define (instruction, offset) \ |
67 | (((instruction) & (0xf << (offset))) >> (offset)) |
68 | #define RN_OFFSET 16 |
69 | #define RT_OFFSET 12 |
70 | #define RT2_OFFSET 0 |
71 | /* |
72 | * Bit 22 of the instruction encoding distinguishes between |
73 | * the SWP and SWPB variants (bit set means SWPB). |
74 | */ |
75 | #define TYPE_SWPB (1 << 22) |
76 | |
77 | static unsigned long swpcounter; |
78 | static unsigned long swpbcounter; |
79 | static unsigned long abtcounter; |
80 | static pid_t previous_pid; |
81 | |
82 | #ifdef CONFIG_PROC_FS |
83 | static int proc_status_show(struct seq_file *m, void *v) |
84 | { |
85 | seq_printf(m, fmt: "Emulated SWP:\t\t%lu\n" , swpcounter); |
86 | seq_printf(m, fmt: "Emulated SWPB:\t\t%lu\n" , swpbcounter); |
87 | seq_printf(m, fmt: "Aborted SWP{B}:\t\t%lu\n" , abtcounter); |
88 | if (previous_pid != 0) |
89 | seq_printf(m, fmt: "Last process:\t\t%d\n" , previous_pid); |
90 | return 0; |
91 | } |
92 | #endif |
93 | |
94 | /* |
95 | * Set up process info to signal segmentation fault - called on access error. |
96 | */ |
97 | static void set_segfault(struct pt_regs *regs, unsigned long addr) |
98 | { |
99 | int si_code; |
100 | |
101 | mmap_read_lock(current->mm); |
102 | if (find_vma(current->mm, addr) == NULL) |
103 | si_code = SEGV_MAPERR; |
104 | else |
105 | si_code = SEGV_ACCERR; |
106 | mmap_read_unlock(current->mm); |
107 | |
108 | pr_debug("SWP{B} emulation: access caused memory abort!\n" ); |
109 | arm_notify_die("Illegal memory access" , regs, |
110 | SIGSEGV, si_code, |
111 | (void __user *)instruction_pointer(regs), |
112 | 0, 0); |
113 | |
114 | abtcounter++; |
115 | } |
116 | |
117 | static int emulate_swpX(unsigned int address, unsigned int *data, |
118 | unsigned int type) |
119 | { |
120 | unsigned int res = 0; |
121 | |
122 | if ((type != TYPE_SWPB) && (address & 0x3)) { |
123 | /* SWP to unaligned address not permitted */ |
124 | pr_debug("SWP instruction on unaligned pointer!\n" ); |
125 | return -EFAULT; |
126 | } |
127 | |
128 | while (1) { |
129 | unsigned long temp; |
130 | unsigned int __ua_flags; |
131 | |
132 | __ua_flags = uaccess_save_and_enable(); |
133 | if (type == TYPE_SWPB) |
134 | __user_swpb_asm(*data, address, res, temp); |
135 | else |
136 | __user_swp_asm(*data, address, res, temp); |
137 | uaccess_restore(__ua_flags); |
138 | |
139 | if (likely(res != -EAGAIN) || signal_pending(current)) |
140 | break; |
141 | |
142 | cond_resched(); |
143 | } |
144 | |
145 | if (res == 0) { |
146 | if (type == TYPE_SWPB) |
147 | swpbcounter++; |
148 | else |
149 | swpcounter++; |
150 | } |
151 | |
152 | return res; |
153 | } |
154 | |
155 | /* |
156 | * swp_handler logs the id of calling process, dissects the instruction, sanity |
157 | * checks the memory location, calls emulate_swpX for the actual operation and |
158 | * deals with fixup/error handling before returning |
159 | */ |
160 | static int swp_handler(struct pt_regs *regs, unsigned int instr) |
161 | { |
162 | unsigned int address, destreg, data, type; |
163 | unsigned int res = 0; |
164 | |
165 | perf_sw_event(event_id: PERF_COUNT_SW_EMULATION_FAULTS, nr: 1, regs, addr: regs->ARM_pc); |
166 | |
167 | res = arm_check_condition(instr, regs->ARM_cpsr); |
168 | switch (res) { |
169 | case ARM_OPCODE_CONDTEST_PASS: |
170 | break; |
171 | case ARM_OPCODE_CONDTEST_FAIL: |
172 | /* Condition failed - return to next instruction */ |
173 | regs->ARM_pc += 4; |
174 | return 0; |
175 | case ARM_OPCODE_CONDTEST_UNCOND: |
176 | /* If unconditional encoding - not a SWP, undef */ |
177 | return -EFAULT; |
178 | default: |
179 | return -EINVAL; |
180 | } |
181 | |
182 | if (current->pid != previous_pid) { |
183 | pr_debug("\"%s\" (%ld) uses deprecated SWP{B} instruction\n" , |
184 | current->comm, (unsigned long)current->pid); |
185 | previous_pid = current->pid; |
186 | } |
187 | |
188 | address = regs->uregs[EXTRACT_REG_NUM(instr, RN_OFFSET)]; |
189 | data = regs->uregs[EXTRACT_REG_NUM(instr, RT2_OFFSET)]; |
190 | destreg = EXTRACT_REG_NUM(instr, RT_OFFSET); |
191 | |
192 | type = instr & TYPE_SWPB; |
193 | |
194 | pr_debug("addr in r%d->0x%08x, dest is r%d, source in r%d->0x%08x)\n" , |
195 | EXTRACT_REG_NUM(instr, RN_OFFSET), address, |
196 | destreg, EXTRACT_REG_NUM(instr, RT2_OFFSET), data); |
197 | |
198 | /* Check access in reasonable access range for both SWP and SWPB */ |
199 | if (!access_ok((void __user *)(address & ~3), 4)) { |
200 | pr_debug("SWP{B} emulation: access to %p not allowed!\n" , |
201 | (void *)address); |
202 | res = -EFAULT; |
203 | } else { |
204 | res = emulate_swpX(address, data: &data, type); |
205 | } |
206 | |
207 | if (res == 0) { |
208 | /* |
209 | * On successful emulation, revert the adjustment to the PC |
210 | * made in kernel/traps.c in order to resume execution at the |
211 | * instruction following the SWP{B}. |
212 | */ |
213 | regs->ARM_pc += 4; |
214 | regs->uregs[destreg] = data; |
215 | } else if (res == -EFAULT) { |
216 | /* |
217 | * Memory errors do not mean emulation failed. |
218 | * Set up signal info to return SEGV, then return OK |
219 | */ |
220 | set_segfault(regs, addr: address); |
221 | } |
222 | |
223 | return 0; |
224 | } |
225 | |
226 | /* |
227 | * Only emulate SWP/SWPB executed in ARM state/User mode. |
228 | * The kernel must be SWP free and SWP{B} does not exist in Thumb/ThumbEE. |
229 | */ |
230 | static struct undef_hook swp_hook = { |
231 | .instr_mask = 0x0fb00ff0, |
232 | .instr_val = 0x01000090, |
233 | .cpsr_mask = MODE_MASK | PSR_T_BIT | PSR_J_BIT, |
234 | .cpsr_val = USR_MODE, |
235 | .fn = swp_handler |
236 | }; |
237 | |
238 | /* |
239 | * Register handler and create status file in /proc/cpu |
240 | * Invoked as late_initcall, since not needed before init spawned. |
241 | */ |
242 | static int __init swp_emulation_init(void) |
243 | { |
244 | if (cpu_architecture() < CPU_ARCH_ARMv7) |
245 | return 0; |
246 | |
247 | #ifdef CONFIG_PROC_FS |
248 | if (!proc_create_single("cpu/swp_emulation" , S_IRUGO, NULL, |
249 | proc_status_show)) |
250 | return -ENOMEM; |
251 | #endif /* CONFIG_PROC_FS */ |
252 | |
253 | pr_notice("Registering SWP/SWPB emulation handler\n" ); |
254 | register_undef_hook(&swp_hook); |
255 | |
256 | return 0; |
257 | } |
258 | |
259 | late_initcall(swp_emulation_init); |
260 | |