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,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 */
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue