blob: 09da7500f2aac2050b62fea0de267c999ae6e807 [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 <assert.h>
#include <inttypes.h>
#include <lib/console.h>
#include <lib/ktrace.h>
#include <lib/zircon-internal/macros.h>
#include <string.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <fbl/null_lock.h>
#include <kernel/mutex.h>
#include <kernel/thread_lock.h>
#include <object/diagnostics.h>
#include <vm/fault.h>
#include <vm/pmm.h>
#include <vm/vm.h>
#include <vm/vm_address_region.h>
#include <vm/vm_aspace.h>
#include "vm_priv.h"
#define LOCAL_TRACE VM_GLOBAL_TRACE(0)
#define TRACE_PAGE_FAULT 0
// This file mostly contains C wrappers around the underlying C++ objects, conforming to
// the older api.
void vmm_context_switch(VmAspace* oldspace, VmAspace* newaspace) {
DEBUG_ASSERT(thread_lock.IsHeld());
ArchVmAspace::ContextSwitch(oldspace ? &oldspace->arch_aspace() : nullptr,
newaspace ? &newaspace->arch_aspace() : nullptr);
}
zx_status_t vmm_accessed_fault_handler(vaddr_t addr) {
// Forward fault to the current aspace.
VmAspace* aspace = VmAspace::vaddr_to_aspace(addr);
if (!aspace) {
return ZX_ERR_NOT_FOUND;
}
ktrace(TAG_ACCESS_FAULT, static_cast<uint32_t>(addr >> 32), static_cast<uint32_t>(addr), 0,
arch_curr_cpu_num());
const zx_status_t status = aspace->AccessedFault(addr);
ktrace(TAG_ACCESS_FAULT_EXIT, static_cast<uint32_t>(addr >> 32), static_cast<uint32_t>(addr), 0,
arch_curr_cpu_num());
return status;
}
zx_status_t vmm_page_fault_handler(vaddr_t addr, uint flags) {
// hardware fault, mark it as such
flags |= VMM_PF_FLAG_HW_FAULT;
if (TRACE_PAGE_FAULT || LOCAL_TRACE) {
Thread* current_thread = Thread::Current::Get();
char flagstr[5];
vmm_pf_flags_to_string(flags, flagstr);
TRACEF("thread %s va %#" PRIxPTR ", flags 0x%x (%s)\n", current_thread->name(), addr, flags,
flagstr);
}
// get the address space object this pointer is in
VmAspace* aspace = VmAspace::vaddr_to_aspace(addr);
if (!aspace) {
printf("PageFault: Invalid virtual address\n");
return ZX_ERR_NOT_FOUND;
}
ktrace(TAG_PAGE_FAULT, (uint32_t)(addr >> 32), (uint32_t)addr, flags, arch_curr_cpu_num());
// page fault it
zx_status_t status = aspace->PageFault(addr, flags);
// If it's a user fault, dump info about process memory usage.
// If it's a kernel fault, the kernel could possibly already
// hold locks on VMOs, Aspaces, etc, so we can't safely do
// this.
if ((status == ZX_ERR_NOT_FOUND) && (flags & VMM_PF_FLAG_USER)) {
printf("PageFault: %zu free pages\n", pmm_count_free_pages());
DumpProcessMemoryUsage("PageFault: MemoryUsed: ", 8 * 256);
} else if (status == ZX_ERR_INTERNAL_INTR_RETRY || status == ZX_ERR_INTERNAL_INTR_KILLED) {
// If we get this, then all checks passed but we were interrupted or killed while waiting
// for the request to be fulfilled. Pretend the fault was successful and let the thread re-fault
// after it is resumed (in case of suspension), or proceed with termination.
status = ZX_OK;
}
if (status != ZX_OK) {
printf("PageFault: error %d\n", status);
}
ktrace(TAG_PAGE_FAULT_EXIT, (uint32_t)(addr >> 32), (uint32_t)addr, flags, arch_curr_cpu_num());
return status;
}
template <typename GuardType, typename Lock>
static void vmm_set_active_aspace_internal(VmAspace* aspace, Lock lock) {
LTRACEF("aspace %p\n", aspace);
Thread* t = Thread::Current::Get();
DEBUG_ASSERT(t);
if (aspace == t->aspace()) {
return;
}
// grab the thread lock and switch to the new address space
GuardType __UNUSED lock_guard{lock, SOURCE_TAG};
VmAspace* old = t->switch_aspace(aspace);
vmm_context_switch(old, t->aspace());
}
void vmm_set_active_aspace(VmAspace* aspace) {
vmm_set_active_aspace_internal<Guard<MonitoredSpinLock, IrqSave>>(aspace, ThreadLock::Get());
}
void vmm_set_active_aspace_locked(VmAspace* aspace) {
fbl::NullLock null_lock;
vmm_set_active_aspace_internal<NullGuard>(aspace, &null_lock);
}
static int cmd_vmm(int argc, const cmd_args* argv, uint32_t flags) {
if (argc < 2) {
notenoughargs:
printf("not enough arguments\n");
usage:
printf("usage:\n");
printf("%s aspaces\n", argv[0].str);
printf("%s kaspace\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 free_region <address>\n", argv[0].str);
printf("%s create_aspace\n", argv[0].str);
printf("%s create_test_aspace\n", argv[0].str);
printf("%s free_aspace <address>\n", argv[0].str);
printf("%s set_test_aspace <address>\n", argv[0].str);
return ZX_ERR_INTERNAL;
}
static fbl::RefPtr<VmAspace> test_aspace;
if (!test_aspace) {
test_aspace = fbl::RefPtr(VmAspace::kernel_aspace());
}
if (!strcmp(argv[1].str, "aspaces")) {
DumpAllAspaces(true);
} else if (!strcmp(argv[1].str, "kaspace")) {
VmAspace::kernel_aspace()->Dump(true);
} else if (!strcmp(argv[1].str, "alloc")) {
if (argc < 3) {
goto notenoughargs;
}
void* ptr = (void*)0x99;
uint8_t align = (argc >= 4) ? (uint8_t)argv[3].u : 0u;
zx_status_t err = test_aspace->Alloc("alloc test", argv[2].u, &ptr, align, 0, 0);
printf("VmAspace::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;
uint8_t align = (argc >= 5) ? (uint8_t)argv[4].u : 0u;
zx_status_t err = test_aspace->AllocPhysical(
"physical test", argv[3].u, &ptr, align, argv[2].u, 0,
ARCH_MMU_FLAG_UNCACHED_DEVICE | ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE);
printf("VmAspace::AllocPhysical returns %d, ptr %p\n", err, ptr);
} else if (!strcmp(argv[1].str, "alloc_contig")) {
if (argc < 3) {
goto notenoughargs;
}
void* ptr = (void*)0x99;
uint8_t align = (argc >= 4) ? (uint8_t)argv[3].u : 0u;
zx_status_t err =
test_aspace->AllocContiguous("contig test", argv[2].u, &ptr, align, 0,
ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE);
printf("VmAspace::AllocContiguous returns %d, ptr %p\n", err, ptr);
} else if (!strcmp(argv[1].str, "free_region")) {
if (argc < 2) {
goto notenoughargs;
}
zx_status_t err = test_aspace->FreeRegion(reinterpret_cast<vaddr_t>(argv[2].u));
printf("VmAspace::FreeRegion returns %d\n", err);
} else if (!strcmp(argv[1].str, "create_aspace")) {
fbl::RefPtr<VmAspace> aspace = VmAspace::Create(0, "test");
printf("VmAspace::Create aspace %p\n", aspace.get());
} else if (!strcmp(argv[1].str, "create_test_aspace")) {
fbl::RefPtr<VmAspace> aspace = VmAspace::Create(0, "test");
printf("VmAspace::Create aspace %p\n", aspace.get());
test_aspace = aspace;
Thread::Current::Get()->switch_aspace(aspace.get());
Thread::Current::Sleep(1); // XXX hack to force it to reschedule and thus load the aspace
} else if (!strcmp(argv[1].str, "free_aspace")) {
if (argc < 2) {
goto notenoughargs;
}
fbl::RefPtr<VmAspace> aspace = fbl::RefPtr((VmAspace*)(void*)argv[2].u);
if (test_aspace == aspace) {
test_aspace = nullptr;
}
if (Thread::Current::Get()->aspace() == aspace.get()) {
Thread::Current::Get()->switch_aspace(nullptr);
Thread::Current::Sleep(1); // hack
}
zx_status_t err = aspace->Destroy();
printf("VmAspace::Destroy() returns %d\n", err);
} else if (!strcmp(argv[1].str, "set_test_aspace")) {
if (argc < 2) {
goto notenoughargs;
}
test_aspace = fbl::RefPtr((VmAspace*)(void*)argv[2].u);
Thread::Current::Get()->switch_aspace(test_aspace.get());
Thread::Current::Sleep(1); // XXX hack to force it to reschedule and thus load the aspace
} else {
printf("unknown command\n");
goto usage;
}
return ZX_OK;
}
STATIC_COMMAND_START
STATIC_COMMAND("vmm", "virtual memory manager", &cmd_vmm)
STATIC_COMMAND_END(vmm)