The UMIP feature can be emulated by KVM, so it's useful to add a test that it works properly. Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx> --- lib/x86/desc.c | 2 +- lib/x86/desc.h | 1 + lib/x86/processor.h | 9 +++ x86/Makefile.common | 1 + x86/umip.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++ x86/unittests.cfg | 4 ++ 6 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 x86/umip.c diff --git a/lib/x86/desc.c b/lib/x86/desc.c index 402204d..a2d66af 100644 --- a/lib/x86/desc.c +++ b/lib/x86/desc.c @@ -63,7 +63,7 @@ static const char* exception_mnemonic(int vector) } } -static void unhandled_exception(struct ex_regs *regs, bool cpu) +void unhandled_exception(struct ex_regs *regs, bool cpu) { printf("Unhandled %sexception %ld %s at ip %016lx\n", cpu ? "cpu " : "", regs->vector, diff --git a/lib/x86/desc.h b/lib/x86/desc.h index be52fd4..7646cfa 100644 --- a/lib/x86/desc.h +++ b/lib/x86/desc.h @@ -154,6 +154,7 @@ void set_gdt_entry(int sel, u32 base, u32 limit, u8 access, u8 gran); void set_intr_alt_stack(int e, void *fn); void print_current_tss_info(void); void handle_exception(u8 v, void (*func)(struct ex_regs *regs)); +void unhandled_exception(struct ex_regs *regs, bool cpu); bool test_for_exception(unsigned int ex, void (*trigger_func)(void *data), void *data); diff --git a/lib/x86/processor.h b/lib/x86/processor.h index ee7f180..398ce4c 100644 --- a/lib/x86/processor.h +++ b/lib/x86/processor.h @@ -25,6 +25,7 @@ #define X86_CR4_DE 0x00000008 #define X86_CR4_PSE 0x00000010 #define X86_CR4_PAE 0x00000020 +#define X86_CR4_UMIP 0x00000800 #define X86_CR4_VMXE 0x00002000 #define X86_CR4_PCIDE 0x00020000 #define X86_CR4_SMAP 0x00200000 @@ -36,6 +37,7 @@ #define X86_EFLAGS_ZF 0x00000040 #define X86_EFLAGS_SF 0x00000080 #define X86_EFLAGS_OF 0x00000800 +#define X86_EFLAGS_IOPL 0x00003000 #define X86_EFLAGS_AC 0x00040000 #define X86_IA32_EFER 0xc0000080 @@ -146,6 +148,13 @@ static inline void write_rflags(unsigned long f) asm volatile ("push %0; popf\n\t" : : "rm"(f)); } +static inline void set_iopl(int iopl) +{ + unsigned long flags = read_rflags() & ~X86_EFLAGS_IOPL; + flags |= iopl * (X86_EFLAGS_IOPL / 3); + write_rflags(flags); +} + static inline u64 rdmsr(u32 index) { u32 a, d; diff --git a/x86/Makefile.common b/x86/Makefile.common index 356d879..32e8e51 100644 --- a/x86/Makefile.common +++ b/x86/Makefile.common @@ -45,6 +45,7 @@ tests-common = $(TEST_DIR)/vmexit.flat $(TEST_DIR)/tsc.flat \ $(TEST_DIR)/tsc_adjust.flat $(TEST_DIR)/asyncpf.flat \ $(TEST_DIR)/init.flat $(TEST_DIR)/smap.flat \ $(TEST_DIR)/hyperv_synic.flat $(TEST_DIR)/hyperv_stimer.flat \ + $(TEST_DIR)/umip.flat ifdef API tests-common += api/api-sample diff --git a/x86/umip.c b/x86/umip.c new file mode 100644 index 0000000..c1a40d6 --- /dev/null +++ b/x86/umip.c @@ -0,0 +1,194 @@ + +#include "libcflat.h" +#include "desc.h" +#include "processor.h" + +#define CPUID_7_ECX_UMIP (1 << 2) +static int cpuid_7_ecx; + + +/* GP handler to skip over faulting instructions */ + +static unsigned long expected_rip; +static int skip_count; +static volatile int gp_count; + +void gp_handler(struct ex_regs *regs) +{ + if (regs->rip == expected_rip) { + gp_count++; + regs->rip += skip_count; + } else { + unhandled_exception(regs, false); + } +} + + +#define GP_ASM(stmt, in, clobber) \ + asm ("mov" W " $1f, %[expected_rip]\n\t" \ + "movl $2f-1f, %[skip_count]\n\t" \ + "1: " stmt "\n\t" \ + "2: " \ + : [expected_rip] "=m" (expected_rip), \ + [skip_count] "=m" (skip_count) \ + : in : clobber) + +static void do_smsw(void) +{ + gp_count = 0; + GP_ASM("smsw %%ax", , "eax"); +} + +static void do_sldt(void) +{ + gp_count = 0; + GP_ASM("sldt %%ax", , "eax"); +} + +static void do_str(void) +{ + gp_count = 0; + GP_ASM("str %%ax", , "eax"); +} + +static void do_sgdt(void) +{ + struct descriptor_table_ptr dt; + gp_count = 0; + GP_ASM("sgdt %[dt]", [dt]"m"(dt), ); +} + +static void do_sidt(void) +{ + struct descriptor_table_ptr dt; + gp_count = 0; + GP_ASM("sidt %[dt]", [dt]"m"(dt), ); +} + +static void do_movcr(void) +{ + gp_count = 0; + GP_ASM("mov %%cr0, %%" R "ax", , "eax"); +} + +static void test_umip_nogp(char *msg) +{ + puts(msg); + + do_smsw(); + report("no exception from smsw", gp_count == 0); + do_sgdt(); + report("no exception from sgdt", gp_count == 0); + do_sidt(); + report("no exception from sidt", gp_count == 0); + do_sldt(); + report("no exception from sldt", gp_count == 0); + do_str(); + report("no exception from str", gp_count == 0); + if (read_cs() & 3) { + do_movcr(); + report("exception from mov %%cr0, %%eax", gp_count == 1); + } +} + +static void test_umip_gp(char *msg) +{ + puts(msg); + + do_smsw(); + report("exception from smsw", gp_count == 1); + do_sgdt(); + report("exception from sgdt", gp_count == 1); + do_sidt(); + report("exception from sidt", gp_count == 1); + do_sldt(); + report("exception from sldt", gp_count == 1); + do_str(); + report("exception from str", gp_count == 1); + if (read_cs() & 3) { + do_movcr(); + report("exception from mov %%cr0, %%eax", gp_count == 1); + } +} + +/* The ugly mode switching code */ + +int do_ring3(void (*fn)(char *), char *arg) +{ + static unsigned char user_stack[4096]; + int ret; + + asm volatile ("mov %[user_ds], %%" R "dx\n\t" + "mov %%dx, %%ds\n\t" + "mov %%dx, %%es\n\t" + "mov %%dx, %%fs\n\t" + "mov %%dx, %%gs\n\t" + "mov %%" R "sp, %%" R "cx\n\t" + "push" W " %%" R "dx \n\t" + "lea %[user_stack_top], %%" R "dx \n\t" + "push" W " %%" R "dx \n\t" + "pushf" W "\n\t" + "push" W " %[user_cs] \n\t" + "push" W " $1f \n\t" + "iret" W "\n" + "1: \n\t" + "push %%" R "cx\n\t" /* save kernel SP */ + +#ifndef __x86_64__ + "push %[arg]\n\t" +#endif + "call *%[fn]\n\t" +#ifndef __x86_64__ + "pop %%ecx\n\t" +#endif + + "pop %%" R "cx\n\t" + "mov $1f, %%" R "dx\n\t" + "int %[kernel_entry_vector]\n\t" + ".section .text.entry \n\t" + "kernel_entry: \n\t" + "mov %%" R "cx, %%" R "sp \n\t" + "mov %[kernel_ds], %%cx\n\t" + "mov %%cx, %%ds\n\t" + "mov %%cx, %%es\n\t" + "mov %%cx, %%fs\n\t" + "mov %%cx, %%gs\n\t" + "jmp *%%" R "dx \n\t" + ".section .text\n\t" + "1:\n\t" + : [ret] "=&a" (ret) + : [user_ds] "i" (USER_DS), + [user_cs] "i" (USER_CS), + [user_stack_top]"m"(user_stack[sizeof user_stack]), + [fn]"r"(fn), + [arg]"D"(arg), + [kernel_ds]"i"(KERNEL_DS), + [kernel_entry_vector]"i"(0x20) + : "rcx", "rdx"); + return ret; +} + +int main() +{ + extern unsigned char kernel_entry; + + setup_idt(); + set_idt_entry(0x20, &kernel_entry, 3); + handle_exception(13, gp_handler); + set_iopl(3); + + test_umip_nogp("UMIP=0, CPL=0\n"); + do_ring3(test_umip_nogp, "UMIP=0, CPL=3\n"); + + cpuid_7_ecx = cpuid(7).c; + if (!(cpuid_7_ecx & CPUID_7_ECX_UMIP)) { + printf("UMIP not available\n"); + return report_summary(); + } + write_cr4(read_cr4() | X86_CR4_UMIP); + + test_umip_nogp("UMIP=0, CPL=0\n"); + do_ring3(test_umip_gp, "UMIP=0, CPL=3\n"); + + return report_summary(); +} diff --git a/x86/unittests.cfg b/x86/unittests.cfg index 60747cf..f76f0d4 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -179,6 +179,10 @@ file = pcid.flat extra_params = -cpu qemu64,+pcid arch = x86_64 +[umip] +file = umip.flat +extra_params = -cpu qemu64,+umip + [vmx] file = vmx.flat extra_params = -cpu host,+vmx -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html