[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/kernel/vm/bootalloc.c b/src/bsp/lk/kernel/vm/bootalloc.c
new file mode 100644
index 0000000..8847bca
--- /dev/null
+++ b/src/bsp/lk/kernel/vm/bootalloc.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <kernel/vm.h>
+#include "vm_priv.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <trace.h>
+
+#define LOCAL_TRACE 0
+
+/* cheezy allocator that chews up space just after the end of the kernel mapping */
+
+/* track how much memory we've used */
+extern int _end;
+
+uintptr_t boot_alloc_start = (uintptr_t)&_end;
+uintptr_t boot_alloc_end = (uintptr_t)&_end;
+
+void *boot_alloc_mem(size_t len)
+{
+    uintptr_t ptr;
+
+    ptr = ALIGN(boot_alloc_end, 8);
+    boot_alloc_end = (ptr + ALIGN(len, 8));
+
+    LTRACEF("len %zu, ptr %p\n", len, (void *)ptr);
+
+    return (void *)ptr;
+}
+
diff --git a/src/bsp/lk/kernel/vm/pmm.c b/src/bsp/lk/kernel/vm/pmm.c
new file mode 100644
index 0000000..3bcdc77
--- /dev/null
+++ b/src/bsp/lk/kernel/vm/pmm.c
@@ -0,0 +1,518 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <kernel/vm.h>
+#include "vm_priv.h"
+
+#include <trace.h>
+#include <assert.h>
+#include <list.h>
+#include <stdlib.h>
+#include <err.h>
+#include <string.h>
+#include <pow2.h>
+#include <lib/console.h>
+#include <kernel/mutex.h>
+
+#define LOCAL_TRACE 0
+
+static struct list_node arena_list = LIST_INITIAL_VALUE(arena_list);
+static mutex_t lock = MUTEX_INITIAL_VALUE(lock);
+
+#define PAGE_BELONGS_TO_ARENA(page, arena) \
+    (((uintptr_t)(page) >= (uintptr_t)(arena)->page_array) && \
+     ((uintptr_t)(page) < ((uintptr_t)(arena)->page_array + (arena)->size / PAGE_SIZE * sizeof(vm_page_t))))
+
+#define PAGE_ADDRESS_FROM_ARENA(page, arena) \
+    (paddr_t)(((uintptr_t)page - (uintptr_t)a->page_array) / sizeof(vm_page_t)) * PAGE_SIZE + a->base;
+
+#define ADDRESS_IN_ARENA(address, arena) \
+    ((address) >= (arena)->base && (address) <= (arena)->base + (arena)->size - 1)
+
+static inline bool page_is_free(const vm_page_t *page)
+{
+    DEBUG_ASSERT(page);
+
+    return !(page->flags & VM_PAGE_FLAG_NONFREE);
+}
+
+paddr_t page_to_address(const vm_page_t *page)
+{
+    DEBUG_ASSERT(page);
+
+    pmm_arena_t *a;
+    list_for_every_entry(&arena_list, a, pmm_arena_t, node) {
+        if (PAGE_BELONGS_TO_ARENA(page, a)) {
+            return PAGE_ADDRESS_FROM_ARENA(page, a);
+        }
+    }
+    return -1;
+}
+
+vm_page_t *address_to_page(paddr_t addr)
+{
+    pmm_arena_t *a;
+    list_for_every_entry(&arena_list, a, pmm_arena_t, node) {
+        if (addr >= a->base && addr <= a->base + a->size - 1) {
+            size_t index = (addr - a->base) / PAGE_SIZE;
+            return &a->page_array[index];
+        }
+    }
+    return NULL;
+}
+
+status_t pmm_add_arena(pmm_arena_t *arena)
+{
+    LTRACEF("arena %p name '%s' base 0x%lx size 0x%zx\n", arena, arena->name, arena->base, arena->size);
+
+    DEBUG_ASSERT(arena);
+    DEBUG_ASSERT(IS_PAGE_ALIGNED(arena->base));
+    DEBUG_ASSERT(IS_PAGE_ALIGNED(arena->size));
+    DEBUG_ASSERT(arena->size > 0);
+
+    /* walk the arena list and add arena based on priority order */
+    pmm_arena_t *a;
+    list_for_every_entry(&arena_list, a, pmm_arena_t, node) {
+        if (a->priority > arena->priority) {
+            list_add_before(&a->node, &arena->node);
+            goto done_add;
+        }
+    }
+
+    /* walked off the end, add it to the end of the list */
+    list_add_tail(&arena_list, &arena->node);
+
+done_add:
+
+    /* zero out some of the structure */
+    arena->free_count = 0;
+    list_initialize(&arena->free_list);
+
+    /* allocate an array of pages to back this one */
+    size_t page_count = arena->size / PAGE_SIZE;
+    arena->page_array = boot_alloc_mem(page_count * sizeof(vm_page_t));
+
+    /* initialize all of the pages */
+    memset(arena->page_array, 0, page_count * sizeof(vm_page_t));
+
+    /* add them to the free list */
+    for (size_t i = 0; i < page_count; i++) {
+        vm_page_t *p = &arena->page_array[i];
+
+        list_add_tail(&arena->free_list, &p->node);
+
+        arena->free_count++;
+    }
+
+    return NO_ERROR;
+}
+
+size_t pmm_alloc_pages(uint count, struct list_node *list)
+{
+    LTRACEF("count %u\n", count);
+
+    uint allocated = 0;
+    if (count == 0)
+        return 0;
+
+    mutex_acquire(&lock);
+
+    /* walk the arenas in order, allocating as many pages as we can from each */
+    pmm_arena_t *a;
+    list_for_every_entry(&arena_list, a, pmm_arena_t, node) {
+        while (allocated < count) {
+            vm_page_t *page = list_remove_head_type(&a->free_list, vm_page_t, node);
+            if (!page)
+                goto done;
+
+            a->free_count--;
+
+            page->flags |= VM_PAGE_FLAG_NONFREE;
+            list_add_tail(list, &page->node);
+
+            allocated++;
+        }
+    }
+
+done:
+    mutex_release(&lock);
+    return allocated;
+}
+
+size_t pmm_alloc_range(paddr_t address, uint count, struct list_node *list)
+{
+    LTRACEF("address 0x%lx, count %u\n", address, count);
+
+    uint allocated = 0;
+    if (count == 0)
+        return 0;
+
+    address = ROUNDDOWN(address, PAGE_SIZE);
+
+    mutex_acquire(&lock);
+
+    /* walk through the arenas, looking to see if the physical page belongs to it */
+    pmm_arena_t *a;
+    list_for_every_entry(&arena_list, a, pmm_arena_t, node) {
+        while (allocated < count && ADDRESS_IN_ARENA(address, a)) {
+            size_t index = (address - a->base) / PAGE_SIZE;
+
+            DEBUG_ASSERT(index < a->size / PAGE_SIZE);
+
+            vm_page_t *page = &a->page_array[index];
+            if (page->flags & VM_PAGE_FLAG_NONFREE) {
+                /* we hit an allocated page */
+                break;
+            }
+
+            DEBUG_ASSERT(list_in_list(&page->node));
+
+            list_delete(&page->node);
+            page->flags |= VM_PAGE_FLAG_NONFREE;
+            list_add_tail(list, &page->node);
+
+            a->free_count--;
+            allocated++;
+            address += PAGE_SIZE;
+        }
+
+        if (allocated == count)
+            break;
+    }
+
+    mutex_release(&lock);
+    return allocated;
+}
+
+size_t pmm_free(struct list_node *list)
+{
+    LTRACEF("list %p\n", list);
+
+    mutex_acquire(&lock);
+
+    uint count = 0;
+    while (!list_is_empty(list)) {
+        vm_page_t *page = list_remove_head_type(list, vm_page_t, node);
+
+        DEBUG_ASSERT(!list_in_list(&page->node));
+        DEBUG_ASSERT(page->flags & VM_PAGE_FLAG_NONFREE);
+
+        /* see which arena this page belongs to and add it */
+        pmm_arena_t *a;
+        list_for_every_entry(&arena_list, a, pmm_arena_t, node) {
+            if (PAGE_BELONGS_TO_ARENA(page, a)) {
+                page->flags &= ~VM_PAGE_FLAG_NONFREE;
+
+                list_add_head(&a->free_list, &page->node);
+                a->free_count++;
+                count++;
+                break;
+            }
+        }
+    }
+
+    mutex_release(&lock);
+    return count;
+}
+
+size_t pmm_free_page(vm_page_t *page)
+{
+    DEBUG_ASSERT(page);
+
+    struct list_node list;
+    list_initialize(&list);
+
+    list_add_head(&list, &page->node);
+
+    return pmm_free(&list);
+}
+
+/* physically allocate a run from arenas marked as KMAP */
+void *pmm_alloc_kpages(uint count, struct list_node *list)
+{
+    LTRACEF("count %u\n", count);
+
+    // XXX do fast path for single page
+
+
+    paddr_t pa;
+    size_t alloc_count = pmm_alloc_contiguous(count, PAGE_SIZE_SHIFT, &pa, list);
+    if (alloc_count == 0)
+        return NULL;
+
+    return paddr_to_kvaddr(pa);
+}
+
+size_t pmm_free_kpages(void *_ptr, uint count)
+{
+    LTRACEF("ptr %p, count %u\n", _ptr, count);
+
+    uint8_t *ptr = (uint8_t *)_ptr;
+
+    struct list_node list;
+    list_initialize(&list);
+
+    while (count > 0) {
+        vm_page_t *p = address_to_page(kvaddr_to_paddr(ptr));
+        if (p) {
+            list_add_tail(&list, &p->node);
+        }
+
+        ptr += PAGE_SIZE;
+        count--;
+    }
+
+    return pmm_free(&list);
+}
+
+size_t pmm_alloc_contiguous(uint count, uint8_t alignment_log2, paddr_t *pa, struct list_node *list)
+{
+    LTRACEF("count %u, align %u\n", count, alignment_log2);
+
+    if (count == 0)
+        return 0;
+    if (alignment_log2 < PAGE_SIZE_SHIFT)
+        alignment_log2 = PAGE_SIZE_SHIFT;
+
+    mutex_acquire(&lock);
+
+    pmm_arena_t *a;
+    list_for_every_entry(&arena_list, a, pmm_arena_t, node) {
+        // XXX make this a flag to only search kmap?
+        if (a->flags & PMM_ARENA_FLAG_KMAP) {
+            /* walk the list starting at alignment boundaries.
+             * calculate the starting offset into this arena, based on the
+             * base address of the arena to handle the case where the arena
+             * is not aligned on the same boundary requested.
+             */
+            paddr_t rounded_base = ROUNDUP(a->base, 1UL << alignment_log2);
+            if (rounded_base < a->base || rounded_base > a->base + a->size - 1)
+                continue;
+
+            uint aligned_offset = (rounded_base - a->base) / PAGE_SIZE;
+            uint start = aligned_offset;
+            LTRACEF("starting search at aligned offset %u\n", start);
+            LTRACEF("arena base 0x%lx size %zu\n", a->base, a->size);
+
+retry:
+            /* search while we're still within the arena and have a chance of finding a slot
+               (start + count < end of arena) */
+            while ((start < a->size / PAGE_SIZE) &&
+                   ((start + count) <= a->size / PAGE_SIZE)) {
+                vm_page_t *p = &a->page_array[start];
+                for (uint i = 0; i < count; i++) {
+                    if (p->flags & VM_PAGE_FLAG_NONFREE) {
+                        /* this run is broken, break out of the inner loop.
+                         * start over at the next alignment boundary
+                         */
+                        start = ROUNDUP(start - aligned_offset + i + 1, 1UL << (alignment_log2 - PAGE_SIZE_SHIFT)) + aligned_offset;
+                        goto retry;
+                    }
+                    p++;
+                }
+
+                /* we found a run */
+                LTRACEF("found run from pn %u to %u\n", start, start + count);
+
+                /* remove the pages from the run out of the free list */
+                for (uint i = start; i < start + count; i++) {
+                    p = &a->page_array[i];
+                    DEBUG_ASSERT(!(p->flags & VM_PAGE_FLAG_NONFREE));
+                    DEBUG_ASSERT(list_in_list(&p->node));
+
+                    list_delete(&p->node);
+                    p->flags |= VM_PAGE_FLAG_NONFREE;
+                    a->free_count--;
+
+                    if (list)
+                        list_add_tail(list, &p->node);
+                }
+
+                if (pa)
+                    *pa = a->base + start * PAGE_SIZE;
+
+                mutex_release(&lock);
+
+                return count;
+            }
+        }
+    }
+
+    mutex_release(&lock);
+
+    LTRACEF("couldn't find run\n");
+    return 0;
+}
+
+static void dump_page(const vm_page_t *page)
+{
+    DEBUG_ASSERT(page);
+
+    printf("page %p: address 0x%lx flags 0x%x\n", page, page_to_address(page), page->flags);
+}
+
+static void dump_arena(const pmm_arena_t *arena, bool dump_pages)
+{
+    DEBUG_ASSERT(arena);
+
+    printf("arena %p: name '%s' base 0x%lx size 0x%zx priority %u flags 0x%x\n",
+           arena, arena->name, arena->base, arena->size, arena->priority, arena->flags);
+    printf("\tpage_array %p, free_count %zu\n",
+           arena->page_array, arena->free_count);
+
+    /* dump all of the pages */
+    if (dump_pages) {
+        for (size_t i = 0; i < arena->size / PAGE_SIZE; i++) {
+            dump_page(&arena->page_array[i]);
+        }
+    }
+
+    /* dump the free pages */
+    printf("\tfree ranges:\n");
+    ssize_t last = -1;
+    for (size_t i = 0; i < arena->size / PAGE_SIZE; i++) {
+        if (page_is_free(&arena->page_array[i])) {
+            if (last == -1) {
+                last = i;
+            }
+        } else {
+            if (last != -1) {
+                printf("\t\t0x%lx - 0x%lx\n", arena->base + last * PAGE_SIZE, arena->base + i * PAGE_SIZE);
+            }
+            last = -1;
+        }
+    }
+
+    if (last != -1) {
+        printf("\t\t0x%lx - 0x%lx\n",  arena->base + last * PAGE_SIZE, arena->base + arena->size);
+    }
+}
+
+static int cmd_pmm(int argc, const cmd_args *argv)
+{
+    if (argc < 2) {
+notenoughargs:
+        printf("not enough arguments\n");
+usage:
+        printf("usage:\n");
+        printf("%s arenas\n", argv[0].str);
+        printf("%s alloc <count>\n", argv[0].str);
+        printf("%s alloc_range <address> <count>\n", argv[0].str);
+        printf("%s alloc_kpages <count>\n", argv[0].str);
+        printf("%s alloc_contig <count> <alignment>\n", argv[0].str);
+        printf("%s dump_alloced\n", argv[0].str);
+        printf("%s free_alloced\n", argv[0].str);
+        return ERR_GENERIC;
+    }
+
+    static struct list_node allocated = LIST_INITIAL_VALUE(allocated);
+
+    if (!strcmp(argv[1].str, "arenas")) {
+        pmm_arena_t *a;
+        list_for_every_entry(&arena_list, a, pmm_arena_t, node) {
+            dump_arena(a, false);
+        }
+    } else if (!strcmp(argv[1].str, "alloc")) {
+        if (argc < 3) goto notenoughargs;
+
+        struct list_node list;
+        list_initialize(&list);
+
+        uint count = pmm_alloc_pages(argv[2].u, &list);
+        printf("alloc returns %u\n", count);
+
+        vm_page_t *p;
+        list_for_every_entry(&list, p, vm_page_t, node) {
+            printf("\tpage %p, address 0x%lx\n", p, page_to_address(p));
+        }
+
+        /* add the pages to the local allocated list */
+        struct list_node *node;
+        while ((node = list_remove_head(&list))) {
+            list_add_tail(&allocated, node);
+        }
+    } else if (!strcmp(argv[1].str, "dump_alloced")) {
+        vm_page_t *page;
+
+        list_for_every_entry(&allocated, page, vm_page_t, node) {
+            dump_page(page);
+        }
+    } else if (!strcmp(argv[1].str, "alloc_range")) {
+        if (argc < 4) goto notenoughargs;
+
+        struct list_node list;
+        list_initialize(&list);
+
+        uint count = pmm_alloc_range(argv[2].u, argv[3].u, &list);
+        printf("alloc returns %u\n", count);
+
+        vm_page_t *p;
+        list_for_every_entry(&list, p, vm_page_t, node) {
+            printf("\tpage %p, address 0x%lx\n", p, page_to_address(p));
+        }
+
+        /* add the pages to the local allocated list */
+        struct list_node *node;
+        while ((node = list_remove_head(&list))) {
+            list_add_tail(&allocated, node);
+        }
+    } else if (!strcmp(argv[1].str, "alloc_kpages")) {
+        if (argc < 3) goto notenoughargs;
+
+        void *ptr = pmm_alloc_kpages(argv[2].u, NULL);
+        printf("pmm_alloc_kpages returns %p\n", ptr);
+    } else if (!strcmp(argv[1].str, "alloc_contig")) {
+        if (argc < 4) goto notenoughargs;
+
+        struct list_node list;
+        list_initialize(&list);
+
+        paddr_t pa;
+        size_t ret = pmm_alloc_contiguous(argv[2].u, argv[3].u, &pa, &list);
+        printf("pmm_alloc_contiguous returns %zu, address 0x%lx\n", ret, pa);
+        printf("address %% align = 0x%lx\n", pa % argv[3].u);
+
+        /* add the pages to the local allocated list */
+        struct list_node *node;
+        while ((node = list_remove_head(&list))) {
+            list_add_tail(&allocated, node);
+        }
+    } else if (!strcmp(argv[1].str, "free_alloced")) {
+        size_t err = pmm_free(&allocated);
+        printf("pmm_free returns %zu\n", err);
+    } else {
+        printf("unknown command\n");
+        goto usage;
+    }
+
+    return NO_ERROR;
+}
+
+STATIC_COMMAND_START
+#if LK_DEBUGLEVEL > 0
+STATIC_COMMAND("pmm", "physical memory manager", &cmd_pmm)
+#endif
+STATIC_COMMAND_END(pmm);
+
+
+
+
diff --git a/src/bsp/lk/kernel/vm/rules.mk b/src/bsp/lk/kernel/vm/rules.mk
new file mode 100644
index 0000000..d511c1c
--- /dev/null
+++ b/src/bsp/lk/kernel/vm/rules.mk
@@ -0,0 +1,11 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/bootalloc.c \
+	$(LOCAL_DIR)/pmm.c \
+	$(LOCAL_DIR)/vm.c \
+	$(LOCAL_DIR)/vmm.c \
+
+include make/module.mk
diff --git a/src/bsp/lk/kernel/vm/vm.c b/src/bsp/lk/kernel/vm/vm.c
new file mode 100644
index 0000000..a5c7aba
--- /dev/null
+++ b/src/bsp/lk/kernel/vm/vm.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <kernel/vm.h>
+#include "vm_priv.h"
+
+#include <trace.h>
+#include <err.h>
+#include <string.h>
+#include <lk/init.h>
+#include <lib/console.h>
+#include <arch/mmu.h>
+#include <debug.h>
+
+#define LOCAL_TRACE 0
+
+extern int _start;
+extern int _end;
+
+/* mark the physical pages backing a range of virtual as in use.
+ * allocate the physical pages and throw them away */
+static void mark_pages_in_use(vaddr_t va, size_t len)
+{
+    LTRACEF("va 0x%lx, len 0x%zx\n", va, len);
+
+    struct list_node list;
+    list_initialize(&list);
+
+    /* make sure we are inclusive of all of the pages in the address range */
+    len = PAGE_ALIGN(len + (va & (PAGE_SIZE - 1)));
+    va = ROUNDDOWN(va, PAGE_SIZE);
+
+    LTRACEF("aligned va 0x%lx, len 0x%zx\n", va, len);
+
+    for (size_t offset = 0; offset < len; offset += PAGE_SIZE) {
+        uint flags;
+        paddr_t pa;
+
+        status_t err = arch_mmu_query(va + offset, &pa, &flags);
+        if (err >= 0) {
+            //LTRACEF("va 0x%x, pa 0x%x, flags 0x%x, err %d\n", va + offset, pa, flags, err);
+
+            /* alloate the range, throw the results away */
+            pmm_alloc_range(pa, 1, &list);
+        } else {
+            panic("Could not find pa for va 0x%lx\n", va);
+        }
+    }
+}
+
+static void vm_init_preheap(uint level)
+{
+    LTRACE_ENTRY;
+
+    /* mark all of the kernel pages in use */
+    LTRACEF("marking all kernel pages as used\n");
+    mark_pages_in_use((vaddr_t)&_start, ((uintptr_t)&_end - (uintptr_t)&_start));
+
+    /* mark the physical pages used by the boot time allocator */
+    if (boot_alloc_end != boot_alloc_start) {
+        LTRACEF("marking boot alloc used from 0x%lx to 0x%lx\n", boot_alloc_start, boot_alloc_end);
+
+        mark_pages_in_use(boot_alloc_start, boot_alloc_end - boot_alloc_start);
+    }
+}
+
+static void vm_init_postheap(uint level)
+{
+    LTRACE_ENTRY;
+
+    vmm_init();
+
+    /* create vmm regions to cover what is already there from the initial mapping table */
+    struct mmu_initial_mapping *map = mmu_initial_mappings;
+    while (map->size > 0) {
+        if (!(map->flags & MMU_INITIAL_MAPPING_TEMPORARY)) {
+            vmm_reserve_space(vmm_get_kernel_aspace(), map->name, map->size, map->virt);
+        }
+
+        map++;
+    }
+}
+
+void *paddr_to_kvaddr(paddr_t pa)
+{
+    /* slow path to do reverse lookup */
+    struct mmu_initial_mapping *map = mmu_initial_mappings;
+    while (map->size > 0) {
+        if (!(map->flags & MMU_INITIAL_MAPPING_TEMPORARY) &&
+            pa >= map->phys &&
+            pa <= map->phys + map->size - 1) {
+            return (void *)(map->virt + (pa - map->phys));
+        }
+        map++;
+    }
+    return NULL;
+}
+
+paddr_t kvaddr_to_paddr(void *ptr)
+{
+    status_t rc;
+    paddr_t  pa;
+
+    rc = arch_mmu_query((vaddr_t)ptr, &pa, NULL);
+    if (rc)
+        return (paddr_t) NULL;
+    return pa;
+}
+
+static int cmd_vm(int argc, const cmd_args *argv)
+{
+    if (argc < 2) {
+notenoughargs:
+        printf("not enough arguments\n");
+usage:
+        printf("usage:\n");
+        printf("%s phys2virt <address>\n", argv[0].str);
+        printf("%s virt2phys <address>\n", argv[0].str);
+        printf("%s map <phys> <virt> <count> <flags>\n", argv[0].str);
+        printf("%s unmap <virt> <count>\n", argv[0].str);
+        return ERR_GENERIC;
+    }
+
+    if (!strcmp(argv[1].str, "phys2virt")) {
+        if (argc < 3) goto notenoughargs;
+
+        void *ptr = paddr_to_kvaddr(argv[2].u);
+        printf("paddr_to_kvaddr returns %p\n", ptr);
+    } else if (!strcmp(argv[1].str, "virt2phys")) {
+        if (argc < 3) goto notenoughargs;
+
+        paddr_t pa;
+        uint flags;
+        status_t err = arch_mmu_query(argv[2].u, &pa, &flags);
+        printf("arch_mmu_query returns %d\n", err);
+        if (err >= 0) {
+            printf("\tpa 0x%lx, flags 0x%x\n", pa, flags);
+        }
+    } else if (!strcmp(argv[1].str, "map")) {
+        if (argc < 6) goto notenoughargs;
+
+        int err = arch_mmu_map(argv[3].u, argv[2].u, argv[4].u, argv[5].u);
+        printf("arch_mmu_map returns %d\n", err);
+    } else if (!strcmp(argv[1].str, "unmap")) {
+        if (argc < 4) goto notenoughargs;
+
+        int err = arch_mmu_unmap(argv[2].u, argv[3].u);
+        printf("arch_mmu_unmap returns %d\n", err);
+    } else {
+        printf("unknown command\n");
+        goto usage;
+    }
+
+    return NO_ERROR;
+}
+
+STATIC_COMMAND_START
+#if LK_DEBUGLEVEL > 0
+STATIC_COMMAND("vm", "vm commands", &cmd_vm)
+#endif
+STATIC_COMMAND_END(vm);
+
+LK_INIT_HOOK(vm_preheap, &vm_init_preheap, LK_INIT_LEVEL_HEAP - 1);
+LK_INIT_HOOK(vm, &vm_init_postheap, LK_INIT_LEVEL_VM);
diff --git a/src/bsp/lk/kernel/vm/vm_priv.h b/src/bsp/lk/kernel/vm/vm_priv.h
new file mode 100644
index 0000000..939a5bf
--- /dev/null
+++ b/src/bsp/lk/kernel/vm/vm_priv.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#pragma once
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <kernel/vm.h>
+
+/* simple boot time allocator */
+void *boot_alloc_mem(size_t len) __MALLOC;
+extern uintptr_t boot_alloc_start;
+extern uintptr_t boot_alloc_end;
+
+paddr_t page_to_address(const vm_page_t *page);
+vm_page_t *address_to_page(paddr_t addr);
+
+void vmm_init(void);
+
diff --git a/src/bsp/lk/kernel/vm/vmm.c b/src/bsp/lk/kernel/vm/vmm.c
new file mode 100644
index 0000000..978d0f3
--- /dev/null
+++ b/src/bsp/lk/kernel/vm/vmm.c
@@ -0,0 +1,756 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <trace.h>
+#include <assert.h>
+#include <err.h>
+#include <string.h>
+#include <lib/console.h>
+#include <kernel/vm.h>
+#include <kernel/mutex.h>
+#include "vm_priv.h"
+
+#define LOCAL_TRACE 0
+
+static struct list_node aspace_list = LIST_INITIAL_VALUE(aspace_list);
+static mutex_t vmm_lock = MUTEX_INITIAL_VALUE(vmm_lock);
+
+vmm_aspace_t _kernel_aspace;
+
+static void dump_aspace(const vmm_aspace_t *a);
+static void dump_region(const vmm_region_t *r);
+
+void vmm_init(void)
+{
+    /* initialize the kernel address space */
+    strlcpy(_kernel_aspace.name, "kernel", sizeof(_kernel_aspace.name));
+    _kernel_aspace.base = KERNEL_ASPACE_BASE,
+    _kernel_aspace.size = KERNEL_ASPACE_SIZE,
+    _kernel_aspace.flags = VMM_FLAG_ASPACE_KERNEL;
+    list_initialize(&_kernel_aspace.region_list);
+
+    list_add_head(&aspace_list, &_kernel_aspace.node);
+}
+
+static inline bool is_inside_aspace(const vmm_aspace_t *aspace, vaddr_t vaddr)
+{
+    DEBUG_ASSERT(aspace);
+
+    return (vaddr >= aspace->base && vaddr <= aspace->base + aspace->size - 1);
+}
+
+static bool is_region_inside_aspace(const vmm_aspace_t *aspace, vaddr_t vaddr, size_t size)
+{
+    DEBUG_ASSERT(aspace);
+
+    /* is the starting address within the address space*/
+    if (!is_inside_aspace(aspace, vaddr))
+        return false;
+
+    if (size == 0)
+        return true;
+
+    /* see if the size is enough to wrap the integer */
+    if (vaddr + size - 1 < vaddr)
+        return false;
+
+    /* test to see if the end address is within the address space's */
+    if (vaddr + size - 1 > aspace->base + aspace->size - 1)
+        return false;
+
+    return true;
+}
+
+static size_t trim_to_aspace(const vmm_aspace_t *aspace, vaddr_t vaddr, size_t size)
+{
+    DEBUG_ASSERT(aspace);
+    DEBUG_ASSERT(is_inside_aspace(aspace, vaddr));
+
+    if (size == 0)
+        return size;
+
+    size_t offset = vaddr - aspace->base;
+
+    //LTRACEF("vaddr 0x%lx size 0x%zx offset 0x%zx aspace base 0x%lx aspace size 0x%zx\n",
+    //        vaddr, size, offset, aspace->base, aspace->size);
+
+    if (offset + size < offset)
+        size = ULONG_MAX - offset - 1;
+
+    //LTRACEF("size now 0x%zx\n", size);
+
+    if (offset + size >= aspace->size - 1)
+        size = aspace->size - offset;
+
+    //LTRACEF("size now 0x%zx\n", size);
+
+    return size;
+}
+
+static vmm_region_t *alloc_region_struct(const char *name, vaddr_t base, size_t size, uint flags, uint arch_mmu_flags)
+{
+    DEBUG_ASSERT(name);
+
+    vmm_region_t *r = malloc(sizeof(vmm_region_t));
+    if (!r)
+        return NULL;
+
+    strlcpy(r->name, name, sizeof(r->name));
+    r->base = base;
+    r->size = size;
+    r->flags = flags;
+    r->arch_mmu_flags = arch_mmu_flags;
+    list_initialize(&r->page_list);
+
+    return r;
+}
+
+/* add a region to the appropriate spot in the address space list,
+ * testing to see if there's a space */
+static status_t add_region_to_aspace(vmm_aspace_t *aspace, vmm_region_t *r)
+{
+    DEBUG_ASSERT(aspace);
+    DEBUG_ASSERT(r);
+
+    LTRACEF("aspace %p base 0x%lx size 0x%zx r %p base 0x%lx size 0x%zx\n",
+            aspace, aspace->base, aspace->size, r, r->base, r->size);
+
+    /* only try if the region will at least fit in the address space */
+    if (r->size == 0 || !is_region_inside_aspace(aspace, r->base, r->size)) {
+        LTRACEF("region was out of range\n");
+        return ERR_OUT_OF_RANGE;
+    }
+
+    vaddr_t r_end = r->base + r->size - 1;
+
+    /* does it fit in front */
+    vmm_region_t *last;
+    last = list_peek_head_type(&aspace->region_list, vmm_region_t, node);
+    if (!last || r_end < last->base) {
+        /* empty list or not empty and fits before the first element */
+        list_add_head(&aspace->region_list, &r->node);
+        return NO_ERROR;
+    }
+
+    /* walk the list, finding the right spot to put it */
+    list_for_every_entry(&aspace->region_list, last, vmm_region_t, node) {
+        /* does it go after last? */
+        if (r->base > last->base + last->size - 1) {
+            /* get the next element in the list */
+            vmm_region_t *next = list_next_type(&aspace->region_list, &last->node, vmm_region_t, node);
+            if (!next || (r_end < next->base)) {
+                /* end of the list or next exists and it goes between them */
+                list_add_after(&last->node, &r->node);
+                return NO_ERROR;
+            }
+        }
+    }
+
+    LTRACEF("couldn't find spot\n");
+    return ERR_NO_MEMORY;
+}
+
+/*
+ *  Try to pick the spot within specified gap
+ *
+ *  Arch can override this to impose it's own restrictions.
+ */
+__WEAK vaddr_t arch_mmu_pick_spot(vaddr_t base, uint prev_region_arch_mmu_flags,
+                                  vaddr_t end,  uint next_region_arch_mmu_flags,
+                                  vaddr_t align, size_t size, uint arch_mmu_flags)
+{
+    /* just align it by default */
+    return ALIGN(base, align);
+}
+
+/*
+ *  Returns true if the caller has to stop search
+ */
+static inline bool check_gap(vmm_aspace_t *aspace,
+                             vmm_region_t *prev, vmm_region_t *next,
+                             vaddr_t *pva, vaddr_t align, size_t size,
+                             uint arch_mmu_flags)
+{
+    vaddr_t gap_beg; /* first byte of a gap */
+    vaddr_t gap_end; /* last byte of a gap */
+
+    DEBUG_ASSERT(aspace);
+    DEBUG_ASSERT(pva);
+
+    if (prev)
+        gap_beg = prev->base + prev->size;
+    else
+        gap_beg = aspace->base;
+
+    if (next) {
+        if (gap_beg == next->base)
+            goto next_gap;  /* no gap between regions */
+        gap_end = next->base - 1;
+    } else {
+        if (gap_beg == (aspace->base + aspace->size))
+            goto not_found;  /* no gap at the end of address space. Stop search */
+        gap_end = aspace->base + aspace->size - 1;
+    }
+
+    *pva = arch_mmu_pick_spot(gap_beg, prev ? prev->arch_mmu_flags : ARCH_MMU_FLAG_INVALID,
+                              gap_end, next ? next->arch_mmu_flags : ARCH_MMU_FLAG_INVALID,
+                              align, size, arch_mmu_flags);
+    if (*pva < gap_beg)
+        goto not_found; /* address wrapped around */
+
+    if (*pva < gap_end && ((gap_end - *pva + 1) >= size)) {
+        /* we have enough room */
+        return true; /* found spot, stop search */
+    }
+
+next_gap:
+    return false; /* continue search */
+
+not_found:
+    *pva = -1;
+    return true; /* not_found: stop search */
+}
+
+static vaddr_t alloc_spot(vmm_aspace_t *aspace, size_t size, uint8_t align_pow2,
+                          uint arch_mmu_flags, struct list_node **before)
+{
+    DEBUG_ASSERT(aspace);
+    DEBUG_ASSERT(size > 0 && IS_PAGE_ALIGNED(size));
+
+    LTRACEF("aspace %p size 0x%zx align %hhu\n", aspace, size, align_pow2);
+
+    if (align_pow2 < PAGE_SIZE_SHIFT)
+        align_pow2 = PAGE_SIZE_SHIFT;
+    vaddr_t align = 1UL << align_pow2;
+
+    vaddr_t spot;
+    vmm_region_t *r = NULL;
+
+    /* try to pick spot at the beginning of address space */
+    if (check_gap(aspace, NULL,
+                  list_peek_head_type(&aspace->region_list, vmm_region_t, node),
+                  &spot, align, size, arch_mmu_flags))
+        goto done;
+
+    /* search the middle of the list */
+    list_for_every_entry(&aspace->region_list, r, vmm_region_t, node) {
+        if (check_gap(aspace, r,
+                      list_next_type(&aspace->region_list, &r->node, vmm_region_t, node),
+                      &spot, align, size, arch_mmu_flags))
+            goto done;
+    }
+
+    /* couldn't find anything */
+    return -1;
+
+done:
+    if (before)
+        *before = r ? &r->node : &aspace->region_list;
+    return spot;
+}
+
+/* allocate a region structure and stick it in the address space */
+static vmm_region_t *alloc_region(vmm_aspace_t *aspace, const char *name, size_t size,
+        vaddr_t vaddr, uint8_t align_pow2,
+        uint vmm_flags, uint region_flags, uint arch_mmu_flags)
+{
+    /* make a region struct for it and stick it in the list */
+    vmm_region_t *r = alloc_region_struct(name, vaddr, size, region_flags, arch_mmu_flags);
+    if (!r)
+        return NULL;
+
+    /* if they ask us for a specific spot, put it there */
+    if (vmm_flags & VMM_FLAG_VALLOC_SPECIFIC) {
+        /* stick it in the list, checking to see if it fits */
+        if (add_region_to_aspace(aspace, r) < 0) {
+            /* didn't fit */
+            free(r);
+            return NULL;
+        }
+    } else {
+        /* allocate a virtual slot for it */
+        struct list_node *before = NULL;
+
+        vaddr = alloc_spot(aspace, size, align_pow2, arch_mmu_flags, &before);
+        LTRACEF("alloc_spot returns 0x%lx, before %p\n", vaddr, before);
+
+        if (vaddr == (vaddr_t)-1) {
+            LTRACEF("failed to find spot\n");
+            free(r);
+            return NULL;
+        }
+
+        DEBUG_ASSERT(before != NULL);
+
+        r->base = (vaddr_t)vaddr;
+
+        /* add it to the region list */
+        list_add_after(before, &r->node);
+    }
+
+    return r;
+}
+
+status_t vmm_reserve_space(vmm_aspace_t *aspace, const char *name, size_t size, vaddr_t vaddr)
+{
+    LTRACEF("aspace %p name '%s' size 0x%zx vaddr 0x%lx\n", aspace, name, size, vaddr);
+
+    DEBUG_ASSERT(IS_PAGE_ALIGNED(vaddr));
+    DEBUG_ASSERT(IS_PAGE_ALIGNED(size));
+
+    if (!name)
+        name = "";
+
+    if (size == 0)
+        return NO_ERROR;
+    if (!IS_PAGE_ALIGNED(vaddr) || !IS_PAGE_ALIGNED(size))
+        return ERR_INVALID_ARGS;
+
+    if (!is_inside_aspace(aspace, vaddr))
+        return ERR_OUT_OF_RANGE;
+
+    /* trim the size */
+    size = trim_to_aspace(aspace, vaddr, size);
+
+    mutex_acquire(&vmm_lock);
+
+    /* lookup how it's already mapped */
+    uint arch_mmu_flags = 0;
+    arch_mmu_query(vaddr, NULL, &arch_mmu_flags);
+
+    /* build a new region structure */
+    vmm_region_t *r = alloc_region(aspace, name, size, vaddr, 0, VMM_FLAG_VALLOC_SPECIFIC, VMM_REGION_FLAG_RESERVED, arch_mmu_flags);
+
+    mutex_release(&vmm_lock);
+    return r ? NO_ERROR : ERR_NO_MEMORY;
+}
+
+status_t vmm_alloc_physical(vmm_aspace_t *aspace, const char *name, size_t size, void **ptr, uint8_t align_log2, paddr_t paddr, uint vmm_flags, uint arch_mmu_flags)
+{
+    status_t ret;
+
+    LTRACEF("aspace %p name '%s' size 0x%zx ptr %p paddr 0x%lx vmm_flags 0x%x arch_mmu_flags 0x%x\n",
+            aspace, name, size, ptr ? *ptr : 0, paddr, vmm_flags, arch_mmu_flags);
+
+    DEBUG_ASSERT(IS_PAGE_ALIGNED(paddr));
+    DEBUG_ASSERT(IS_PAGE_ALIGNED(size));
+
+    if (!name)
+        name = "";
+
+    if (size == 0)
+        return NO_ERROR;
+    if (!IS_PAGE_ALIGNED(paddr) || !IS_PAGE_ALIGNED(size))
+        return ERR_INVALID_ARGS;
+
+    vaddr_t vaddr = 0;
+
+    /* if they're asking for a specific spot, copy the address */
+    if (vmm_flags & VMM_FLAG_VALLOC_SPECIFIC) {
+        /* can't ask for a specific spot and then not provide one */
+        if (!ptr) {
+            return ERR_INVALID_ARGS;
+        }
+        vaddr = (vaddr_t)*ptr;
+    }
+
+    mutex_acquire(&vmm_lock);
+
+    /* allocate a region and put it in the aspace list */
+    vmm_region_t *r = alloc_region(aspace, name, size, vaddr, align_log2, vmm_flags,
+            VMM_REGION_FLAG_PHYSICAL, arch_mmu_flags);
+    if (!r) {
+        ret = ERR_NO_MEMORY;
+        goto err_alloc_region;
+    }
+
+    /* return the vaddr if requested */
+    if (ptr)
+        *ptr = (void *)r->base;
+
+    /* map all of the pages */
+    int err = arch_mmu_map(r->base, paddr, size / PAGE_SIZE, arch_mmu_flags);
+    LTRACEF("arch_mmu_map returns %d\n", err);
+
+    ret = NO_ERROR;
+
+err_alloc_region:
+    mutex_release(&vmm_lock);
+    return ret;
+}
+
+status_t vmm_alloc_contiguous(vmm_aspace_t *aspace, const char *name, size_t size, void **ptr,
+        uint8_t align_pow2, uint vmm_flags, uint arch_mmu_flags)
+{
+    status_t err = NO_ERROR;
+
+    LTRACEF("aspace %p name '%s' size 0x%zx ptr %p align %hhu vmm_flags 0x%x arch_mmu_flags 0x%x\n",
+            aspace, name, size, ptr ? *ptr : 0, align_pow2, vmm_flags, arch_mmu_flags);
+
+    size = ROUNDUP(size, PAGE_SIZE);
+    if (size == 0)
+        return ERR_INVALID_ARGS;
+
+    if (!name)
+        name = "";
+
+    vaddr_t vaddr = 0;
+
+    /* if they're asking for a specific spot, copy the address */
+    if (vmm_flags & VMM_FLAG_VALLOC_SPECIFIC) {
+        /* can't ask for a specific spot and then not provide one */
+        if (!ptr) {
+            err = ERR_INVALID_ARGS;
+            goto err;
+        }
+        vaddr = (vaddr_t)*ptr;
+    }
+
+    /* allocate physical memory up front, in case it cant be satisfied */
+    struct list_node page_list;
+    list_initialize(&page_list);
+
+    paddr_t pa = 0;
+    /* allocate a run of physical pages */
+    size_t count = pmm_alloc_contiguous(size / PAGE_SIZE, align_pow2, &pa, &page_list);
+    if (count < size / PAGE_SIZE) {
+        DEBUG_ASSERT(count == 0); /* check that the pmm didn't allocate a partial run */
+        err = ERR_NO_MEMORY;
+        goto err;
+    }
+
+    mutex_acquire(&vmm_lock);
+
+    /* allocate a region and put it in the aspace list */
+    vmm_region_t *r = alloc_region(aspace, name, size, vaddr, align_pow2, vmm_flags,
+            VMM_REGION_FLAG_PHYSICAL, arch_mmu_flags);
+    if (!r) {
+        err = ERR_NO_MEMORY;
+        goto err1;
+    }
+
+    /* return the vaddr if requested */
+    if (ptr)
+        *ptr = (void *)r->base;
+
+    /* map all of the pages */
+    arch_mmu_map(r->base, pa, size / PAGE_SIZE, arch_mmu_flags);
+    // XXX deal with error mapping here
+
+    vm_page_t *p;
+    while ((p = list_remove_head_type(&page_list, vm_page_t, node))) {
+        list_add_tail(&r->page_list, &p->node);
+    }
+
+    mutex_release(&vmm_lock);
+    return NO_ERROR;
+
+err1:
+    mutex_release(&vmm_lock);
+    pmm_free(&page_list);
+err:
+    return err;
+}
+
+status_t vmm_alloc(vmm_aspace_t *aspace, const char *name, size_t size, void **ptr,
+        uint8_t align_pow2, uint vmm_flags, uint arch_mmu_flags)
+{
+    status_t err = NO_ERROR;
+
+    LTRACEF("aspace %p name '%s' size 0x%zx ptr %p align %hhu vmm_flags 0x%x arch_mmu_flags 0x%x\n",
+            aspace, name, size, ptr ? *ptr : 0, align_pow2, vmm_flags, arch_mmu_flags);
+
+    size = ROUNDUP(size, PAGE_SIZE);
+    if (size == 0)
+        return ERR_INVALID_ARGS;
+
+    if (!name)
+        name = "";
+
+    vaddr_t vaddr = 0;
+
+    /* if they're asking for a specific spot, copy the address */
+    if (vmm_flags & VMM_FLAG_VALLOC_SPECIFIC) {
+        /* can't ask for a specific spot and then not provide one */
+        if (!ptr) {
+            err = ERR_INVALID_ARGS;
+            goto err;
+        }
+        vaddr = (vaddr_t)*ptr;
+    }
+
+    /* allocate physical memory up front, in case it cant be satisfied */
+
+    /* allocate a random pile of pages */
+    struct list_node page_list;
+    list_initialize(&page_list);
+
+    size_t count = pmm_alloc_pages(size / PAGE_SIZE, &page_list);
+    DEBUG_ASSERT(count <= size);
+    if (count < size / PAGE_SIZE) {
+        LTRACEF("failed to allocate enough pages (asked for %zu, got %zu)\n", size / PAGE_SIZE, count);
+        pmm_free(&page_list);
+        err = ERR_NO_MEMORY;
+        goto err;
+    }
+
+    mutex_acquire(&vmm_lock);
+
+    /* allocate a region and put it in the aspace list */
+    vmm_region_t *r = alloc_region(aspace, name, size, vaddr, align_pow2, vmm_flags,
+            VMM_REGION_FLAG_PHYSICAL, arch_mmu_flags);
+    if (!r) {
+        err = ERR_NO_MEMORY;
+        goto err1;
+    }
+
+    /* return the vaddr if requested */
+    if (ptr)
+        *ptr = (void *)r->base;
+
+    /* map all of the pages */
+    /* XXX use smarter algorithm that tries to build runs */
+    vm_page_t *p;
+    vaddr_t va = r->base;
+    DEBUG_ASSERT(IS_PAGE_ALIGNED(va));
+    while ((p = list_remove_head_type(&page_list, vm_page_t, node))) {
+        DEBUG_ASSERT(va <= r->base + r->size - 1);
+
+        paddr_t pa = page_to_address(p);
+        DEBUG_ASSERT(IS_PAGE_ALIGNED(pa));
+
+        arch_mmu_map(va, pa, 1, arch_mmu_flags);
+        // XXX deal with error mapping here
+
+        list_add_tail(&r->page_list, &p->node);
+
+        va += PAGE_SIZE;
+    }
+
+    mutex_release(&vmm_lock);
+    return NO_ERROR;
+
+err1:
+    mutex_release(&vmm_lock);
+    pmm_free(&page_list);
+err:
+    return err;
+}
+
+static vmm_region_t *vmm_find_region(const vmm_aspace_t *aspace, vaddr_t vaddr)
+{
+    vmm_region_t *r;
+
+    DEBUG_ASSERT(aspace);
+
+    if (!aspace)
+        return NULL;
+
+    /* search the region list */
+    list_for_every_entry(&aspace->region_list, r, vmm_region_t, node) {
+        if ((vaddr >= r->base) && (vaddr <= r->base + r->size - 1))
+            return r;
+    }
+
+    return NULL;
+}
+
+status_t vmm_free_region(vmm_aspace_t *aspace, vaddr_t vaddr)
+{
+    DEBUG_ASSERT(aspace);
+
+    mutex_acquire(&vmm_lock);
+
+    vmm_region_t *r = vmm_find_region (aspace, vaddr);
+    if (!r) {
+        mutex_release(&vmm_lock);
+        return ERR_NOT_FOUND;
+    }
+
+    /* remove it from aspace */
+    list_delete(&r->node);
+
+    /* unmap it */
+    arch_mmu_unmap(r->base, r->size / PAGE_SIZE);
+
+    mutex_release(&vmm_lock);
+
+    /* return physical pages if any */
+    pmm_free(&r->page_list);
+
+    /* free it */
+    free(r);
+
+    return NO_ERROR;
+}
+
+status_t vmm_create_aspace(vmm_aspace_t **_aspace, const char *name, uint flags)
+{
+    DEBUG_ASSERT(_aspace);
+
+    vmm_aspace_t *aspace = malloc(sizeof(vmm_aspace_t));
+    if (!aspace)
+        return ERR_NO_MEMORY;
+
+    if (name)
+        strlcpy(aspace->name, name, sizeof(aspace->name));
+    else
+        strlcpy(aspace->name, "unnamed", sizeof(aspace->name));
+
+    if (flags & VMM_FLAG_ASPACE_KERNEL) {
+        aspace->base = KERNEL_ASPACE_BASE;
+        aspace->size = KERNEL_ASPACE_SIZE;
+    } else {
+        aspace->base = USER_ASPACE_BASE;
+        aspace->size = USER_ASPACE_SIZE;
+    }
+
+    list_clear_node(&aspace->node);
+    list_initialize(&aspace->region_list);
+
+    mutex_acquire(&vmm_lock);
+    list_add_head(&aspace_list, &aspace->node);
+    mutex_release(&vmm_lock);
+
+    *_aspace = aspace;
+
+    return NO_ERROR;
+}
+
+status_t vmm_free_aspace(vmm_aspace_t *aspace)
+{
+    DEBUG_ASSERT(aspace);
+
+    /* pop it out of the global aspace list */
+    mutex_acquire(&vmm_lock);
+    if (!list_in_list(&aspace->node)) {
+        mutex_release(&vmm_lock);
+        return ERR_INVALID_ARGS;
+    }
+    list_delete(&aspace->node);
+
+    /* free all of the regions */
+    struct list_node region_list = LIST_INITIAL_VALUE(region_list);
+
+    vmm_region_t *r;
+    while ((r = list_remove_head_type(&aspace->region_list, vmm_region_t, node))) {
+        /* add it to our tempoary list */
+        list_add_tail(&region_list, &r->node);
+
+        /* unmap it */
+        arch_mmu_unmap(r->base, r->size / PAGE_SIZE);
+    }
+    mutex_release(&vmm_lock);
+
+    /* without the vmm lock held, free all of the pmm pages and the structure */
+    while ((r = list_remove_head_type(&region_list, vmm_region_t, node))) {
+        /* return physical pages if any */
+        pmm_free(&r->page_list);
+
+        /* free it */
+        free(r);
+    }
+
+    /* free the aspace */
+    free(aspace);
+
+    return NO_ERROR;
+}
+
+static void dump_region(const vmm_region_t *r)
+{
+    DEBUG_ASSERT(r);
+
+    printf("\tregion %p: name '%s' range 0x%lx - 0x%lx size 0x%zx flags 0x%x mmu_flags 0x%x\n",
+            r, r->name, r->base, r->base + r->size - 1, r->size, r->flags, r->arch_mmu_flags);
+}
+
+static void dump_aspace(const vmm_aspace_t *a)
+{
+    DEBUG_ASSERT(a);
+
+    printf("aspace %p: name '%s' range 0x%lx - 0x%lx size 0x%zx flags 0x%x\n",
+            a, a->name, a->base, a->base + a->size - 1, a->size, a->flags);
+
+    printf("regions:\n");
+    vmm_region_t *r;
+    list_for_every_entry(&a->region_list, r, vmm_region_t, node) {
+        dump_region(r);
+    }
+}
+
+static int cmd_vmm(int argc, const cmd_args *argv)
+{
+    if (argc < 2) {
+notenoughargs:
+        printf("not enough arguments\n");
+usage:
+        printf("usage:\n");
+        printf("%s aspaces\n", argv[0].str);
+        printf("%s alloc <size> <align_pow2>\n", argv[0].str);
+        printf("%s alloc_physical <paddr> <size> <align_pow2>\n", argv[0].str);
+        printf("%s alloc_contig <size> <align_pow2>\n", argv[0].str);
+        printf("%s create_aspace\n", argv[0].str);
+        return ERR_GENERIC;
+    }
+
+    if (!strcmp(argv[1].str, "aspaces")) {
+        vmm_aspace_t *a;
+        list_for_every_entry(&aspace_list, a, vmm_aspace_t, node) {
+            dump_aspace(a);
+        }
+    } else if (!strcmp(argv[1].str, "alloc")) {
+        if (argc < 4) goto notenoughargs;
+
+        void *ptr = (void *)0x99;
+        status_t err = vmm_alloc(vmm_get_kernel_aspace(), "alloc test", argv[2].u, &ptr, argv[3].u, 0, 0);
+        printf("vmm_alloc returns %d, ptr %p\n", err, ptr);
+    } else if (!strcmp(argv[1].str, "alloc_physical")) {
+        if (argc < 4) goto notenoughargs;
+
+        void *ptr = (void *)0x99;
+        status_t err = vmm_alloc_physical(vmm_get_kernel_aspace(), "physical test", argv[3].u, &ptr, argv[4].u, argv[2].u, 0, ARCH_MMU_FLAG_UNCACHED_DEVICE);
+        printf("vmm_alloc_physical returns %d, ptr %p\n", err, ptr);
+    } else if (!strcmp(argv[1].str, "alloc_contig")) {
+        if (argc < 4) goto notenoughargs;
+
+        void *ptr = (void *)0x99;
+        status_t err = vmm_alloc_contiguous(vmm_get_kernel_aspace(), "contig test", argv[2].u, &ptr, argv[3].u, 0, 0);
+        printf("vmm_alloc_contig returns %d, ptr %p\n", err, ptr);
+    } else if (!strcmp(argv[1].str, "create_aspace")) {
+        vmm_aspace_t *aspace;
+        status_t err = vmm_create_aspace(&aspace, "test", 0);
+        printf("vmm_create_aspace returns %d, aspace %p\n", err, aspace);
+    } else {
+        printf("unknown command\n");
+        goto usage;
+    }
+
+    return NO_ERROR;
+}
+
+STATIC_COMMAND_START
+#if LK_DEBUGLEVEL > 0
+STATIC_COMMAND("vmm", "virtual memory manager", &cmd_vmm)
+#endif
+STATIC_COMMAND_END(vmm);
+