Skip to content

Commit bd35a4a

Browse files
punitagrawalwildea01
authored andcommitted
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>
1 parent 587064b commit bd35a4a

File tree

5 files changed

+233
-0
lines changed

5 files changed

+233
-0
lines changed

Documentation/arm64/legacy_instructions.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ behaviours and the corresponding values of the sysctl nodes -
3131
The default mode depends on the status of the instruction in the
3232
architecture. Deprecated instructions should default to emulation
3333
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)

arch/arm64/Kconfig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,27 @@ menuconfig ARMV8_DEPRECATED
180180

181181
if ARMV8_DEPRECATED
182182

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+
183204
endif
184205

185206
endmenu

arch/arm64/include/asm/insn.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,12 @@ int aarch64_insn_patch_text_sync(void *addrs[], u32 insns[], int cnt);
356356
int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt);
357357

358358
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);
359365
#endif /* __ASSEMBLY__ */
360366

361367
#endif /* __ASM_INSN_H */

arch/arm64/kernel/armv8_deprecated.c

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@
88

99
#include <linux/init.h>
1010
#include <linux/list.h>
11+
#include <linux/perf_event.h>
12+
#include <linux/sched.h>
1113
#include <linux/slab.h>
1214
#include <linux/sysctl.h>
1315

16+
#include <asm/insn.h>
17+
#include <asm/opcodes.h>
18+
#include <asm/system_misc.h>
1419
#include <asm/traps.h>
20+
#include <asm/uaccess.h>
1521

1622
/*
1723
* The runtime support for deprecated instruction support can be in one of
@@ -203,11 +209,196 @@ static void register_insn_emulation_sysctl(struct ctl_table *table)
203209
register_sysctl_table(table);
204210
}
205211

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(&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+
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+
206394
/*
207395
* Invoked as late_initcall, since not needed before init spawned.
208396
*/
209397
static int __init armv8_deprecated_init(void)
210398
{
399+
if (IS_ENABLED(CONFIG_SWP_EMULATION))
400+
register_insn_emulation(&swp_ops);
401+
211402
register_insn_emulation_sysctl(ctl_abi);
212403

213404
return 0;

arch/arm64/kernel/insn.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,3 +964,11 @@ bool aarch32_insn_is_wide(u32 insn)
964964
{
965965
return insn >= 0xe800;
966966
}
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+
}

0 commit comments

Comments
 (0)