From bb0140b925cb2adce03ebc0885067ea3bfd19a20 Mon Sep 17 00:00:00 2001 From: Jun Nakajima Date: Fri, 27 May 2011 18:24:21 -0700 Subject: x86: Workaorund for the KVM GS_BASE MSR save/restore issue. In some versions of the kvm module in Ubuntu, the host's GS_BASE MSR is not save/restored correctly when running guests on 64-bit hosts if the qemu/emulator is 32-bit. This patch implements a workaround in the emulator code. Change-Id: If3ebe3cb49f377c5d0547c75f6ac3a3ceacdc375 Signed-off-by: Yunhong Jiang Signed-off-by: Jun Nakajima --- target-i386/kvm-gs-restore.c | 144 +++++++++++++++++++++++++++++++++++++++++++ target-i386/kvm-gs-restore.h | 12 ++++ target-i386/kvm.c | 21 +++++++ 3 files changed, 177 insertions(+) create mode 100644 target-i386/kvm-gs-restore.c create mode 100644 target-i386/kvm-gs-restore.h (limited to 'target-i386') diff --git a/target-i386/kvm-gs-restore.c b/target-i386/kvm-gs-restore.c new file mode 100644 index 0000000..a0ff565 --- /dev/null +++ b/target-i386/kvm-gs-restore.c @@ -0,0 +1,144 @@ +#include +#include +#include "qemu-common.h" + +#ifdef CONFIG_KVM_GS_RESTORE + +#define INVALID_GS_REG 0xffff +#define KVM_GS_RESTORE_NODETECTED 0x1 +#define KVM_GS_RESTORE_NO 0x2 +#define KVM_GS_RESTORE_YES 0x3 +int initial_gs = INVALID_GS_REG; +int gs_need_restore = KVM_GS_RESTORE_NODETECTED; + +static void restoregs(int gs) +{ + asm("movl %0, %%gs"::"r"(gs)); +} + +static unsigned int _getgs() +{ + unsigned int gs = 0; + asm("movl %%gs,%0" :"=r"(gs):); + return gs; +} + +/* No fprintf or any system call before the gs is restored successfully */ +static void check_and_restore_gs(void) +{ + if (gs_need_restore == KVM_GS_RESTORE_NO) + return; + + restoregs(initial_gs); +} + +static struct sigaction old_alarm_act, old_gio_act, old_pipe_act,old_usr1_act, old_chld_act; +static void temp_sig_handler(int host_signum) +{ + /* !!! must restore gs firstly */ + check_and_restore_gs(); + switch (host_signum) + { + case SIGALRM: + old_alarm_act.sa_handler(host_signum); + break; + + case SIGIO: + old_gio_act.sa_handler(host_signum); + break; + + case SIGUSR1: + old_usr1_act.sa_handler(host_signum); + break; + + case SIGPIPE: + old_pipe_act.sa_handler(host_signum); + break; + + case SIGCHLD: + old_chld_act.sa_handler(host_signum); + break; + + default: + fprintf(stderr, "Not take signal %x!!\n", host_signum); + break; + } +} + +static int sig_taken = 0; +static int take_signal_handler(void) +{ + struct sigaction act; + int ret; + + if (gs_need_restore == KVM_GS_RESTORE_NO) + return 0; + if (sig_taken) + return 0; + + sig_taken = 1; + sigfillset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = temp_sig_handler; + /* Did we missed any other signal ? */ + sigaction(SIGALRM, &act, &old_alarm_act); + sigaction(SIGIO, &act, &old_gio_act); + sigaction(SIGUSR1, &act, &old_usr1_act); + sigaction(SIGPIPE, &act, &old_pipe_act); + act.sa_flags = SA_NOCLDSTOP; + sigaction(SIGCHLD, &act, &old_chld_act); + return 1; +} + +int gs_base_pre_run(void) +{ + if (unlikely(initial_gs == INVALID_GS_REG) ) + { + initial_gs = _getgs(); + /* + * As 2.6.35-28 lucid will get correct gs but clobbered GS_BASE + * we have to always re-write the gs base + */ + if (initial_gs == 0x0) + gs_need_restore = KVM_GS_RESTORE_NO; + else + gs_need_restore = KVM_GS_RESTORE_YES; + } + + take_signal_handler(); + return 0; +} + +int gs_base_post_run(void) +{ + check_and_restore_gs(); + return 0; +} + +/* + * ioctl may update errno, which is in thread local storage and + * requires gs register, we have to provide our own ioctl + * XXX should "call %%gs:$0x10" be replaced with call to vsyscall + * page, which is more generic and clean? + */ +int no_gs_ioctl(int fd, int type, void *arg) +{ + int ret=0; + + asm( + "movl %3, %%edx;\n" + "movl %2, %%ecx;\n" + "movl %1, %%ebx;\n" + "movl $0x36, %%eax;\n" + "call *%%gs:0x10;\n" + "movl %%eax, %0\n" + : "=m"(ret) + :"m"(fd),"m"(type),"m"(arg) + :"%edx","%ecx","%eax","%ebx" + ); + + return ret; +} + +#endif + diff --git a/target-i386/kvm-gs-restore.h b/target-i386/kvm-gs-restore.h new file mode 100644 index 0000000..0947b33 --- /dev/null +++ b/target-i386/kvm-gs-restore.h @@ -0,0 +1,12 @@ +#ifndef _KVM_GS_RESTORE_H +#define _KVM_GS_RESTORE_H + +#ifdef CONFIG_KVM_GS_RESTORE +int no_gs_ioctl(int fd, int type, void *arg); +int gs_base_post_run(void); +int gs_base_pre_run(void); +extern int gs_need_restore; +#define KVM_GS_RESTORE_NO 0x2 +#endif + +#endif diff --git a/target-i386/kvm.c b/target-i386/kvm.c index 4b135b1..07efac0 100644 --- a/target-i386/kvm.c +++ b/target-i386/kvm.c @@ -26,6 +26,10 @@ #include "cpu.h" #include "gdbstub.h" +#ifdef CONFIG_KVM_GS_RESTORE +#include "kvm-gs-restore.h" +#endif + //#define DEBUG_KVM #ifdef DEBUG_KVM @@ -690,6 +694,16 @@ int kvm_arch_get_registers(CPUState *env) return 0; } +int kvm_arch_vcpu_run(CPUState *env) +{ +#ifdef CONFIG_KVM_GS_RESTORE + if (gs_need_restore != KVM_GS_RESTORE_NO) + return no_gs_ioctl(env->kvm_fd, KVM_RUN, 0); + else +#endif + return kvm_vcpu_ioctl(env, KVM_RUN, 0); +} + int kvm_arch_pre_run(CPUState *env, struct kvm_run *run) { /* Try to inject an interrupt if the guest can accept it */ @@ -721,11 +735,18 @@ int kvm_arch_pre_run(CPUState *env, struct kvm_run *run) dprintf("setting tpr\n"); run->cr8 = cpu_get_apic_tpr(env); +#ifdef CONFIG_KVM_GS_RESTORE + gs_base_pre_run(); +#endif + return 0; } int kvm_arch_post_run(CPUState *env, struct kvm_run *run) { +#ifdef CONFIG_KVM_GS_RESTORE + gs_base_post_run(); +#endif if (run->if_flag) env->eflags |= IF_MASK; else -- cgit v1.1