Patchwork [2/7] linux-user: Implement new ARM 64 bit cmpxchg kernel helper

login
register
mail settings
Submitter riku.voipio@linaro.org
Date Sept. 9, 2011, 9:32 a.m.
Message ID <97cc75606aef406e90a243cdb25347039003e7f0.1315560307.git.riku.voipio@linaro.org>
Download mbox | patch
Permalink /patch/114036/
State New
Headers show

Comments

riku.voipio@linaro.org - Sept. 9, 2011, 9:32 a.m.
From: Dr. David Alan Gilbert <david.gilbert@linaro.org>

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>
---
 linux-user/elfload.c |   56 ++++++++++++++++++++++++++++++-
 linux-user/main.c    |   89 +++++++++++++++++++++++++++++++++++++++++++++++++-
 linux-user/qemu.h    |    6 +++
 3 files changed, 148 insertions(+), 3 deletions(-)

Patch

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 */