| // 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) |