blob: 37028f5c936284b4ec1562d088769db4abb225cc [file] [log] [blame]
// 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 <lib/fit/function.h>
#include <sys/types.h>
#include <trace.h>
#include <fbl/algorithm.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;
// Used directly by boot_reserve_wire, and implicitly by boot_reserve_unwire_page
static list_node reserved_page_list = LIST_INITIAL_VALUE(reserved_page_list);
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() {
// This should only be called once to populate the list.
ASSERT(list_is_empty(&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;
list_node alloc_page_list = LIST_INITIAL_VALUE(alloc_page_list);
zx_status_t status = pmm_alloc_range(res[i].pa, pages, &alloc_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
}
if (list_is_empty(&reserved_page_list)) {
list_move(&alloc_page_list, &reserved_page_list);
} else {
list_splice_after(&alloc_page_list, list_peek_tail(&reserved_page_list));
}
}
// 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 fit::inline_function<bool(const reserve_range_t)>& cb) {
for (size_t i = 0; i < res_idx; i++) {
if (!cb(res[i])) {
return false;
}
}
return true;
}