vmm: Added initial pagetable setup code
authorGan Shun <ganshun@gmail.com>
Wed, 6 Sep 2017 17:56:37 +0000 (10:56 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 14 Sep 2017 19:26:23 +0000 (15:26 -0400)
This commit changes the way we set up initial pagetables for both
regular VMs and vthreads. We now parse /proc/self/maps to find out
what memory has been mapped in the process' space and create the
pagetables accordingly. Userspace can call add_pte_entries if they
want to add more entries after setup_paging has been called. Also
copied a CPUID helper function from Barret's fputest into parlib.

Signed-off-by: Gan Shun <ganshun@gmail.com>
Change-Id: I742e9c84e126b6d1688888255deac01b2843786e
[ removed vthread.c debug, unused ]
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tests/dune/dune.c
tests/mmap_file_vmm.c
tests/vmm/vmrunkernel.c
user/parlib/include/parlib/x86/arch.h
user/vmm/include/vmm/vmm.h
user/vmm/pagetables.c
user/vmm/vthread.c

index 5a990c4..ff9383c 100644 (file)
@@ -37,7 +37,8 @@ bool linuxemu(struct guest_thread *gth, struct vm_trapframe *tf);
 
 extern char **environ;
 
-static struct virtual_machine vm = {.halt_exit = true,};
+static struct virtual_machine vm = {.halt_exit = true,
+                                    .root_mtx = UTH_MUTEX_INIT};
 
 static unsigned long long memsize = GiB;
 static uintptr_t memstart = MinMemory;
index 81880d5..243c3c4 100644 (file)
@@ -21,7 +21,7 @@
 
 #include <vmm/vmm.h>
 
-static struct virtual_machine vm;
+static struct virtual_machine vm = {.root_mtx = UTH_MUTEX_INIT};
 
 int safe_to_exit;
 void *addr;
index 741f9d4..91793f1 100644 (file)
@@ -39,7 +39,9 @@
 #include <sys/uio.h>
 #include <parlib/opts.h>
 
-struct virtual_machine local_vm, *vm = &local_vm;
+struct virtual_machine local_vm = {.root_mtx = UTH_MUTEX_INIT},
+                            *vm = &local_vm;
+
 struct vmm_gpcore_init *gpcis;
 
 void vapic_status_dump(FILE *f, void *vapic);
@@ -369,7 +371,6 @@ void init_timer_alarms(void)
 
 int main(int argc, char **argv)
 {
-       void *cr3;
        int debug = 0;
        unsigned long long memsize = GiB;
        uintptr_t memstart = MinMemory;
@@ -592,11 +593,27 @@ int main(int argc, char **argv)
         * known MMIO address, and fulfill the MMIO read or write on the guest's
         * behalf accordingly. We place the virtio space at 512 GB higher than the
         * guest physical memory to avoid a full page table walk. */
-       uint64_t virtio_mmio_base_addr;
+       uintptr_t virtio_mmio_base_addr_hint;
+       uintptr_t virtio_mmio_base_addr;
+
+       virtio_mmio_base_addr_hint =
+           ROUNDUP((bp->e820_map[bp->e820_entries - 1].addr +
+                    bp->e820_map[bp->e820_entries - 1].size),
+                    PML4_PTE_REACH);
+
+       /* mmap with prot_none so we don't accidentally mmap something else here.
+        * We give space for 512 devices right now.
+        * TODO(ganshun): Make it dynamic based on number of virtio devices. */
+       virtio_mmio_base_addr =
+           (uintptr_t) mmap((void *) virtio_mmio_base_addr_hint, 512 * PGSIZE,
+                            PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+
+       if (!virtio_mmio_base_addr || virtio_mmio_base_addr >= BRK_START) {
+               /* Either we were unable to mmap at all or we mapped it too high. */
+               panic("Unable to mmap protect space for virtio devices, got 0x%016x",
+                     virtio_mmio_base_addr);
+       }
 
-       virtio_mmio_base_addr = ROUNDUP((bp->e820_map[bp->e820_entries - 1].addr +
-                                        bp->e820_map[bp->e820_entries - 1].size),
-                                        512ULL * GiB);
        cons_mmio_dev.addr =
                virtio_mmio_base_addr + PGSIZE * VIRTIO_MMIO_CONSOLE_DEV;
        cons_mmio_dev.vqdev = &cons_vqdev;
@@ -669,16 +686,17 @@ int main(int argc, char **argv)
        ret = vmm_init(vm, vmmflags);
        assert(!ret);
 
-       cr3 = setup_paging(vm, debug);
        init_timer_alarms();
 
+       setup_paging(vm);
+
        vm_tf = gth_to_vmtf(vm->gths[0]);
-       vm_tf->tf_cr3 = (uint64_t) cr3;
+       vm_tf->tf_cr3 = (uint64_t) vm->root;
        vm_tf->tf_rip = entry;
        vm_tf->tf_rsp = 0xe0000;
        vm_tf->tf_rsi = (uint64_t) bp;
        vm->up_gpcs = 1;
-       fprintf(stderr, "Start guest: cr3 %p rip %p\n", cr3, entry);
+       fprintf(stderr, "Start guest: cr3 %p rip %p\n", vm_tf->tf_cr3, entry);
        start_guest_thread(vm->gths[0]);
 
        uthread_sleep_forever();
index 3866189..00d632d 100644 (file)
@@ -161,4 +161,24 @@ static inline void restore_fp_state(struct ancillary_state *silly)
        }
 }
 
+/* Cpuid helper function originally from Barret's fputest. */
+static inline void parlib_cpuid(uint32_t level1, uint32_t level2,
+                                uint32_t *eaxp, uint32_t *ebxp,
+                                uint32_t *ecxp, uint32_t *edxp)
+{
+       uint32_t eax, ebx, ecx, edx;
+
+       asm volatile("cpuid"
+                    : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
+                    : "a"(level1), "c"(level2));
+       if (eaxp)
+               *eaxp = eax;
+       if (ebxp)
+               *ebxp = ebx;
+       if (ecxp)
+               *ecxp = ecx;
+       if (edxp)
+               *edxp = edx;
+}
+
 __END_DECLS
index 1088e73..a6c1ff3 100644 (file)
@@ -67,14 +67,17 @@ struct virtual_machine {
         * default page tables we use this range. Note that even if the "physical"
         * memory has holes, we'll create PTEs for it. This seems enough for now but
         * we shall see. */
-       uintptr_t                   minphys;
-       uintptr_t                   maxphys;
+       uintptr_t                                       minphys;
+       uintptr_t                                       maxphys;
 
        /* Default root pointer to use if one is not set in a
         * guest thread. We expect this to be the common case,
         * where all guests share a page table. It's not required
-        * however. */
+        * however. setup_paging now updates this to point to the initial set of
+        * page tables for the guest. */
        void                                            *root;
+       /* lock to control access to root */
+       uth_mutex_t                                     root_mtx;
 
        /* Default value for whether guest threads halt on an exit. */
        bool                                            halt_exit;
@@ -134,7 +137,9 @@ void mmap_memory(struct virtual_machine *vm, uintptr_t memstart,
                  size_t memsize);
 bool mmap_file(const char *path, uintptr_t memstart, uintptr_t memsize,
                uint64_t protections, uint64_t offset);
-void *setup_paging(struct virtual_machine *vm, bool debug);
+void add_pte_entries(struct virtual_machine *vm, uintptr_t start,
+                     uintptr_t end);
+void setup_paging(struct virtual_machine *vm);
 void *setup_biostables(struct virtual_machine *vm,
                        void *a, void *smbiostable);
 void *populate_stack(uintptr_t *stack, int argc, char *argv[],
index 023511d..ac486ba 100644 (file)
 #include <sys/mman.h>
 #include <ros/arch/mmu.h>
 #include <vmm/vmm.h>
+#include <vmm/util.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <parlib/uthread.h>
+#include <parlib/arch/arch.h>
 
-typedef struct {
-       uint64_t pte[512];
-} ptp;
+static bool debug;
 
-void *setup_paging(struct virtual_machine *vm, bool debug)
+struct ptp {
+       uintptr_t pte[NPTENTRIES];
+};
+
+#define PAGE_RESOLUTION PML3_PTE_REACH
+
+/* We put the page tables after 4Gb, where it exactly is doesn't matter as long
+ * as it's accessible by the guest. */
+#define PAGE_TABLE_ROOT_START 0x100000000
+
+static void check_jumbo_pages(void *arg)
+{
+       uint32_t edx;
+
+       parlib_cpuid(0x80000001, 0x0, NULL, NULL, NULL, &edx);
+       if (!(edx & (1 << 26)))
+               panic("1 GB Jumbo Pages are not supported on this hardware!");
+}
+
+/*
+ * This function assumes that after the p512 page table, there is memory mapped
+ * (though not necessarily populated) for each PML3 page table. This assumes
+ * a total of 2M + 4K memory mapped. PML3 table n is located at 4K*(n+1) from
+ * the start of the p512 table.
+ * This function does a 1:1 mapping of va to pa. vm->root must be set
+ * */
+void add_pte_entries(struct virtual_machine *vm, uintptr_t start, uintptr_t end)
 {
-       ptp *p512, *p1, *p2m;
-       int nptp, npml4, npml3, npml2;
-       uintptr_t memstart = vm->minphys;
-       size_t memsize = vm->maxphys - vm->minphys + 1;
-
-       /* This test is redundant when booting kernels, as it is also
-        * performed in memory(), but not all users call that function,
-        * hence we must do it here too. */
-       checkmemaligned(memstart, memsize);
-
-       /* How many page table pages do we need?  We conservatively
-        * assume that we are in low memory, and hence assume a
-        * 0-based range.  Note that in many cases, kernels will
-        * immediately set up their own map. But for "dune" like
-        * applications, it's necessary. Note also that in most cases,
-        * the total number of pages will be < 16 or so. */
-       npml4 = DIV_ROUND_UP(memstart + memsize, PML4_REACH);
-       nptp = npml4;
-
-       npml3 = DIV_ROUND_UP(memstart + memsize, PML3_REACH);
-       nptp += npml3;
-
-       /* and 1 for each 2 MiB of memory */
-       npml2 = DIV_ROUND_UP(memstart + memsize, PML2_REACH);
-       nptp += npml2;
-
-       fprintf(stderr,
-               "Memstart is %llx, memsize is %llx,"
-               "memstart + memsize is %llx; \n",
-               memstart, memsize, memstart + memsize);
-       fprintf(stderr, "\t%d pml4 %d pml3 %d pml2\n",
-               npml4, npml3, npml2);
-
-       /* Place these page tables right after VM memory. We
-        * used to use posix_memalign but that puts them
-        * outside EPT-accessible space on some CPUs. */
-       p512 = mmap((void *)memstart + memsize, nptp * 4096, PROT_READ | PROT_WRITE,
-                    MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
-       if (p512 == MAP_FAILED) {
-               perror("page table page alloc");
-               exit(1);
+       struct ptp *p512;
+       uintptr_t cur_page, aligned_start, aligned_end, pml4, pml3;
+       static parlib_once_t once = PARLIB_ONCE_INIT;
+
+       /* We check once if we can use 1Gb pages and die if we can't. */
+       parlib_run_once(&once, check_jumbo_pages, NULL);
+
+       uth_mutex_lock(&vm->root_mtx);
+       p512 = vm->root;
+       if (!p512)
+               panic("vm->root page table pointer was not set!");
+
+       /* We align the start down and the end up to make sure we cover the full
+        * area. */
+       aligned_start = ALIGN_DOWN(start, PAGE_RESOLUTION);
+       aligned_end = ALIGN(end, PAGE_RESOLUTION);
+
+       cur_page = aligned_start;
+       /* We always do end-1 because end from /proc/self/maps is not inclusive */
+       for (pml4 = PML4(start); pml4 <= PML4(end - 1); pml4++) {
+               struct ptp *p1 = p512 + pml4 + 1;
+
+               /* Create the PML4 entry. Rather than check, I just overwrite it. */
+               p512->pte[pml4] = (uintptr_t) p1 | PTE_KERN_RW;
+
+               for (pml3 = PML3(cur_page); pml3 < NPTENTRIES &&
+                    cur_page < aligned_end; pml3++, cur_page += PML3_PTE_REACH) {
+
+                       /* Create the PML3 entry. */
+                       p1->pte[pml3] = cur_page | PTE_KERN_RW | PTE_PS;
+               }
        }
-       p1 = &p512[npml4];
-       p2m = &p1[npml3];
-
-       /* Set up a 1:1 ("identity") page mapping from guest virtual
-        * to guest physical using the (host virtual)
-        * `kerneladdress`. This mapping may be used for only a short
-        * time, until the guest sets up its own page tables. Be aware
-        * that the values stored in the table are physical addresses.
-        * This is subtle and mistakes are easily disguised due to the
-        * identity mapping, so take care when manipulating these
-        * mappings. */
-
-       p2m->pte[PML2(0)] = (uint64_t)0 | PTE_KERN_RW | PTE_PS;
-
-       fprintf(stderr, "Map %p for %zu bytes\n", memstart, memsize);
-       for (uintptr_t p4 = memstart, i4 = PML4(p4);
-                p4 < memstart + memsize && i4 < NPTENTRIES;
-            p4 = ROUNDUP(p4 + 1, PML4_PTE_REACH), p1++, i4++) {
-               p512->pte[PML4(p4)] = (uint64_t)p1 | PTE_KERN_RW;
+       uth_mutex_unlock(&vm->root_mtx);
+}
+
+/* This function sets up the default page tables for the guest. It parses
+ * /proc/self/maps to figure out what pages are mapped for the uthread, and
+ * sets up a 1:1 mapping for the vm guest. This function can be called
+ * multiple times after startup to update the page tables, though regular
+ * vmms should call add_pte_entries if they mmap something for the guest after
+ * calling setup_paging to avoid having to parse /proc/self/maps again. */
+void setup_paging(struct virtual_machine *vm)
+{
+       FILE *maps;
+       char *line = NULL;
+       size_t line_sz;
+       char *strtok_save;
+       char *p;
+       uintptr_t first, second;
+
+       /* How many page table pages do we need?
+        * If we create 1G PTEs for the whole space, it just takes 2M + 4k worth of
+        * memory. Perhaps we should just identity map the whole space upfront.
+        * Right now we don't MAP_POPULATE because we don't expect all the PTEs
+        * to be used. */
+       if (!vm->root)
+               vm->root = mmap((void *)PAGE_TABLE_ROOT_START, 0x201000,
+                               PROT_READ | PROT_WRITE,
+                               MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+
+       if (vm->root == MAP_FAILED || (uintptr_t)vm->root >= BRK_START)
+               panic("page table page alloc");
+
+       /* We parse /proc/self/maps to figure out the currently mapped memory.
+        * This way all the memory that's available to the HR3 process is also
+        * reflected in the page tables. Our smallest PTEs here are 1Gb so there
+        * may be memory locations described in the page tables that are not
+        * mapped though. /proc/self/maps parsing code courtesy of Barret. */
+       maps = fopen("/proc/self/maps", "r");
+       if (!maps)
+               panic("unable to open /proc/self/maps");
+
+       /* returns -1 on error or EOF. */
+       while (getline(&line, &line_sz, maps) >= 0) {
                if (debug)
-                       fprintf(stderr, "l4@%p: %p set index 0x%x to 0x%llx\n",
-                                       &p512->pte[PML4(p4)],
-                                       p4, PML4(p4), p512->pte[PML4(p4)]);
-               for (uintptr_t p3 = p4, i3 = PML3(p3);
-                        p3 < memstart + memsize && i3 < NPTENTRIES;
-                    p3 = ROUNDUP(p3 + 1, PML3_PTE_REACH), p2m++, i3++) {
-                       p1->pte[PML3(p3)] = (uint64_t)p2m | PTE_KERN_RW;
-                       if (debug)
-                               fprintf(stderr, "\tl3@%p: %p set index 0x%x to 0x%llx\n",
-                                               &p1->pte[PML3(p3)],
-                                               p3, PML3(p3), p1->pte[PML3(p3)]);
-                       for (uintptr_t p2 = p3, i2 = PML2(p2);
-                                p2 < memstart + memsize && i2 < NPTENTRIES;
-                            p2 += PML2_PTE_REACH, i2++) {
-                               p2m->pte[PML2(p2)] = (uint64_t)p2 | PTE_KERN_RW | PTE_PS;
-                               if (debug)
-                                       fprintf(stderr, "\t\tl2@%p: %p set index 0x%x to 0x%llx\n",
-                                                       &p2m->pte[PML2(p2)],
-                                                       p2, PML2(p2), p2m->pte[PML2(p2)]);
-                       }
-               }
+                       fprintf(stderr, "Got line %s", line);
 
-       }
+               p = strchr(line, ' ');
+               /* No space, probably an error */
+               if (!p)
+                       continue;
+               *p = '\0';
+               p = strtok_r(line, "-", &strtok_save);
+               /* No first element! */
+               if (!p)
+                       continue;
+               first = strtoul(p, NULL, 16);
+               p = strtok_r(NULL, "-", &strtok_save);
+               /* No second element! */
+               if (!p)
+                       continue;
+               second = strtoul(p, NULL, 16);
 
-       return (void *)p512;
+               if (debug)
+                       printf("first %p, second %p\n\n", first, second);
+
+               add_pte_entries(vm, first, second);
+
+       }
+       free(line);
+       fclose(maps);
 }
index 105e32d..d6c33dc 100644 (file)
@@ -27,7 +27,6 @@ static void *page(void *addr, int count)
  * and vthread_attr_kernel_init. */
 static int vmsetup(struct virtual_machine *vm, int flags)
 {
-       unsigned long long *p512, *p1;
        struct vm_trapframe *vm_tf;
        int i, ret;
        uint8_t *p;
@@ -40,51 +39,6 @@ static int vmsetup(struct virtual_machine *vm, int flags)
 
        vm->gpcis = calloc(vm->nr_gpcs, sizeof(*vm->gpcis));
 
-       /* Set up default page mappings. The common case,
-        * for user VM threads and kernel VM threads, is that
-        * they need some kind of initial page tables. The kernels
-        * will almost always throw them away; the user VM threads
-        * will almost always continue to use them. Using two
-        * pages and setting up an initial page table is
-        * cheap and makes users lives easier. This initial
-        * page table can grow to 512 GiB, which should be enough
-        * for now.
-        *
-        * At the same time, we allow users to select other
-        * arrangements if they wish.  Here's a simple example: is it
-        * possible someone will want a different guest page table for
-        * every guest? Yes.
-        *
-        * We lock the page table to 0x1000000 for now. We can't just
-        * let it pick anything as it may pick something the guest
-        * can't address (i.e. outside EPT range). */
-
-       /* Allocate 2 pages for page table pages: a page of
-        * 512 GiB PTEs with only one entry filled to point to
-        * a page of 1 GiB PTEs; a page of 1 GiB PTEs with
-        * only one entry filled. */
-
-       p512 = page((void *)0x1000000, 2);
-       if (!p512) {
-               werrstr("page table allocation failed: %r\n");
-               return -1;
-       }
-       p1 = &p512[512];
-       vm->root = p512;
-
-       /* Set up a 1:1 ("identity") page mapping from host
-        * virtual to guest physical for 1 GiB.  This mapping
-        * is used unless the guest (e.g. Linux) sets up its
-        * own page tables. Be aware that the values stored in
-        * the table are physical addresses.  This is subtle
-        * and mistakes are easily disguised due to the
-        * identity mapping, so take care when manipulating
-        * these mappings. Note: we don't yet have symbols for
-        * "start of virtual address common to host and guest"
-        * so we just use  the first GiB for now. */
-       p512[PML4(0x400000)] = (uint64_t)p1 | PTE_KERN_RW;
-       p1[PML3(0x400000)] = PTE_PS | PTE_KERN_RW;
-
        /* technically, we don't need these pages for the
         * all guests. Currently, the kernel requires them. */
        for (i = 0; i < vm->nr_gpcs; i++) {
@@ -98,6 +52,9 @@ static int vmsetup(struct virtual_machine *vm, int flags)
                vm->gpcis[i].apic_addr = &p[8192];
        }
 
+       /* Set up default page mappings. */
+       setup_paging(vm);
+
        ret = vmm_init(vm, flags);
        if (ret)
                return ret;
@@ -105,7 +62,7 @@ static int vmsetup(struct virtual_machine *vm, int flags)
        for (i = 0; i < vm->nr_gpcs; i++) {
                vm->gths[i]->halt_exit = vm->halt_exit;
                vm_tf = gth_to_vmtf(vm->gths[i]);
-               vm_tf->tf_cr3 = (uint64_t)p512;
+               vm_tf->tf_cr3 = (uint64_t) vm->root;
        }
        vm->vminit = 1;