diff options
-rw-r--r-- | Documentation/arm64/legacy_instructions.txt | 7 | ||||
-rw-r--r-- | arch/arm64/Kconfig | 21 | ||||
-rw-r--r-- | arch/arm64/include/asm/insn.h | 6 | ||||
-rw-r--r-- | arch/arm64/kernel/armv8_deprecated.c | 191 | ||||
-rw-r--r-- | arch/arm64/kernel/insn.c | 8 |
5 files changed, 233 insertions, 0 deletions
diff --git a/Documentation/arm64/legacy_instructions.txt b/Documentation/arm64/legacy_instructions.txt index 49d4867c0c09..5ab58614b7ed 100644 --- a/Documentation/arm64/legacy_instructions.txt +++ b/Documentation/arm64/legacy_instructions.txt | |||
@@ -31,3 +31,10 @@ behaviours and the corresponding values of the sysctl nodes - | |||
31 | The default mode depends on the status of the instruction in the | 31 | The default mode depends on the status of the instruction in the |
32 | architecture. Deprecated instructions should default to emulation | 32 | architecture. Deprecated instructions should default to emulation |
33 | while obsolete instructions must be undefined by default. | 33 | while obsolete instructions must be undefined by default. |
34 | |||
35 | Supported legacy instructions | ||
36 | ----------------------------- | ||
37 | * SWP{B} | ||
38 | Node: /proc/sys/abi/swp | ||
39 | Status: Obsolete | ||
40 | Default: Undef (0) | ||
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index aa8f4bea3738..2b6213840ec8 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig | |||
@@ -180,6 +180,27 @@ menuconfig ARMV8_DEPRECATED | |||
180 | 180 | ||
181 | if ARMV8_DEPRECATED | 181 | if ARMV8_DEPRECATED |
182 | 182 | ||
183 | config SWP_EMULATION | ||
184 | bool "Emulate SWP/SWPB instructions" | ||
185 | help | ||
186 | ARMv8 obsoletes the use of A32 SWP/SWPB instructions such that | ||
187 | they are always undefined. Say Y here to enable software | ||
188 | emulation of these instructions for userspace using LDXR/STXR. | ||
189 | |||
190 | In some older versions of glibc [<=2.8] SWP is used during futex | ||
191 | trylock() operations with the assumption that the code will not | ||
192 | be preempted. This invalid assumption may be more likely to fail | ||
193 | with SWP emulation enabled, leading to deadlock of the user | ||
194 | application. | ||
195 | |||
196 | NOTE: when accessing uncached shared regions, LDXR/STXR rely | ||
197 | on an external transaction monitoring block called a global | ||
198 | monitor to maintain update atomicity. If your system does not | ||
199 | implement a global monitor, this option can cause programs that | ||
200 | perform SWP operations to uncached memory to deadlock. | ||
201 | |||
202 | If unsure, say Y | ||
203 | |||
183 | endif | 204 | endif |
184 | 205 | ||
185 | endmenu | 206 | endmenu |
diff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h index 1bb043018315..3ecc57c47c04 100644 --- a/arch/arm64/include/asm/insn.h +++ b/arch/arm64/include/asm/insn.h | |||
@@ -356,6 +356,12 @@ int aarch64_insn_patch_text_sync(void *addrs[], u32 insns[], int cnt); | |||
356 | int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt); | 356 | int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt); |
357 | 357 | ||
358 | bool aarch32_insn_is_wide(u32 insn); | 358 | bool aarch32_insn_is_wide(u32 insn); |
359 | |||
360 | #define A32_RN_OFFSET 16 | ||
361 | #define A32_RT_OFFSET 12 | ||
362 | #define A32_RT2_OFFSET 0 | ||
363 | |||
364 | u32 aarch32_insn_extract_reg_num(u32 insn, int offset); | ||
359 | #endif /* __ASSEMBLY__ */ | 365 | #endif /* __ASSEMBLY__ */ |
360 | 366 | ||
361 | #endif /* __ASM_INSN_H */ | 367 | #endif /* __ASM_INSN_H */ |
diff --git a/arch/arm64/kernel/armv8_deprecated.c b/arch/arm64/kernel/armv8_deprecated.c index db3b79da6a48..98865a7868f1 100644 --- a/arch/arm64/kernel/armv8_deprecated.c +++ b/arch/arm64/kernel/armv8_deprecated.c | |||
@@ -8,10 +8,16 @@ | |||
8 | 8 | ||
9 | #include <linux/init.h> | 9 | #include <linux/init.h> |
10 | #include <linux/list.h> | 10 | #include <linux/list.h> |
11 | #include <linux/perf_event.h> | ||
12 | #include <linux/sched.h> | ||
11 | #include <linux/slab.h> | 13 | #include <linux/slab.h> |
12 | #include <linux/sysctl.h> | 14 | #include <linux/sysctl.h> |
13 | 15 | ||
16 | #include <asm/insn.h> | ||
17 | #include <asm/opcodes.h> | ||
18 | #include <asm/system_misc.h> | ||
14 | #include <asm/traps.h> | 19 | #include <asm/traps.h> |
20 | #include <asm/uaccess.h> | ||
15 | 21 | ||
16 | /* | 22 | /* |
17 | * The runtime support for deprecated instruction support can be in one of | 23 | * The runtime support for deprecated instruction support can be in one of |
@@ -204,10 +210,195 @@ static void register_insn_emulation_sysctl(struct ctl_table *table) | |||
204 | } | 210 | } |
205 | 211 | ||
206 | /* | 212 | /* |
213 | * Implement emulation of the SWP/SWPB instructions using load-exclusive and | ||
214 | * store-exclusive. | ||
215 | * | ||
216 | * Syntax of SWP{B} instruction: SWP{B}<c> <Rt>, <Rt2>, [<Rn>] | ||
217 | * Where: Rt = destination | ||
218 | * Rt2 = source | ||
219 | * Rn = address | ||
220 | */ | ||
221 | |||
222 | /* | ||
223 | * Error-checking SWP macros implemented using ldxr{b}/stxr{b} | ||
224 | */ | ||
225 | #define __user_swpX_asm(data, addr, res, temp, B) \ | ||
226 | __asm__ __volatile__( \ | ||
227 | " mov %w2, %w1\n" \ | ||
228 | "0: ldxr"B" %w1, [%3]\n" \ | ||
229 | "1: stxr"B" %w0, %w2, [%3]\n" \ | ||
230 | " cbz %w0, 2f\n" \ | ||
231 | " mov %w0, %w4\n" \ | ||
232 | "2:\n" \ | ||
233 | " .pushsection .fixup,\"ax\"\n" \ | ||
234 | " .align 2\n" \ | ||
235 | "3: mov %w0, %w5\n" \ | ||
236 | " b 2b\n" \ | ||
237 | " .popsection" \ | ||
238 | " .pushsection __ex_table,\"a\"\n" \ | ||
239 | " .align 3\n" \ | ||
240 | " .quad 0b, 3b\n" \ | ||
241 | " .quad 1b, 3b\n" \ | ||
242 | " .popsection" \ | ||
243 | : "=&r" (res), "+r" (data), "=&r" (temp) \ | ||
244 | : "r" (addr), "i" (-EAGAIN), "i" (-EFAULT) \ | ||
245 | : "memory") | ||
246 | |||
247 | #define __user_swp_asm(data, addr, res, temp) \ | ||
248 | __user_swpX_asm(data, addr, res, temp, "") | ||
249 | #define __user_swpb_asm(data, addr, res, temp) \ | ||
250 | __user_swpX_asm(data, addr, res, temp, "b") | ||
251 | |||
252 | /* | ||
253 | * Bit 22 of the instruction encoding distinguishes between | ||
254 | * the SWP and SWPB variants (bit set means SWPB). | ||
255 | */ | ||
256 | #define TYPE_SWPB (1 << 22) | ||
257 | |||
258 | /* | ||
259 | * Set up process info to signal segmentation fault - called on access error. | ||
260 | */ | ||
261 | static void set_segfault(struct pt_regs *regs, unsigned long addr) | ||
262 | { | ||
263 | siginfo_t info; | ||
264 | |||
265 | down_read(¤t->mm->mmap_sem); | ||
266 | if (find_vma(current->mm, addr) == NULL) | ||
267 | info.si_code = SEGV_MAPERR; | ||
268 | else | ||
269 | info.si_code = SEGV_ACCERR; | ||
270 | up_read(¤t->mm->mmap_sem); | ||
271 | |||
272 | info.si_signo = SIGSEGV; | ||
273 | info.si_errno = 0; | ||
274 | info.si_addr = (void *) instruction_pointer(regs); | ||
275 | |||
276 | pr_debug("SWP{B} emulation: access caused memory abort!\n"); | ||
277 | arm64_notify_die("Illegal memory access", regs, &info, 0); | ||
278 | } | ||
279 | |||
280 | static int emulate_swpX(unsigned int address, unsigned int *data, | ||
281 | unsigned int type) | ||
282 | { | ||
283 | unsigned int res = 0; | ||
284 | |||
285 | if ((type != TYPE_SWPB) && (address & 0x3)) { | ||
286 | /* SWP to unaligned address not permitted */ | ||
287 | pr_debug("SWP instruction on unaligned pointer!\n"); | ||
288 | return -EFAULT; | ||
289 | } | ||
290 | |||
291 | while (1) { | ||
292 | unsigned long temp; | ||
293 | |||
294 | if (type == TYPE_SWPB) | ||
295 | __user_swpb_asm(*data, address, res, temp); | ||
296 | else | ||
297 | __user_swp_asm(*data, address, res, temp); | ||
298 | |||
299 | if (likely(res != -EAGAIN) || signal_pending(current)) | ||
300 | break; | ||
301 | |||
302 | cond_resched(); | ||
303 | } | ||
304 | |||
305 | return res; | ||
306 | } | ||
307 | |||
308 | /* | ||
309 | * swp_handler logs the id of calling process, dissects the instruction, sanity | ||
310 | * checks the memory location, calls emulate_swpX for the actual operation and | ||
311 | * deals with fixup/error handling before returning | ||
312 | */ | ||
313 | static int swp_handler(struct pt_regs *regs, u32 instr) | ||
314 | { | ||
315 | u32 destreg, data, type, address = 0; | ||
316 | int rn, rt2, res = 0; | ||
317 | |||
318 | perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, regs->pc); | ||
319 | |||
320 | type = instr & TYPE_SWPB; | ||
321 | |||
322 | switch (arm_check_condition(instr, regs->pstate)) { | ||
323 | case ARM_OPCODE_CONDTEST_PASS: | ||
324 | break; | ||
325 | case ARM_OPCODE_CONDTEST_FAIL: | ||
326 | /* Condition failed - return to next instruction */ | ||
327 | goto ret; | ||
328 | case ARM_OPCODE_CONDTEST_UNCOND: | ||
329 | /* If unconditional encoding - not a SWP, undef */ | ||
330 | return -EFAULT; | ||
331 | default: | ||
332 | return -EINVAL; | ||
333 | } | ||
334 | |||
335 | rn = aarch32_insn_extract_reg_num(instr, A32_RN_OFFSET); | ||
336 | rt2 = aarch32_insn_extract_reg_num(instr, A32_RT2_OFFSET); | ||
337 | |||
338 | address = (u32)regs->user_regs.regs[rn]; | ||
339 | data = (u32)regs->user_regs.regs[rt2]; | ||
340 | destreg = aarch32_insn_extract_reg_num(instr, A32_RT_OFFSET); | ||
341 | |||
342 | pr_debug("addr in r%d->0x%08x, dest is r%d, source in r%d->0x%08x)\n", | ||
343 | rn, address, destreg, | ||
344 | aarch32_insn_extract_reg_num(instr, A32_RT2_OFFSET), data); | ||
345 | |||
346 | /* Check access in reasonable access range for both SWP and SWPB */ | ||
347 | if (!access_ok(VERIFY_WRITE, (address & ~3), 4)) { | ||
348 | pr_debug("SWP{B} emulation: access to 0x%08x not allowed!\n", | ||
349 | address); | ||
350 | goto fault; | ||
351 | } | ||
352 | |||
353 | res = emulate_swpX(address, &data, type); | ||
354 | if (res == -EFAULT) | ||
355 | goto fault; | ||
356 | else if (res == 0) | ||
357 | regs->user_regs.regs[destreg] = data; | ||
358 | |||
359 | ret: | ||
360 | pr_warn_ratelimited("\"%s\" (%ld) uses obsolete SWP{B} instruction at 0x%llx\n", | ||
361 | current->comm, (unsigned long)current->pid, regs->pc); | ||
362 | |||
363 | regs->pc += 4; | ||
364 | return 0; | ||
365 | |||
366 | fault: | ||
367 | set_segfault(regs, address); | ||
368 | |||
369 | return 0; | ||
370 | } | ||
371 | |||
372 | /* | ||
373 | * Only emulate SWP/SWPB executed in ARM state/User mode. | ||
374 | * The kernel must be SWP free and SWP{B} does not exist in Thumb. | ||
375 | */ | ||
376 | static struct undef_hook swp_hooks[] = { | ||
377 | { | ||
378 | .instr_mask = 0x0fb00ff0, | ||
379 | .instr_val = 0x01000090, | ||
380 | .pstate_mask = COMPAT_PSR_MODE_MASK, | ||
381 | .pstate_val = COMPAT_PSR_MODE_USR, | ||
382 | .fn = swp_handler | ||
383 | }, | ||
384 | { } | ||
385 | }; | ||
386 | |||
387 | static struct insn_emulation_ops swp_ops = { | ||
388 | .name = "swp", | ||
389 | .status = INSN_OBSOLETE, | ||
390 | .hooks = swp_hooks, | ||
391 | .set_hw_mode = NULL, | ||
392 | }; | ||
393 | |||
394 | /* | ||
207 | * Invoked as late_initcall, since not needed before init spawned. | 395 | * Invoked as late_initcall, since not needed before init spawned. |
208 | */ | 396 | */ |
209 | static int __init armv8_deprecated_init(void) | 397 | static int __init armv8_deprecated_init(void) |
210 | { | 398 | { |
399 | if (IS_ENABLED(CONFIG_SWP_EMULATION)) | ||
400 | register_insn_emulation(&swp_ops); | ||
401 | |||
211 | register_insn_emulation_sysctl(ctl_abi); | 402 | register_insn_emulation_sysctl(ctl_abi); |
212 | 403 | ||
213 | return 0; | 404 | return 0; |
diff --git a/arch/arm64/kernel/insn.c b/arch/arm64/kernel/insn.c index ab00eb58d385..63122dcd8524 100644 --- a/arch/arm64/kernel/insn.c +++ b/arch/arm64/kernel/insn.c | |||
@@ -964,3 +964,11 @@ bool aarch32_insn_is_wide(u32 insn) | |||
964 | { | 964 | { |
965 | return insn >= 0xe800; | 965 | return insn >= 0xe800; |
966 | } | 966 | } |
967 | |||
968 | /* | ||
969 | * Macros/defines for extracting register numbers from instruction. | ||
970 | */ | ||
971 | u32 aarch32_insn_extract_reg_num(u32 insn, int offset) | ||
972 | { | ||
973 | return (insn & (0xf << offset)) >> offset; | ||
974 | } | ||