| /* | |
| * Framework for testing nested virtualization | |
| */ | |
| #include "svm.h" | |
| #include "libcflat.h" | |
| #include "processor.h" | |
| #include "desc.h" | |
| #include "msr.h" | |
| #include "vm.h" | |
| #include "fwcfg.h" | |
| #include "smp.h" | |
| #include "alloc_page.h" | |
| #include "isr.h" | |
| #include "apic.h" | |
| /* for the nested page table*/ | |
| u64 *pml4e; | |
| struct vmcb *vmcb; | |
| u64 *npt_get_pte(u64 address) | |
| { | |
| return get_pte(npt_get_pml4e(), (void*)address); | |
| } | |
| u64 *npt_get_pde(u64 address) | |
| { | |
| struct pte_search search; | |
| search = find_pte_level(npt_get_pml4e(), (void*)address, 2); | |
| return search.pte; | |
| } | |
| u64 *npt_get_pdpe(u64 address) | |
| { | |
| struct pte_search search; | |
| search = find_pte_level(npt_get_pml4e(), (void*)address, 3); | |
| return search.pte; | |
| } | |
| u64 *npt_get_pml4e(void) | |
| { | |
| return pml4e; | |
| } | |
| bool smp_supported(void) | |
| { | |
| return cpu_count() > 1; | |
| } | |
| bool default_supported(void) | |
| { | |
| return true; | |
| } | |
| bool fep_supported(void) | |
| { | |
| return is_fep_available; | |
| } | |
| bool vgif_supported(void) | |
| { | |
| return this_cpu_has(X86_FEATURE_VGIF); | |
| } | |
| bool lbrv_supported(void) | |
| { | |
| return this_cpu_has(X86_FEATURE_LBRV); | |
| } | |
| bool tsc_scale_supported(void) | |
| { | |
| return this_cpu_has(X86_FEATURE_TSCRATEMSR); | |
| } | |
| bool pause_filter_supported(void) | |
| { | |
| return this_cpu_has(X86_FEATURE_PAUSEFILTER); | |
| } | |
| bool pause_threshold_supported(void) | |
| { | |
| return this_cpu_has(X86_FEATURE_PFTHRESHOLD); | |
| } | |
| void default_prepare(struct svm_test *test) | |
| { | |
| vmcb_ident(vmcb); | |
| } | |
| void default_prepare_gif_clear(struct svm_test *test) | |
| { | |
| } | |
| bool default_finished(struct svm_test *test) | |
| { | |
| return true; /* one vmexit */ | |
| } | |
| bool npt_supported(void) | |
| { | |
| return this_cpu_has(X86_FEATURE_NPT); | |
| } | |
| bool vnmi_supported(void) | |
| { | |
| return this_cpu_has(X86_FEATURE_VNMI); | |
| } | |
| int get_test_stage(struct svm_test *test) | |
| { | |
| barrier(); | |
| return test->scratch; | |
| } | |
| void set_test_stage(struct svm_test *test, int s) | |
| { | |
| barrier(); | |
| test->scratch = s; | |
| barrier(); | |
| } | |
| void inc_test_stage(struct svm_test *test) | |
| { | |
| barrier(); | |
| test->scratch++; | |
| barrier(); | |
| } | |
| static void vmcb_set_seg(struct vmcb_seg *seg, u16 selector, | |
| u64 base, u32 limit, u32 attr) | |
| { | |
| seg->selector = selector; | |
| seg->attrib = attr; | |
| seg->limit = limit; | |
| seg->base = base; | |
| } | |
| inline void vmmcall(void) | |
| { | |
| asm volatile ("vmmcall" : : : "memory"); | |
| } | |
| static test_guest_func guest_main; | |
| void test_set_guest(test_guest_func func) | |
| { | |
| guest_main = func; | |
| } | |
| static void test_thunk(struct svm_test *test) | |
| { | |
| guest_main(test); | |
| vmmcall(); | |
| } | |
| u8 *io_bitmap; | |
| u8 io_bitmap_area[16384]; | |
| u8 *msr_bitmap; | |
| u8 msr_bitmap_area[MSR_BITMAP_SIZE + PAGE_SIZE]; | |
| void vmcb_ident(struct vmcb *vmcb) | |
| { | |
| u64 vmcb_phys = virt_to_phys(vmcb); | |
| struct vmcb_save_area *save = &vmcb->save; | |
| struct vmcb_control_area *ctrl = &vmcb->control; | |
| u32 data_seg_attr = 3 | SVM_SELECTOR_S_MASK | SVM_SELECTOR_P_MASK | |
| | SVM_SELECTOR_DB_MASK | SVM_SELECTOR_G_MASK; | |
| u32 code_seg_attr = 9 | SVM_SELECTOR_S_MASK | SVM_SELECTOR_P_MASK | |
| | SVM_SELECTOR_L_MASK | SVM_SELECTOR_G_MASK; | |
| struct descriptor_table_ptr desc_table_ptr; | |
| memset(vmcb, 0, sizeof(*vmcb)); | |
| asm volatile ("vmsave %0" : : "a"(vmcb_phys) : "memory"); | |
| vmcb_set_seg(&save->es, read_es(), 0, -1U, data_seg_attr); | |
| vmcb_set_seg(&save->cs, read_cs(), 0, -1U, code_seg_attr); | |
| vmcb_set_seg(&save->ss, read_ss(), 0, -1U, data_seg_attr); | |
| vmcb_set_seg(&save->ds, read_ds(), 0, -1U, data_seg_attr); | |
| sgdt(&desc_table_ptr); | |
| vmcb_set_seg(&save->gdtr, 0, desc_table_ptr.base, desc_table_ptr.limit, 0); | |
| sidt(&desc_table_ptr); | |
| vmcb_set_seg(&save->idtr, 0, desc_table_ptr.base, desc_table_ptr.limit, 0); | |
| ctrl->asid = 1; | |
| save->cpl = 0; | |
| save->efer = rdmsr(MSR_EFER); | |
| save->cr4 = read_cr4(); | |
| save->cr3 = read_cr3(); | |
| save->cr0 = read_cr0(); | |
| save->dr7 = read_dr7(); | |
| save->dr6 = read_dr6(); | |
| save->cr2 = read_cr2(); | |
| save->g_pat = rdmsr(MSR_IA32_CR_PAT); | |
| save->dbgctl = rdmsr(MSR_IA32_DEBUGCTLMSR); | |
| ctrl->intercept = (1ULL << INTERCEPT_VMRUN) | | |
| (1ULL << INTERCEPT_VMMCALL) | | |
| (1ULL << INTERCEPT_SHUTDOWN); | |
| ctrl->iopm_base_pa = virt_to_phys(io_bitmap); | |
| ctrl->msrpm_base_pa = virt_to_phys(msr_bitmap); | |
| if (npt_supported()) { | |
| ctrl->nested_ctl = 1; | |
| ctrl->nested_cr3 = (u64)pml4e; | |
| ctrl->tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID; | |
| } | |
| } | |
| struct regs regs; | |
| struct regs get_regs(void) | |
| { | |
| return regs; | |
| } | |
| // rax handled specially below | |
| struct svm_test *v2_test; | |
| u64 guest_stack[10000]; | |
| void svm_setup_vmrun(u64 rip) | |
| { | |
| vmcb->save.rip = (ulong)rip; | |
| vmcb->save.rsp = (ulong)(guest_stack + ARRAY_SIZE(guest_stack)); | |
| } | |
| int __svm_vmrun(u64 rip) | |
| { | |
| svm_setup_vmrun(rip); | |
| regs.rdi = (ulong)v2_test; | |
| asm volatile ( | |
| ASM_PRE_VMRUN_CMD | |
| "vmrun %%rax\n\t" \ | |
| ASM_POST_VMRUN_CMD | |
| : | |
| : "a" (virt_to_phys(vmcb)) | |
| : "memory", "r15"); | |
| return (vmcb->control.exit_code); | |
| } | |
| int svm_vmrun(void) | |
| { | |
| return __svm_vmrun((u64)test_thunk); | |
| } | |
| extern u8 vmrun_rip; | |
| static noinline void test_run(struct svm_test *test) | |
| { | |
| u64 vmcb_phys = virt_to_phys(vmcb); | |
| cli(); | |
| vmcb_ident(vmcb); | |
| test->prepare(test); | |
| guest_main = test->guest_func; | |
| vmcb->save.rip = (ulong)test_thunk; | |
| vmcb->save.rsp = (ulong)(guest_stack + ARRAY_SIZE(guest_stack)); | |
| regs.rdi = (ulong)test; | |
| do { | |
| struct svm_test *the_test = test; | |
| u64 the_vmcb = vmcb_phys; | |
| asm volatile ( | |
| "clgi;\n\t" // semi-colon needed for LLVM compatibility | |
| "sti \n\t" | |
| "call *%c[PREPARE_GIF_CLEAR](%[test]) \n \t" | |
| "mov %[vmcb_phys], %%rax \n\t" | |
| ASM_PRE_VMRUN_CMD | |
| ".global vmrun_rip\n\t" \ | |
| "vmrun_rip: vmrun %%rax\n\t" \ | |
| ASM_POST_VMRUN_CMD | |
| "cli \n\t" | |
| "stgi" | |
| : // inputs clobbered by the guest: | |
| "=D" (the_test), // first argument register | |
| "=b" (the_vmcb) // callee save register! | |
| : [test] "0" (the_test), | |
| [vmcb_phys] "1"(the_vmcb), | |
| [PREPARE_GIF_CLEAR] "i" (offsetof(struct svm_test, prepare_gif_clear)) | |
| : "rax", "rcx", "rdx", "rsi", | |
| "r8", "r9", "r10", "r11" , "r12", "r13", "r14", "r15", | |
| "memory"); | |
| ++test->exits; | |
| } while (!test->finished(test)); | |
| sti(); | |
| report(test->succeeded(test), "%s", test->name); | |
| if (test->on_vcpu) | |
| test->on_vcpu_done = true; | |
| } | |
| static void set_additional_vcpu_msr(void *msr_efer) | |
| { | |
| void *hsave = alloc_page(); | |
| wrmsr(MSR_VM_HSAVE_PA, virt_to_phys(hsave)); | |
| wrmsr(MSR_EFER, (ulong)msr_efer | EFER_SVME); | |
| } | |
| static void setup_npt(void) | |
| { | |
| u64 size = fwcfg_get_u64(FW_CFG_RAM_SIZE); | |
| /* Ensure all <4gb is mapped, e.g. if there's no RAM above 4gb. */ | |
| if (size < BIT_ULL(32)) | |
| size = BIT_ULL(32); | |
| pml4e = alloc_page(); | |
| /* NPT accesses are treated as "user" accesses. */ | |
| __setup_mmu_range(pml4e, 0, size, X86_MMU_MAP_USER); | |
| } | |
| static void setup_svm(void) | |
| { | |
| void *hsave = alloc_page(); | |
| int i; | |
| wrmsr(MSR_VM_HSAVE_PA, virt_to_phys(hsave)); | |
| wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SVME); | |
| io_bitmap = (void *) ALIGN((ulong)io_bitmap_area, PAGE_SIZE); | |
| msr_bitmap = (void *) ALIGN((ulong)msr_bitmap_area, PAGE_SIZE); | |
| if (!npt_supported()) | |
| return; | |
| for (i = 1; i < cpu_count(); i++) | |
| on_cpu(i, (void *)set_additional_vcpu_msr, (void *)rdmsr(MSR_EFER)); | |
| printf("NPT detected - running all tests with NPT enabled\n"); | |
| /* | |
| * Nested paging supported - Build a nested page table | |
| * Build the page-table bottom-up and map everything with 4k | |
| * pages to get enough granularity for the NPT unit-tests. | |
| */ | |
| setup_npt(); | |
| } | |
| int matched; | |
| static bool | |
| test_wanted(const char *name, char *filters[], int filter_count) | |
| { | |
| int i; | |
| bool positive = false; | |
| bool match = false; | |
| char clean_name[strlen(name) + 1]; | |
| char *c; | |
| const char *n; | |
| /* Replace spaces with underscores. */ | |
| n = name; | |
| c = &clean_name[0]; | |
| do *c++ = (*n == ' ') ? '_' : *n; | |
| while (*n++); | |
| for (i = 0; i < filter_count; i++) { | |
| const char *filter = filters[i]; | |
| if (filter[0] == '-') { | |
| if (simple_glob(clean_name, filter + 1)) | |
| return false; | |
| } else { | |
| positive = true; | |
| match |= simple_glob(clean_name, filter); | |
| } | |
| } | |
| if (!positive || match) { | |
| matched++; | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| int run_svm_tests(int ac, char **av, struct svm_test *svm_tests) | |
| { | |
| int i = 0; | |
| ac--; | |
| av++; | |
| if (!this_cpu_has(X86_FEATURE_SVM)) { | |
| printf("SVM not available\n"); | |
| return report_summary(); | |
| } | |
| setup_svm(); | |
| vmcb = alloc_page(); | |
| for (; svm_tests[i].name != NULL; i++) { | |
| if (!test_wanted(svm_tests[i].name, av, ac)) | |
| continue; | |
| if (svm_tests[i].supported && !svm_tests[i].supported()) { | |
| report_skip("%s (not supported)", svm_tests[i].name); | |
| continue; | |
| } | |
| printf("SVM test: %s\n", svm_tests[i].name); | |
| if (svm_tests[i].v2 == NULL) { | |
| if (svm_tests[i].on_vcpu) { | |
| if (cpu_count() <= svm_tests[i].on_vcpu) | |
| continue; | |
| on_cpu_async(svm_tests[i].on_vcpu, (void *)test_run, &svm_tests[i]); | |
| while (!svm_tests[i].on_vcpu_done) | |
| cpu_relax(); | |
| } | |
| else | |
| test_run(&svm_tests[i]); | |
| } else { | |
| vmcb_ident(vmcb); | |
| v2_test = &(svm_tests[i]); | |
| svm_tests[i].v2(); | |
| } | |
| } | |
| if (!matched) | |
| report(matched, "command line didn't match any tests!"); | |
| return report_summary(); | |
| } |