Add page table walk for guest va to pa translation
authorGan Shun <ganshun@gmail.com>
Thu, 4 Aug 2016 17:45:16 +0000 (10:45 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 4 Aug 2016 18:00:47 +0000 (11:00 -0700)
We used to assume that the gpa would be the same as the gva minus the
high order bits. That is not the case for some kernels. This actually
walks the page table that the guest sets up to find the correct gpa.

Signed-off-by: Gan Shun <ganshun@gmail.com>
Change-Id: Ieed65f23476dc2c5e9e882ef5599870b3758565b
[minor formatting]
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
user/vmm/decode.c
user/vmm/include/vmm/vmm.h
user/vmm/io.c
user/vmm/vmexit.c
user/vmm/vmx.c

index a38438c..7f457d8 100644 (file)
@@ -36,6 +36,7 @@
 #include <vmm/virtio_mmio.h>
 #include <vmm/virtio_ids.h>
 #include <vmm/virtio_config.h>
+#include <ros/arch/mmu.h>
 #include <ros/arch/trapframe.h>
 
 int debug_decode = 0;
@@ -138,21 +139,21 @@ char *regname(uint8_t reg)
 
 static int insize(void *rip)
 {
-       uint8_t *kva = rip;
+       uint8_t *rip_gpa = rip;
        int advance = 3;
        int extra = 0;
-       if (kva[0] == 0x44) {
+       if (rip_gpa[0] == 0x44) {
                extra = 1;
-               kva++;
+               rip_gpa++;
        }
 
        /* the dreaded mod/rm byte. */
-       int mod = kva[1]>>6;
-       int rm = kva[1] & 7;
+       int mod = rip_gpa[1] >> 6;
+       int rm = rip_gpa[1] & 7;
 
-       switch(kva[0]) {
+       switch (rip_gpa[0]) {
        default:
-               fprintf(stderr, "BUG! %s got 0x%x\n", __func__, kva[0]);
+               fprintf(stderr, "BUG! %s got 0x%x\n", __func__, rip_gpa[0]);
        case 0x0f:
                break;
        case 0x81:
@@ -197,6 +198,7 @@ int decode(struct guest_thread *vm_thread, uint64_t *gpa, uint8_t *destreg,
            uint64_t **regp, int *store, int *size, int *advance)
 {
        struct vm_trapframe *vm_tf = &(vm_thread->uthread.u_ctx.tf.vm_tf);
+       uint8_t *rip_gpa = NULL;
 
        DPRINTF("v is %p\n", vm_tf);
 
@@ -210,27 +212,26 @@ int decode(struct guest_thread *vm_thread, uint64_t *gpa, uint8_t *destreg,
        *gpa = vm_tf->tf_guest_pa;
        DPRINTF("gpa is %p\n", *gpa);
 
-       // To find out what to do, we have to look at
-       // RIP. Technically, we should read RIP, walk the page tables
-       // to find the PA, and read that. But we're in the kernel, so
-       // we take a shortcut for now: read the low 30 bits and use
-       // that as the kernel PA, or our VA, and see what's
-       // there. Hokey. Works.
-       uint8_t *kva = (void *)(vm_tf->tf_rip & 0x3fffffff);
-       DPRINTF("kva is %p\n", kva);
+       DPRINTF("rip is %p\n", vm_tf->tf_rip);
+
+       if (rippa(vm_thread, (uint64_t *)&rip_gpa))
+               return VM_PAGE_FAULT;
+       DPRINTF("rip_gpa is %p\n", kva);
 
        // fail fast. If we can't get the size we're done.
-       *size = target(kva, store);
+       *size = target(rip_gpa, store);
+       DPRINTF("store is %d\n", *store);
        if (*size < 0)
                return -1;
 
-       *advance = insize(kva);
+       *advance = insize(rip_gpa);
 
-       uint16_t ins = *(uint16_t *)(kva + (kva[0] == 0x44) + (kva[0] == 0x0f));
+       uint16_t ins =
+           *(uint16_t *)(rip_gpa + (kva[0] == 0x44) + (kva[0] == 0x0f));
        DPRINTF("ins is %04x\n", ins);
 
        *destreg = (ins>>11) & 7;
-       *destreg += 8*(kva[0] == 0x44);
+       *destreg += 8 * (rip_gpa[0] == 0x44);
        // Our primitive approach wins big here.
        // We don't have to decode the register or the offset used
        // in the computation; that was done by the CPU and is the gpa.
index fdfb24d..5139b2a 100644 (file)
@@ -9,6 +9,8 @@
 #include <ros/vmm.h>
 #include <vmm/sched.h>
 
+#define VM_PAGE_FAULT                  14
+
 /* The listing of VIRTIO MMIO devices. We currently only expect to have 2,
  * console and network. Only the console is fully implemented right now.*/
 enum {
@@ -36,10 +38,10 @@ struct virtual_machine {
 char *regname(uint8_t reg);
 int decode(struct guest_thread *vm_thread, uint64_t *gpa, uint8_t *destreg,
            uint64_t **regp, int *store, int *size, int *advance);
-bool io(struct guest_thread *vm_thread);
+int io(struct guest_thread *vm_thread);
 void showstatus(FILE *f, struct guest_thread *vm_thread);
-uint64_t gvatogpa(struct guest_thread *vm_thread, uint64_t va);
-uint64_t rippa(struct guest_thread *vm_thread);
+int gvatogpa(struct guest_thread *vm_thread, uint64_t va, uint64_t *pa);
+int rippa(struct guest_thread *vm_thread, uint64_t *pa);
 int msrio(struct guest_thread *vm_thread, struct vmm_gpcore_init *gpci,
           uint32_t opcode);
 int do_ioapic(struct guest_thread *vm_thread, uint64_t gpa,
index 4368ed5..62766ee 100644 (file)
@@ -112,7 +112,7 @@ static void configwrite8(uint32_t addr, uint8_t val)
  * It would have been nice had intel encoded the IO exit info as nicely as they
  * encoded, some of the other exits.
  */
-bool io(struct guest_thread *vm_thread)
+int io(struct guest_thread *vm_thread)
 {
 
        /* Get a pointer to the memory at %rip. This is quite messy and part of the
@@ -125,11 +125,10 @@ bool io(struct guest_thread *vm_thread)
        uintptr_t ip;
        uint32_t edx;
        struct vm_trapframe *vm_tf = &(vm_thread->uthread.u_ctx.tf.vm_tf);
-       /* for now, we're going to be a bit crude. In kernel, p is about v, so we just blow away
-        * the upper 34 bits and take the rest + 1M as our address
-        * TODO: put this in vmctl somewhere?
-        */
-       ip = vm_tf->tf_rip & 0x3fffffff;
+
+       /* Get the RIP of the io access. */
+       if (rippa(vm_thread, (uint64_t *)&ip))
+               return VM_PAGE_FAULT;
        edx = vm_tf->tf_rdx;
        ip8 = (void *)ip;
        ip16 = (void *)ip;
@@ -141,12 +140,12 @@ bool io(struct guest_thread *vm_thread)
                if (edx == 0xcf8) {
                        //printf("Set cf8 ");
                        configaddr(vm_tf->tf_rax);
-                       return true;
+                       return 0;
                }
                if (edx == 0xcfc) {
                        //printf("Set cfc ");
                        configwrite32(edx, vm_tf->tf_rax);
-                       return true;
+                       return 0;
                }
                /* While it is perfectly legal to do IO operations to
                 * nonexistant places, we print a warning here as it
@@ -164,7 +163,7 @@ bool io(struct guest_thread *vm_thread)
                 * Windows 98, not Linux.
                 */
                printf("(out rax, edx): unhandled IO address dx @%p is 0x%x\n", ip8, edx);
-               return true;
+               return 0;
        }
        // out %al, %dx
        if (*ip8 == 0xee) {
@@ -172,37 +171,37 @@ bool io(struct guest_thread *vm_thread)
                /* out al %edx */
                if (edx == 0xcfb) { // special!
                        printf("Just ignore the damned cfb write\n");
-                       return true;
+                       return 0;
                }
                if ((edx&~3) == 0xcfc) {
                        //printf("ignoring write to cfc ");
-                       return true;
+                       return 0;
                }
                /* Another case where we print a message but it's not an error. */
                printf("out al, dx: unhandled IO address dx @%p is 0x%x\n", ip8, edx);
-               return true;
+               return 0;
        }
        if (*ip8 == 0xec) {
                vm_tf->tf_rip += 1;
                //printf("configread8 ");
                configread8(edx, &vm_tf->tf_rax);
-               return true;
+               return 0;
        }
        if (*ip8 == 0xed) {
                vm_tf->tf_rip += 1;
                if (edx == 0xcf8) {
                        //printf("read cf8 0x%lx\n", v->regs.tf_rax);
                        vm_tf->tf_rax = cf8;
-                       return true;
+                       return 0;
                }
                //printf("configread32 ");
                configread32(edx, &vm_tf->tf_rax);
-               return true;
+               return 0;
        }
        /* Detects when something is written to the PIC. */
        if (*ip8 == 0xe6) {
                vm_tf->tf_rip += 2;
-               return true;
+               return 0;
        }
        /* Detects when something is read from the PIC, so
         * a value signifying there is no PIC is given.
@@ -210,13 +209,13 @@ bool io(struct guest_thread *vm_thread)
        if (*ip16 == 0x21e4) {
                vm_tf->tf_rip += 2;
                vm_tf->tf_rax = ~0ULL;
-               return true;
+               return 0;
        }
        if (*ip16 == 0xed66) {
                vm_tf->tf_rip += 2;
                //printf("configread16 ");
                configread16(edx, &vm_tf->tf_rax);
-               return true;
+               return 0;
        }
 
        /* This is, so far, the only case in which we indicate
@@ -228,6 +227,6 @@ bool io(struct guest_thread *vm_thread)
         * instructions to the CPU.
         */
        printf("unknown IO %p %x %x\n", ip8, *ip8, *ip16);
-       return false;
+       return -1;
 }
 
index 173afa6..9ab6f44 100644 (file)
@@ -73,8 +73,19 @@ static bool handle_ept_fault(struct guest_thread *gth)
        int store, size;
        int advance;
 
-       if (decode(gth, &gpa, &regx, &regp, &store, &size, &advance))
+       int ret = decode(gth, &gpa, &regx, &regp, &store, &size, &advance);
+
+       if (ret < 0)
                return FALSE;
+       if (ret == VM_PAGE_FAULT) {
+               /* We were unable to translate RIP due to an ept fault */
+               vm_tf->tf_trap_inject = VM_TRAP_VALID
+                                     | VM_TRAP_ERROR_CODE
+                                     | VM_TRAP_HARDWARE
+                                     | HW_TRAP_PAGE_FAULT;
+               return TRUE;
+       }
+
        assert(size >= 0);
        /* TODO use helpers for some of these addr checks.  the fee/fec ones might
         * be wrong too. */
@@ -125,7 +136,19 @@ static bool handle_vmcall(struct guest_thread *gth)
 
 static bool handle_io(struct guest_thread *gth)
 {
-       return io(gth);
+       struct vm_trapframe *vm_tf = gth_to_vmtf(gth);
+       int ret = io(gth);
+
+       if (ret < 0)
+               return FALSE;
+       if (ret == VM_PAGE_FAULT) {
+               /* We were unable to translate RIP due to an ept fault */
+               vm_tf->tf_trap_inject = VM_TRAP_VALID
+                                     | VM_TRAP_ERROR_CODE
+                                     | VM_TRAP_HARDWARE
+                                     | HW_TRAP_PAGE_FAULT;
+       }
+       return TRUE;
 }
 
 static bool handle_msr(struct guest_thread *gth)
index d024537..221f8cb 100644 (file)
@@ -1,4 +1,4 @@
-#include <stdio.h> 
+#include <stdio.h>
 #include <pthread.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -20,6 +20,7 @@
 #include <vmm/virtio_ids.h>
 #include <ros/arch/vmx.h>
 #include <vmm/sched.h>
+#include <ros/arch/mmu.h>
 #include <ros/arch/trapframe.h>
 
 char *vmxexit[] = {
@@ -63,16 +64,33 @@ void showstatus(FILE *f, struct guest_thread *vm_thread)
  * TODO: Takes the vm_thread argument so that we can walk the page tables
  * instead of just coercing the pointer. Therefore, this is not in vmm.h
  * since it may get complex. */
-uint64_t gvatogpa(struct guest_thread *vm_thread, uint64_t va)
+int gvatogpa(struct guest_thread *vm_thread, uint64_t va, uint64_t *pa)
 {
        assert(vm_thread != NULL);
-       assert(va >= 0xffffffffc0000000ULL);
-       return va & 0x3fffffff;
+       struct vm_trapframe *vm_tf = gth_to_vmtf(vm_thread);
+       uint64_t *ptptr = (uint64_t *)vm_tf->tf_cr3;
+       uint64_t entry;
+
+       for (int shift = PML4_SHIFT; shift >= PML1_SHIFT; shift -= BITS_PER_PML) {
+               entry = ptptr[PMLx(va, shift)];
+
+               if (!PAGE_PRESENT(entry))
+                       return -1;
+               if ((entry & PTE_PS) != 0) {
+                       uint64_t bitmask = ((1 << shift) - 1);
+
+                       *pa = (((uint64_t)va & bitmask) | (entry & ~bitmask));
+                       return 0;
+               }
+               ptptr = (uint64_t *)PG_ADDR(entry);
+       }
+       *pa = ((uint64_t)va & 0xfff) | (uint64_t)ptptr;
+       return 0;
 }
 
 /* Get the RIP as a physical address. */
-uint64_t rippa(struct guest_thread *vm_thread)
+int rippa(struct guest_thread *vm_thread, uint64_t *pa)
 {
        assert(vm_thread != NULL);
-       return gvatogpa(vm_thread, gth_to_vmtf(vm_thread)->tf_rip);
+       return gvatogpa(vm_thread, gth_to_vmtf(vm_thread)->tf_rip, pa);
 }