alarm: Do not hold the tchain lock during handlers
[akaros.git] / kern / src / pmap.c
index 3d944e5..9935287 100644 (file)
-/* See COPYRIGHT for copyright information. */
-#ifdef __DEPUTY__
-#pragma nodeputy
-#endif
+/* Copyright (c) 2009,13 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Arch independent physical memory and page table management.
+ *
+ * For page allocation, check out the family of page_alloc files. */
 
-#include <arch/x86.h>
+#include <arch/arch.h>
 #include <arch/mmu.h>
-#include <arch/apic.h>
 
-#include <ros/error.h>
+#include <error.h>
 
+#include <kmalloc.h>
 #include <atomic.h>
 #include <string.h>
 #include <assert.h>
 #include <pmap.h>
-#include <kclock.h>
-#include <env.h>
-
-// These variables are set by i386_detect_memory()
-static physaddr_t maxpa;       // Maximum physical address
-static physaddr_t maxaddrpa;   // Maximum directly addressable physical address
-size_t npage;                  // Amount of physical memory (in pages)
-size_t naddrpage;                      // Amount of addressable physical memory (in pages)
-static size_t basemem;         // Amount of base memory (in bytes)
-static size_t extmem;          // Amount of extended memory (in bytes)
-
-// These variables are set in i386_vm_init()
-pde_t* boot_pgdir;             // Virtual address of boot time page directory
-physaddr_t boot_cr3;           // Physical address of boot time page directory
-static char* boot_freemem;     // Pointer to next byte of free mem
-
-page_t *pages;         // Virtual address of physical page array
-static page_list_t page_free_list;     // Free list of physical pages
-
-extern env_t *envs;
-
-// Global descriptor table.
-//
-// The kernel and user segments are identical (except for the DPL).
-// To load the SS register, the CPL must equal the DPL.  Thus,
-// we must duplicate the segments for the user and the kernel.
-//
-segdesc_t gdt[] =
-{
-       // 0x0 - unused (always faults -- for trapping NULL far pointers)
-       SEG_NULL,
-
-       // 0x8 - kernel code segment
-       [GD_KT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 0),
-
-       // 0x10 - kernel data segment
-       [GD_KD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 0),
-
-       // 0x18 - user code segment
-       [GD_UT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 3),
-
-       // 0x20 - user data segment
-       [GD_UD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 3),
-
-       // 0x28 - tss, initialized in idt_init()
-       [GD_TSS >> 3] = SEG_NULL
-};
-
-pseudodesc_t gdt_pd = {
-       sizeof(gdt) - 1, (unsigned long) gdt
-};
-
-static int
-nvram_read(int r)
-{
-       return mc146818_read(r) | (mc146818_read(r + 1) << 8);
-}
-
-void i386_print_memory_map(multiboot_info_t *mbi) {
-       const char* memory_type[] = {"", "FREE", "RESERVED", "UNDEFINED", "UNDEFINED4"};
-
-
-       if(CHECK_FLAG(mbi->flags, 6)) {
-               cprintf ("mmap_addr = 0x%x, mmap_length = 0x%x\n", (unsigned long)mbi->mmap_addr,
-                                                                  (unsigned long)mbi->mmap_length);
-               
-               memory_map_t* mmap = (memory_map_t*) ((uint32_t)mbi->mmap_addr + KERNBASE);
-               while((uint32_t)mmap < ((uint32_t)mbi->mmap_addr + KERNBASE) + mbi->mmap_length) {                      
-                       cprintf ("base = 0x%08x%08x, length = 0x%08x%08x, type = %s\n",
-                               (unsigned) mmap->base_addr_high,
-                               (unsigned) mmap->base_addr_low,
-                               (unsigned) mmap->length_high,
-                               (unsigned) mmap->length_low,
-                               (unsigned) memory_type[mmap->type]);
-                       mmap = (memory_map_t*) ((uint32_t) mmap + mmap->size + sizeof (mmap->size));
-               }
-       }
-}
-
-void i386_detect_memory(multiboot_info_t *mbi)
+#include <process.h>
+#include <stdio.h>
+#include <mm.h>
+#include <multiboot.h>
+#include <arena.h>
+#include <init.h>
+
+physaddr_t max_pmem = 0;       /* Total amount of physical memory (bytes) */
+physaddr_t max_paddr = 0;      /* Maximum addressable physical address */
+size_t max_nr_pages = 0;       /* Number of addressable physical memory pages */
+struct page *pages = 0;
+struct multiboot_info *multiboot_kaddr = 0;
+uintptr_t boot_freemem = 0;
+uintptr_t boot_freelimit = 0;
+
+static size_t sizeof_mboot_mmentry(struct multiboot_mmap_entry *entry)
 {
-       // Tells us how many kilobytes there are
-       basemem = ROUNDDOWN(mbi->mem_lower*1024, PGSIZE);
-       extmem = ROUNDDOWN(mbi->mem_upper*1024, PGSIZE);
-
-       // Calculate the maximum physical address based on whether
-       // or not there is any extended memory.  See comment in <inc/memlayout.h>
-       if (extmem)
-               maxpa = EXTPHYSMEM + extmem;
-       else
-               maxpa = basemem;
-
-       npage = maxpa / PGSIZE;
-       // IOAPIC - KERNBASE is the max amount of virtual addresses we can use
-       // for the physical memory mapping (aka - the KERNBASE mapping)
-       maxaddrpa = MIN(maxpa, IOAPIC_BASE - KERNBASE);
-       naddrpage = maxaddrpa / PGSIZE;
-
-       cprintf("Physical memory: %dK available, ", (int)(maxpa/1024));
-       cprintf("base = %dK, extended = %dK\n", (int)(basemem/1024), (int)(extmem/1024));
-       printk("Maximum directly addressable physical memory: %dK\n", (int)(maxaddrpa/1024));
+       /* Careful - len is a uint64 (need to cast down for 32 bit) */
+       return (size_t)(entry->len);
 }
 
-bool enable_pse(void)
+static void adjust_max_pmem(struct multiboot_mmap_entry *entry, void *data)
 {
-       uint32_t edx, cr4;
-       cpuid(1, 0, 0, 0, &edx);
-       if (edx & CPUID_PSE_SUPPORT) {
-               cr4 = rcr4();
-               cr4 |= CR4_PSE;
-               lcr4(cr4);
-               return 1;
-       } else
-               return 0;
-}
-
-// --------------------------------------------------------------
-// Set up initial memory mappings and turn on MMU.
-// --------------------------------------------------------------
-
-static void check_boot_pgdir(bool pse);
-
-//
-// Allocate n bytes of physical memory aligned on an 
-// align-byte boundary.  Align must be a power of two.
-// Return kernel virtual address.  Returned memory is uninitialized.
-//
-// If we're out of memory, boot_alloc should panic.
-// This function may ONLY be used during initialization,
-// before the page_free_list has been set up.
-// 
-static void*
-boot_alloc(uint32_t n, uint32_t align)
-{
-       extern char end[];
-       void *v;
-
-       // Initialize boot_freemem if this is the first time.
-       // 'end' is a magic symbol automatically generated by the linker,
-       // which points to the end of the kernel's bss segment -
-       // i.e., the first virtual address that the linker
-       // did _not_ assign to any kernel code or global variables.
-       if (boot_freemem == 0)
-               boot_freemem = end;
-
-       //      Step 1: round boot_freemem up to be aligned properly
-       boot_freemem = ROUNDUP(boot_freemem, align);
-
-       //      Step 2: save current value of boot_freemem as allocated chunk
-       v = boot_freemem;
-       //  Step 2.5: check if we can alloc
-       if (PADDR(boot_freemem + n) > maxaddrpa)
-               panic("Out of memory in boot alloc, you fool!\n");
-       //      Step 3: increase boot_freemem to record allocation
-       boot_freemem += n;      
-       //      Step 4: return allocated chunk
-       return v;
+       if (entry->type != MULTIBOOT_MEMORY_AVAILABLE)
+               return;
+       /* Careful - addr + len is a uint64 (need to cast down for 32 bit) */
+       max_pmem = MAX(max_pmem, (size_t)(entry->addr + entry->len));
 }
 
-//
-// Given pgdir, a pointer to a page directory,
-// walk the 2-level page table structure to find
-// the page table entry (PTE) for linear address la.
-// Return a pointer to this PTE.
-//
-// If the relevant page table doesn't exist in the page directory:
-//     - If create == 0, return 0.
-//     - Otherwise allocate a new page table, install it into pgdir,
-//       and return a pointer into it.
-//        (Questions: What data should the new page table contain?
-//       And what permissions should the new pgdir entry have?
-//       Note that we use the 486-only "WP" feature of %cr0, which
-//       affects the way supervisor-mode writes are checked.)
-//
-// This function abstracts away the 2-level nature of
-// the page directory by allocating new page tables
-// as needed.
-// 
-// boot_pgdir_walk may ONLY be used during initialization,
-// before the page_free_list has been set up.
-// It should panic on failure.  (Note that boot_alloc already panics
-// on failure.)
-//
-// Supports returning jumbo (4MB PSE) PTEs.  To create with a jumbo, pass in 2.
-// 
-// Maps non-PSE PDEs as U/W.  W so the kernel can, U so the user can read via
-// UVPT.  UVPT security comes from the UVPT mapping (U/R).  All other kernel pages
-// protected at the second layer
-static pte_t*
-boot_pgdir_walk(pde_t *pgdir, uintptr_t la, int create)
+static void kpages_arena_init(void)
 {
-       pde_t* the_pde = &pgdir[PDX(la)];
-       void* new_table;
+       void *kpages_pg;
 
-       if (*the_pde & PTE_P) {
-               if (*the_pde & PTE_PS)
-                       return (pte_t*)the_pde;
-               return &((pde_t*)KADDR(PTE_ADDR(*the_pde)))[PTX(la)];
-       }
-       if (!create)
-               return NULL;
-       if (create == 2) {
-               if (JPGOFF(la))
-                       panic("Attempting to find a Jumbo PTE at an unaligned VA!");
-               *the_pde = PTE_PS | PTE_P;
-               return (pte_t*)the_pde;
-       }
-       new_table = boot_alloc(PGSIZE, PGSIZE);
-       memset(new_table, 0, PGSIZE);
-       *the_pde = (pde_t)PADDR(new_table) | PTE_P | PTE_W | PTE_U | PTE_G;
-       return &((pde_t*)KADDR(PTE_ADDR(*the_pde)))[PTX(la)];
+       kpages_pg = arena_alloc(base_arena, PGSIZE, MEM_WAIT);
+       kpages_arena = arena_builder(kpages_pg, "kpages", PGSIZE, arena_alloc,
+                                    arena_free, base_arena, 8 * PGSIZE);
 }
 
-//
-// Map [la, la+size) of linear address space to physical [pa, pa+size)
-// in the page table rooted at pgdir.  Size is a multiple of PGSIZE.
-// Use permission bits perm|PTE_P for the entries.
-//
-// This function may ONLY be used during initialization,
-// before the page_free_list has been set up.
-//
-// To map with Jumbos, set PTE_PS in perm
-static void
-boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, physaddr_t pa, int perm)
+/**
+ * @brief Initializes physical memory.  Determines the pmem layout, sets up the
+ * base and kpages arenas, and turns on virtual memory/page tables.
+ *
+ * Regarding max_pmem vs max_paddr and max_nr_pages: max_pmem is the largest
+ * physical address that is in a FREE region.  It includes RESERVED regions that
+ * are below this point.  max_paddr is the largest physical address, <=
+ * max_pmem, that the KERNBASE mapping can map.  It too may include reserved
+ * ranges.  The 'pages' array will track all physical pages up to max_paddr.
+ * There are max_nr_pages of them.  On 64 bit systems, max_pmem == max_paddr. */
+void pmem_init(struct multiboot_info *mbi)
 {
-       uintptr_t i;
-       pte_t *pte;
-       // la can be page unaligned, but weird things will happen
-       // unless pa has the same offset.  pa always truncates any
-       // possible offset.  will warn.  size can be weird too. 
-       if (PGOFF(la)) {
-               warn("la not page aligned in boot_map_segment!");
-               size += PGOFF(la);
-       }
-       if (perm & PTE_PS) {
-               if (JPGOFF(la) || JPGOFF(pa))
-                       panic("Tried to map a Jumbo page at an unaligned address!");
-               // need to index with i instead of la + size, in case of wrap-around
-               for (i = 0; i < size; i += JPGSIZE, la += JPGSIZE, pa += JPGSIZE) {
-                       pte = boot_pgdir_walk(pgdir, la, 2);
-                       *pte = PTE_ADDR(pa) | PTE_P | perm;
-               }
-       } else {
-               for (i = 0; i < size; i += PGSIZE, la += PGSIZE, pa += PGSIZE) {
-                       pte = boot_pgdir_walk(pgdir, la, 1);
-                       if (*pte & PTE_PS)
-                               // if we start using the extra flag for PAT, which we aren't,
-                               // this will warn, since PTE_PS and PTE_PAT are the same....
-                               warn("Possibly attempting to map a regular page into a Jumbo PDE");
-                       *pte = PTE_ADDR(pa) | PTE_P | perm;
-               }
-       }
+       mboot_detect_memory(mbi);
+       mboot_print_mmap(mbi);
+       /* adjust the max memory based on the mmaps, since the old detection doesn't
+        * help much on 64 bit systems */
+       mboot_foreach_mmap(mbi, adjust_max_pmem, 0);
+       /* KERN_VMAP_TOP - KERNBASE is the max amount of virtual addresses we can
+        * use for the physical memory mapping (aka - the KERNBASE mapping).
+        * Should't be an issue on 64b, but is usually for 32 bit. */
+       max_paddr = MIN(max_pmem, KERN_VMAP_TOP - KERNBASE);
+       /* Note not all of this memory is free. */
+       max_nr_pages = max_paddr / PGSIZE;
+       printk("Max physical RAM (appx, bytes): %lu\n", max_pmem);
+       printk("Max addressable physical RAM (appx): %lu\n", max_paddr);
+       printk("Highest page number (including reserved): %lu\n", max_nr_pages);
+       /* We should init the page structs, but zeroing happens to work, except for
+        * the sems.  Those are init'd by the page cache before they are used. */
+       pages = (struct page*)boot_zalloc(max_nr_pages * sizeof(struct page),
+                                         PGSIZE);
+       base_arena_init(mbi);
+       /* kpages will use some of the basic slab caches.  kmem_cache_init needs to
+        * not do memory allocations (which it doesn't, and it can base_alloc()). */
+       kmem_cache_init();
+       kpages_arena_init();
+       printk("Base arena total mem: %lu\n", arena_amt_total(base_arena));
+       vm_init();
+
+       static_assert(PROCINFO_NUM_PAGES*PGSIZE <= PTSIZE);
+       static_assert(PROCDATA_NUM_PAGES*PGSIZE <= PTSIZE);
 }
 
-// could consider having an API to allow these to dynamically change
-// MTRRs are for physical, static ranges.  PAT are linear, more granular, and 
-// more dynamic
-void setup_default_mtrrs(barrier_t* smp_barrier)
+static void set_largest_freezone(struct multiboot_mmap_entry *entry, void *data)
 {
-       // disable interrupts
-       int8_t state = 0;
-       disable_irqsave(&state);
-       // barrier - if we're meant to do this for all cores, we'll be 
-       // passed a pointer to an initialized barrier
-       if (smp_barrier)
-               waiton_barrier(smp_barrier);
-       
-       // disable caching      cr0: set CD and clear NW
-       lcr0((rcr0() | CR0_CD) & ~CR0_NW);
-       // flush caches
-       cache_flush();
-       // flush tlb
-       tlb_flush_global();
-       // disable MTRRs, and sets default type to WB (06)
-       write_msr(IA32_MTRR_DEF_TYPE, 0x00000006);
-
-       // Now we can actually safely adjust the MTRRs
-       // MTRR for IO Holes (note these are 64 bit values we are writing)
-       // 0x000a0000 - 0x000c0000 : VGA - WC 0x01
-       write_msr(IA32_MTRR_PHYSBASE0, PTE_ADDR(VGAPHYSMEM) | 0x01);
-       // if we need to have a full 64bit val, use the UINT64 macro
-       write_msr(IA32_MTRR_PHYSMASK0, 0x0000000ffffe0800);
-       // 0x000c0000 - 0x00100000 : IO devices (and ROM BIOS) - UC 0x00
-       write_msr(IA32_MTRR_PHYSBASE1, PTE_ADDR(DEVPHYSMEM) | 0x00);
-       write_msr(IA32_MTRR_PHYSMASK1, 0x0000000ffffc0800);
-       // APIC/IOAPIC holes
-       /* Going to skip them, since we set their mode using PAT when we 
-        * map them in 
-        */
-       // make sure all other MTRR ranges are disabled (should be unnecessary)
-       write_msr(IA32_MTRR_PHYSMASK2, 0);
-       write_msr(IA32_MTRR_PHYSMASK3, 0);
-       write_msr(IA32_MTRR_PHYSMASK4, 0);
-       write_msr(IA32_MTRR_PHYSMASK5, 0);
-       write_msr(IA32_MTRR_PHYSMASK6, 0);
-       write_msr(IA32_MTRR_PHYSMASK7, 0);
-
-       // keeps default type to WB (06), turns MTRRs on, and turns off fixed ranges
-       write_msr(IA32_MTRR_DEF_TYPE, 0x00000806);
-       // reflush caches and TLB
-       cache_flush();
-       tlb_flush_global();
-       // turn on caching
-       lcr0(rcr0() & ~(CR0_CD | CR0_NW));
-       // barrier
-       if (smp_barrier)
-               waiton_barrier(smp_barrier);
-       // enable interrupts
-       enable_irqsave(&state);
-}
-
-
-// Set up a two-level page table:
-//    boot_pgdir is its linear (virtual) address of the root
-//    boot_cr3 is the physical adresss of the root
-// Then turn on paging.  Then effectively turn off segmentation.
-// (i.e., the segment base addrs are set to zero).
-// 
-// This function only sets up the kernel part of the address space
-// (ie. addresses >= UTOP).  The user part of the address space
-// will be setup later.
-//
-// From UTOP to ULIM, the user is allowed to read but not write.
-// Above ULIM the user cannot read (or write). 
-void
-i386_vm_init(void)
-{
-       pde_t* pgdir;
-       uint32_t cr0, edx;
-       size_t n;
-       bool pse;
-
-       pse = enable_pse();
-       if (pse)
-               cprintf("PSE capability detected.\n");
+       struct multiboot_mmap_entry **boot_zone =
+              (struct multiboot_mmap_entry**)data;
 
-       // we paniced earlier if we don't support PGE.  turn it on now.
-       // it's used in boot_map_segment, which covers all of the mappings that are
-       // the same for all address spaces.  and also for the VPT mapping below.
-       lcr4(rcr4() | CR4_PGE);
-
-       // set up mtrr's for core0.  other cores will do the same later
-       setup_default_mtrrs(0);
-
-       /*
-        * PSE status: 
-        * - can walk and set up boot_map_segments with jumbos but can't
-        *   insert yet.  need to look at the page_dir and friends.
-        * - anything related to a single struct Page still can't handle 
-        *   jumbos.  will need to think about and adjust Page functions
-        * - do we want to store info like this in the struct Page?  or just check
-        *   by walking the PTE
-        * - when we alloc a page, and we want it to be 4MB, we'll need
-        *   to have contiguous memory, etc
-        * - there's a difference between having 4MB page table entries
-        *   and having 4MB Page tracking structs.  changing the latter will
-        *   break a lot of things
-        * - showmapping and friends work on a 4KB granularity, but map to the
-        *   correct entries
-        * - need to not insert / boot_map a single page into an area that is 
-        *   already holding a jumbo page.  will need to break the jumbo up so that
-        *   we can then insert the lone page.  currently warns.
-        * - some inherent issues with the pgdir_walks returning a PTE, and we
-        *   don't know whether it is a jumbo (PDE) or a regular PTE.
-        */
-
-       //////////////////////////////////////////////////////////////////////
-       // create initial page directory.
-       pgdir = boot_alloc(PGSIZE, PGSIZE);
-       memset(pgdir, 0, PGSIZE);
-       boot_pgdir = pgdir;
-       boot_cr3 = PADDR(pgdir);
-       // helpful if you want to manually walk with kvm / bochs
-       //printk("pgdir va = %08p, pgdir pa = %08p\n\n", pgdir, PADDR(pgdir));
-
-       //////////////////////////////////////////////////////////////////////
-       // Recursively insert PD in itself as a page table, to form
-       // a virtual page table at virtual address VPT.
-       // (For now, you don't have understand the greater purpose of the
-       // following two lines.  Unless you are eagle-eyed, in which case you
-       // should already know.)
-
-       // Permissions: kernel RW, user NONE, Global Page
-       pgdir[PDX(VPT)] = PADDR(pgdir) | PTE_W | PTE_P | PTE_G;
-
-       // same for UVPT
-       // Permissions: kernel R, user R, Global Page
-       pgdir[PDX(UVPT)] = PADDR(pgdir) | PTE_U | PTE_P | PTE_G;
-
-       //////////////////////////////////////////////////////////////////////
-       // Map the kernel stack (symbol name "bootstack").  The complete VA
-       // range of the stack, [KSTACKTOP-PTSIZE, KSTACKTOP), breaks into two
-       // pieces:
-       //     * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory
-       //     * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed => faults
-       //     Permissions: kernel RW, user NONE
-       // Your code goes here:
-
-       // remember that the space for the kernel stack is allocated in the binary.
-       // bootstack and bootstacktop point to symbols in the data section, which 
-       // at this point are like 0xc010b000.  KSTACKTOP is the desired loc in VM
-       boot_map_segment(pgdir, (uintptr_t)KSTACKTOP - KSTKSIZE, 
-                        KSTKSIZE, PADDR(bootstack), PTE_W | PTE_G);
-
-       //////////////////////////////////////////////////////////////////////
-       // Map all of physical memory at KERNBASE. 
-       // Ie.  the VA range [KERNBASE, 2^32) should map to
-       //      the PA range [0, 2^32 - KERNBASE)
-       // We might not have 2^32 - KERNBASE bytes of physical memory, but
-       // we just set up the mapping anyway.
-       // Permissions: kernel RW, user NONE
-       // Your code goes here: 
-       
-       // this maps all of the possible phys memory
-       // note the use of unsigned underflow to get size = 0x40000000
-       //boot_map_segment(pgdir, KERNBASE, -KERNBASE, 0, PTE_W);
-       // but this only maps what is available, and saves memory.  every 4MB of
-       // mapped memory requires a 2nd level page: 2^10 entries, each covering 2^12
-       // need to modify tests below to account for this
-       if (pse) {
-               // map the first 4MB as regular entries, to support different MTRRs
-               boot_map_segment(pgdir, KERNBASE, JPGSIZE, 0, PTE_W | PTE_G);
-               boot_map_segment(pgdir, KERNBASE + JPGSIZE, maxaddrpa - JPGSIZE, JPGSIZE,
-                                PTE_W | PTE_G | PTE_PS);
-       } else
-               boot_map_segment(pgdir, KERNBASE, maxaddrpa, 0, PTE_W | PTE_G);
-
-       // APIC mapping: using PAT (but not *the* PAT flag) to make these type UC
-       // IOAPIC
-       boot_map_segment(pgdir, (uintptr_t)IOAPIC_BASE, PGSIZE, IOAPIC_BASE, 
-                        PTE_PCD | PTE_PWT | PTE_W | PTE_G);
-       // Local APIC
-       boot_map_segment(pgdir, (uintptr_t)LAPIC_BASE, PGSIZE, LAPIC_BASE,
-                        PTE_PCD | PTE_PWT | PTE_W | PTE_G);
-
-       //////////////////////////////////////////////////////////////////////
-       // Make 'pages' point to an array of size 'npage' of 'struct Page'.
-       // The kernel uses this structure to keep track of physical pages;
-       // 'npage' equals the number of physical pages in memory.  User-level
-       // programs get read-only access to the array as well.
-       // You must allocate the array yourself.
-       // Map this array read-only by the user at linear address UPAGES
-       // (ie. perm = PTE_U | PTE_P)
-       // Permissions:
-       //    - pages -- kernel RW, user NONE
-       //    - the read-only version mapped at UPAGES -- kernel R, user R
-       // Your code goes here: 
-       
-       // round up to the nearest page
-       size_t page_array_size = ROUNDUP(npage*sizeof(page_t), PGSIZE);
-       pages = (page_t *)boot_alloc(page_array_size, PGSIZE);
-       memset(pages, 0, page_array_size);
-       if (page_array_size > PTSIZE) {
-               warn("page_array_size bigger than PTSIZE, userland will not see all pages");
-               page_array_size = PTSIZE;
-       }
-       boot_map_segment(pgdir, UPAGES, page_array_size, PADDR(pages), PTE_U | PTE_G);
-
-       //////////////////////////////////////////////////////////////////////
-       // Make 'envs' point to an array of size 'NENV' of 'env_t'.
-       // No longer mapping ENVS into the address space
-       
-       // round up to the nearest page
-       size_t env_array_size = ROUNDUP(NENV*sizeof(env_t), PGSIZE);
-       envs = (env_t *)boot_alloc(env_array_size, PGSIZE);
-       memset(envs, 0, env_array_size);
-
-       // Check that the initial page directory has been set up correctly.
-       check_boot_pgdir(pse);
-
-       //////////////////////////////////////////////////////////////////////
-       // On x86, segmentation maps a VA to a LA (linear addr) and
-       // paging maps the LA to a PA.  I.e. VA => LA => PA.  If paging is
-       // turned off the LA is used as the PA.  Note: there is no way to
-       // turn off segmentation.  The closest thing is to set the base
-       // address to 0, so the VA => LA mapping is the identity.
-
-       // Current mapping: VA KERNBASE+x => PA x.
-       //     (segmentation base=-KERNBASE and paging is off)
-
-       // From here on down we must maintain this VA KERNBASE + x => PA x
-       // mapping, even though we are turning on paging and reconfiguring
-       // segmentation.
-
-       // Map VA 0:4MB same as VA KERNBASE, i.e. to PA 0:4MB.
-       // (Limits our kernel to <4MB)
-       /* They mean linear address 0:4MB, and the kernel < 4MB is only until 
-        * segmentation is turned off.
-        * once we turn on paging, segmentation is still on, so references to
-        * KERNBASE+x will get mapped to linear address x, which we need to make 
-        * sure can map to phys addr x, until we can turn off segmentation and
-        * KERNBASE+x maps to LA KERNBASE+x, which maps to PA x, via paging
-        */
-       pgdir[0] = pgdir[PDX(KERNBASE)];
-
-       // Install page table.
-       lcr3(boot_cr3);
-
-       // Turn on paging.
-       cr0 = rcr0();
-       // CD and NW should already be on, but just in case these turn on caching
-       cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_MP;
-       cr0 &= ~(CR0_TS|CR0_EM|CR0_CD|CR0_NW);
-       lcr0(cr0);
-
-       // Current mapping: KERNBASE+x => x => x.
-       // (x < 4MB so uses paging pgdir[0])
-
-       // Reload all segment registers.
-       asm volatile("lgdt gdt_pd");
-       asm volatile("movw %%ax,%%gs" :: "a" (GD_UD|3));
-       asm volatile("movw %%ax,%%fs" :: "a" (GD_UD|3));
-       asm volatile("movw %%ax,%%es" :: "a" (GD_KD));
-       asm volatile("movw %%ax,%%ds" :: "a" (GD_KD));
-       asm volatile("movw %%ax,%%ss" :: "a" (GD_KD));
-       asm volatile("ljmp %0,$1f\n 1:\n" :: "i" (GD_KT));  // reload cs
-       asm volatile("lldt %%ax" :: "a" (0));
-
-       // Final mapping: KERNBASE+x => KERNBASE+x => x.
-
-       // This mapping was only used after paging was turned on but
-       // before the segment registers were reloaded.
-       pgdir[0] = 0;
-
-       // Flush the TLB for good measure, to kill the pgdir[0] mapping.
-       lcr3(boot_cr3);
+       if (entry->type != MULTIBOOT_MEMORY_AVAILABLE)
+               return;
+       if (!*boot_zone || (sizeof_mboot_mmentry(entry) >
+                          sizeof_mboot_mmentry(*boot_zone)))
+               *boot_zone = entry;
 }
 
-//
-// Checks that the kernel part of virtual address space
-// has been setup roughly correctly(by i386_vm_init()).
-//
-// This function doesn't test every corner case,
-// in fact it doesn't test the permission bits at all,
-// but it is a pretty good sanity check. 
-//
-static physaddr_t check_va2pa(pde_t *pgdir, uintptr_t va);
-static pte_t get_vaperms(pde_t *pgdir, uintptr_t va);
-
-static void
-check_boot_pgdir(bool pse)
+/* Initialize boot freemem and its limit.
+ *
+ * "end" is a symbol marking the end of the kernel.  This covers anything linked
+ * in with the kernel (KFS, etc).  However, 'end' is a kernel load address,
+ * which differs from kernbase addrs in 64 bit.  We need to use the kernbase
+ * mapping for anything dynamic (because it could go beyond 1 GB).
+ *
+ * Ideally, we'll use the largest mmap zone, as reported by multiboot.  If we
+ * don't have one (riscv), we'll just use the memory after the kernel.
+ *
+ * If we do have a zone, there is a chance we've already used some of it (for
+ * the kernel, etc).  We'll use the lowest address in the zone that is
+ * greater than "end" (and adjust the limit accordingly).  */
+static void boot_alloc_init(void)
 {
-       uint32_t i, n;
-       pde_t *pgdir, pte;
-
-       pgdir = boot_pgdir;
-
-       // check pages array
-       n = ROUNDUP(naddrpage*sizeof(page_t), PGSIZE);
-       for (i = 0; i < n; i += PGSIZE)
-               assert(check_va2pa(pgdir, UPAGES + i) == PADDR(pages) + i);
-
-       // check phys mem
-       //for (i = 0; KERNBASE + i != 0; i += PGSIZE)
-       // adjusted check to account for only mapping avail mem
-       if (pse)
-               for (i = 0; i < maxaddrpa; i += JPGSIZE)
-                       assert(check_va2pa(pgdir, KERNBASE + i) == i);
-       else
-               for (i = 0; i < maxaddrpa; i += PGSIZE)
-                       assert(check_va2pa(pgdir, KERNBASE + i) == i);
-
-       // check kernel stack
-       for (i = 0; i < KSTKSIZE; i += PGSIZE)
-               assert(check_va2pa(pgdir, KSTACKTOP - KSTKSIZE + i) == PADDR(bootstack) + i);
-
-       // check for zero/non-zero in PDEs
-       for (i = 0; i < NPDENTRIES; i++) {
-               switch (i) {
-               case PDX(VPT):
-               case PDX(UVPT):
-               case PDX(KSTACKTOP-1):
-               case PDX(UPAGES):
-               case PDX(LAPIC_BASE): // LAPIC mapping.  TODO: remove when MTRRs are up
-                       assert(pgdir[i]);
-                       break;
-               default:
-                       //if (i >= PDX(KERNBASE))
-                       // adjusted check to account for only mapping avail mem
-                       // and you can't KADDR maxpa (just above legal range)
-                       // maxaddrpa can be up to maxpa, so assume the worst
-                       if (i >= PDX(KERNBASE) && i <= PDX(KADDR(maxaddrpa-1)))
-                               assert(pgdir[i]);
-                       else
-                               assert(pgdir[i] == 0);
-                       break;
-               }
-       }
-
-       // check permissions
-       // user read-only.  check for user and write, should be only user
-       // eagle-eyed viewers should be able to explain the extra cases
-       for (i = UTOP; i < ULIM; i+=PGSIZE) {
-               pte = get_vaperms(pgdir, i);
-               if ((pte & PTE_P) && (i != UVPT+(VPT>>10))) {
-                       if (pte & PTE_PS) {
-                               assert((pte & PTE_U) != PTE_U);
-                               assert((pte & PTE_W) != PTE_W);
-                       } else {
-                               assert((pte & PTE_U) == PTE_U);
-                               assert((pte & PTE_W) != PTE_W);
-                       }
-               }
-       }
-       // kernel read-write.
-       for (i = ULIM; i <= KERNBASE + maxaddrpa - PGSIZE; i+=PGSIZE) {
-               pte = get_vaperms(pgdir, i);
-               if ((pte & PTE_P) && (i != VPT+(UVPT>>10))) {
-                       assert((pte & PTE_U) != PTE_U);
-                       assert((pte & PTE_W) == PTE_W);
-               }
+       extern char end[];
+       uintptr_t boot_zone_start, boot_zone_end;
+       uintptr_t end_kva = (uintptr_t)KBASEADDR(end);
+       struct multiboot_mmap_entry *boot_zone = 0;
+
+       /* Find our largest mmap_entry; that will set bootzone */
+       mboot_foreach_mmap(multiboot_kaddr, set_largest_freezone, &boot_zone);
+       if (boot_zone) {
+               boot_zone_start = (uintptr_t)KADDR(boot_zone->addr);
+               /* one issue for 32b is that the boot_zone_end could be beyond max_paddr
+                * and even wrap-around.  Do the min check as a uint64_t.  The result
+                * should be a safe, unwrapped 32/64b when cast to physaddr_t. */
+               boot_zone_end = (uintptr_t)KADDR(MIN(boot_zone->addr + boot_zone->len,
+                                                (uint64_t)max_paddr));
+               /* using KERNBASE (kva, btw) which covers the kernel and anything before
+                * it (like the stuff below EXTPHYSMEM on x86) */
+               if (regions_collide_unsafe(KERNBASE, end_kva,
+                                          boot_zone_start, boot_zone_end))
+                       boot_freemem = end_kva;
+               else
+                       boot_freemem = boot_zone_start;
+               boot_freelimit = boot_zone_end;
+       } else {
+               boot_freemem = end_kva;
+               boot_freelimit = max_paddr + KERNBASE;
        }
-       // special mappings
-       pte = get_vaperms(pgdir, UVPT+(VPT>>10));
-       assert((pte & PTE_U) != PTE_U);
-       assert((pte & PTE_W) != PTE_W);
-
-       // note this means the kernel cannot directly manipulate this virtual address
-       // convince yourself this isn't a big deal, eagle-eyes!
-       pte = get_vaperms(pgdir, VPT+(UVPT>>10));
-       assert((pte & PTE_U) != PTE_U);
-       assert((pte & PTE_W) != PTE_W);
-
-       cprintf("check_boot_pgdir() succeeded!\n");
+       printd("boot_zone: %p, paddr base: 0x%llx, paddr len: 0x%llx\n", boot_zone,
+              boot_zone ? boot_zone->addr : 0,
+              boot_zone ? boot_zone->len : 0);
+       printd("boot_freemem: %p, boot_freelimit %p\n", boot_freemem,
+              boot_freelimit);
 }
 
-// This function returns the physical address of the page containing 'va',
-// defined by the page directory 'pgdir'.  The hardware normally performs
-// this functionality for us!  We define our own version to help check
-// the check_boot_pgdir() function; it shouldn't be used elsewhere.
-
-static physaddr_t
-check_va2pa(pde_t *pgdir, uintptr_t va)
+/* Low-level allocator, used before page_alloc is on.  Returns size bytes,
+ * aligned to align (should be a power of 2).  Retval is a kernbase addr.  Will
+ * panic on failure. */
+void *boot_alloc(size_t amt, size_t align)
 {
-       pte_t *p;
-
-       pgdir = &pgdir[PDX(va)];
-       if (!(*pgdir & PTE_P))
-               return ~0;
-       if (*pgdir & PTE_PS)
-               return PTE_ADDR(*pgdir);
-       p = (pte_t*) KADDR(PTE_ADDR(*pgdir));
-       if (!(p[PTX(va)] & PTE_P))
-               return ~0;
-       return PTE_ADDR(p[PTX(va)]);
-}
+       uintptr_t retval;
 
-/* 
- * This function returns a PTE with the aggregate permissions equivalent
- * to walking the two levels of paging.  PPN = 0.  Somewhat fragile, in that
- * it returns PTE_PS if either entry has PTE_PS (which should only happen
- * for some of the recusive walks)
- */
-
-static pte_t
-get_vaperms(pde_t *pgdir, uintptr_t va)
-{
-       pde_t* pde = &pgdir[PDX(va)];
-       pte_t* pte = pgdir_walk(pgdir, (void*)va, 0);
-       if (!pte || !(*pte & PTE_P))
-               return 0;
-       return PGOFF(*pde & *pte) + PTE_PS & (*pde | *pte);
-}
-               
-// --------------------------------------------------------------
-// Tracking of physical pages.
-// The 'pages' array has one 'page_t' entry per physical page.
-// Pages are reference counted, and free pages are kept on a linked list.
-// --------------------------------------------------------------
-
-//  
-// Initialize page structure and memory free list.
-// After this point, ONLY use the functions below
-// to allocate and deallocate physical memory via the page_free_list,
-// and NEVER use boot_alloc() or the related boot-time functions above.
-//
-void
-page_init(void)
-{
-       // The example code here marks all pages as free.
-       // However this is not truly the case.  What memory is free?
-       //  1) Mark page 0 as in use.
-       //     This way we preserve the real-mode IDT and BIOS structures
-       //     in case we ever need them.  (Currently we don't, but...)
-       //  2) Mark the rest of base memory as free.
-       //  3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM).
-       //     Mark it as in use so that it can never be allocated.      
-       //  4) Then extended memory [EXTPHYSMEM, ...).
-       //     Some of it is in use, some is free. Where is the kernel?
-       //     Which pages are used for page tables and other data structures?
-       //
-       // Change the code to reflect this.
-       int i;
-       physaddr_t physaddr_after_kernel = PADDR(ROUNDUP(boot_freemem, PGSIZE));
-       LIST_INIT(&page_free_list);
-
-       pages[0].pp_ref = 1;
-       // alloc the second page, since we will need it later to init the other cores
-       // probably need to be smarter about what page we use (make this dynamic) TODO
-       pages[1].pp_ref = 1;
-       for (i = 2; i < PPN(IOPHYSMEM); i++) {
-               pages[i].pp_ref = 0;
-               LIST_INSERT_HEAD(&page_free_list, &pages[i], pp_link);
-       }
-       for (i = PPN(IOPHYSMEM); i < PPN(EXTPHYSMEM); i++) {
-               pages[i].pp_ref = 1;
-       }
-       for (i = PPN(EXTPHYSMEM); i < PPN(physaddr_after_kernel); i++) {
-               pages[i].pp_ref = 1;
-       }
-       for (i = PPN(physaddr_after_kernel); i < PPN(maxaddrpa); i++) {
-               pages[i].pp_ref = 0;
-               LIST_INSERT_HEAD(&page_free_list, &pages[i], pp_link);
-       }
-       // this block out all memory above maxaddrpa.  will need another mechanism
-       // to allocate and map these into the kernel address space
-       for (i = PPN(maxaddrpa); i < npage; i++) {
-               pages[i].pp_ref = 1;
+       if (!boot_freemem)
+               boot_alloc_init();
+       boot_freemem = ROUNDUP(boot_freemem, align);
+       retval = boot_freemem;
+       if (boot_freemem + amt > boot_freelimit){
+               printk("boot_alloc: boot_freemem is 0x%x\n", boot_freemem);
+               printk("boot_alloc: amt is %d\n", amt);
+               printk("boot_freelimit is 0x%x\n", boot_freelimit);
+               printk("boot_freemem + amt is > boot_freelimit\n");
+               panic("Out of memory in boot alloc, you fool!\n");
        }
+       boot_freemem += amt;
+       printd("boot alloc from %p to %p\n", retval, boot_freemem);
+       /* multiboot info probably won't ever conflict with our boot alloc */
+       if (mboot_region_collides(multiboot_kaddr, retval, boot_freemem))
+               panic("boot allocation could clobber multiboot info!  Get help!");
+       return (void*)retval;
 }
 
-//
-// Initialize a Page structure.
-// The result has null links and 0 refcount.
-// Note that the corresponding physical page is NOT initialized!
-//
-static void
-page_initpp(page_t *pp)
+void *boot_zalloc(size_t amt, size_t align)
 {
-       memset(pp, 0, sizeof(*pp));
+       /* boot_alloc panics on failure */
+       void *v = boot_alloc(amt, align);
+       memset(v, 0, amt);
+       return v;
 }
 
-/*
- * Allocates a physical page.
- * Does NOT set the contents of the physical page to zero -
- * the caller must do that if necessary.
+/**
+ * @brief Map the physical page 'pp' into the virtual address 'va' in page
+ *        directory 'pgdir'
  *
- * *pp_store   -- is set to point to the Page struct 
- *                of the newly allocated page
+ * Map the physical page 'pp' at virtual address 'va'.
+ * The permissions (the low 12 bits) of the page table
+ * entry should be set to 'perm|PTE_P'.
  *
- * RETURNS 
- *   0         -- on success
- *   -E_NO_MEM -- otherwise 
- */
-int page_alloc(page_t **pp_store)
-{
-       if (LIST_EMPTY(&page_free_list))
-               return -E_NO_MEM;
-       *pp_store = LIST_FIRST(&page_free_list);
-       LIST_REMOVE(*pp_store, pp_link);
-       page_initpp(*pp_store);
-       return 0;
-}
-
-/*
- * Allocates a specific physical page.
- * Does NOT set the contents of the physical page to zero -
- * the caller must do that if necessary.
+ * Details:
+ *   - If there is already a page mapped at 'va', it is page_remove()d.
+ *   - If necessary, on demand, allocates a page table and inserts it into
+ *     'pgdir'.
+ *   - This saves your refcnt in the pgdir (refcnts going away soon).
+ *   - The TLB must be invalidated if a page was formerly present at 'va'.
+ *     (this is handled in page_remove)
  *
- * *pp_store   -- is set to point to the Page struct 
- *                of the newly allocated page
+ * No support for jumbos here.  We will need to be careful when trying to
+ * insert regular pages into something that was already jumbo.  We will
+ * also need to be careful with our overloading of the PTE_PS and
+ * PTE_PAT flags...
+ *
+ * @param[in] pgdir the page directory to insert the page into
+ * @param[in] pp    a pointr to the page struct representing the
+ *                  physical page that should be inserted.
+ * @param[in] va    the virtual address where the page should be
+ *                  inserted.
+ * @param[in] perm  the permition bits with which to set up the
+ *                  virtual mapping.
+ *
+ * @return ESUCCESS  on success
+ * @return -ENOMEM   if a page table could not be allocated
+ *                   into which the page should be inserted
  *
- * RETURNS 
- *   0         -- on success
- *   -E_NO_MEM -- otherwise 
  */
-int page_alloc_specific(page_t **pp_store, size_t ppn)
+int page_insert(pgdir_t pgdir, struct page *page, void *va, int perm)
 {
-       page_t* page = ppn2page(ppn);
-       if( page->pp_ref != 0 )
-               return -E_NO_MEM;
-       *pp_store = page;
-       LIST_REMOVE(*pp_store, pp_link);
-       page_initpp(*pp_store);
+       pte_t pte = pgdir_walk(pgdir, va, 1);
+       if (!pte_walk_okay(pte))
+               return -ENOMEM;
+       /* Leftover from older times, but we no longer suppor this: */
+       assert(!pte_is_mapped(pte));
+       pte_write(pte, page2pa(page), perm);
        return 0;
 }
 
-int page_is_free(size_t ppn) {
-       page_t* page = ppn2page(ppn);
-       if( page->pp_ref == 0 )
-               return TRUE;
-       return FALSE;
-}
-
-//
-// Return a page to the free list.
-// (This function should only be called when pp->pp_ref reaches 0.)
-//
-void page_free(page_t *pp)
-{
-       // this check allows us to call this on null ptrs, which helps when
-       // allocating and checking for errors on several pages at once
-       if (pp) {
-               if (pp->pp_ref)
-                       panic("Attempting to free page with non-zero reference count!");
-               LIST_INSERT_HEAD(&page_free_list, pp, pp_link);
-       }
-}
-
-/* 
- * Remove the second level page table associated with virtual address va.
- * Will 0 out the PDE for that page table.
- * Panics if the page table has any present entries.
- * This should be called rarely and with good cause.
- * Currently errors if the PDE is jumbo or not present.
+/**
+ * @brief Return the page mapped at virtual address 'va' in
+ * page directory 'pgdir'.
+ *
+ * If pte_store is not NULL, then we store in it the address
+ * of the pte for this page.  This is used by page_remove
+ * but should not be used by other callers.
+ *
+ * For jumbos, right now this returns the first Page* in the 4MB range
+ *
+ * @param[in]  pgdir     the page directory from which we should do the lookup
+ * @param[in]  va        the virtual address of the page we are looking up
+ * @param[out] pte_store the address of the page table entry for the returned page
+ *
+ * @return PAGE the page mapped at virtual address 'va'
+ * @return NULL No mapping exists at virtual address 'va', or it's paged out
  */
-error_t        pagetable_remove(pde_t *pgdir, void *va)
-{
-       pde_t* the_pde = &pgdir[PDX(va)];
-
-       if (!(*the_pde & PTE_P) || (*the_pde & PTE_PS))
-               return -E_FAULT;
-       pte_t* page_table = (pde_t*)KADDR(PTE_ADDR(*the_pde));
-       for (int i = 0; i < NPTENTRIES; i++) 
-               if (page_table[i] & PTE_P)
-                       panic("Page table not empty during attempted removal!");
-       *the_pde = 0;
-       page_decref(pa2page(PADDR(page_table)));
-       return 0;
-}
-
-//
-// Decrement the reference count on a page,
-// freeing it if there are no more refs.
-//
-void
-page_decref(page_t *pp)
-{
-       if (--pp->pp_ref == 0)
-               page_free(pp);
-}
-
-// Given 'pgdir', a pointer to a page directory, pgdir_walk returns
-// a pointer to the page table entry (PTE) for linear address 'va'.
-// This requires walking the two-level page table structure.
-//
-// If the relevant page table doesn't exist in the page directory, then:
-//    - If create == 0, pgdir_walk returns NULL.
-//    - Otherwise, pgdir_walk tries to allocate a new page table
-//     with page_alloc.  If this fails, pgdir_walk returns NULL.
-//    - Otherwise, pgdir_walk returns a pointer into the new page table.
-//
-// This is boot_pgdir_walk, but using page_alloc() instead of boot_alloc().
-// Unlike boot_pgdir_walk, pgdir_walk can fail.
-//
-// Hint: you can turn a Page * into the physical address of the
-// page it refers to with page2pa() from kern/pmap.h.
-//
-// Supports returning jumbo (4MB PSE) PTEs.  To create with a jumbo, pass in 2.
-pte_t*
-pgdir_walk(pde_t *pgdir, const void *SNT va, int create)
-{
-       pde_t* the_pde = &pgdir[PDX(va)];
-       page_t *new_table;
-
-       if (*the_pde & PTE_P) {
-               if (*the_pde & PTE_PS)
-                       return (pte_t*)the_pde;
-               return &((pde_t*)KADDR(PTE_ADDR(*the_pde)))[PTX(va)];
-       }
-       if (!create)
-               return NULL;
-       if (create == 2) {
-               if (JPGOFF(va))
-                       panic("Attempting to find a Jumbo PTE at an unaligned VA!");
-               *the_pde = PTE_PS | PTE_P;
-               return (pte_t*)the_pde;
-       }
-       if (page_alloc(&new_table))
-               return NULL;
-       new_table->pp_ref = 1;
-       memset(page2kva(new_table), 0, PGSIZE);
-       *the_pde = (pde_t)page2pa(new_table) | PTE_P | PTE_W | PTE_U;
-       return &((pde_t*)KADDR(PTE_ADDR(*the_pde)))[PTX(va)];
-}
-//
-// Map the physical page 'pp' at virtual address 'va'.
-// The permissions (the low 12 bits) of the page table
-//  entry should be set to 'perm|PTE_P'.
-//
-// Details
-//   - If there is already a page mapped at 'va', it is page_remove()d.
-//   - If necessary, on demand, allocates a page table and inserts it into
-//     'pgdir'.
-//   - pp->pp_ref should be incremented if the insertion succeeds.
-//   - The TLB must be invalidated if a page was formerly present at 'va'.
-//     (this is handled in page_remove)
-//
-// RETURNS: 
-//   0 on success
-//   -E_NO_MEM, if page table couldn't be allocated
-//
-// Hint: The TA solution is implemented using pgdir_walk, page_remove,
-// and page2pa.
-//
-// No support for jumbos here.  will need to be careful of trying to insert
-// regular pages into something that was already jumbo, and the overloading
-// of the PTE_PS and PTE_PAT flags...
-int
-page_insert(pde_t *pgdir, page_t *pp, void *va, int perm) 
+page_t *page_lookup(pgdir_t pgdir, void *va, pte_t *pte_store)
 {
-       pte_t* pte = pgdir_walk(pgdir, va, 1);
-       if (!pte)
-               return -E_NO_MEM;
-       // need to up the ref count in case pp is already mapped at va
-       // and we don't want to page_remove (which could free pp) and then 
-       // continue as if pp wasn't freed.  moral = up the ref asap
-       pp->pp_ref++;
-       if (*pte & PTE_P) {
-               page_remove(pgdir, va);
-       }
-       *pte = page2pa(pp) | PTE_P | perm;
-       return 0;
-}
-
-//
-// Return the page mapped at virtual address 'va'.
-// If pte_store is not zero, then we store in it the address
-// of the pte for this page.  This is used by page_remove
-// but should not be used by other callers.
-//
-// Return 0 if there is no page mapped at va.
-//
-// Hint: the TA solution uses pgdir_walk and pa2page.
-//
-// For jumbos, right now this returns the first Page* in the 4MB
-page_t *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
-{
-       pte_t* pte = pgdir_walk(pgdir, va, 0);
-       if (!pte || !(*pte & PTE_P))
+       pte_t pte = pgdir_walk(pgdir, va, 0);
+       if (!pte_walk_okay(pte) || !pte_is_mapped(pte))
                return 0;
        if (pte_store)
                *pte_store = pte;
-       return pa2page(PTE_ADDR(*pte));
+       return pa2page(pte_get_paddr(pte));
 }
 
-//
-// Unmaps the physical page at virtual address 'va'.
-// If there is no physical page at that address, silently does nothing.
-//
-// Details:
-//   - The ref count on the physical page should decrement.
-//   - The physical page should be freed if the refcount reaches 0.
-//   - The pg table entry corresponding to 'va' should be set to 0.
-//     (if such a PTE exists)
-//   - The TLB must be invalidated if you remove an entry from
-//     the pg dir/pg table.
-//
-// Hint: The TA solution is implemented using page_lookup,
-//     tlb_invalidate, and page_decref.
-//
-// This may be wonky wrt Jumbo pages and decref.  
-void
-page_remove(pde_t *pgdir, void *va)
+/**
+ * @brief Unmaps the physical page at virtual address 'va' in page directory
+ * 'pgdir'.
+ *
+ * If there is no physical page at that address, this function silently
+ * does nothing.
+ *
+ * Details:
+ *   - The ref count on the physical page is decrement when the page is removed
+ *   - The physical page is freed if the refcount reaches 0.
+ *   - The pg table entry corresponding to 'va' is set to 0.
+ *     (if such a PTE exists)
+ *   - The TLB is invalidated if an entry is removes from the pg dir/pg table.
+ *
+ * This may be wonky wrt Jumbo pages and decref.
+ *
+ * @param pgdir the page directory from with the page sholuld be removed
+ * @param va    the virtual address at which the page we are trying to
+ *              remove is mapped
+ * TODO: consider deprecating this, or at least changing how it works with TLBs.
+ * Might want to have the caller need to manage the TLB.  Also note it is used
+ * in env_user_mem_free, minus the walk. */
+void page_remove(pgdir_t pgdir, void *va)
 {
-       pte_t* pte;
+       pte_t pte;
        page_t *page;
-       page = page_lookup(pgdir, va, &pte);
-       if (!page)
+
+       pte = pgdir_walk(pgdir,va,0);
+       if (!pte_walk_okay(pte) || pte_is_unmapped(pte))
                return;
-       *pte = 0;
-       tlb_invalidate(pgdir, va);
-       page_decref(page);
+
+       if (pte_is_mapped(pte)) {
+               /* TODO: (TLB) need to do a shootdown, inval sucks.  And might want to
+                * manage the TLB / free pages differently. (like by the caller).
+                * Careful about the proc/memory lock here. */
+               page = pa2page(pte_get_paddr(pte));
+               pte_clear(pte);
+               tlb_invalidate(pgdir, va);
+               page_decref(page);
+       } else if (pte_is_paged_out(pte)) {
+               /* TODO: (SWAP) need to free this from the swap */
+               panic("Swapping not supported!");
+               pte_clear(pte);
+       }
 }
 
-//
-// Invalidate a TLB entry, but only if the page tables being
-// edited are the ones currently in use by the processor.
-//
-// Need to sort this for cross core lovin'  TODO
-void
-tlb_invalidate(pde_t *pgdir, void *va)
+/**
+ * @brief Invalidate a TLB entry, but only if the page tables being
+ * edited are the ones currently in use by the processor.
+ *
+ * TODO: (TLB) Need to sort this for cross core lovin'
+ *
+ * @param pgdir the page directory assocaited with the tlb entry
+ *              we are trying to invalidate
+ * @param va    the virtual address associated with the tlb entry
+ *              we are trying to invalidate
+ */
+void tlb_invalidate(pgdir_t pgdir, void *va)
 {
        // Flush the entry only if we're modifying the current address space.
        // For now, there is only one address space, so always invalidate.
        invlpg(va);
 }
 
-/* Flushes a TLB, including global pages.  We should always have the CR4_PGE
- * flag set, but just in case, we'll check.  Toggling this bit flushes the TLB.
- */
-void tlb_flush_global(void)
-{
-       uint32_t cr4 = rcr4();
-       if (cr4 & CR4_PGE) {
-               lcr4(cr4 & ~CR4_PGE);
-               lcr4(cr4);
-       } else 
-               lcr3(rcr3());
-}
-
-static void *DANGEROUS user_mem_check_addr;
-
-//
-// Check that an environment is allowed to access the range of memory
-// [va, va+len) with permissions 'perm | PTE_P'.
-// Normally 'perm' will contain PTE_U at least, but this is not required.
-// 'va' and 'len' need not be page-aligned; you must test every page that
-// contains any of that range.  You will test either 'len/PGSIZE',
-// 'len/PGSIZE + 1', or 'len/PGSIZE + 2' pages.
-//
-// A user program can access a virtual address if (1) the address is below
-// ULIM, and (2) the page table gives it permission.  These are exactly
-// the tests you should implement here.
-//
-// If there is an error, set the 'user_mem_check_addr' variable to the first
-// erroneous virtual address.
-//
-// Returns 0 if the user program can access this range of addresses,
-// and -E_FAULT otherwise.
-//
-// Hint: The TA solution uses pgdir_walk.
-//
-
-// zra: I've modified the interface to these two functions so that Ivy can
-// check that user pointers aren't dereferenced. User pointers get the
-// DANGEROUS qualifier. After validation, these functions return a
-// COUNT(len) pointer. user_mem_check now returns NULL on error instead of
-// -E_FAULT.
-
-void *COUNT(len)
-user_mem_check(env_t *env, const void *DANGEROUS va, size_t len, int perm)
+static void __tlb_global(uint32_t srcid, long a0, long a1, long a2)
 {
-       // TODO - will need to sort this out wrt page faulting / PTE_P
-       // also could be issues with sleeping and waking up to find pages
-       // are unmapped, though i think the lab ignores this since the 
-       // kernel is uninterruptible
-       void *DANGEROUS start, *DANGEROUS end;
-       size_t num_pages, i;
-       pte_t *pte;
-
-       perm |= PTE_P;
-       start = ROUNDDOWN((void*)va, PGSIZE);
-       end = ROUNDUP((void*)va + len, PGSIZE);
-       if (start >= end) {
-               warn("Blimey!  Wrap around in VM range calculation!");  
-               return NULL;
-       }
-       num_pages = PPN(end - start);
-       for (i = 0; i < num_pages; i++, start += PGSIZE) {
-               pte = pgdir_walk(env->env_pgdir, start, 0);
-               // ensures the bits we want on are turned on.  if not, error out
-               if ( !pte || ((*pte & perm) != perm) ) {
-                       if (i = 0)
-                               user_mem_check_addr = (void*)va;
-                       else
-                               user_mem_check_addr = start;
-                       return NULL;
-               }
-       }
-       // this should never be needed, since the perms should catch it
-       if ((uintptr_t)end > ULIM) {
-               warn ("I suck - Bug in user permission mappings!");
-               return NULL;
-       }
-       return (void *COUNT(len))TC(va);
+       tlb_flush_global();
 }
 
-//
-// Checks that environment 'env' is allowed to access the range
-// of memory [va, va+len) with permissions 'perm | PTE_U'.
-// If it can, then the function simply returns.
-// If it cannot, 'env' is destroyed.
-//
-void *COUNT(len)
-user_mem_assert(env_t *env, const void *DANGEROUS va, size_t len, int perm)
+/* Does a global TLB flush on all cores. */
+void tlb_shootdown_global(void)
 {
-    void *COUNT(len) res = user_mem_check(env,va,len,perm | PTE_U);
-       if (!res) {
-               cprintf("[%08x] user_mem_check assertion failure for "
-                       "va %08x\n", env->env_id, user_mem_check_addr);
-               env_destroy(env);       // may not return
-        return NULL;
+       tlb_flush_global();
+       if (booting)
+               return;
+       /* TODO: consider a helper for broadcast messages, though note that we're
+        * doing our flush immediately, which our caller expects from us before it
+        * returns. */
+       for (int i = 0; i < num_cores; i++) {
+               if (i == core_id())
+                       continue;
+               send_kernel_message(i, __tlb_global, 0, 0, 0, KMSG_IMMEDIATE);
        }
-    return res;
 }
 
-void
-page_check(void)
+/* Helper, returns true if any part of (start1, end1) is within (start2, end2).
+ * Equality of endpoints (like end1 == start2) is okay.
+ * Assumes no wrap-around. */
+bool regions_collide_unsafe(uintptr_t start1, uintptr_t end1,
+                            uintptr_t start2, uintptr_t end2)
 {
-       page_t *pp, *pp0, *pp1, *pp2;
-       page_list_t fl;
-       pte_t *ptep;
-
-       // should be able to allocate three pages
-       pp0 = pp1 = pp2 = 0;
-       assert(page_alloc(&pp0) == 0);
-       assert(page_alloc(&pp1) == 0);
-       assert(page_alloc(&pp2) == 0);
-
-       assert(pp0);
-       assert(pp1 && pp1 != pp0);
-       assert(pp2 && pp2 != pp1 && pp2 != pp0);
-
-       // temporarily steal the rest of the free pages
-       fl = page_free_list;
-       LIST_INIT(&page_free_list);
-
-       // should be no free memory
-       assert(page_alloc(&pp) == -E_NO_MEM);
-
-       // Fill pp1 with bogus data and check for invalid tlb entries
-       memset(page2kva(pp1), 0xFFFFFFFF, PGSIZE);
-
-       // there is no page allocated at address 0
-       assert(page_lookup(boot_pgdir, (void *) 0x0, &ptep) == NULL);
-
-       // there is no free memory, so we can't allocate a page table 
-       assert(page_insert(boot_pgdir, pp1, 0x0, 0) < 0);
-
-       // free pp0 and try again: pp0 should be used for page table
-       page_free(pp0);
-       assert(page_insert(boot_pgdir, pp1, 0x0, 0) == 0);
-       tlb_invalidate(boot_pgdir, 0x0);
-       // DEP Should have shot down invalid TLB entry - let's check
-       {
-         int *x = 0x0;
-         assert(*x == 0xFFFFFFFF);
-       }
-       assert(PTE_ADDR(boot_pgdir[0]) == page2pa(pp0));
-       assert(check_va2pa(boot_pgdir, 0x0) == page2pa(pp1));
-       assert(pp1->pp_ref == 1);
-       assert(pp0->pp_ref == 1);
-
-       // should be able to map pp2 at PGSIZE because pp0 is already allocated for page table
-       assert(page_insert(boot_pgdir, pp2, (void*) PGSIZE, 0) == 0);
-       assert(check_va2pa(boot_pgdir, PGSIZE) == page2pa(pp2));
-       assert(pp2->pp_ref == 1);
-
-       // Make sure that pgdir_walk returns a pointer to the pte and
-       // not the table or some other garbage
-       {
-         pte_t *p = KADDR(PTE_ADDR(boot_pgdir[PDX(PGSIZE)]));
-         assert(pgdir_walk(boot_pgdir, (void *)PGSIZE, 0) == &p[PTX(PGSIZE)]);
-       }
-
-       // should be no free memory
-       assert(page_alloc(&pp) == -E_NO_MEM);
-
-       // should be able to map pp2 at PGSIZE because it's already there
-       assert(page_insert(boot_pgdir, pp2, (void*) PGSIZE, PTE_U) == 0);
-       assert(check_va2pa(boot_pgdir, PGSIZE) == page2pa(pp2));
-       assert(pp2->pp_ref == 1);
-
-       // Make sure that we actually changed the permission on pp2 when we re-mapped it
-       {
-         pte_t *p = pgdir_walk(boot_pgdir, (void*)PGSIZE, 0);
-         assert(((*p) & PTE_U) == PTE_U);
-       }
-
-       // pp2 should NOT be on the free list
-       // could happen in ref counts are handled sloppily in page_insert
-       assert(page_alloc(&pp) == -E_NO_MEM);
-
-       // should not be able to map at PTSIZE because need free page for page table
-       assert(page_insert(boot_pgdir, pp0, (void*) PTSIZE, 0) < 0);
-
-       // insert pp1 at PGSIZE (replacing pp2)
-       assert(page_insert(boot_pgdir, pp1, (void*) PGSIZE, 0) == 0);
-
-       // should have pp1 at both 0 and PGSIZE, pp2 nowhere, ...
-       assert(check_va2pa(boot_pgdir, 0) == page2pa(pp1));
-       assert(check_va2pa(boot_pgdir, PGSIZE) == page2pa(pp1));
-       // ... and ref counts should reflect this
-       assert(pp1->pp_ref == 2);
-       assert(pp2->pp_ref == 0);
-
-       // pp2 should be returned by page_alloc
-       assert(page_alloc(&pp) == 0 && pp == pp2);
-
-       // unmapping pp1 at 0 should keep pp1 at PGSIZE
-       page_remove(boot_pgdir, 0x0);
-       assert(check_va2pa(boot_pgdir, 0x0) == ~0);
-       assert(check_va2pa(boot_pgdir, PGSIZE) == page2pa(pp1));
-       assert(pp1->pp_ref == 1);
-       assert(pp2->pp_ref == 0);
-
-       // unmapping pp1 at PGSIZE should free it
-       page_remove(boot_pgdir, (void*) PGSIZE);
-       assert(check_va2pa(boot_pgdir, 0x0) == ~0);
-       assert(check_va2pa(boot_pgdir, PGSIZE) == ~0);
-       assert(pp1->pp_ref == 0);
-       assert(pp2->pp_ref == 0);
-
-       // so it should be returned by page_alloc
-       assert(page_alloc(&pp) == 0 && pp == pp1);
-
-       // should be no free memory
-       assert(page_alloc(&pp) == -E_NO_MEM);
-
-       // forcibly take pp0 back
-       assert(PTE_ADDR(boot_pgdir[0]) == page2pa(pp0));
-       boot_pgdir[0] = 0;
-       assert(pp0->pp_ref == 1);
-       pp0->pp_ref = 0;
-
-       // Catch invalid pointer addition in pgdir_walk - i.e. pgdir + PDX(va)
-       {
-         // Give back pp0 for a bit
-         page_free(pp0);
-
-         void * va = (void *)((PGSIZE * NPDENTRIES) + PGSIZE);
-         pte_t *p2 = pgdir_walk(boot_pgdir, va, 1);
-         pte_t *p = KADDR(PTE_ADDR(boot_pgdir[PDX(va)]));
-         assert(p2 == &p[PTX(va)]);
-
-         // Clean up again
-         boot_pgdir[PDX(va)] = 0;
-         pp0->pp_ref = 0;
+       if (start1 <= start2) {
+               if (end1 <= start2)
+                       return FALSE;
+               return TRUE;
+       } else {
+               if (end2 <= start1)
+                       return FALSE;
+               return TRUE;
        }
-
-       // give free list back
-       page_free_list = fl;
-
-       // free the pages we took
-       page_free(pp0);
-       page_free(pp1);
-       page_free(pp2);
-
-       cprintf("page_check() succeeded!\n");
 }
-
-/* 
-
-    // testing code for boot_pgdir_walk 
-       pte_t* temp;
-       temp = boot_pgdir_walk(pgdir, VPT + (VPT >> 10), 1);
-       cprintf("pgdir = %p\n", pgdir);
-       cprintf("test recursive walking pte_t* = %p\n", temp);
-       cprintf("test recursive walking entry = %p\n", PTE_ADDR(temp));
-       temp = boot_pgdir_walk(pgdir, 0xc0400000, 1);
-       cprintf("LA = 0xc0400000 = %p\n", temp);
-       temp = boot_pgdir_walk(pgdir, 0xc0400070, 1);
-       cprintf("LA = 0xc0400070 = %p\n", temp);
-       temp = boot_pgdir_walk(pgdir, 0xc0800000, 0);
-       cprintf("LA = 0xc0800000, no create = %p\n", temp);
-       temp = boot_pgdir_walk(pgdir, 0xc0600070, 1);
-       cprintf("LA = 0xc0600070 = %p\n", temp);
-       temp = boot_pgdir_walk(pgdir, 0xc0600090, 0);
-       cprintf("LA = 0xc0600090, nc = %p\n", temp);
-       temp = boot_pgdir_walk(pgdir, 0xc0608070, 0);
-       cprintf("LA = 0xc0608070, nc = %p\n", temp);
-       temp = boot_pgdir_walk(pgdir, 0xc0800070, 1);
-       cprintf("LA = 0xc0800070 = %p\n", temp);
-       temp = boot_pgdir_walk(pgdir, 0xc0b00070, 0);
-       cprintf("LA = 0xc0b00070, nc = %p\n", temp);
-       temp = boot_pgdir_walk(pgdir, 0xc0c00000, 0);
-       cprintf("LA = 0xc0c00000, nc = %p\n", temp);
-
-       // testing for boot_map_seg
-       cprintf("\n");
-       cprintf("before mapping 1 page to 0x00350000\n");
-       cprintf("0xc4000000's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xc4000000, 1));
-       cprintf("0xc4000000's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xc4000000, 1)));
-       boot_map_segment(pgdir, 0xc4000000, 4096, 0x00350000, PTE_W);
-       cprintf("after mapping\n");
-       cprintf("0xc4000000's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xc4000000, 1));
-       cprintf("0xc4000000's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xc4000000, 1)));
-
-       cprintf("\n");
-       cprintf("before mapping 3 pages to 0x00700000\n");
-       cprintf("0xd0000000's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xd0000000, 1));
-       cprintf("0xd0000000's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xd0000000, 1)));
-       cprintf("0xd0001000's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xd0001000, 1));
-       cprintf("0xd0001000's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xd0001000, 1)));
-       cprintf("0xd0002000's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xd0002000, 1));
-       cprintf("0xd0002000's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xd0002000, 1)));
-       boot_map_segment(pgdir, 0xd0000000, 4096*3, 0x00700000, 0);
-       cprintf("after mapping\n");
-       cprintf("0xd0000000's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xd0000000, 1));
-       cprintf("0xd0000000's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xd0000000, 1)));
-       cprintf("0xd0001000's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xd0001000, 1));
-       cprintf("0xd0001000's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xd0001000, 1)));
-       cprintf("0xd0002000's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xd0002000, 1));
-       cprintf("0xd0002000's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xd0002000, 1)));
-
-       cprintf("\n");
-       cprintf("before mapping 1 unaligned to 0x00500010\n");
-       cprintf("0xc8000010's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xc8000010, 1));
-       cprintf("0xc8000010's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xc8000010, 1)));
-       cprintf("0xc8001010's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xc8001010, 1));
-       cprintf("0xc8001010's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xc8001010, 1)));
-       boot_map_segment(pgdir, 0xc8000010, 4096, 0x00500010, PTE_W);
-       cprintf("after mapping\n");
-       cprintf("0xc8000010's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xc8000010, 1));
-       cprintf("0xc8000010's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xc8000010, 1)));
-       cprintf("0xc8001010's &pte: %08x\n",boot_pgdir_walk(pgdir, 0xc8001010, 1));
-       cprintf("0xc8001010's pte: %08x\n",*(boot_pgdir_walk(pgdir, 0xc8001010, 1)));
-
-       cprintf("\n");
-       boot_map_segment(pgdir, 0xe0000000, 4096, 0x10000000, PTE_W);
-
-*/