|  | // Copyright 2018 The Fuchsia Authors | 
|  | // | 
|  | // Use of this source code is governed by a MIT-style | 
|  | // license that can be found in the LICENSE file or at | 
|  | // https://opensource.org/licenses/MIT | 
|  | #include "vm/bootreserve.h" | 
|  |  | 
|  | #include <inttypes.h> | 
|  | #include <sys/types.h> | 
|  | #include <trace.h> | 
|  |  | 
|  | #include <fbl/algorithm.h> | 
|  | #include <fbl/function.h> | 
|  | #include <vm/pmm.h> | 
|  |  | 
|  | #include "vm_priv.h" | 
|  |  | 
|  | #define LOCAL_TRACE VM_GLOBAL_TRACE(0) | 
|  |  | 
|  | static const size_t NUM_RESERVES = 16; | 
|  | static reserve_range_t res[NUM_RESERVES]; | 
|  | static size_t res_idx; | 
|  |  | 
|  | void boot_reserve_init() { | 
|  | // add the kernel to the boot reserve list | 
|  | boot_reserve_add_range(get_kernel_base_phys(), get_kernel_size()); | 
|  | } | 
|  |  | 
|  | zx_status_t boot_reserve_add_range(paddr_t pa, size_t len) { | 
|  | dprintf(INFO, "PMM: boot reserve add [%#" PRIxPTR ", %#" PRIxPTR "]\n", pa, pa + len - 1); | 
|  |  | 
|  | if (res_idx == NUM_RESERVES) { | 
|  | panic("too many boot reservations\n"); | 
|  | } | 
|  |  | 
|  | // insert into the list, sorted | 
|  | paddr_t end = pa + len - 1; | 
|  | DEBUG_ASSERT(end > pa); | 
|  | for (size_t i = 0; i < res_idx; i++) { | 
|  | if (Intersects(res[i].pa, res[i].len, pa, len)) { | 
|  | // we have a problem that we are not equipped to handle right now | 
|  | panic("boot_reserve_add_range: pa %#" PRIxPTR " len %zx intersects existing range\n", pa, | 
|  | len); | 
|  | } | 
|  |  | 
|  | if (res[i].pa > end) { | 
|  | // insert before this one | 
|  | memmove(&res[i + 1], &res[i], (res_idx - i) * sizeof(res[0])); | 
|  | res[i].pa = pa; | 
|  | res[i].len = len; | 
|  | res_idx++; | 
|  | return ZX_OK; | 
|  | } | 
|  | } | 
|  |  | 
|  | // insert it at the end | 
|  | res[res_idx].pa = pa; | 
|  | res[res_idx].len = len; | 
|  | res_idx++; | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | // iterate through the reserved ranges and mark them as WIRED in the pmm | 
|  | void boot_reserve_wire() { | 
|  | static list_node reserved_page_list = LIST_INITIAL_VALUE(reserved_page_list); | 
|  |  | 
|  | for (size_t i = 0; i < res_idx; i++) { | 
|  | dprintf(INFO, "PMM: boot reserve marking WIRED [%#" PRIxPTR ", %#" PRIxPTR "]\n", res[i].pa, | 
|  | res[i].pa + res[i].len - 1); | 
|  |  | 
|  | size_t pages = ROUNDUP_PAGE_SIZE(res[i].len) / PAGE_SIZE; | 
|  | zx_status_t status = pmm_alloc_range(res[i].pa, pages, &reserved_page_list); | 
|  | if (status != ZX_OK) { | 
|  | printf("PMM: unable to reserve reserved range [%#" PRIxPTR ", %#" PRIxPTR "]\n", res[i].pa, | 
|  | res[i].pa + res[i].len - 1); | 
|  | continue;  // this is probably fatal but go ahead and continue | 
|  | } | 
|  | } | 
|  |  | 
|  | // mark all of the pages we allocated as WIRED | 
|  | vm_page_t* p; | 
|  | list_for_every_entry (&reserved_page_list, p, vm_page_t, queue_node) { | 
|  | p->set_state(vm_page_state::WIRED); | 
|  | } | 
|  | } | 
|  |  | 
|  | void boot_reserve_unwire_page(struct vm_page* page) { | 
|  | DEBUG_ASSERT(page->state() == vm_page_state::WIRED); | 
|  | page->set_state(vm_page_state::ALLOC); | 
|  | // Remove from the reserved page list. | 
|  | list_delete(&page->queue_node); | 
|  | } | 
|  |  | 
|  | static paddr_t upper_align(paddr_t range_pa, size_t range_len, size_t len) { | 
|  | return (range_pa + range_len - len); | 
|  | } | 
|  |  | 
|  | zx_status_t boot_reserve_range_search(paddr_t range_pa, size_t range_len, size_t alloc_len, | 
|  | reserve_range_t* alloc_range) { | 
|  | LTRACEF("range pa %#" PRIxPTR " len %#zx alloc_len %#zx\n", range_pa, range_len, alloc_len); | 
|  |  | 
|  | paddr_t alloc_pa = upper_align(range_pa, range_len, alloc_len); | 
|  |  | 
|  | retry: | 
|  | // see if it intersects any reserved range | 
|  | LTRACEF("trying alloc range %#" PRIxPTR " len %#zx\n", alloc_pa, alloc_len); | 
|  | for (size_t i = 0; i < res_idx; i++) { | 
|  | if (Intersects(res[i].pa, res[i].len, alloc_pa, alloc_len)) { | 
|  | // it intersects this range, move the search spot back to just before it and try again | 
|  | LTRACEF("alloc range %#" PRIxPTR " len %zx intersects with reserve range\n", alloc_pa, | 
|  | alloc_len); | 
|  | alloc_pa = res[i].pa - alloc_len; | 
|  |  | 
|  | LTRACEF("moving and retrying at %#" PRIxPTR "\n", alloc_pa); | 
|  |  | 
|  | // make sure this still works with our input constraints | 
|  | if (alloc_pa < range_pa) { | 
|  | LTRACEF("failed to allocate\n"); | 
|  | return ZX_ERR_NO_MEMORY; | 
|  | } | 
|  |  | 
|  | goto retry; | 
|  | } | 
|  | } | 
|  |  | 
|  | // fell off the list without retrying, must have succeeded | 
|  | LTRACEF("returning [%#" PRIxPTR ", %#" PRIxPTR "]\n", alloc_pa, alloc_pa + alloc_len - 1); | 
|  |  | 
|  | *alloc_range = {alloc_pa, alloc_len}; | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | // Returns false and exits early if the callback returns false, true otherwise. | 
|  | bool boot_reserve_foreach(const fbl::Function<bool(const reserve_range_t)>& cb) { | 
|  | for (size_t i = 0; i < res_idx; i++) { | 
|  | if (!cb(res[i])) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } |