aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPunit Agrawal <punit.agrawal@arm.com>2014-11-18 06:41:25 -0500
committerWill Deacon <will.deacon@arm.com>2014-11-20 11:34:31 -0500
commitbd35a4adc4131c530ec7d90242555eac7b3dbe3f (patch)
tree61db668ee8924e5dae4aa53fadf8b8ffde6bba3b
parent587064b610c703f259317d00dc37bf6d40f4fc74 (diff)
arm64: Port SWP/SWPB emulation support from arm
The SWP instruction was deprecated in the ARMv6 architecture. The ARMv7 multiprocessing extensions mandate that SWP/SWPB instructions are treated as undefined from reset, with the ability to enable them through the System Control Register SW bit. With ARMv8, the option to enable these instructions through System Control Register was dropped as well. To support legacy applications using these instructions, port the emulation of the SWP and SWPB instructions from the arm port to arm64. Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Punit Agrawal <punit.agrawal@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
-rw-r--r--Documentation/arm64/legacy_instructions.txt7
-rw-r--r--arch/arm64/Kconfig21
-rw-r--r--arch/arm64/include/asm/insn.h6
-rw-r--r--arch/arm64/kernel/armv8_deprecated.c191
-rw-r--r--arch/arm64/kernel/insn.c8
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 -
31The default mode depends on the status of the instruction in the 31The default mode depends on the status of the instruction in the
32architecture. Deprecated instructions should default to emulation 32architecture. Deprecated instructions should default to emulation
33while obsolete instructions must be undefined by default. 33while obsolete instructions must be undefined by default.
34
35Supported legacy instructions
36-----------------------------
37* SWP{B}
38Node: /proc/sys/abi/swp
39Status: Obsolete
40Default: 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
181if ARMV8_DEPRECATED 181if ARMV8_DEPRECATED
182 182
183config 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
183endif 204endif
184 205
185endmenu 206endmenu
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);
356int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt); 356int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt);
357 357
358bool aarch32_insn_is_wide(u32 insn); 358bool 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
364u32 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 */
261static void set_segfault(struct pt_regs *regs, unsigned long addr)
262{
263 siginfo_t info;
264
265 down_read(&current->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(&current->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
280static 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 */
313static 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
359ret:
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
366fault:
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 */
376static 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
387static 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 */
209static int __init armv8_deprecated_init(void) 397static 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 */
971u32 aarch32_insn_extract_reg_num(u32 insn, int offset)
972{
973 return (insn & (0xf << offset)) >> offset;
974}