blob: 7353294dd564f7f5046f5ed44c1c54cade5428bf [file] [log] [blame]
// 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
#include <assert.h>
#include <lib/crypto/prng.h>
#include <zircon/types.h>
#include <arch/aspace.h>
#include <fbl/canary.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/macros.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <kernel/lockdep.h>
#include <kernel/mutex.h>
#include <vm/arch_vm_aspace.h>
#include <vm/vm.h>
class VmAddressRegion;
class VmEnumerator;
class VmMapping;
class VmAddressRegionOrMapping;
namespace hypervisor {
class GuestPhysicalAddressSpace;
} // namespace hypervisor
class VmObject;
class VmAspace : public fbl::DoublyLinkedListable<VmAspace*>, public fbl::RefCounted<VmAspace> {
// Create an address space of the type specified in |flags| with name |name|.
// Although reference counted, the returned VmAspace must be explicitly destroyed via Destroy.
// Returns null on failure (e.g. due to resource starvation).
static fbl::RefPtr<VmAspace> Create(uint flags, const char* name);
// Destroy this address space.
// Destroy does not free this object, but rather allows it to be freed when the last retaining
// RefPtr is destroyed.
zx_status_t Destroy();
void Rename(const char* name);
// flags
static const uint32_t TYPE_USER = (0 << 0);
static const uint32_t TYPE_KERNEL = (1 << 0);
// You probably do not want to use LOW_KERNEL. It is primarily
// used for SMP bootstrap to allow mappings of very low memory using
// the standard VMM subsystem.
static const uint32_t TYPE_LOW_KERNEL = (2 << 0);
static const uint32_t TYPE_GUEST_PHYS = (3 << 0);
static const uint32_t TYPE_MASK = (3 << 0);
// simple accessors
vaddr_t base() const { return base_; }
size_t size() const { return size_; }
const char* name() const { return name_; }
ArchVmAspace& arch_aspace() { return arch_aspace_; }
bool is_user() const { return (flags_ & TYPE_MASK) == TYPE_USER; }
bool is_aslr_enabled() const { return aslr_enabled_; }
// Get the root VMAR (briefly acquires the aspace lock)
// May return nullptr if the aspace has been destroyed or is not yet initialized.
fbl::RefPtr<VmAddressRegion> RootVmar();
// Returns true if the address space has been destroyed.
bool is_destroyed() const;
// accessor for singleton kernel address space
static VmAspace* kernel_aspace() { return kernel_aspace_; }
// given an address, return either the kernel aspace or the current user one
static VmAspace* vaddr_to_aspace(uintptr_t address);
// set the per thread aspace pointer to this
void AttachToThread(Thread* t);
void Dump(bool verbose) const;
static void DropAllUserPageTables();
void DropUserPageTables();
// Harvests all used page tables based on the passed in action by calling the
// |HarvestUserPageTables| in every VmAspace. This requires holding the aspaces_list_lock_ over
// the entire duration and whilst not a commonly used lock this function should still only be
// called infrequently to avoid monopolizing the lock.
using NonTerminalAction = ArchVmAspace::NonTerminalAction;
static void HarvestAllUserPageTables(NonTerminalAction action);
// If this is a user aspace instructs the backing arch aspace to free any unaccessed page tables
// using ArchVmAspace::HarvestNonTerminalAccessed() and the supplied |NonTerminalAction|.
void HarvestUserPageTables(NonTerminalAction action);
// Traverses the VM tree rooted at this node, in depth-first pre-order. If
// any methods of |ve| return false, the traversal stops and this method
// returns false. Returns true otherwise.
bool EnumerateChildren(VmEnumerator* ve);
// A collection of memory usage counts.
struct vm_usage_t {
// A count of pages covered by VmMapping ranges.
size_t mapped_pages;
// For the fields below, a page is considered committed if a VmMapping
// covers a range of a VmObject that contains that page, and that page
// has physical memory allocated to it.
// A count of committed pages that are only mapped into this address
// space.
size_t private_pages;
// A count of committed pages that are mapped into this and at least
// one other address spaces.
size_t shared_pages;
// A number that estimates the fraction of shared_pages that this
// address space is responsible for keeping alive.
// An estimate of:
// For each shared, committed page:
// scaled_shared_bytes +=
// PAGE_SIZE / (number of address spaces mapping this page)
// This number is strictly smaller than shared_pages * PAGE_SIZE.
size_t scaled_shared_bytes;
// Counts memory usage under the VmAspace.
zx_status_t GetMemoryUsage(vm_usage_t* usage);
size_t AllocatedPages() const;
// Generates a soft fault against this aspace. This is similar to a PageFault except:
// * This aspace may not currently be active and this does not have to be called from the
// hardware exception handler.
// * May be invoked spuriously in situations where the hardware mappings would have prevented a
// real PageFault from occurring.
zx_status_t SoftFault(vaddr_t va, uint flags);
// Generates an accessed flag fault against this aspace. This is a specialized version of
// SoftFault that will only resolve a potential missing access flag and nothing else.
zx_status_t AccessedFault(vaddr_t va);
// Convenience method for traversing the tree of VMARs to find the deepest
// VMAR in the tree that includes *va*.
// Returns nullptr if the aspace has been destroyed or is not yet initialized.
fbl::RefPtr<VmAddressRegionOrMapping> FindRegion(vaddr_t va);
// For region creation routines
static const uint VMM_FLAG_VALLOC_SPECIFIC = (1u << 0); // allocate at specific address
static const uint VMM_FLAG_COMMIT = (1u << 1); // commit memory up front (no demand paging)
// legacy functions to assist in the transition to VMARs
// These all assume a flat VMAR structure in which all VMOs are mapped
// as children of the root. They will all assert if used on user aspaces
// TODO(teisenbe): remove uses of these in favor of new VMAR interfaces
zx_status_t ReserveSpace(const char* name, size_t size, vaddr_t vaddr);
zx_status_t AllocPhysical(const char* name, size_t size, void** ptr, uint8_t align_pow2,
paddr_t paddr, uint vmm_flags, uint arch_mmu_flags);
zx_status_t AllocContiguous(const char* name, size_t size, void** ptr, uint8_t align_pow2,
uint vmm_flags, uint arch_mmu_flags);
zx_status_t Alloc(const char* name, size_t size, void** ptr, uint8_t align_pow2, uint vmm_flags,
uint arch_mmu_flags);
zx_status_t FreeRegion(vaddr_t va);
// Internal use function for mapping VMOs. Do not use. This is exposed in
// the public API purely for tests.
zx_status_t MapObjectInternal(fbl::RefPtr<VmObject> vmo, const char* name, uint64_t offset,
size_t size, void** ptr, uint8_t align_pow2, uint vmm_flags,
uint arch_mmu_flags);
uintptr_t vdso_base_address() const;
uintptr_t vdso_code_address() const;
// Helper function to test for collision with vdso_code_mapping_.
bool IntersectsVdsoCode(vaddr_t base, size_t size) const;
// Share the aspace lock with VmAddressRegion/VmMapping so they can serialize
// changes to the aspace.
friend class VmAddressRegionOrMapping;
friend class VmAddressRegion;
friend class VmMapping;
Lock<Mutex>* lock() const TA_RET_CAP(lock_) { return &lock_; }
Lock<Mutex>& lock_ref() const TA_RET_CAP(lock_) { return lock_; }
// Expose the PRNG for ASLR to VmAddressRegion
crypto::PRNG& AslrPrng() {
return aslr_prng_;
uint8_t AslrEntropyBits(bool compact) const {
return compact ? aslr_compact_entropy_bits_ : aslr_entropy_bits_;
// can only be constructed via factory
VmAspace(vaddr_t base, size_t size, uint32_t flags, const char* name);
// private destructor that can only be used from the ref ptr
friend fbl::RefPtr<VmAspace>;
// complete initialization, may fail in OOM cases
zx_status_t Init();
void InitializeAslr();
static constexpr uint arch_aspace_flags_from_flags(uint32_t flags) {
bool is_high_kernel = (flags & TYPE_MASK) == TYPE_KERNEL;
bool is_guest = (flags & TYPE_MASK) == TYPE_GUEST_PHYS;
return (is_high_kernel ? ARCH_ASPACE_FLAG_KERNEL : 0u) |
(is_guest ? ARCH_ASPACE_FLAG_GUEST : 0u);
// internal page fault routine, friended to be only called by vmm_page_fault_handler
zx_status_t PageFault(vaddr_t va, uint flags);
friend zx_status_t vmm_page_fault_handler(vaddr_t va, uint flags);
friend class hypervisor::GuestPhysicalAddressSpace;
// magic
fbl::Canary<fbl::magic("VMAS")> canary_;
// members
const vaddr_t base_;
const size_t size_;
const uint32_t flags_;
char name_[32];
bool aspace_destroyed_ = false;
bool aslr_enabled_ = false;
uint8_t aslr_entropy_bits_ = 0;
uint8_t aslr_compact_entropy_bits_ = 0;
mutable DECLARE_MUTEX(VmAspace) lock_;
// root of virtual address space
// Access to this reference is guarded by lock_.
fbl::RefPtr<VmAddressRegion> root_vmar_;
// PRNG used by VMARs for address choices. We record the seed to enable
// reproducible debugging.
crypto::PRNG aslr_prng_;
uint8_t aslr_seed_[crypto::PRNG::kMinEntropy];
// architecturally specific part of the aspace
ArchVmAspace arch_aspace_;
fbl::RefPtr<VmMapping> vdso_code_mapping_;
// initialization routines need to construct the singleton kernel address space
// at a particular points in the bootup process
static void KernelAspaceInitPreHeap();
static VmAspace* kernel_aspace_;
friend void vm_init_preheap();
void DumpAllAspaces(bool verbose);