blob: 3c113cc07d8ea25b81eefbe112c4a7596b58acda [file] [edit]
// Copyright 2016 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/pmm_arena.h"
#include <align.h>
#include <inttypes.h>
#include <lib/counters.h>
#include <lib/page/size.h>
#include <lib/zx/result.h>
#include <string.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <kernel/range_check.h>
#include <ktl/limits.h>
#include <pretty/cpp/sizes.h>
#include <vm/physmap.h>
#include <vm/pmm_node.h>
#include "vm_priv.h"
#include <ktl/enforce.h>
#define LOCAL_TRACE VM_GLOBAL_TRACE(0)
// A possibly "lossy" estimate of the maximum number of page runs examined while performing a
// contiguous allocation. See the comment where this counter is updated.
KCOUNTER_DECLARE(counter_max_runs_examined, "vm.pmm.max_runs_examined", Max)
void PmmArena::Init(const PmmArenaSelection& selected, PmmNode* node) {
DEBUG_ASSERT(IsPageRounded(selected.arena.base));
DEBUG_ASSERT(IsPageRounded(selected.arena.size));
DEBUG_ASSERT(IsPageRounded(selected.bookkeeping.base));
DEBUG_ASSERT(IsPageRounded(selected.bookkeeping.size));
size_t page_count = selected.arena.size / kPageSize;
DEBUG_ASSERT(selected.bookkeeping.size == RoundUpPageSize(page_count * sizeof(vm_page)));
DEBUG_ASSERT(selected.bookkeeping.size < selected.arena.size);
dprintf(INFO, "PMM: adding arena [%#" PRIx64 ", %#" PRIx64 ")\n", selected.arena.base,
selected.arena.end());
// Intentionally similar to the logging in PmmNode::InitReservedRange().
dprintf(INFO, "PMM: reserved [%#" PRIx64 ", %#" PRIx64 "): bookkeeping\n",
selected.bookkeeping.base, selected.bookkeeping.end());
info_ = pmm_arena_info_t{
.flags = 0,
.base = selected.arena.base,
.size = selected.arena.size,
};
snprintf(info_.name, sizeof(info_.name), "%s", "ram");
// get the kernel pointer
size_t page_array_size = selected.bookkeeping.size;
void* raw_page_array = paddr_to_physmap(selected.bookkeeping.base);
LTRACEF("arena for base 0%#" PRIxPTR " size %#zx page array at %p size %#zx\n", base(), size(),
raw_page_array, page_array_size);
memset(raw_page_array, 0, page_array_size);
page_array_ = (vm_page_t*)raw_page_array;
// we've just constructed |page_count| pages in the state vm_page_state::FREE
vm_page::add_to_initial_count(vm_page_state::FREE, page_count);
// compute the range of the array that backs the array itself
size_t array_start_index = (selected.bookkeeping.base - info_.base) / kPageSize;
size_t array_end_index = array_start_index + page_array_size / kPageSize;
LTRACEF("array_start_index %zu, array_end_index %zu, page_count %zu\n", array_start_index,
array_end_index, page_count);
DEBUG_ASSERT(array_start_index < page_count && array_end_index <= page_count);
// add all pages that aren't part of the page array to the free list
// pages part of the free array go to the WIRED state
list_node list;
list_initialize(&list);
for (size_t i = 0; i < page_count; i++) {
auto& p = page_array_[i];
p.paddr_priv = base() + i * kPageSize;
if (i >= array_start_index && i < array_end_index) {
p.set_state(vm_page_state::WIRED);
} else {
list_add_tail(&list, &p.queue_node);
}
}
node->AddFreePages(&list);
}
void PmmArena::InitForTest(const pmm_arena_info_t& info, vm_page_t* page_array) {
info_ = info;
page_array_ = page_array;
}
vm_page_t* PmmArena::FindSpecific(paddr_t pa) {
if (!address_in_arena(pa)) {
return nullptr;
}
size_t index = (pa - base()) / kPageSize;
DEBUG_ASSERT(index < size() / kPageSize);
return get_page(index);
}
// Computes and returns the offset from |page_array_| of the first element at or
// after |offset| whose physical address alignment satisfies |alignment_log2|.
//
// Note, the returned value may exceed the bounds of |page_array_|.
static uint64_t Align(uint64_t offset, uint8_t alignment_log2, uint64_t first_aligned_offset) {
if (offset < first_aligned_offset) {
return first_aligned_offset;
}
DEBUG_ASSERT(alignment_log2 >= kPageShift);
// The "extra" alignment required above and beyond kPageSize alignment.
const uint64_t offset_alignment = alignment_log2 - kPageShift;
return ROUNDUP(offset - first_aligned_offset, 1UL << (offset_alignment)) + first_aligned_offset;
}
zx::result<uint64_t> PmmArena::FindLastNonFree(uint64_t offset, size_t count) const {
uint64_t i = offset + count - 1;
do {
if (!page_array_[i].is_free()) {
return zx::ok(i);
}
} while (i-- > offset);
return zx::error(ZX_ERR_NOT_FOUND);
}
vm_page_t* PmmArena::FindFreeContiguous(size_t count, uint8_t alignment_log2) {
DEBUG_ASSERT(count > 0);
if (alignment_log2 < kPageShift) {
alignment_log2 = kPageShift;
}
// Number of pages in this arena.
const uint64_t arena_count = size() / kPageSize;
// Offset of the first page that satisfies the required alignment.
const uint64_t first_aligned_offset =
(ROUNDUP(base(), 1UL << alignment_log2) - base()) / kPageSize;
// Start our search at the hint so that we can skip over regions previously
// known to be in use.
const uint64_t initial = search_hint_;
DEBUG_ASSERT_MSG(initial < arena_count, "initial %lu\n", initial);
uint64_t candidate = Align(initial, alignment_log2, first_aligned_offset);
// Keep track of how many runs of pages we examine before finding a
// sufficiently long contiguous run.
int64_t num_runs_examined = 0;
// Indicates whether we have wrapped around back to the start of the arena.
bool wrapped = false;
vm_page_t* result = nullptr;
// Keep searching until we've wrapped and "lapped" our initial starting point.
while (!wrapped || candidate < initial) {
LTRACEF(
"num_runs_examined=%ld candidate=%lu count=%zu alignment_log2=%d arena_count=%lu "
"initial=%lu\n",
num_runs_examined, candidate, count, alignment_log2, arena_count, initial);
num_runs_examined++;
if (!InRange(candidate, count, arena_count)) {
if (wrapped) {
break;
}
wrapped = true;
candidate = first_aligned_offset;
} else {
// Is the candidate region free? Walk the pages of the region back to
// front, stopping at the first non-free page.
zx::result<uint64_t> last_non_free = FindLastNonFree(candidate, count);
if (last_non_free.is_error()) {
// Candidate region is free. We're done.
search_hint_ = (candidate + count) % arena_count;
result = &page_array_[candidate];
DEBUG_ASSERT_MSG(candidate < arena_count, "candidate=%lu arena_count=%lu\n", candidate,
arena_count);
break;
}
// Candidate region is not completely free. Skip over the "broken" run,
// maintaining alignment.
candidate = Align(last_non_free.value() + 1, alignment_log2, first_aligned_offset);
}
}
// If called with preemption enabled, then the counter may fail to observe the true max.
int64_t max = counter_max_runs_examined.ValueCurrCpu();
if (num_runs_examined > max) {
counter_max_runs_examined.Set(num_runs_examined);
}
return result;
}
void PmmArena::CountStates(PmmStateCount* state_count) const {
for (size_t i = 0; i < size() / kPageSize; i++) {
(*state_count)[VmPageStateIndex(page_array_[i].state())]++;
}
}
void PmmArena::Dump(bool dump_pages, bool dump_free_ranges, PmmStateCount* counts_sum) const {
printf(" arena %p: name '%s' base %#" PRIxPTR " size %s (0x%zx) flags 0x%x\n", this, name(),
base(), pretty::FormattedBytes(size()).c_str(), size(), flags());
printf("\tpage_array %p search_hint %lu\n", page_array_, search_hint_);
// dump all of the pages
if (dump_pages) {
for (size_t i = 0; i < size() / kPageSize; i++) {
page_array_[i].dump();
}
}
// count the number of pages in every state
PmmStateCount state_count = {};
CountStates(&state_count);
for (size_t ix = 0; ix < counts_sum->size(); ++ix) {
(*counts_sum)[ix] += state_count[ix];
}
PrintPageStateCounts(state_count);
// dump the free pages
if (dump_free_ranges) {
printf("\tfree ranges:\n");
ssize_t last = -1;
for (size_t i = 0; i < size() / kPageSize; i++) {
if (page_array_[i].is_free()) {
if (last == -1) {
last = i;
}
} else {
if (last != -1) {
printf("\t\t%#" PRIxPTR " - %#" PRIxPTR "\n", base() + last * kPageSize,
base() + i * kPageSize);
}
last = -1;
}
}
if (last != -1) {
printf("\t\t%#" PRIxPTR " - %#" PRIxPTR "\n", base() + last * kPageSize, base() + size());
}
}
}
void PrintPageStateCounts(const PmmStateCount& state_count) {
printf("\tpage states:\n");
for (unsigned int i = 0; i < VmPageStateIndex(vm_page_state::COUNT_); i++) {
printf("\t\t%-12s %-16zu (%zu bytes)\n", page_state_to_string(vm_page_state(i)), state_count[i],
state_count[i] * kPageSize);
}
}