From 330ea9d1d819680ac7501457220b8cd1c29f9d2f Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 22 Mar 2022 17:58:39 -0700 Subject: [PATCH] linux-user/arm: Implement __kernel_cmpxchg64 with host atomics If CONFIG_ATOMIC64, we can use a host cmpxchg and provide atomicity across processes; otherwise we have no choice but to continue using start/end_exclusive. Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson Message-Id: <20220323005839.94327-4-richard.henderson@linaro.org> Signed-off-by: Laurent Vivier --- linux-user/arm/cpu_loop.c | 79 +++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/linux-user/arm/cpu_loop.c b/linux-user/arm/cpu_loop.c index aa2d777bf4..aae375d617 100644 --- a/linux-user/arm/cpu_loop.c +++ b/linux-user/arm/cpu_loop.c @@ -138,7 +138,7 @@ static void arm_kernel_cmpxchg32_helper(CPUARMState *env) } /* - * See the Linux kernel's Documentation/arm/kernel_user_helpers.txt + * See the Linux kernel's Documentation/arm/kernel_user_helpers.rst * Input: * r0 = pointer to oldval * r1 = pointer to newval @@ -155,57 +155,54 @@ static void arm_kernel_cmpxchg64_helper(CPUARMState *env) { uint64_t oldval, newval, val; uint32_t addr, cpsr; + uint64_t *host_addr; - /* Based on the 32 bit code in do_kernel_trap */ + addr = env->regs[0]; + if (get_user_u64(oldval, addr)) { + goto segv; + } - /* XXX: This only works between threads, not between processes. - It's probably possible to implement this with native host - operations. However things like ldrex/strex are much harder so - there's not much point trying. */ - start_exclusive(); - cpsr = cpsr_read(env); + addr = env->regs[1]; + if (get_user_u64(newval, addr)) { + goto segv; + } + + mmap_lock(); addr = env->regs[2]; - - if (get_user_u64(oldval, env->regs[0])) { - env->exception.vaddress = env->regs[0]; - goto segv; - }; - - if (get_user_u64(newval, env->regs[1])) { - env->exception.vaddress = env->regs[1]; - goto segv; - }; - - if (get_user_u64(val, addr)) { - env->exception.vaddress = addr; - goto segv; + host_addr = atomic_mmu_lookup(env, addr, 8); + if (!host_addr) { + mmap_unlock(); + return; } +#ifdef CONFIG_ATOMIC64 + val = qatomic_cmpxchg__nocheck(host_addr, oldval, newval); + cpsr = (val == oldval) * CPSR_C; +#else + /* + * This only works between threads, not between processes, but since + * the host has no 64-bit cmpxchg, it is the best that we can do. + */ + start_exclusive(); + val = *host_addr; if (val == oldval) { - val = newval; - - if (put_user_u64(val, addr)) { - env->exception.vaddress = addr; - goto segv; - }; - - env->regs[0] = 0; - cpsr |= CPSR_C; + *host_addr = newval; + cpsr = CPSR_C; } else { - env->regs[0] = -1; - cpsr &= ~CPSR_C; + cpsr = 0; } - cpsr_write(env, cpsr, CPSR_C, CPSRWriteByInstr); end_exclusive(); +#endif + mmap_unlock(); + + cpsr_write(env, cpsr, CPSR_C, CPSRWriteByInstr); + env->regs[0] = cpsr ? 0 : -1; return; -segv: - end_exclusive(); - /* We get the PC of the entry address - which is as good as anything, - on a real kernel what you get depends on which mode it uses. */ - /* XXX: check env->error_code */ - force_sig_fault(TARGET_SIGSEGV, TARGET_SEGV_MAPERR, - env->exception.vaddress); + segv: + force_sig_fault(TARGET_SIGSEGV, + page_get_flags(addr) & PAGE_VALID ? + TARGET_SEGV_ACCERR : TARGET_SEGV_MAPERR, addr); } /* Handle a jump to the kernel code page. */