From: "Dr. David Alan Gilbert" <david.gilbert@linaro.org>
To: qemu-devel@nongnu.org
Cc: peter.maydell@linaro.org, riku.voipio@iki.fi, patches@linaro.org
Subject: [Qemu-devel] [PATCH] linux-user: Implement new ARM 64 bit cmpxchg kernel helper
Date: Wed, 31 Aug 2011 17:24:34 +0100 [thread overview]
Message-ID: <20110831162434.GA24474@davesworkthinkpad> (raw)
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: Dr. David Alan Gilbert <david.gilbert@linaro.org>
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index 04e8e6e..8677bba 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -332,6 +332,49 @@ enum
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 \
| ARM_HWCAP_ARM_THUMB | ARM_HWCAP_ARM_FAST_MULT \
| 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;
}
+#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,
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) {
goto exit_perror;
}
- if (real_start == host_start) {
+ guest_base = real_start - loaddr;
+ if ((real_start == host_start) &&
+ guest_validate_base(guest_base)) {
break;
}
/* 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"
TARGET_ABI_FMT_lx " to 0x%lx\n",
loaddr, real_start);
- guest_base = real_start - loaddr;
}
return;
diff --git a/linux-user/main.c b/linux-user/main.c
index 89a51d7..25cb4dd 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -456,6 +456,83 @@ void cpu_loop(CPUX86State *env)
#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. */
static int
do_kernel_trap(CPUARMState *env)
@@ -495,6 +572,10 @@ do_kernel_trap(CPUARMState *env)
case 0xffff0fe0: /* __kernel_get_tls */
env->regs[0] = env->cp15.c13_tls2;
break;
+ case 0xffff0f60: /* __kernel_cmpxchg64 */
+ arm_kernel_cmpxchg64_helper(env);
+ break;
+
default:
return 1;
}
@@ -752,7 +833,6 @@ void cpu_loop(CPUARMState *env)
goto do_segv;
case EXCP_DATA_ABORT:
addr = env->cp15.c6_data;
- goto do_segv;
do_segv:
{
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);
}
+
+ 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 */
/*
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 627c8b3..55ad9d8 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -202,6 +202,12 @@ int get_osversion(void);
void fork_start(void);
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"
/* strace.c */
next reply other threads:[~2011-08-31 16:24 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-08-31 16:24 Dr. David Alan Gilbert [this message]
2011-09-08 7:12 ` [Qemu-devel] [PATCH] linux-user: Implement new ARM 64 bit cmpxchg kernel helper Peter Maydell
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20110831162434.GA24474@davesworkthinkpad \
--to=david.gilbert@linaro.org \
--cc=patches@linaro.org \
--cc=peter.maydell@linaro.org \
--cc=qemu-devel@nongnu.org \
--cc=riku.voipio@iki.fi \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.