blob: 404f493440fec9d72f0b95d025bff92923b16ed8 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2014 Travis Geiselbrecht
//
// 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.h>
#include <assert.h>
#include <err.h>
#include <inttypes.h>
#include <kernel/mp.h>
#include <kernel/timer.h>
#include <kernel/vm.h>
#include <lib/console.h>
#include <list.h>
#include <lk/init.h>
#include <platform.h>
#include <pow2.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#include "pmm_arena.h"
#include "vm_priv.h"
#include <zircon/thread_annotations.h>
#include <zxcpp/new.h>
#include <fbl/auto_lock.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/mutex.h>
using fbl::AutoLock;
#define LOCAL_TRACE MAX(VM_GLOBAL_TRACE, 0)
// the main arena list
static fbl::Mutex arena_lock;
static fbl::DoublyLinkedList<PmmArena*> arena_list TA_GUARDED(arena_lock);
static size_t arena_cumulative_size TA_GUARDED(arena_lock);
#if PMM_ENABLE_FREE_FILL
static void pmm_enforce_fill(uint level) {
for (auto& a : arena_list) {
a.EnforceFill();
}
}
LK_INIT_HOOK(pmm_fill, &pmm_enforce_fill, LK_INIT_LEVEL_VM);
#endif
// We don't need to hold the arena lock while executing this, since it is
// only accesses values that are set once during system initialization.
paddr_t vm_page_to_paddr(const vm_page_t* page) TA_NO_THREAD_SAFETY_ANALYSIS {
for (const auto& a : arena_list) {
// LTRACEF("testing page %p against arena %p\n", page, &a);
if (a.page_belongs_to_arena(page)) {
return a.page_address_from_arena(page);
}
}
return -1;
}
// We don't need to hold the arena lock while executing this, since it is
// only accesses values that are set once during system initialization.
vm_page_t* paddr_to_vm_page(paddr_t addr) TA_NO_THREAD_SAFETY_ANALYSIS {
for (auto& a : arena_list) {
if (a.address_in_arena(addr)) {
size_t index = (addr - a.base()) / PAGE_SIZE;
return a.get_page(index);
}
}
return nullptr;
}
// We disable thread safety analysis here, since this function is only called
// during early boot before threading exists.
status_t pmm_add_arena(const pmm_arena_info_t* info) TA_NO_THREAD_SAFETY_ANALYSIS {
LTRACEF("arena %p name '%s' base %#" PRIxPTR " size %#zx\n", info, info->name, info->base, info->size);
// Make sure we're in early boot (ints disabled and no active CPUs according
// to the scheduler).
DEBUG_ASSERT(mp_get_active_mask() == 0);
DEBUG_ASSERT(arch_ints_disabled());
DEBUG_ASSERT(IS_PAGE_ALIGNED(info->base));
DEBUG_ASSERT(IS_PAGE_ALIGNED(info->size));
DEBUG_ASSERT(info->size > 0);
// allocate a c++ arena object
PmmArena* arena = new (boot_alloc_mem(sizeof(PmmArena))) PmmArena(info);
// walk the arena list and add arena based on priority order
for (auto& a : arena_list) {
if (a.priority() > arena->priority()) {
arena_list.insert(a, arena);
goto done_add;
}
}
// walked off the end, add it to the end of the list
arena_list.push_back(arena);
done_add:
// tell the arena to allocate a page array
arena->BootAllocArray();
arena_cumulative_size += info->size;
return ZX_OK;
}
vm_page_t* pmm_alloc_page(uint alloc_flags, paddr_t* pa) {
AutoLock al(&arena_lock);
/* walk the arenas in order until we find one with a free page */
for (auto& a : arena_list) {
/* skip the arena if it's not KMAP and the KMAP only allocation flag was passed */
if (alloc_flags & PMM_ALLOC_FLAG_KMAP) {
if ((a.flags() & PMM_ARENA_FLAG_KMAP) == 0)
continue;
}
// try to allocate the page out of the arena
vm_page_t* page = a.AllocPage(pa);
if (page)
return page;
}
LTRACEF("failed to allocate page\n");
return nullptr;
}
size_t pmm_alloc_pages(size_t count, uint alloc_flags, struct list_node* list) {
LTRACEF("count %zu\n", count);
/* list must be initialized prior to calling this */
DEBUG_ASSERT(list);
if (count == 0)
return 0;
AutoLock al(&arena_lock);
/* walk the arenas in order, allocating as many pages as we can from each */
size_t allocated = 0;
for (auto& a : arena_list) {
DEBUG_ASSERT(count > allocated);
/* skip the arena if it's not KMAP and the KMAP only allocation flag was passed */
if (alloc_flags & PMM_ALLOC_FLAG_KMAP) {
if ((a.flags() & PMM_ARENA_FLAG_KMAP) == 0)
continue;
}
// ask the arena to allocate some pages
allocated += a.AllocPages(count - allocated, list);
DEBUG_ASSERT(allocated <= count);
if (allocated == count)
break;
}
return allocated;
}
size_t pmm_alloc_range(paddr_t address, size_t count, struct list_node* list) {
LTRACEF("address %#" PRIxPTR ", count %zu\n", address, count);
uint allocated = 0;
if (count == 0)
return 0;
address = ROUNDDOWN(address, PAGE_SIZE);
AutoLock al(&arena_lock);
/* walk through the arenas, looking to see if the physical page belongs to it */
for (auto& a : arena_list) {
while (allocated < count && a.address_in_arena(address)) {
vm_page_t* page = a.AllocSpecific(address);
if (!page)
break;
if (list)
list_add_tail(list, &page->free.node);
allocated++;
address += PAGE_SIZE;
}
if (allocated == count)
break;
}
return allocated;
}
size_t pmm_alloc_contiguous(size_t count, uint alloc_flags, uint8_t alignment_log2, paddr_t* pa,
struct list_node* list) {
LTRACEF("count %zu, align %u\n", count, alignment_log2);
if (count == 0)
return 0;
if (alignment_log2 < PAGE_SIZE_SHIFT)
alignment_log2 = PAGE_SIZE_SHIFT;
AutoLock al(&arena_lock);
for (auto& a : arena_list) {
/* skip the arena if it's not KMAP and the KMAP only allocation flag was passed */
if (alloc_flags & PMM_ALLOC_FLAG_KMAP) {
if ((a.flags() & PMM_ARENA_FLAG_KMAP) == 0)
continue;
}
size_t allocated = a.AllocContiguous(count, alignment_log2, pa, list);
if (allocated > 0) {
DEBUG_ASSERT(allocated == count);
return allocated;
}
}
LTRACEF("couldn't find run\n");
return 0;
}
/* physically allocate a run from arenas marked as KMAP */
void* pmm_alloc_kpages(size_t count, struct list_node* list, paddr_t* _pa) {
LTRACEF("count %zu\n", count);
paddr_t pa;
/* fast path for single count allocations */
if (count == 1) {
vm_page_t* p = pmm_alloc_page(PMM_ALLOC_FLAG_KMAP, &pa);
if (!p)
return nullptr;
if (list) {
list_add_tail(list, &p->free.node);
}
} else {
size_t alloc_count = pmm_alloc_contiguous(count, PMM_ALLOC_FLAG_KMAP, PAGE_SIZE_SHIFT, &pa, list);
if (alloc_count == 0)
return nullptr;
}
LTRACEF("pa %#" PRIxPTR "\n", pa);
void* ptr = paddr_to_kvaddr(pa);
DEBUG_ASSERT(ptr);
if (_pa)
*_pa = pa;
return ptr;
}
/* allocate a single page from a KMAP arena and return its virtual address */
void* pmm_alloc_kpage(paddr_t* _pa, vm_page_t** _p) {
LTRACE_ENTRY;
paddr_t pa;
vm_page_t* p = pmm_alloc_page(PMM_ALLOC_FLAG_KMAP, &pa);
if (!p)
return nullptr;
void* ptr = paddr_to_kvaddr(pa);
DEBUG_ASSERT(ptr);
if (_pa)
*_pa = pa;
if (_p)
*_p = p;
return ptr;
}
size_t pmm_free_kpages(void* _ptr, size_t count) {
LTRACEF("ptr %p, count %zu\n", _ptr, count);
uint8_t* ptr = (uint8_t*)_ptr;
struct list_node list;
list_initialize(&list);
while (count > 0) {
vm_page_t* p = paddr_to_vm_page(vaddr_to_paddr(ptr));
if (p) {
list_add_tail(&list, &p->free.node);
}
ptr += PAGE_SIZE;
count--;
}
return pmm_free(&list);
}
size_t pmm_free(struct list_node* list) {
LTRACEF("list %p\n", list);
DEBUG_ASSERT(list);
AutoLock al(&arena_lock);
uint count = 0;
while (!list_is_empty(list)) {
vm_page_t* page = list_remove_head_type(list, vm_page_t, free.node);
DEBUG_ASSERT_MSG(!page_is_free(page), "page %p state %u\n", page, page->state);
/* see which arena this page belongs to and add it */
for (auto& a : arena_list) {
if (a.FreePage(page) >= 0) {
count++;
break;
}
}
}
LTRACEF("returning count %u\n", count);
return count;
}
size_t pmm_free_page(vm_page_t* page) {
struct list_node list;
list_initialize(&list);
list_add_head(&list, &page->free.node);
return pmm_free(&list);
}
static size_t pmm_count_free_pages_locked() TA_REQ(arena_lock) {
size_t free = 0u;
for (const auto& a : arena_list) {
free += a.free_count();
}
return free;
}
size_t pmm_count_free_pages() {
AutoLock al(&arena_lock);
return pmm_count_free_pages_locked();
}
static void pmm_dump_free() TA_REQ(arena_lock) {
auto megabytes_free = pmm_count_free_pages_locked() / 256u;
printf(" %zu free MBs\n", megabytes_free);
}
static size_t pmm_count_total_bytes_locked() TA_REQ(arena_lock) {
return arena_cumulative_size;
}
size_t pmm_count_total_bytes() {
AutoLock al(&arena_lock);
return pmm_count_total_bytes_locked();
}
void pmm_count_total_states(size_t state_count[_VM_PAGE_STATE_COUNT]) {
// TODO(MG-833): This is extremely expensive, holding a global lock
// and touching every page/arena. We should keep a running count instead.
AutoLock al(&arena_lock);
for (auto& a : arena_list) {
a.CountStates(state_count);
}
}
extern "C" enum handler_return pmm_dump_timer(struct timer* t, lk_time_t now, void*) TA_REQ(arena_lock) {
timer_set(t, now + LK_SEC(1), TIMER_SLACK_CENTER, LK_MSEC(20), &pmm_dump_timer, nullptr);
pmm_dump_free();
return INT_NO_RESCHEDULE;
}
// No lock analysis here, as we want to just go for it in the panic case without the lock.
static void arena_dump(bool is_panic) TA_NO_THREAD_SAFETY_ANALYSIS {
if (!is_panic) {
arena_lock.Acquire();
}
for (auto& a : arena_list) {
a.Dump(false, false);
}
if (!is_panic) {
arena_lock.Release();
}
}
static int cmd_pmm(int argc, const cmd_args* argv, uint32_t flags) {
bool is_panic = flags & CMD_FLAG_PANIC;
if (argc < 2) {
notenoughargs:
printf("not enough arguments\n");
usage:
printf("usage:\n");
printf("%s arenas\n", argv[0].str);
if (!is_panic) {
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);
printf("%s free\n", argv[0].str);
}
return ZX_ERR_INTERNAL;
}
static struct list_node allocated = LIST_INITIAL_VALUE(allocated);
if (!strcmp(argv[1].str, "arenas")) {
arena_dump(is_panic);
} else if (is_panic) {
// No other operations will work during a panic.
printf("Only the \"arenas\" command is available during a panic.\n");
goto usage;
} else if (!strcmp(argv[1].str, "free")) {
static bool show_mem = false;
static timer_t timer;
if (!show_mem) {
printf("pmm free: issue the same command to stop.\n");
timer_init(&timer);
timer_set(&timer, current_time() + LK_SEC(1), TIMER_SLACK_CENTER, LK_MSEC(20),
&pmm_dump_timer, nullptr);
show_mem = true;
} else {
timer_cancel(&timer);
show_mem = false;
}
} else if (!strcmp(argv[1].str, "alloc")) {
if (argc < 3)
goto notenoughargs;
struct list_node list;
list_initialize(&list);
size_t count = pmm_alloc_pages((uint)argv[2].u, 0, &list);
printf("alloc returns %zu\n", count);
vm_page_t* p;
list_for_every_entry (&list, p, vm_page_t, free.node) {
paddr_t paddr;
{
DEBUG_ASSERT(!is_panic);
AutoLock al(&arena_lock);
paddr = vm_page_to_paddr(p);
};
printf("\tpage %p, address %#" PRIxPTR "\n", p, paddr);
}
/* 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, free.node) { dump_page(page); }
} else if (!strcmp(argv[1].str, "alloc_range")) {
if (argc < 4)
goto notenoughargs;
struct list_node list;
list_initialize(&list);
size_t count = pmm_alloc_range(argv[2].u, (uint)argv[3].u, &list);
printf("alloc returns %zu\n", count);
vm_page_t* p;
list_for_every_entry (&list, p, vm_page_t, free.node) {
paddr_t paddr;
{
DEBUG_ASSERT(!is_panic);
AutoLock al(&arena_lock);
paddr = vm_page_to_paddr(p);
}
printf("\tpage %p, address %#" PRIxPTR "\n", p, paddr);
}
/* 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;
paddr_t pa;
void* ptr = pmm_alloc_kpages((uint)argv[2].u, nullptr, &pa);
printf("pmm_alloc_kpages returns %p pa %#" PRIxPTR "\n", ptr, pa);
} 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((uint)argv[2].u, 0, (uint8_t)argv[3].u, &pa, &list);
printf("pmm_alloc_contiguous returns %zu, address %#" PRIxPTR "\n", ret, pa);
printf("address %% align = %#" PRIxPTR "\n", static_cast<uintptr_t>(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 ZX_OK;
}
STATIC_COMMAND_START
#if LK_DEBUGLEVEL > 0
STATIC_COMMAND_MASKED("pmm", "physical memory manager", &cmd_pmm, CMD_AVAIL_ALWAYS)
#endif
STATIC_COMMAND_END(pmm);