linux-user: Implement new ARM 64 bit cmpxchg kernel helper
linux-user: Implement new ARM 64 bit cmpxchg kernel helper Linux 3.1 will have a new kernel-page helper for ARM implementing 64 bit cmpxchg. Implement this helper in QEMU linux-user mode: * Provide kernel helper emulation for 64bit cmpxchg * Allow guest to object to guest offset to ensure it can map a page * Populate page with kernel helper version Signed-off-by: Riku Voipio <riku.voipio@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Dr. David Alan Gilbert <david.gilbert@linaro.org>
This commit is contained in:
parent
70afc343c7
commit
97cc75606a
@ -332,6 +332,49 @@ enum
|
|||||||
ARM_HWCAP_ARM_VFPv3D16 = 1 << 13,
|
ARM_HWCAP_ARM_VFPv3D16 = 1 << 13,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define TARGET_HAS_GUEST_VALIDATE_BASE
|
||||||
|
/* We want the opportunity to check the suggested base */
|
||||||
|
bool guest_validate_base(unsigned long guest_base)
|
||||||
|
{
|
||||||
|
unsigned long real_start, test_page_addr;
|
||||||
|
|
||||||
|
/* We need to check that we can force a fault on access to the
|
||||||
|
* commpage at 0xffff0fxx
|
||||||
|
*/
|
||||||
|
test_page_addr = guest_base + (0xffff0f00 & qemu_host_page_mask);
|
||||||
|
/* Note it needs to be writeable to let us initialise it */
|
||||||
|
real_start = (unsigned long)
|
||||||
|
mmap((void *)test_page_addr, qemu_host_page_size,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_ANONYMOUS | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
|
||||||
|
/* If we can't map it then try another address */
|
||||||
|
if (real_start == -1ul) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (real_start != test_page_addr) {
|
||||||
|
/* OS didn't put the page where we asked - unmap and reject */
|
||||||
|
munmap((void *)real_start, qemu_host_page_size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Leave the page mapped
|
||||||
|
* Populate it (mmap should have left it all 0'd)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Kernel helper versions */
|
||||||
|
__put_user(5, (uint32_t *)g2h(0xffff0ffcul));
|
||||||
|
|
||||||
|
/* Now it's populated make it RO */
|
||||||
|
if (mprotect((void *)test_page_addr, qemu_host_page_size, PROT_READ)) {
|
||||||
|
perror("Protecting guest commpage");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1; /* All good */
|
||||||
|
}
|
||||||
|
|
||||||
#define ELF_HWCAP (ARM_HWCAP_ARM_SWP | ARM_HWCAP_ARM_HALF \
|
#define ELF_HWCAP (ARM_HWCAP_ARM_SWP | ARM_HWCAP_ARM_HALF \
|
||||||
| ARM_HWCAP_ARM_THUMB | ARM_HWCAP_ARM_FAST_MULT \
|
| ARM_HWCAP_ARM_THUMB | ARM_HWCAP_ARM_FAST_MULT \
|
||||||
| ARM_HWCAP_ARM_FPA | ARM_HWCAP_ARM_VFP \
|
| ARM_HWCAP_ARM_FPA | ARM_HWCAP_ARM_VFP \
|
||||||
@ -1309,6 +1352,14 @@ static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc,
|
|||||||
return sp;
|
return sp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef TARGET_HAS_GUEST_VALIDATE_BASE
|
||||||
|
/* If the guest doesn't have a validation function just agree */
|
||||||
|
bool guest_validate_base(unsigned long guest_base)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void probe_guest_base(const char *image_name,
|
static void probe_guest_base(const char *image_name,
|
||||||
abi_ulong loaddr, abi_ulong hiaddr)
|
abi_ulong loaddr, abi_ulong hiaddr)
|
||||||
{
|
{
|
||||||
@ -1345,7 +1396,9 @@ static void probe_guest_base(const char *image_name,
|
|||||||
if (real_start == (unsigned long)-1) {
|
if (real_start == (unsigned long)-1) {
|
||||||
goto exit_perror;
|
goto exit_perror;
|
||||||
}
|
}
|
||||||
if (real_start == host_start) {
|
guest_base = real_start - loaddr;
|
||||||
|
if ((real_start == host_start) &&
|
||||||
|
guest_validate_base(guest_base)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* That address didn't work. Unmap and try a different one.
|
/* That address didn't work. Unmap and try a different one.
|
||||||
@ -1368,7 +1421,6 @@ static void probe_guest_base(const char *image_name,
|
|||||||
qemu_log("Relocating guest address space from 0x"
|
qemu_log("Relocating guest address space from 0x"
|
||||||
TARGET_ABI_FMT_lx " to 0x%lx\n",
|
TARGET_ABI_FMT_lx " to 0x%lx\n",
|
||||||
loaddr, real_start);
|
loaddr, real_start);
|
||||||
guest_base = real_start - loaddr;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -456,6 +456,83 @@ void cpu_loop(CPUX86State *env)
|
|||||||
|
|
||||||
#ifdef TARGET_ARM
|
#ifdef TARGET_ARM
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See the Linux kernel's Documentation/arm/kernel_user_helpers.txt
|
||||||
|
* Input:
|
||||||
|
* r0 = pointer to oldval
|
||||||
|
* r1 = pointer to newval
|
||||||
|
* r2 = pointer to target value
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* r0 = 0 if *ptr was changed, non-0 if no exchange happened
|
||||||
|
* C set if *ptr was changed, clear if no exchange happened
|
||||||
|
*
|
||||||
|
* Note segv's in kernel helpers are a bit tricky, we can set the
|
||||||
|
* data address sensibly but the PC address is just the entry point.
|
||||||
|
*/
|
||||||
|
static void arm_kernel_cmpxchg64_helper(CPUARMState *env)
|
||||||
|
{
|
||||||
|
uint64_t oldval, newval, val;
|
||||||
|
uint32_t addr, cpsr;
|
||||||
|
target_siginfo_t info;
|
||||||
|
|
||||||
|
/* Based on the 32 bit code in do_kernel_trap */
|
||||||
|
|
||||||
|
/* 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[2];
|
||||||
|
|
||||||
|
if (get_user_u64(oldval, env->regs[0])) {
|
||||||
|
env->cp15.c6_data = env->regs[0];
|
||||||
|
goto segv;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (get_user_u64(newval, env->regs[1])) {
|
||||||
|
env->cp15.c6_data = env->regs[1];
|
||||||
|
goto segv;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (get_user_u64(val, addr)) {
|
||||||
|
env->cp15.c6_data = addr;
|
||||||
|
goto segv;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val == oldval) {
|
||||||
|
val = newval;
|
||||||
|
|
||||||
|
if (put_user_u64(val, addr)) {
|
||||||
|
env->cp15.c6_data = addr;
|
||||||
|
goto segv;
|
||||||
|
};
|
||||||
|
|
||||||
|
env->regs[0] = 0;
|
||||||
|
cpsr |= CPSR_C;
|
||||||
|
} else {
|
||||||
|
env->regs[0] = -1;
|
||||||
|
cpsr &= ~CPSR_C;
|
||||||
|
}
|
||||||
|
cpsr_write(env, cpsr, CPSR_C);
|
||||||
|
end_exclusive();
|
||||||
|
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. */
|
||||||
|
info.si_signo = SIGSEGV;
|
||||||
|
info.si_errno = 0;
|
||||||
|
/* XXX: check env->error_code */
|
||||||
|
info.si_code = TARGET_SEGV_MAPERR;
|
||||||
|
info._sifields._sigfault._addr = env->cp15.c6_data;
|
||||||
|
queue_signal(env, info.si_signo, &info);
|
||||||
|
|
||||||
|
end_exclusive();
|
||||||
|
}
|
||||||
|
|
||||||
/* Handle a jump to the kernel code page. */
|
/* Handle a jump to the kernel code page. */
|
||||||
static int
|
static int
|
||||||
do_kernel_trap(CPUARMState *env)
|
do_kernel_trap(CPUARMState *env)
|
||||||
@ -495,6 +572,10 @@ do_kernel_trap(CPUARMState *env)
|
|||||||
case 0xffff0fe0: /* __kernel_get_tls */
|
case 0xffff0fe0: /* __kernel_get_tls */
|
||||||
env->regs[0] = env->cp15.c13_tls2;
|
env->regs[0] = env->cp15.c13_tls2;
|
||||||
break;
|
break;
|
||||||
|
case 0xffff0f60: /* __kernel_cmpxchg64 */
|
||||||
|
arm_kernel_cmpxchg64_helper(env);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -752,7 +833,6 @@ void cpu_loop(CPUARMState *env)
|
|||||||
goto do_segv;
|
goto do_segv;
|
||||||
case EXCP_DATA_ABORT:
|
case EXCP_DATA_ABORT:
|
||||||
addr = env->cp15.c6_data;
|
addr = env->cp15.c6_data;
|
||||||
goto do_segv;
|
|
||||||
do_segv:
|
do_segv:
|
||||||
{
|
{
|
||||||
info.si_signo = SIGSEGV;
|
info.si_signo = SIGSEGV;
|
||||||
@ -3180,6 +3260,13 @@ int main(int argc, char **argv, char **envp)
|
|||||||
}
|
}
|
||||||
qemu_log("Reserved 0x%lx bytes of guest address space\n", reserved_va);
|
qemu_log("Reserved 0x%lx bytes of guest address space\n", reserved_va);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reserved_va || have_guest_base) {
|
||||||
|
if (!guest_validate_base(guest_base)) {
|
||||||
|
fprintf(stderr, "Guest base/Reserved VA rejected by guest code\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif /* CONFIG_USE_GUEST_BASE */
|
#endif /* CONFIG_USE_GUEST_BASE */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -202,6 +202,12 @@ int get_osversion(void);
|
|||||||
void fork_start(void);
|
void fork_start(void);
|
||||||
void fork_end(int child);
|
void fork_end(int child);
|
||||||
|
|
||||||
|
/* Return true if the proposed guest_base is suitable for the guest.
|
||||||
|
* The guest code may leave a page mapped and populate it if the
|
||||||
|
* address is suitable.
|
||||||
|
*/
|
||||||
|
bool guest_validate_base(unsigned long guest_base);
|
||||||
|
|
||||||
#include "qemu-log.h"
|
#include "qemu-log.h"
|
||||||
|
|
||||||
/* strace.c */
|
/* strace.c */
|
||||||
|
Loading…
Reference in New Issue
Block a user