The GDT and the TSS base were left to zero, and this has interesting effects when the TSS descriptor is later read to set up a VMCS's TR_BASE. Basically it worked by chance, and this patch fixes it by setting up all the protected mode data structures properly. Because the GDT and TSS addresses are virtual, the page tables now always exist at the time of vcpu setup. Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx> --- tools/testing/selftests/kvm/include/kvm_util.h | 2 +- tools/testing/selftests/kvm/include/x86.h | 11 +- tools/testing/selftests/kvm/lib/kvm_util.c | 4 +- .../testing/selftests/kvm/lib/kvm_util_internal.h | 5 +- tools/testing/selftests/kvm/lib/x86.c | 111 +++++++++++++-------- 5 files changed, 86 insertions(+), 47 deletions(-) diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h index 637b7017b6ee..87e05664c7f9 100644 --- a/tools/testing/selftests/kvm/include/kvm_util.h +++ b/tools/testing/selftests/kvm/include/kvm_util.h @@ -75,7 +75,7 @@ void vcpu_ioctl(struct kvm_vm *vm, uint32_t vcpuid, unsigned long ioctl, void *arg); void vm_ioctl(struct kvm_vm *vm, unsigned long ioctl, void *arg); void vm_mem_region_set_flags(struct kvm_vm *vm, uint32_t slot, uint32_t flags); -void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid); +void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid, int pgd_memslot, int gdt_memslot); vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min, uint32_t data_memslot, uint32_t pgd_memslot); void *addr_gpa2hva(struct kvm_vm *vm, vm_paddr_t gpa); diff --git a/tools/testing/selftests/kvm/include/x86.h b/tools/testing/selftests/kvm/include/x86.h index 4a5b2c4c1a0f..d8788ddb0210 100644 --- a/tools/testing/selftests/kvm/include/x86.h +++ b/tools/testing/selftests/kvm/include/x86.h @@ -56,8 +56,8 @@ enum x86_register { struct desc64 { uint16_t limit0; uint16_t base0; - unsigned base1:8, type:5, dpl:2, p:1; - unsigned limit1:4, zero0:3, g:1, base2:8; + unsigned base1:8, s:1, type:4, dpl:2, p:1; + unsigned limit1:4, avl:1, l:1, db:1, g:1, base2:8; uint32_t base3; uint32_t zero1; } __attribute__((packed)); diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c index 37e2a787d2fc..610d1326f03d 100644 --- a/tools/testing/selftests/kvm/lib/kvm_util.c +++ b/tools/testing/selftests/kvm/lib/kvm_util.c @@ -701,7 +701,7 @@ static int vcpu_mmap_sz(void) * Creates and adds to the VM specified by vm and virtual CPU with * the ID given by vcpuid. */ -void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid) +void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid, int pgd_memslot, int gdt_memslot) { struct vcpu *vcpu; @@ -736,7 +736,7 @@ void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid) vcpu->next = vm->vcpu_head; vm->vcpu_head = vcpu; - vcpu_setup(vm, vcpuid); + vcpu_setup(vm, vcpuid, pgd_memslot, gdt_memslot); } /* VM Virtual Address Unused Gap diff --git a/tools/testing/selftests/kvm/lib/kvm_util_internal.h b/tools/testing/selftests/kvm/lib/kvm_util_internal.h index a0bd1980c81c..cbb40288890a 100644 --- a/tools/testing/selftests/kvm/lib/kvm_util_internal.h +++ b/tools/testing/selftests/kvm/lib/kvm_util_internal.h @@ -51,13 +51,16 @@ struct kvm_vm { struct userspace_mem_region *userspace_mem_region_head; struct sparsebit *vpages_valid; struct sparsebit *vpages_mapped; + bool pgd_created; vm_paddr_t pgd; + vm_vaddr_t gdt; + vm_vaddr_t tss; }; struct vcpu *vcpu_find(struct kvm_vm *vm, uint32_t vcpuid); -void vcpu_setup(struct kvm_vm *vm, int vcpuid); +void vcpu_setup(struct kvm_vm *vm, int vcpuid, int pgd_memslot, int gdt_memslot); void virt_dump(FILE *stream, struct kvm_vm *vm, uint8_t indent); void regs_dump(FILE *stream, struct kvm_regs *regs, uint8_t indent); diff --git a/tools/testing/selftests/kvm/lib/x86.c b/tools/testing/selftests/kvm/lib/x86.c index 2f17675f4275..024e95f1b470 100644 --- a/tools/testing/selftests/kvm/lib/x86.c +++ b/tools/testing/selftests/kvm/lib/x86.c @@ -239,25 +239,6 @@ void virt_pgd_alloc(struct kvm_vm *vm, uint32_t pgd_memslot) vm_paddr_t paddr = vm_phy_page_alloc(vm, KVM_GUEST_PAGE_TABLE_MIN_PADDR, pgd_memslot); vm->pgd = paddr; - - /* Set pointer to pgd tables in all the VCPUs that - * have already been created. Future VCPUs will have - * the value set as each one is created. - */ - for (struct vcpu *vcpu = vm->vcpu_head; vcpu; - vcpu = vcpu->next) { - struct kvm_sregs sregs; - - /* Obtain the current system register settings */ - vcpu_sregs_get(vm, vcpu->id, &sregs); - - /* Set and store the pointer to the start of the - * pgd tables. - */ - sregs.cr3 = vm->pgd; - vcpu_sregs_set(vm, vcpu->id, &sregs); - } - vm->pgd_created = true; } } @@ -460,9 +441,32 @@ static void kvm_seg_set_unusable(struct kvm_segment *segp) segp->unusable = true; } +static void kvm_seg_fill_gdt_64bit(struct kvm_vm *vm, struct kvm_segment *segp) +{ + void *gdt = addr_gva2hva(vm, vm->gdt); + struct desc64 *desc = gdt + (segp->selector >> 3) * 8; + + desc->limit0 = segp->limit & 0xFFFF; + desc->base0 = segp->base & 0xFFFF; + desc->base1 = segp->base >> 16; + desc->s = segp->s; + desc->type = segp->type; + desc->dpl = segp->dpl; + desc->p = segp->present; + desc->limit1 = segp->limit >> 16; + desc->l = segp->l; + desc->db = segp->db; + desc->g = segp->g; + desc->base2 = segp->base >> 24; + if (!segp->s) + desc->base3 = segp->base >> 32; +} + + /* Set Long Mode Flat Kernel Code Segment * * Input Args: + * vm - VM whose GDT is being filled, or NULL to only write segp * selector - selector value * * Output Args: @@ -473,7 +477,7 @@ static void kvm_seg_set_unusable(struct kvm_segment *segp) * Sets up the KVM segment pointed to by segp, to be a code segment * with the selector value given by selector. */ -static void kvm_seg_set_kernel_code_64bit(uint16_t selector, +static void kvm_seg_set_kernel_code_64bit(struct kvm_vm *vm, uint16_t selector, struct kvm_segment *segp) { memset(segp, 0, sizeof(*segp)); @@ -486,11 +490,14 @@ static void kvm_seg_set_kernel_code_64bit(uint16_t selector, segp->g = true; segp->l = true; segp->present = 1; + if (vm) + kvm_seg_fill_gdt_64bit(vm, segp); } /* Set Long Mode Flat Kernel Data Segment * * Input Args: + * vm - VM whose GDT is being filled, or NULL to only write segp * selector - selector value * * Output Args: @@ -501,7 +508,7 @@ static void kvm_seg_set_kernel_code_64bit(uint16_t selector, * Sets up the KVM segment pointed to by segp, to be a data segment * with the selector value given by selector. */ -static void kvm_seg_set_kernel_data_64bit(uint16_t selector, +static void kvm_seg_set_kernel_data_64bit(struct kvm_vm *vm, uint16_t selector, struct kvm_segment *segp) { memset(segp, 0, sizeof(*segp)); @@ -513,6 +520,8 @@ static void kvm_seg_set_kernel_data_64bit(uint16_t selector, */ segp->g = true; segp->present = true; + if (vm) + kvm_seg_fill_gdt_64bit(vm, segp); } /* Address Guest Virtual to Guest Physical @@ -575,13 +584,45 @@ vm_paddr_t addr_gva2gpa(struct kvm_vm *vm, vm_vaddr_t gva) "gva: 0x%lx", gva); } -void vcpu_setup(struct kvm_vm *vm, int vcpuid) +static void kvm_setup_gdt(struct kvm_vm *vm, struct kvm_dtable *dt, int gdt_memslot, + int pgd_memslot) +{ + if (!vm->gdt) + vm->gdt = vm_vaddr_alloc(vm, getpagesize(), + KVM_UTIL_MIN_VADDR, gdt_memslot, pgd_memslot); + + dt->base = vm->gdt; + dt->limit = getpagesize(); +} + +static void kvm_setup_tss_64bit(struct kvm_vm *vm, struct kvm_segment *segp, + int selector, int gdt_memslot, + int pgd_memslot) +{ + if (!vm->tss) + vm->tss = vm_vaddr_alloc(vm, getpagesize(), + KVM_UTIL_MIN_VADDR, gdt_memslot, pgd_memslot); + + memset(segp, 0, sizeof(*segp)); + segp->base = vm->tss; + segp->limit = 0x67; + segp->selector = selector; + segp->type = 0xb; + segp->present = 1; + kvm_seg_fill_gdt_64bit(vm, segp); +} + +void vcpu_setup(struct kvm_vm *vm, int vcpuid, int pgd_memslot, int gdt_memslot) { struct kvm_sregs sregs; /* Set mode specific system register values. */ vcpu_sregs_get(vm, vcpuid, &sregs); + sregs.idt.limit = 0; + + kvm_setup_gdt(vm, &sregs.gdt, gdt_memslot, pgd_memslot); + switch (vm->mode) { case VM_MODE_FLAT48PG: sregs.cr0 = X86_CR0_PE | X86_CR0_NE | X86_CR0_PG; @@ -589,30 +630,18 @@ void vcpu_setup(struct kvm_vm *vm, int vcpuid) sregs.efer |= (EFER_LME | EFER_LMA | EFER_NX); kvm_seg_set_unusable(&sregs.ldt); - kvm_seg_set_kernel_code_64bit(0x8, &sregs.cs); - kvm_seg_set_kernel_data_64bit(0x10, &sregs.ds); - kvm_seg_set_kernel_data_64bit(0x10, &sregs.es); + kvm_seg_set_kernel_code_64bit(vm, 0x8, &sregs.cs); + kvm_seg_set_kernel_data_64bit(vm, 0x10, &sregs.ds); + kvm_seg_set_kernel_data_64bit(vm, 0x10, &sregs.es); + kvm_setup_tss_64bit(vm, &sregs.tr, 0x18, gdt_memslot, pgd_memslot); break; default: TEST_ASSERT(false, "Unknown guest mode, mode: 0x%x", vm->mode); } - vcpu_sregs_set(vm, vcpuid, &sregs); - - /* If virtual translation table have been setup, set system register - * to point to the tables. It's okay if they haven't been setup yet, - * in that the code that sets up the virtual translation tables, will - * go back through any VCPUs that have already been created and set - * their values. - */ - if (vm->pgd_created) { - struct kvm_sregs sregs; - vcpu_sregs_get(vm, vcpuid, &sregs); - - sregs.cr3 = vm->pgd; - vcpu_sregs_set(vm, vcpuid, &sregs); - } + sregs.cr3 = vm->pgd; + vcpu_sregs_set(vm, vcpuid, &sregs); } /* Adds a vCPU with reasonable defaults (i.e., a stack) * @@ -629,7 +658,7 @@ void vm_vcpu_add_default(struct kvm_vm *vm, uint32_t vcpuid, void *guest_code) DEFAULT_GUEST_STACK_VADDR_MIN, 0, 0); /* Create VCPU */ - vm_vcpu_add(vm, vcpuid); + vm_vcpu_add(vm, vcpuid, 0, 0); /* Setup guest general purpose registers */ vcpu_regs_get(vm, vcpuid, ®s); -- 1.8.3.1