vmap: Make kernel intermediate mappings permanent
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 28 Nov 2016 03:19:22 +0000 (22:19 -0500)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 29 Nov 2016 16:27:40 +0000 (11:27 -0500)
There are two parts to this.

1) All kernel mappings, including all potential mappings (e.g. the vmap
dynamic range) have PML4 PTEs that point to allocated PML3s (which can
be all zero).

2) No kernel intermediate mapping will ever be removed.  Once a PT
exists for a kernel mapping, that PT will exist forever, even if all of
its mappings are removed.

Now we can safely copy the boot_pgdir's kernel PML4 PTEs to processes'
pgdirs and dynamically update the mappings without modifying the pgdirs
of each process.  The outer mapping is the same for all pgdirs, and it
never changes.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/arch/riscv/pmap.c
kern/arch/x86/pmap64.c
kern/include/pmap.h
kern/src/mm.c

index 7191b4e..48bd962 100644 (file)
@@ -133,3 +133,10 @@ int arch_max_jumbo_page_shift(void)
        #warning "What jumbo page sizes does RISC support?"
        return PGSHIFT;
 }
+
+#warning "Not sure where you do your PT destruction.  Be sure to not unmap any intermediate page tables for kernel mappings.  At least not the PML(n-1) maps"
+
+void arch_add_intermediate_pts(pgdir_t pgdir, uintptr_t va, size_t len)
+{
+       #error "Implement me"
+}
index ff71d7b..6531e7c 100644 (file)
@@ -352,9 +352,9 @@ int pml_for_each(kpte_t *pml, uintptr_t start, size_t len, kpte_cb_t callback,
        return __pml_for_each(pml, start, len, callback, arg, PML4_SHIFT);
 }
 
-/* Unmaps [va, va + size) from pgdir, freeing any intermediate page tables.
- * This does not free the actual memory pointed to by the page tables, nor does
- * it flush the TLB. */
+/* Unmaps [va, va + size) from pgdir, freeing any intermediate page tables for
+ * non-kernel mappings.  This does not free the actual memory pointed to by the
+ * page tables, nor does it flush the TLB. */
 int unmap_segment(pgdir_t pgdir, uintptr_t va, size_t size)
 {
        int pt_free_cb(kpte_t *kpte, uintptr_t kva, int shift, bool visited_subs,
@@ -366,6 +366,11 @@ int unmap_segment(pgdir_t pgdir, uintptr_t va, size_t size)
                        pte_clear(kpte);
                        return 0;
                }
+               /* Never remove intermediate pages for any kernel mappings.  This is
+                * also important for x86 so that we don't accidentally free any of the
+                * boot PMLs, which aren't two-page alloc'd from kpages_arena. */
+               if (kva >= ULIM)
+                       return 0;
                /* If we haven't visited all of our subs, we might still have some
                 * mappings hanging off this page table. */
                if (!visited_subs) {
@@ -380,8 +385,6 @@ int unmap_segment(pgdir_t pgdir, uintptr_t va, size_t size)
                pte_clear(kpte);
                return 0;
        }
-       /* Don't accidentally unmap the boot mappings */
-       assert((va < KERNBASE) && (va + size < KERNBASE));
        return pml_for_each(pgdir_get_kpt(pgdir), va, size, pt_free_cb, 0);
 }
 
@@ -678,6 +681,45 @@ int arch_max_jumbo_page_shift(void)
        return edx & (1 << 26) ? PML3_SHIFT : PML2_SHIFT;
 }
 
+/* Adds empty intermediate PTs to the top-most PML in pgdir for the given range.
+ * On a 4-PML system, this will add entries to PML4, consisting of a bunch of
+ * empty PML3s, such that [va, va+len) has intermediate tables in pgdir.
+ *
+ * A few related notes:
+ *
+ * The boot_pgdir is where we do the original kernel mappings.  All of the PML4
+ * entries are filled in, pointing to intermediate PML3s.  All other pgdirs copy
+ * the kernel mapping, which means they have the same content.  That content
+ * never changes at runtime.  What changes is the contents of the PML3s and
+ * below, which are pointed to by all pgdirs.
+ *
+ * The proc pgdirs do not have KPT or EPT mappings above ULIM, so if the
+ * intermediate PTs have EPT entries, it's just a waste of memory, but not a
+ * mapping the user could exploit.
+ *
+ * On occasion, there might be code that maps things into boot_pgdir below ULIM,
+ * though right now this is just an out-of-branch "mmap a page at 0" debugging
+ * hack. */
+void arch_add_intermediate_pts(pgdir_t pgdir, uintptr_t va, size_t len)
+{
+       kpte_t *pml4 = pgdir_get_kpt(pgdir);
+       kpte_t *kpte;
+       epte_t *epte;
+       void *new_pml_kva;
+
+       for (size_t i = 0; i < len; i += PML4_PTE_REACH, va += PML4_PTE_REACH) {
+               kpte = &pml4[PML4(va)];
+               epte = kpte_to_epte(kpte);
+               if (kpte_is_present(kpte))
+                       continue;
+               new_pml_kva = kpages_zalloc(2 * PGSIZE, MEM_WAIT);
+               /* We insert the same as for __pml_walk. */
+               *kpte = PADDR(new_pml_kva) | PTE_P | PTE_U | PTE_W;
+               if (va < ULIM)
+                       *epte = (PADDR(new_pml_kva) + PGSIZE) | EPTE_R | EPTE_X | EPTE_W;
+       }
+}
+
 /* Debugging */
 static int print_pte(kpte_t *kpte, uintptr_t kva, int shift, bool visited_subs,
                      void *data)
index 73cc995..6504d02 100644 (file)
@@ -102,6 +102,7 @@ int arch_pgdir_setup(pgdir_t boot_copy, pgdir_t *new_pd);
 physaddr_t arch_pgdir_get_cr3(pgdir_t pd);
 void arch_pgdir_clear(pgdir_t *pd);
 int arch_max_jumbo_page_shift(void);
+void arch_add_intermediate_pts(pgdir_t pgdir, uintptr_t va, size_t len);
 
 static inline page_t *ppn2page(size_t ppn)
 {
index c10bf34..cb896e2 100644 (file)
@@ -26,7 +26,6 @@
 #include <smp.h>
 #include <profiler.h>
 #include <umem.h>
-#include <init.h>
 
 struct kmem_cache *vmr_kcache;
 
@@ -1230,6 +1229,12 @@ void vmap_init(void)
                                  vmap_addr_arena, 0, MEM_WAIT);
        vmap_to_free = kmalloc(sizeof(struct vmap_free_tracker) * VMAP_MAX_TO_FREE,
                               MEM_WAIT);
+       /* This ensures the boot_pgdir's top-most PML (PML4) has entries pointing to
+        * PML3s that cover the dynamic mapping range.  Now, it's safe to create
+        * processes that copy from boot_pgdir and still dynamically change the
+        * kernel mappings. */
+       arch_add_intermediate_pts(boot_pgdir, KERN_DYN_BOT,
+                                 KERN_DYN_TOP - KERN_DYN_BOT);
 }
 
 uintptr_t get_vmap_segment(size_t nr_bytes)
@@ -1258,12 +1263,6 @@ void put_vmap_segment(uintptr_t vaddr, size_t nr_bytes)
 int map_vmap_segment(uintptr_t vaddr, uintptr_t paddr, unsigned long num_pages,
                      int perm)
 {
-       /* For now, we only handle the root pgdir, and not any of the other ones
-        * (like for processes).  To do so, we'll need to insert into every pgdir,
-        * and send tlb shootdowns to those that are active (which we don't track
-        * yet). */
-       assert(booting);
-
        /* TODO: (MM) you should lock on boot pgdir modifications.  A vm region lock
         * isn't enough, since there might be a race on outer levels of page tables.
         * For now, we'll just use the vmap_lock (which technically works). */