blob: b960c5b93f075d186c90a5ac937d5912f403ba78 [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/vm.h"
#include <align.h>
#include <assert.h>
#include <debug.h>
#include <inttypes.h>
#include <lib/boot-options/boot-options.h>
#include <lib/cmpctmalloc.h>
#include <lib/console.h>
#include <lib/crypto/global_prng.h>
#include <lib/instrumentation/asan.h>
#include <lib/lazy_init/lazy_init.h>
#include <lib/zircon-internal/macros.h>
#include <string.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <fbl/algorithm.h>
#include <kernel/thread.h>
#include <ktl/array.h>
#include <vm/anonymous_page_requester.h>
#include <vm/init.h>
#include <vm/physmap.h>
#include <vm/pmm.h>
#include <vm/vm_address_region.h>
#include <vm/vm_aspace.h>
#include <vm/vm_object_paged.h>
#include "vm_priv.h"
#include <ktl/enforce.h>
#define LOCAL_TRACE VM_GLOBAL_TRACE(0)
// boot time allocated page full of zeros
vm_page_t* zero_page;
paddr_t zero_page_paddr;
// set early in arch code to record the start address of the kernel
paddr_t kernel_base_phys;
// construct an array of kernel program segment descriptors for use here
// and elsewhere
namespace {
const ktl::array _kernel_regions = {
kernel_region{
.name = "kernel_code",
.base = (vaddr_t)__code_start,
.size = ROUNDUP((uintptr_t)__code_end - (uintptr_t)__code_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_EXECUTE,
},
kernel_region{
.name = "kernel_rodata",
.base = (vaddr_t)__rodata_start,
.size = ROUNDUP((uintptr_t)__rodata_end - (uintptr_t)__rodata_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ,
},
kernel_region{
.name = "kernel_relro",
.base = (vaddr_t)__relro_start,
.size = ROUNDUP((uintptr_t)__relro_end - (uintptr_t)__relro_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ,
},
kernel_region{
.name = "kernel_data",
.base = (vaddr_t)__data_start,
.size = ROUNDUP((uintptr_t)__data_end - (uintptr_t)__data_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
},
kernel_region{
.name = "kernel_bss",
.base = (vaddr_t)__bss_start,
.size = ROUNDUP((uintptr_t)_end - (uintptr_t)__bss_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
},
};
} // namespace
const ktl::span<const kernel_region> kernel_regions{_kernel_regions};
namespace {
// Declare storage for vmars that make up the statically known initial kernel regions. These are
// used to roughly sketch out and reserve portions of the kernel's aspace before we have the heap.
lazy_init::LazyInit<VmAddressRegion> kernel_physmap_vmar;
lazy_init::LazyInit<VmAddressRegion> kernel_image_vmar;
#if !DISABLE_KASLR
lazy_init::LazyInit<VmAddressRegion> kernel_random_padding_vmar;
#endif
lazy_init::LazyInit<VmAddressRegion> kernel_heap_vmar;
} // namespace
// Request the heap dimensions.
vaddr_t vm_get_kernel_heap_base() {
ASSERT(VIRTUAL_HEAP);
return kernel_heap_vmar->base();
}
size_t vm_get_kernel_heap_size() {
ASSERT(VIRTUAL_HEAP);
return kernel_heap_vmar->size();
}
// Initializes the statically known initial kernel region vmars. It needs to be global so that
// VmAddressRegion can friend it.
void vm_init_preheap_vmars() {
fbl::RefPtr<VmAddressRegion> root_vmar = VmAspace::kernel_aspace()->RootVmar();
// For VMARs that we are just reserving we request full RWX permissions. This will get refined
// later in the proper vm_init.
constexpr uint32_t kKernelVmarFlags = VMAR_FLAG_CAN_MAP_SPECIFIC | VMAR_CAN_RWX_FLAGS;
// Hold the vmar in a temporary refptr until we can activate it. Activating it will cause the
// address space to acquire a refptr allowing us to then safely drop our ref without triggering
// the object to get destroyed.
fbl::RefPtr<VmAddressRegion> vmar =
fbl::AdoptRef<VmAddressRegion>(&kernel_physmap_vmar.Initialize(
*root_vmar, PHYSMAP_BASE, PHYSMAP_SIZE, kKernelVmarFlags, "physmap vmar"));
{
Guard<CriticalMutex> guard(kernel_physmap_vmar->lock());
kernel_physmap_vmar->Activate();
}
// |kernel_image_size| is the size in bytes of the region of memory occupied by the kernel
// program's various segments (code, rodata, data, bss, etc.), inclusive of any gaps between
// them.
const size_t kernel_image_size = get_kernel_size();
const uintptr_t kernel_vaddr = reinterpret_cast<uintptr_t>(__executable_start);
// Create a VMAR that covers the address space occupied by the kernel program segments (code,
// rodata, data, bss ,etc.). By creating this VMAR, we are effectively marking these addresses as
// off limits to the VM. That way, the VM won't inadvertently use them for something else. This is
// consistent with the initial mapping in start.S where the whole kernel region mapping was
// written into the page table.
//
// Note: Even though there might be usable gaps in between the segments, we're covering the whole
// regions. The thinking is that it's both simpler and safer to not use the address space that
// exists between kernel program segments.
vmar = fbl::AdoptRef<VmAddressRegion>(&kernel_image_vmar.Initialize(
*root_vmar, kernel_vaddr, kernel_image_size, kKernelVmarFlags, "kernel region vmar"));
{
Guard<CriticalMutex> guard(kernel_image_vmar->lock());
kernel_image_vmar->Activate();
}
#if !DISABLE_KASLR // Disable random memory padding for KASLR
// Reserve random padding of up to 64GB after first mapping. It will make
// the adjacent memory mappings (kstack_vmar, arena:handles and others) at
// non-static virtual addresses.
size_t size_entropy;
crypto::global_prng::GetInstance()->Draw(&size_entropy, sizeof(size_entropy));
const size_t random_size = PAGE_ALIGN(size_entropy % (64ULL * GB));
vmar = fbl::AdoptRef<VmAddressRegion>(
&kernel_random_padding_vmar.Initialize(*root_vmar, PHYSMAP_BASE + PHYSMAP_SIZE, random_size,
kKernelVmarFlags, "random padding vmar"));
{
Guard<CriticalMutex> guard(kernel_random_padding_vmar->lock());
kernel_random_padding_vmar->Activate();
}
LTRACEF("VM: aspace random padding size: %#" PRIxPTR "\n", random_size);
#endif
if constexpr (VIRTUAL_HEAP) {
// Reserve the range for the heap.
const size_t heap_bytes =
ROUNDUP(gBootOptions->heap_max_size_mb * MB, size_t(1) << ARCH_HEAP_ALIGN_BITS);
vaddr_t kernel_heap_base = 0;
{
Guard<CriticalMutex> guard(root_vmar->lock());
zx_status_t status = root_vmar->AllocSpotLocked(
heap_bytes, ARCH_HEAP_ALIGN_BITS, ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
&kernel_heap_base);
ASSERT_MSG(status == ZX_OK, "Failed to allocate VMAR for heap");
}
// The heap has nothing to initialize later and we can create this from the beginning with only
// read and write and no execute.
vmar = fbl::AdoptRef<VmAddressRegion>(&kernel_heap_vmar.Initialize(
*root_vmar, kernel_heap_base, heap_bytes,
VMAR_FLAG_CAN_MAP_SPECIFIC | VMAR_FLAG_CAN_MAP_READ | VMAR_FLAG_CAN_MAP_WRITE,
"kernel heap"));
{
Guard<CriticalMutex> guard(kernel_heap_vmar->lock());
kernel_heap_vmar->Activate();
}
dprintf(INFO, "VM: kernel heap placed in range [%#" PRIxPTR ", %#" PRIxPTR ")\n",
kernel_heap_vmar->base(), kernel_heap_vmar->base() + kernel_heap_vmar->size());
}
}
void vm_init_preheap() {
LTRACE_ENTRY;
// allow the vmm a shot at initializing some of its data structures
VmAspace::KernelAspaceInitPreHeap();
vm_init_preheap_vmars();
zx_status_t status;
#if !DISABLE_KASLR // Disable random memory padding for KASLR
// Reserve up to 15 pages as a random padding in the kernel physical mapping
unsigned char entropy;
crypto::global_prng::GetInstance()->Draw(&entropy, sizeof(entropy));
struct list_node list;
list_initialize(&list);
size_t page_count = entropy % 16;
status = pmm_alloc_pages(page_count, 0, &list);
DEBUG_ASSERT(status == ZX_OK);
vm_page_t* page;
list_for_every_entry (&list, page, vm_page, queue_node) {
page->set_state(vm_page_state::WIRED);
}
LTRACEF("physical mapping padding page count %#" PRIxPTR "\n", page_count);
#endif
// grab a page and mark it as the zero page
status = pmm_alloc_page(0, &zero_page, &zero_page_paddr);
DEBUG_ASSERT(status == ZX_OK);
// consider the zero page a wired page part of the kernel.
zero_page->set_state(vm_page_state::WIRED);
void* ptr = paddr_to_physmap(zero_page_paddr);
DEBUG_ASSERT(ptr);
arch_zero_page(ptr);
AnonymousPageRequester::Init();
}
void vm_init() {
LTRACE_ENTRY;
// Protect the regions of the physmap that are not backed by normal memory.
//
// See the comments for |phsymap_protect_non_arena_regions| for why we're doing this.
//
physmap_protect_non_arena_regions();
// Mark the physmap no-execute.
physmap_protect_arena_regions_noexecute();
// Finish reserving the sections in the kernel_region
for (const auto& region : kernel_regions) {
if (region.size == 0) {
continue;
}
ASSERT(IS_PAGE_ALIGNED(region.base));
dprintf(ALWAYS,
"VM: reserving kernel region [%#" PRIxPTR ", %#" PRIxPTR ") flags %#x name '%s'\n",
region.base, region.base + region.size, region.arch_mmu_flags, region.name);
zx_status_t status = kernel_image_vmar->ReserveSpace(region.name, region.base, region.size,
region.arch_mmu_flags);
ASSERT(status == ZX_OK);
#if __has_feature(address_sanitizer)
asan_map_shadow_for(region.base, region.size);
#endif // __has_feature(address_sanitizer)
}
// Finishing reserving the physmap
zx_status_t status = kernel_physmap_vmar->ReserveSpace(
"physmap", PHYSMAP_BASE, PHYSMAP_SIZE, ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE);
ASSERT(status == ZX_OK);
#if !DISABLE_KASLR
status = kernel_random_padding_vmar->ReserveSpace(
"random_padding", kernel_random_padding_vmar->base(), kernel_random_padding_vmar->size(), 0);
ASSERT(status == ZX_OK);
#endif
cmpct_set_fill_on_alloc_threshold(gBootOptions->alloc_fill_threshold);
}
paddr_t vaddr_to_paddr(const void* va) {
if (is_physmap_addr(va)) {
return physmap_to_paddr(va);
}
// It doesn't make sense to be calling this on a non-kernel address, since we would otherwise be
// querying some 'random' active user address space, which is unlikely to be what the caller
// wants.
if (!is_kernel_address(reinterpret_cast<vaddr_t>(va))) {
return 0;
}
paddr_t pa;
zx_status_t rc =
VmAspace::kernel_aspace()->arch_aspace().Query(reinterpret_cast<vaddr_t>(va), &pa, nullptr);
if (rc != ZX_OK) {
return 0;
}
return pa;
}
static int cmd_vm(int argc, const cmd_args* argv, uint32_t) {
if (argc < 2) {
notenoughargs:
printf("not enough arguments\n");
usage:
printf("usage:\n");
printf("%s phys2virt <address>\n", argv[0].str);
printf("%s virt2phys <address>\n", argv[0].str);
printf("%s map <phys> <virt> <count> <flags>\n", argv[0].str);
printf("%s unmap <virt> <count>\n", argv[0].str);
return ZX_ERR_INTERNAL;
}
if (!strcmp(argv[1].str, "phys2virt")) {
if (argc < 3) {
goto notenoughargs;
}
if (!is_physmap_phys_addr(argv[2].u)) {
printf("address isn't in physmap\n");
return -1;
}
void* ptr = paddr_to_physmap((paddr_t)argv[2].u);
printf("paddr_to_physmap returns %p\n", ptr);
} else if (!strcmp(argv[1].str, "virt2phys")) {
if (argc < 3) {
goto notenoughargs;
}
if (!is_kernel_address(reinterpret_cast<vaddr_t>(argv[2].u))) {
printf("ERROR: outside of kernel address space\n");
return -1;
}
paddr_t pa;
uint flags;
zx_status_t err = VmAspace::kernel_aspace()->arch_aspace().Query(argv[2].u, &pa, &flags);
printf("arch_mmu_query returns %d\n", err);
if (err >= 0) {
printf("\tpa %#" PRIxPTR ", flags %#x\n", pa, flags);
}
} else if (!strcmp(argv[1].str, "map")) {
if (argc < 6) {
goto notenoughargs;
}
if (!is_kernel_address(reinterpret_cast<vaddr_t>(argv[3].u))) {
printf("ERROR: outside of kernel address space\n");
return -1;
}
size_t mapped;
auto err = VmAspace::kernel_aspace()->arch_aspace().MapContiguous(
argv[3].u, argv[2].u, (uint)argv[4].u, (uint)argv[5].u, &mapped);
printf("arch_mmu_map returns %d, mapped %zu\n", err, mapped);
} else if (!strcmp(argv[1].str, "unmap")) {
if (argc < 4) {
goto notenoughargs;
}
if (!is_kernel_address(reinterpret_cast<vaddr_t>(argv[2].u))) {
printf("ERROR: outside of kernel address space\n");
return -1;
}
size_t unmapped;
// Strictly only attempt to unmap exactly what the user requested, they can deal with any
// failure that might result.
auto err = VmAspace::kernel_aspace()->arch_aspace().Unmap(
argv[2].u, (uint)argv[3].u, ArchVmAspace::EnlargeOperation::No, &unmapped);
printf("arch_mmu_unmap returns %d, unmapped %zu\n", err, unmapped);
} else {
printf("unknown command\n");
goto usage;
}
return ZX_OK;
}
STATIC_COMMAND_START
STATIC_COMMAND("vm", "vm commands", &cmd_vm)
STATIC_COMMAND_END(vm)