blob: 9897eb1bfcf878b92ba48ee6b8fd783846f85593 [file] [edit]
// 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
// https://opensource.org/licenses/MIT
#ifndef ZIRCON_KERNEL_VM_INCLUDE_VM_VM_ADDRESS_REGION_H_
#define ZIRCON_KERNEL_VM_INCLUDE_VM_VM_ADDRESS_REGION_H_
#include <assert.h>
#include <lib/btree.h>
#include <lib/crypto/prng.h>
#include <lib/fit/function.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <stdint.h>
#include <zircon/types.h>
#include <fbl/canary.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <ffl/saturating_arithmetic.h>
#include <ktl/limits.h>
#include <ktl/optional.h>
#include <vm/vm_address_region_observer.h>
#include <vm/vm_aspace.h>
#include <vm/vm_cow_pages.h>
#include <vm/vm_object.h>
#include <vm/vm_page_list.h>
// Creation flags for VmAddressRegion and VmMappings
// When randomly allocating subregions, reduce sprawl by placing allocations
// near each other.
#define VMAR_FLAG_COMPACT (1 << 0)
// Request that the new region be at the specified offset in its parent region.
#define VMAR_FLAG_SPECIFIC (1 << 1)
// Like VMAR_FLAG_SPECIFIC, but permits overwriting existing mappings. This
// flag will not overwrite through a subregion.
#define VMAR_FLAG_SPECIFIC_OVERWRITE (1 << 2)
// Allow VmMappings to be created inside the new region with the SPECIFIC or
// OFFSET_IS_UPPER_LIMIT flag.
#define VMAR_FLAG_CAN_MAP_SPECIFIC (1 << 3)
// When on a VmAddressRegion, allow VmMappings to be created inside the region
// with read permissions. When on a VmMapping, controls whether or not the
// mapping can gain this permission.
#define VMAR_FLAG_CAN_MAP_READ (1 << 4)
// When on a VmAddressRegion, allow VmMappings to be created inside the region
// with write permissions. When on a VmMapping, controls whether or not the
// mapping can gain this permission.
#define VMAR_FLAG_CAN_MAP_WRITE (1 << 5)
// When on a VmAddressRegion, allow VmMappings to be created inside the region
// with execute permissions. When on a VmMapping, controls whether or not the
// mapping can gain this permission.
#define VMAR_FLAG_CAN_MAP_EXECUTE (1 << 6)
// Require that VMO backing the mapping is non-resizable.
#define VMAR_FLAG_REQUIRE_NON_RESIZABLE (1 << 7)
// Allow VMO backings that could result in faults.
#define VMAR_FLAG_ALLOW_FAULTS (1 << 8)
// Treat the offset as an upper limit when allocating a VMO or child VMAR.
#define VMAR_FLAG_OFFSET_IS_UPPER_LIMIT (1 << 9)
// Opt this VMAR out of certain debugging checks. This allows for kernel mappings that have a more
// dynamic management strategy, that the regular checks would otherwise spuriously trip on.
#define VMAR_FLAG_DEBUG_DYNAMIC_KERNEL_MAPPING (1 << 10)
// Memory accesses past the stream size rounded up to the page boundary will fault.
#define VMAR_FLAG_FAULT_BEYOND_STREAM_SIZE (1 << 11)
#define VMAR_CAN_RWX_FLAGS \
(VMAR_FLAG_CAN_MAP_READ | VMAR_FLAG_CAN_MAP_WRITE | VMAR_FLAG_CAN_MAP_EXECUTE)
enum class VmAddressRegionOpChildren : bool {
Yes,
No,
};
// forward declarations
class VmAddressRegion;
class VmMapping;
class VmEnumerator;
enum class VmAddressRegionEnumeratorType : bool;
template <VmAddressRegionEnumeratorType, typename>
class VmAddressRegionEnumerator;
class MultiPageRequest;
// A VmAddressRegion represents a contiguous region of the virtual address
// space. It is partitioned by non-overlapping children of the following types:
// 1) child VmAddressRegion
// 2) child VmMapping (leafs that map VmObjects into the address space)
// 3) gaps (logical, not actually objects).
//
// VmAddressRegionOrMapping represents a tagged union of the two types.
//
// A VmAddressRegion/VmMapping may be in one of two states: ALIVE or DEAD. If
// it is ALIVE, then the VmAddressRegion is a description of the virtual memory
// mappings of the address range it represents in its parent VmAspace. If it is
// DEAD, then the VmAddressRegion is invalid and has no meaning.
//
// All VmAddressRegion and VmMapping state is protected by the aspace lock.
class VmAddressRegionOrMapping : public fbl::RefCounted<VmAddressRegionOrMapping> {
public:
// If a VMO-mapping, unmap all pages and remove dependency on vm object it has a ref to.
// Otherwise recursively destroy child VMARs and transition to the DEAD state.
//
// Returns ZX_OK on success, ZX_ERR_BAD_STATE if already dead, and other
// values on error (typically unmap failure).
virtual zx_status_t Destroy();
// accessors
vaddr_t base() const { return base_; }
size_t size() const { return size_; }
uint32_t flags() const { return flags_; }
const fbl::RefPtr<VmAspace>& aspace() const { return aspace_; }
// Subtype information and safe down-casting
bool is_mapping() const { return is_mapping_; }
fbl::RefPtr<VmAddressRegion> as_vm_address_region();
fbl::RefPtr<VmMapping> as_vm_mapping();
VmAddressRegion* as_vm_address_region_ptr();
const VmAddressRegion* as_vm_address_region_ptr() const;
VmMapping* as_vm_mapping_ptr();
const VmMapping* as_vm_mapping_ptr() const;
static fbl::RefPtr<VmAddressRegion> downcast_as_vm_address_region(
fbl::RefPtr<VmAddressRegionOrMapping>* region_or_map);
static fbl::RefPtr<VmMapping> downcast_as_vm_mapping(
fbl::RefPtr<VmAddressRegionOrMapping>* region_or_map);
// Dump debug info
virtual void DumpLocked(uint depth, bool verbose) const TA_REQ(lock()) = 0;
// Expose our backing lock for annotation purposes.
Lock<CriticalMutex>* lock() const TA_RET_CAP(aspace_->lock()) { return aspace_->lock(); }
Lock<CriticalMutex>& lock_ref() const TA_RET_CAP(aspace_->lock()) { return aspace_->lock_ref(); }
Lock<CriticalMutex>* region_lock() const TA_RET_CAP(aspace_->region_lock_) {
return aspace_->region_lock();
}
Lock<CriticalMutex>& region_lock_ref() const TA_RET_CAP(aspace_->region_lock_) {
return aspace_->region_lock_ref();
}
bool is_in_range(vaddr_t base, size_t size) const {
const size_t offset = base - base_;
return base >= base_ && offset < size_ && size_ - offset >= size;
}
// Memory priorities that can be applied to VMARs and mappings to propagate to VMOs and page
// tables.
enum class MemoryPriority : bool {
// Default overcommit priority where reclamation is allowed.
DEFAULT,
// High priority prevents all reclamation.
HIGH,
};
// Returns true if the instance is alive and reporting information that
// reflects the address space layout. |aspace()->lock()| must be held.
bool IsAliveLocked() const TA_REQ(lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
canary_.Assert();
return state_ == LifeCycleState::ALIVE;
}
private:
fbl::Canary<fbl::magic("VMRM")> canary_;
const bool is_mapping_;
protected:
// friend VmAddressRegion so it can access DestroyLocked
friend VmAddressRegion;
template <VmAddressRegionEnumeratorType, typename>
friend class VmAddressRegionEnumerator;
// destructor, should only be invoked from RefPtr
virtual ~VmAddressRegionOrMapping();
friend fbl::RefPtr<VmAddressRegionOrMapping>;
enum class LifeCycleState : uint8_t {
// Initial state: if NOT_READY, then do not invoke Destroy() in the
// destructor
NOT_READY,
// Usual state: information is representative of the address space layout
ALIVE,
// Object is invalid
DEAD
};
LifeCycleState state_locked() const TA_REQ(lock()) TA_NO_THREAD_SAFETY_ANALYSIS { return state_; }
LifeCycleState state_locked_region() const TA_REQ(region_lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return state_;
}
VmAddressRegionOrMapping(vaddr_t base, size_t size, uint32_t flags, VmAspace* aspace,
VmAddressRegion* parent, bool is_mapping);
// Check if the given *arch_mmu_flags* are allowed under this
// regions *flags_*
bool is_valid_mapping_flags(arch_mmu_flags_t arch_mmu_flags) {
// Work out what flags we must support for these arch_mmu_flags
uint32_t needed = 0;
if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_READ) {
needed |= VMAR_FLAG_CAN_MAP_READ;
}
if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) {
needed |= VMAR_FLAG_CAN_MAP_WRITE;
}
if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) {
needed |= VMAR_FLAG_CAN_MAP_EXECUTE;
}
// Mask out the actual relevant mappings flags we have.
const uint32_t actual =
flags_ & (VMAR_FLAG_CAN_MAP_READ | VMAR_FLAG_CAN_MAP_WRITE | VMAR_FLAG_CAN_MAP_EXECUTE);
// Validate that every |needed| occurs in |actual|
return (needed & actual) == needed;
}
virtual zx_status_t DestroyLocked() TA_REQ(lock()) TA_REQ(region_lock()) = 0;
// Performs any actions necessary to apply a high memory priority over the given range.
// This method is always safe to call as it will internally check the memory priority status and
// skip if necessary, so the caller does not need to worry about races with a different memory
// priority being applied.
// As this may need to acquire the lock even to check the memory priority, if the caller knows
// they have not caused this to become high priority (i.e. they have called
// SetMemoryPriorityLocked with MemoryPriority::DEFAULT), then calling this should be skipped for
// performance.
// Memory that needs to be committed for a high memory priority are user pager backed pages and
// any compressed or loaned pages. Anonymous pages and copy-on-write pages do not allocated /
// committed.
// This method has no return value as it is entirely best effort and no part of its operation is
// needed for correctness.
virtual void CommitHighMemoryPriority() TA_EXCL(lock()) = 0;
// Transition from NOT_READY to READY, and add references to self to related
// structures.
// On error no state is changed and no references are added.
virtual zx_status_t Activate() TA_REQ(region_lock()) TA_REQ(lock()) = 0;
// current state of the VMAR. If LifeCycleState::DEAD, then all other
// fields are invalid.
LifeCycleState state_ TA_GUARDED(region_lock()) TA_GUARDED(lock()) = LifeCycleState::ALIVE;
// Priority of the VMAR. This starts at DEFAULT and must be reset back to default as part of the
// destroy path to ensure any propagation is undone correctly.
MemoryPriority memory_priority_ TA_GUARDED(lock()) = MemoryPriority::DEFAULT;
// flags from VMAR creation time
const uint32_t flags_;
// address/size within the container address space
const vaddr_t base_;
const size_t size_;
// pointer back to our member address space. The aspace's lock is used
// to serialize all modifications.
const fbl::RefPtr<VmAspace> aspace_;
// pointer back to our parent region (nullptr if root or destroyed)
VmAddressRegion* parent_ TA_GUARDED(lock());
};
// A list of regions ordered by virtual address. Templated to allow for test code to avoid needing
// to instantiate 'real' VmAddressRegionOrMapping instances.
// TODO(https://fxbug.dev/503042881): The RegionList API is quite object reference focused, instead
// of iterator focused, and this leads to a lot of theoretically redundant 'find' operations on the
// btree. These APIs, and the VMAR logic using them, should be re-designed to be more iterator
// based.
template <typename T = VmAddressRegionOrMapping>
class RegionList final {
public:
using PtrType = fbl::RefPtr<T>;
using ChildList = btree::BTree<vaddr_t, PtrType, VmAddressRegionObserver>;
RegionList() = default;
// Remove *region* from the list, returns the removed region.
void RemoveRegion(T* region) {
auto it = regions_.find(region->base());
ASSERT(it.IsValid());
regions_.erase(it);
}
// Request the region to the left or right of the given region.
ChildList::iterator LeftOf(T* region) {
auto it = regions_.find(region->base());
DEBUG_ASSERT(it.IsValid());
it--;
return it;
}
ChildList::iterator RightOf(T* region) {
auto it = regions_.find(region->base());
DEBUG_ASSERT(it.IsValid());
it++;
return it;
}
// Insert *region* to the region list. On failure the region list is unmodified.
zx_status_t InsertRegion(fbl::RefPtr<T> region) {
const vaddr_t base = region->base();
return regions_.insert(base, ktl::move(region)).IsValid() ? ZX_OK : ZX_ERR_NO_MEMORY;
}
// Replaces the target region with a new region at the same address. Unlike insertion this cannot
// fail.
void ReplaceRegion(T* prev, fbl::RefPtr<T> region) {
ASSERT(prev->base() == region->base());
auto it = regions_.find(prev->base());
ASSERT(it.IsValid());
regions_.update(it, ktl::move(region));
}
// Use a static template to allow for returning a const and non-const pointer depending on the
// constness of self.
template <typename S>
static ktl::conditional_t<ktl::is_const_v<S>, const T, T>* FindRegion(S* self, vaddr_t addr) {
// Find the first region with a base greater than *addr*. If a region
// exists for *addr*, it will be immediately before it.
auto itr = --self->regions_.upper_bound(addr);
if (!itr.IsValid()) {
return nullptr;
}
auto region = (*itr).second;
// Subregion size should never be zero unless during unmapping which should never overlap with
// this operation.
DEBUG_ASSERT(region->size() > 0);
vaddr_t region_end;
bool overflowed = add_overflow(region->base(), region->size() - 1, &region_end);
ASSERT(!overflowed);
if (region->base() > addr || addr > region_end) {
return nullptr;
}
return region;
}
// Find the region that covers addr, returns nullptr if not found.
const T* FindRegion(vaddr_t addr) const { return FindRegion(this, addr); }
T* FindRegion(vaddr_t addr) { return FindRegion(this, addr); }
template <typename S>
static ktl::conditional_t<ktl::is_const_v<S>, typename ChildList::const_iterator,
typename ChildList::iterator>
IncludeOrHigher(S* self, vaddr_t base) {
// Find the first region with a base greater than *base*. If a region
// exists for *base*, it will be immediately before it.
auto itr = self->regions_.upper_bound(base);
itr--;
if (!itr.IsValid()) {
itr = self->regions_.begin();
} else {
const T* region = (*itr).second;
if (base >= region->base() && base - region->base() >= region->size()) {
// If *base* isn't in this region, ignore it.
++itr;
}
}
return itr;
}
// Find the region that contains |base|, or if that doesn't exist, the first region that contains
// an address greater than |base|.
ChildList::iterator IncludeOrHigher(vaddr_t base) { return IncludeOrHigher(this, base); }
ChildList::const_iterator IncludeOrHigher(vaddr_t base) const {
return IncludeOrHigher(this, base);
}
ChildList::iterator UpperBound(vaddr_t base) { return regions_.upper_bound(base); }
ChildList::const_iterator UpperBound(vaddr_t base) const { return regions_.upper_bound(base); }
// Check whether it would be valid to create a child in the range [base, base+size).
bool IsRangeAvailable(vaddr_t base, size_t size) const {
DEBUG_ASSERT(size > 0);
// Find the first region with base > *base*. Since subregions_ has no
// overlapping elements, we just need to check this one and the prior
// child.
auto prev = regions_.upper_bound(base);
auto next = prev--;
if (prev.IsValid()) {
const T* p = (*prev).second;
vaddr_t prev_last_byte;
if (add_overflow(p->base(), p->size() - 1, &prev_last_byte)) {
return false;
}
if (prev_last_byte >= base) {
return false;
}
}
if (next.IsValid()) {
const T* n = (*next).second;
vaddr_t last_byte;
if (add_overflow(base, size - 1, &last_byte)) {
return false;
}
if (n->base() <= last_byte) {
return false;
}
}
return true;
}
// Returns the base address of an available spot in the address range that satisfies the given
// entropy, alignment, size, and upper limit requirements. If no spot is found that satisfies the
// given entropy (i.e. target_index), the number of candidate spots encountered is returned.
//
// See vm/vm_address_region_subtree_state.h for an explanation of the augmented state used by this
// method to perform efficient tree traversal.
struct FindSpotAtIndexFailed {
size_t candidate_spot_count;
};
fit::result<FindSpotAtIndexFailed, vaddr_t> FindSpotAtIndex(vaddr_t target_index,
uint8_t align_pow2, size_t size,
vaddr_t parent_base,
size_t parent_size,
vaddr_t upper_limit) const {
// Returns the number of addresses that satisfy the size and alignment in the given range,
// accounting for ranges that overlap the upper limit.
const auto spots_in_range = [align_pow2, size, upper_limit](vaddr_t aligned_base,
size_t aligned_size) -> size_t {
DEBUG_ASSERT(aligned_base < upper_limit);
const size_t range_limit = ffl::SaturateAddAs<size_t>(aligned_base, aligned_size);
const size_t clamped_range_size =
range_limit < upper_limit ? aligned_size : aligned_size - (range_limit - upper_limit);
if (clamped_range_size >= size) {
return ((clamped_range_size - size) >> align_pow2) + 1;
}
return 0;
};
// Returns the given range with the base aligned and the size adjusted to maintain the same end
// address. If the aligned base address is greater than the end address, the returned size is
// zero.
struct AlignedRange {
vaddr_t base;
size_t size;
};
const auto align_range = [align_pow2](vaddr_t range_base, size_t range_size) -> AlignedRange {
const vaddr_t aligned_base = ROUNDUP(range_base, 1UL << align_pow2);
const size_t base_delta = aligned_base - range_base;
const size_t aligned_size = ffl::SaturateSubtractAs<size_t>(range_size, base_delta);
return {.base = aligned_base, .size = aligned_size};
};
// Track the number of candidate spots encountered.
size_t candidate_spot_count = 0;
// As we iterate through regions we remember the end of the last allocated region as the start
// of a potential gap.
vaddr_t next_gap_start = parent_base;
// Because an allocation can end at the very top of the 64-bit address space the calculation of
// the logical start of the next gap (end + 1) could overflow. To assist with debug validation
// we track this overflow explicitly.
bool next_gap_overflow = false;
// When we do find something record it here. Use UINT64_MAX, which can never be a valid base
// address, to track the difference between the walk terminating early with success, or
// completing with success (and hence not yet having a spot).
vaddr_t spot = UINT64_MAX;
// Helper to process a gap and count our spots. Returns true if a spot was found.
auto record_gap = [&](vaddr_t gap_base, size_t gap_size) -> bool {
const AlignedRange aligned_gap = align_range(gap_base, gap_size);
if (aligned_gap.base >= upper_limit) {
return false;
}
const size_t spot_count = spots_in_range(aligned_gap.base, aligned_gap.size);
candidate_spot_count += spot_count;
if (target_index < spot_count) {
spot = aligned_gap.base + (target_index << align_pow2);
return true;
}
target_index -= spot_count;
return false;
};
// Lambda passed to the walker for handling an intermediate btree node.
auto examine_subtree = [&](VmAddressRegionObserver::State state) -> zx_status_t {
if (next_gap_start < state.min_addr()) {
if (record_gap(next_gap_start, state.min_addr() - next_gap_start)) {
return ZX_ERR_STOP;
}
}
if (state.min_addr() >= upper_limit) {
return ZX_ERR_OUT_OF_RANGE;
}
if (auto max_gap = state.max_gap(); max_gap && *max_gap < size) {
// max_addr is inclusive, but already_visited is exclusive, so we add 1. Should this
// overflow then that means we have reached the end of the possible address space and will
// be handled when we check for trailing gaps at the end.
next_gap_overflow = add_overflow(state.max_addr, 1, &next_gap_start);
// This subtree has no gaps that would fit, so skip the entire subtree with ZX_ERR_NEXT.
return ZX_ERR_NEXT;
}
// Set already_visited to the start of the subtree as we are about to descend into it and we
// previously processed any gap up to min_addr.
next_gap_start = state.min_addr();
return ZX_OK;
};
// Lambda passed to the walker for handling leaf btree nodes.
auto examine_leaf = [&](VmAddressRegionObserver::State state, auto first,
auto last) -> zx_status_t {
if (next_gap_start < state.min_addr()) {
if (record_gap(next_gap_start, state.min_addr() - next_gap_start)) {
return ZX_ERR_STOP;
}
}
if (state.min_addr() >= upper_limit) {
return ZX_ERR_OUT_OF_RANGE;
}
// No matter what happens, we will have processed till max_addr + 1. See examine_subtree for
// why explanation of +1 and overflow.
next_gap_overflow = add_overflow(state.max_addr, 1, &next_gap_start);
if (auto max_gap = state.max_gap(); max_gap && *max_gap < size) {
// No gaps in this leaf node what would fit, can skip the iteration and go to the next node.
return ZX_ERR_NEXT;
}
auto prev = first;
// The provided iterators are inclusive, so increment last to simplify our loop.
last++;
for (first++; first != last; first++) {
vaddr_t gap_start = (*prev).second->base() + (*prev).second->size();
vaddr_t gap_end = (*first).second->base();
if (gap_start < gap_end) {
if (record_gap(gap_start, gap_end - gap_start)) {
// Location found, can cease walking.
return ZX_ERR_STOP;
}
}
prev = first;
}
// Continue to the next node.
return ZX_ERR_NEXT;
};
// Walk the btree examining the augmented state to optimally skip irrelevant subtrees.
zx_status_t status = regions_.walk(examine_subtree, examine_leaf);
if (status != ZX_OK && status != ZX_ERR_OUT_OF_RANGE) {
return fit::error(FindSpotAtIndexFailed{candidate_spot_count});
}
// Check if we already found a spot or if we need to consider any trailing gap.
if (spot != UINT64_MAX) {
return fit::success{spot};
}
if (unlikely(next_gap_overflow)) {
vaddr_t parent_top;
// The next gap should only overflow if the parent region is the end of the 64-bit address
// space. There should also, therefore, not actually be any gap.
ASSERT(add_overflow(parent_base, parent_size, &parent_top));
ASSERT(parent_top == next_gap_start);
} else {
// Any potential remaining gap has not wrapped, but the parent could still be at the end of
// address space, so operate on its max_byte and not its top to avoid overflow.
const vaddr_t parent_max_byte = parent_base + (parent_size - 1);
if (next_gap_start <= parent_max_byte) {
const size_t remaining_size = parent_max_byte - next_gap_start + 1;
if (record_gap(next_gap_start, remaining_size)) {
return fit::success{spot};
}
}
}
return fit::error(FindSpotAtIndexFailed{candidate_spot_count});
}
// Get the allocation spot that is free and large enough for the aligned size.
zx_status_t GetAllocSpot(vaddr_t* alloc_spot, uint8_t align_pow2, uint8_t entropy, size_t size,
vaddr_t parent_base, size_t parent_size, crypto::Prng* prng,
vaddr_t upper_limit = ktl::numeric_limits<vaddr_t>::max()) const {
DEBUG_ASSERT(entropy < sizeof(size_t) * 8);
// The number of addresses to consider based on the configured entropy.
const size_t max_candidate_spaces = 1ul << entropy;
// We first pick an index in [0, max_candidate_spaces] and hope to find a spot there. If the
// number of available spots is less than the selected index, the attempt fails, returning the
// actual number of candidate spots found, and we try again in this smaller range.
//
// This is mathematically equivalent to randomly picking a spot within [0, candidate_spot_count]
// when selected_index <= candidate_spot_count.
//
// Prove as following:
// Define M = candidate_spot_count
// Define N = max_candidate_spaces (M < N, otherwise we can randomly allocate any spot from
// [0, max_candidate_spaces], thus allocate a specific slot has (1 / N) probability).
// Define slot X0 where X0 belongs to [1, M].
// Define event A: randomly pick a slot X in [1, N], N = X0.
// Define event B: randomly pick a slot X in [1, N], N belongs to [1, M].
// Define event C: randomly pick a slot X in [1, N], N = X0 when N belongs to [1, M].
// P(C) = P(A | B)
// Since when A happens, B definitely happens, so P(AB) = P(A)
// P(C) = P(A) / P(B) = (1 / N) / (M / N) = (1 / M)
// which is equal to the probability of picking a specific spot in [0, M].
vaddr_t selected_index = prng != nullptr ? prng->RandInt(max_candidate_spaces) : 0;
fit::result allocation_result =
FindSpotAtIndex(selected_index, align_pow2, size, parent_base, parent_size, upper_limit);
if (allocation_result.is_error()) {
const size_t candidate_spot_count = allocation_result.error_value().candidate_spot_count;
if (candidate_spot_count == 0) {
return ZX_ERR_NO_RESOURCES;
}
// If the number of available spaces is smaller than the selected index, pick again from the
// available range.
DEBUG_ASSERT(candidate_spot_count < max_candidate_spaces);
DEBUG_ASSERT(prng);
selected_index = prng->RandInt(candidate_spot_count);
allocation_result =
FindSpotAtIndex(selected_index, align_pow2, size, parent_base, parent_size, upper_limit);
}
DEBUG_ASSERT(allocation_result.is_ok());
*alloc_spot = allocation_result.value();
ASSERT_MSG(IS_ROUNDED(*alloc_spot, 1UL << align_pow2), "size=%zu align_pow2=%u alloc_spot=%zx",
size, align_pow2, *alloc_spot);
return ZX_OK;
}
// Returns whether the region list is empty.
bool IsEmpty() const { return regions_.is_empty(); }
// Returns the first element of the list.
T& front() {
DEBUG_ASSERT(!IsEmpty());
return *(*regions_.begin()).second;
}
ChildList::iterator begin() { return regions_.begin(); }
ChildList::const_iterator begin() const { return regions_.begin(); }
ChildList::iterator end() { return regions_.end(); }
ChildList::const_iterator end() const { return regions_.end(); }
size_t size_slow() const { return regions_.calculate_utilization_slow().stored_values; }
private:
// list of memory regions, indexed by base address.
ChildList regions_;
};
// A representation of a contiguous range of virtual address space
class VmAddressRegion final : public VmAddressRegionOrMapping {
public:
// Creates a root region. This will span the entire aspace
static zx_status_t CreateRootLocked(VmAspace& aspace, uint32_t vmar_flags,
fbl::RefPtr<VmAddressRegion>* out)
TA_REQ(aspace.region_lock()) TA_REQ(aspace.lock());
// Creates a subregion of this region
zx_status_t CreateSubVmar(size_t offset, size_t size, uint8_t align_pow2, uint32_t vmar_flags,
const char* name, fbl::RefPtr<VmAddressRegion>* out);
// Creates a VmMapping within this region. To avoid leaks, this should be paired with a call to
// VmMapping::Destroy if desired; dropping `MapResult::mapping` will *not* destroy the mapping.
struct MapResult {
// This will never be null
fbl::RefPtr<VmMapping> mapping;
// Represents the virtual address of |mapping| at the time of creation, which is equivalent to
// |mapping->base_locking()|.
vaddr_t base;
};
zx::result<MapResult> CreateVmMapping(size_t mapping_offset, size_t size, uint8_t align_pow2,
uint32_t vmar_flags, fbl::RefPtr<VmObject> vmo,
uint64_t vmo_offset, arch_mmu_flags_t arch_mmu_flags,
const char* name);
// Finds the child region that contains the given addr. If addr is in a gap,
// returns nullptr. This is a non-recursive search.
fbl::RefPtr<VmAddressRegionOrMapping> FindRegion(vaddr_t addr);
fbl::RefPtr<VmAddressRegionOrMapping> FindRegionLocked(vaddr_t addr) TA_REQ(lock());
// Applies the given memory priority to this VMAR, which may or may not result in a change. Up to
// the derived type to know how to apply and update the |memory_priority_| field.
zx_status_t SetMemoryPriorityLocked(MemoryPriority priority) TA_REQ(lock());
enum class RangeOpType {
Commit,
Decommit,
MapRange,
Zero,
DontNeed,
AlwaysNeed,
Prefetch,
};
// Apply |op| to VMO mappings in the specified range of pages.
zx_status_t RangeOp(RangeOpType op, vaddr_t base, size_t len,
VmAddressRegionOpChildren op_children, user_inout_ptr<void> buffer,
size_t buffer_size);
// Unmap a subset of the region of memory in the containing address space,
// returning it to this region to allocate. If a subregion is entirely in
// the range, and op_children is Yes, that subregion is destroyed. If a subregion is partially in
// the range, Unmap() will fail.
zx_status_t Unmap(vaddr_t base, size_t size, VmAddressRegionOpChildren op_children);
// Change protections on a subset of the region of memory in the containing
// address space. If the requested range overlaps with a subregion and op_children is No,
// Protect() will fail, otherwise the mapping permissions in the sub-region may only be reduced.
zx_status_t Protect(vaddr_t base, size_t size, arch_mmu_flags_t new_arch_mmu_flags,
VmAddressRegionOpChildren op_children);
// Reserve a memory region within this VMAR. This region is already mapped in the page table with
// |arch_mmu_flags|. VMAR should create a VmMapping for this region even though no physical pages
// need to be allocated for this region.
zx_status_t ReserveSpace(const char* name, size_t base, size_t size,
arch_mmu_flags_t arch_mmu_flags);
const char* name() const { return name_; }
bool has_parent() const;
void DumpLocked(uint depth, bool verbose) const TA_REQ(region_lock()) TA_REQ(lock()) override;
// Recursively traverses the regions for a given virtual address and returns a raw pointer to a
// mapping if one is found. The returned pointer is only valid as long as the aspace lock remains
// held.
VmMapping* FindMappingLocked(vaddr_t va) TA_REQ(lock());
// Apply a memory priority to this VMAR and all of its subregions.
zx_status_t SetMemoryPriority(MemoryPriority priority);
// Recursively compute the amount of attributed memory within this region
using AttributionCounts = VmObject::AttributionCounts;
AttributionCounts GetAttributedMemory() const;
// Constructors are public as LazyInit cannot use them otherwise, even if friended, but
// otherwise should be considered private and Create...() should be used instead.
VmAddressRegion(VmAspace& aspace, vaddr_t base, size_t size, uint32_t vmar_flags);
VmAddressRegion(VmAddressRegion& parent, vaddr_t base, size_t size, uint32_t vmar_flags,
const char* name);
bool is_in_range(vaddr_t base, size_t size) const {
const size_t offset = base - base_;
return base >= base_ && offset < size_ && size_ - offset >= size;
}
// Traverses this vmar (and any sub-vmars) starting at this node, in depth-first pre-order. See
// VmEnumerator for more details. If this vmar is not alive (in the LifeCycleState sense) or
// otherwise not enumerable this returns ZX_ERR_BAD_STATE, otherwise the result of enumeration is
// returned.
zx_status_t EnumerateChildren(VmEnumerator* ve) TA_EXCL(lock());
protected:
friend class VmAspace;
friend lazy_init::Access;
friend void vm_init_preheap();
// constructor for use in creating the kernel aspace singleton
explicit VmAddressRegion(VmAspace& kernel_aspace);
void CommitHighMemoryPriority() override TA_EXCL(lock());
friend class VmMapping;
template <VmAddressRegionEnumeratorType, typename>
friend class VmAddressRegionEnumerator;
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(VmAddressRegion);
fbl::Canary<fbl::magic("VMAR")> canary_;
zx_status_t DestroyLocked() TA_REQ(lock()) TA_REQ(region_lock()) override;
zx_status_t Activate() TA_REQ(region_lock()) TA_REQ(lock()) override;
// Helpers to share code between CreateSubVmar and CreateVmMapping
zx_status_t CreateSubVmarInternal(size_t offset, size_t size, uint8_t align_pow2,
uint32_t vmar_flags, fbl::RefPtr<VmObject> vmo,
uint64_t vmo_offset, arch_mmu_flags_t arch_mmu_flags,
const char* name, vaddr_t* base_out,
fbl::RefPtr<VmAddressRegionOrMapping>* out);
zx_status_t CreateSubVmarInner(size_t offset, size_t size, uint8_t align_pow2,
uint32_t vmar_flags, fbl::RefPtr<VmObject> vmo,
uint64_t vmo_offset, arch_mmu_flags_t arch_mmu_flags,
const char* name, vaddr_t* base_out,
fbl::RefPtr<VmAddressRegionOrMapping>* out);
// Create a new VmMapping within this region, overwriting any existing
// mappings that are in the way. If the range crosses a subregion, the call
// fails.
zx_status_t OverwriteVmMappingLocked(vaddr_t base, size_t size, uint32_t vmar_flags,
fbl::RefPtr<VmObject> vmo, uint64_t vmo_offset,
arch_mmu_flags_t arch_mmu_flags,
fbl::RefPtr<VmAddressRegionOrMapping>* out)
TA_REQ(region_lock()) TA_REQ(lock());
// Implementation for Unmap() and OverwriteVmMapping() that does not hold
// the aspace lock. If |can_destroy_regions| is true, then this may destroy
// VMARs that it completely covers.
zx_status_t UnmapInternalLocked(vaddr_t base, size_t size, bool can_destroy_regions)
TA_REQ(region_lock()) TA_REQ(lock());
// If the allocation between the given children can be met this returns a virtual address of the
// base address of that allocation, otherwise a nullopt is returned.
ktl::optional<vaddr_t> CheckGapLockedRegion(const VmAddressRegionOrMapping* prev,
const VmAddressRegionOrMapping* next,
vaddr_t search_base, vaddr_t align,
size_t region_size, size_t min_gap,
arch_mmu_flags_t arch_mmu_flags)
TA_REQ(region_lock());
// search for a spot to allocate for a region of a given size
zx_status_t AllocSpotLockedRegion(size_t size, uint8_t align_pow2,
arch_mmu_flags_t arch_mmu_flags, vaddr_t* spot,
vaddr_t upper_limit = ktl::numeric_limits<vaddr_t>::max())
TA_REQ(region_lock());
const RegionList<VmAddressRegionOrMapping>& subregions_locked() const
TA_REQ(lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return subregions_;
}
const RegionList<VmAddressRegionOrMapping>& subregions_locked_region() const
TA_REQ(region_lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return subregions_;
}
RegionList<VmAddressRegionOrMapping> subregions_ TA_GUARDED(lock()) TA_GUARDED(region_lock());
const char name_[ZX_MAX_NAME_LEN] = {};
};
extern "C" void cpp_vm_mapping_free(VmMapping* mapping);
// A representation of the mapping of a VMO into the address space
class VmMapping final : public VmAddressRegionOrMapping {
public:
// Accessors for VMO-mapping state
// These can be read under either lock (both locks being held for writing), so we provide two
// different accessors, one for each lock.
arch_mmu_flags_t arch_mmu_flags_locked(vaddr_t offset) const
TA_REQ(lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return FlagsRangeAtAddrLocked(offset).mmu_flags;
}
arch_mmu_flags_t arch_mmu_flags_locked_object(vaddr_t offset) const
TA_REQ(object_->lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return FlagsRangeAtAddrLocked(offset).mmu_flags;
}
struct FlagsRange {
arch_mmu_flags_t mmu_flags;
uint64_t region_top;
};
FlagsRange arch_mmu_flags_range_locked(vaddr_t offset) const
TA_REQ(lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return FlagsRangeAtAddrLocked(offset);
}
uint64_t object_offset() const { return object_offset_; }
Lock<CriticalMutex>* object_lock() const TA_RET_CAP(object_->lock()) TA_REQ(lock()) {
return object_->lock();
}
Lock<CriticalMutex>& object_lock_ref() const TA_RET_CAP(object_->lock()) TA_REQ(lock()) {
return object_->lock_ref();
}
// Intended to be used from VmEnumerator callbacks where the aspace_->lock() will be held.
fbl::RefPtr<VmObject> vmo_locked() const TA_REQ(lock()) { return object_; }
fbl::RefPtr<VmObject> vmo() const TA_EXCL(lock());
// Convenience wrapper for vmo()->DecommitRange() with the necessary
// offset modification and locking.
zx_status_t DecommitRange(size_t offset, size_t len) TA_EXCL(lock());
// Map in pages from the underlying vm object, optionally committing pages as it goes.
// |ignore_existing| controls whether existing hardware mappings in the specified range should be
// ignored or treated as an error. |ignore_existing| should only be set to true for user mappings
// where populating mappings may already be racy with multiple threads, and where we are already
// tolerant of mappings being arbitrarily created and destroyed.
zx_status_t MapRange(size_t offset, size_t len, bool commit, bool ignore_existing = false)
TA_EXCL(lock());
using AttributionCounts = VmObject::AttributionCounts;
AttributionCounts GetAttributedMemory() const;
// Unlocked convenience wrapper of UnmapLocked for testing.
zx_status_t DebugUnmap(vaddr_t base, size_t size) TA_EXCL(lock()) {
Guard<CriticalMutex> region_guard{region_lock()};
Guard<CriticalMutex> guard{lock()};
return UnmapLocked(base, size);
}
// Unlocked convenience wrapper of ProtectLocked for testing.
zx_status_t DebugProtect(vaddr_t base, size_t size, arch_mmu_flags_t new_arch_mmu_flags)
TA_EXCL(lock()) {
Guard<CriticalMutex> guard{lock()};
return ProtectLocked(base, size, new_arch_mmu_flags);
}
void DumpLocked(uint depth, bool verbose) const TA_REQ(lock()) override;
// Page fault in an address within the mapping. The requested address must be paged aligned. If
// |additional_pages| is non-zero, then up to that many additional pages may be resolved using the
// same |pf_flags|. It is not an error for the |additional_pages| to span beyond the mapping or
// underlying VMO, although the range will get truncated internally. As such only the page
// containing va is required to be resolved, and this method may return ZX_OK if any number,
// including zero, of the additional pages are resolved.
// |object| is required to be the value of object_ with the requirement that if the aspace lock()
// is not held over the call to this function, then the caller is required to ensure that |object|
// will remain alive for the duration of the call.
// As the |additional_pages| are resolved with the same |pf_flags| they may trigger copy-on-write
// or other allocations in the underlying VMO.
// If this returns ZX_ERR_SHOULD_WAIT, then the caller should wait on |page_request|
// and try again. In addition to a status this returns how many pages got mapped in.
// This may return ZX_ERR_UNAVAILABLE if the aspace lock() is not held and means that the mapping
// was destroyed before the page fault could be handled.
// If ZX_OK is returned then the number of pages mapped in is guaranteed to be >0.
// If |additional_pages| was non-zero, then the maximum number of pages that will be mapped is
// |additional_pages + 1|. Otherwise the maximum number of pages that will be mapped is
// kPageFaultMaxOptimisticPages.
ktl::pair<zx_status_t, uint32_t> PageFault(vaddr_t va, uint pf_flags, size_t additional_pages,
VmObject* object, MultiPageRequest* page_request);
// Convenience wrapper around PageFault that can be called with the aspace lock held and will
// never return ZX_ERR_UNAVAILABLE.
ktl::pair<zx_status_t, uint32_t> PageFaultLocked(vaddr_t va, uint pf_flags,
size_t additional_pages,
MultiPageRequest* page_request) TA_REQ(lock());
// Apis intended for use by VmObject
// |assert_object_lock| exists to satisfy clang capability analysis since there are circumstances
// when the object_->lock() is actually being held, but it was not acquired by dereferencing
// object_. In this scenario we need to explain to the analysis that the lock held is actually the
// same as object_->lock(), and even though we otherwise have no intention of using object_, the
// only way to do this is to notionally dereferencing object_ to compare the lock.
// Since this is asserting that the lock is held, and not just returning a reference to the lock,
// this method is logically correct since object_ itself is only modified if object_->lock() is
// held.
void assert_object_lock() const TA_ASSERT(object_->lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
AssertHeld(object_->lock_ref());
}
enum UnmapOptions : uint8_t {
kNone = 0u,
OnlyHasZeroPages = (1u << 0),
Harvest = (1u << 1),
};
// Unmap any pages that map the passed in vmo range from the arch aspace.
// May not intersect with this range.
// If |only_has_zero_pages| is true then the caller is asserting that it knows that any mappings
// in the region will only be for the shared zero page.
void AspaceUnmapLockedObject(uint64_t offset, uint64_t len, UnmapOptions options) const
TA_REQ(object_->lock());
// Removes any writeable mappings for the passed in vmo range from the arch aspace.
// May fall back to unmapping pages from the arch aspace if necessary.
void AspaceRemoveWriteLockedObject(uint64_t offset, uint64_t len) const TA_REQ(object_->lock());
// Checks if this is a kernel mapping within the given VMO range, which would be an error to be
// unpinning.
void AspaceDebugUnpinLockedObject(uint64_t offset, uint64_t len) const TA_REQ(object_->lock());
// Marks this mapping as being a candidate for merging, and will immediately attempt to merge with
// any neighboring mappings. Making a mapping mergeable essentially indicates that you will no
// longer use this specific VmMapping instance to refer to the referenced region, and will access
// the region via the parent vmar in the future, and so the region merely needs to remain valid
// through some VmMapping.
// For this the function requires you to hand in your last remaining refptr to the mapping.
static void MarkMergeable(fbl::RefPtr<VmMapping> mapping);
// Enumerates any different protection ranges that exist inside this mapping. The virtual range
// specified by range_base and range_size must be within this mappings base_ and size_. The
// provided callback is called in virtual address order for each protection type. ZX_ERR_NEXT
// and ZX_ERR_STOP can be used to control iteration, with any other status becoming the return
// value of this method.
template <typename F>
zx_status_t EnumerateProtectionRangesLocked(vaddr_t base, size_t size, F func) const
TA_REQ(lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
DEBUG_ASSERT(is_in_range(base, size));
// If the mapping is no longer alive, then return early since there's nothing to enumerate.
if (!IsAliveLocked()) {
return ZX_OK;
}
const vaddr_t end = base + size;
// Find the first transition strictly after 'base'.
auto it = rest_protection_ranges_.upper_bound(base);
// |it| now represents the end point of the first range, so look backwards to determine the
// start flags.
auto prev = it;
prev--;
arch_mmu_flags_t flags = prev ? (*prev).second : first_region_arch_mmu_flags_;
vaddr_t range_start = base;
while (true) {
// The current range ends at either 'end' or the next transition point, whichever is earlier.
const vaddr_t range_end = it ? ktl::min((*it).first, end) : end;
DEBUG_ASSERT(range_start < range_end);
zx_status_t result = func(range_start, range_end - range_start, flags);
if (result != ZX_ERR_NEXT) {
if (result == ZX_ERR_STOP) {
return ZX_OK;
}
return result;
}
// If we've reached the end of the requested range, or there are no more transitions, stop.
// Placing this check here allows the range generation above to also generate the 'trailing'
// range for the last transition point without needing an extra invocation of |func|, which
// helps inlining.
if (!it || (*it).first >= end) {
break;
}
// Move to the next transition point.
range_start = range_end;
flags = (*it).second;
it++;
}
return ZX_OK;
}
template <typename F>
zx_status_t EnumerateProtectionRangesLockedObject(vaddr_t base, size_t size, F func) const
TA_REQ(object_->lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return EnumerateProtectionRangesLocked(base, size, func);
}
FlagsRange FlagsRangeAtAddrLocked(vaddr_t va) const TA_REQ(lock()) TA_REQ(object_->lock()) {
if (rest_protection_ranges_.is_empty()) {
return FlagsRange{first_region_arch_mmu_flags_, base_ + size_};
}
// Find the first transition strictly after 'va'.
auto it = rest_protection_ranges_.upper_bound(va);
// |it| represents the end of the range that includes |va|
vaddr_t top = it ? (*it).first : base_ + size_;
// Now go backwards to find the start of the range that includes |va|, which tells us the flags.
it--;
arch_mmu_flags_t flags = it ? (*it).second : first_region_arch_mmu_flags_;
return FlagsRange{flags, top};
}
FlagsRange FlagsRangeAtAddrLockedObject(vaddr_t va) const
TA_REQ(object_->lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return FlagsRangeAtAddrLocked(va);
}
// The maximum number of pages that a page fault can optimistically extend the fault to include.
// This is defined and exposed here for the purposes of unittests.
static constexpr uint64_t kPageFaultMaxOptimisticPages = 16;
// TODO(https://fxbug.dev/42106188): Informs the mapping that a write is going to be performed to
// the backing VMO, even if the VMO is not writable. This gives the mapping an opportunity to
// create a private clone of the VMO if necessary and use that to back a new mapping instead,
// providing a way to 'safely' perform the write. On success a RefPtr is returned either to the
// current mapping, or to a new mapping if one was created. If a new mapping was created then this
// mapping is no longer valid.
zx::result<fbl::RefPtr<VmMapping>> ForceWritable();
protected:
~VmMapping() override;
friend fbl::RefPtr<VmMapping>;
friend void ::cpp_vm_mapping_free(VmMapping*);
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(VmMapping);
fbl::Canary<fbl::magic("VMAP")> canary_;
enum class Mergeable : bool { YES = true, NO = false };
// allow VmAddressRegion to manipulate VmMapping internals for construction
// and bookkeeping
friend class VmAddressRegion;
// private constructors, use VmAddressRegion::Create...() instead
VmMapping(VmAddressRegion& parent, bool private_clone, vaddr_t base, size_t size,
uint32_t vmar_flags, fbl::RefPtr<VmObject> vmo, uint64_t vmo_offset,
arch_mmu_flags_t arch_mmu_flags, Mergeable mergeable);
VmMapping(VmAddressRegion& parent, bool private_clone, vaddr_t base, size_t size,
uint32_t vmar_flags, fbl::RefPtr<VmObject> vmo, uint64_t vmo_offset,
arch_mmu_flags_t first_mmu_flags, btree::BTree<vaddr_t, arch_mmu_flags_t>&& ranges,
Mergeable mergeable);
zx_status_t DestroyLocked() TA_REQ(region_lock()) TA_REQ(lock()) override;
// Internal fully locked version of Destroy. Has controls to both skip the arch aspace unmapping
// as well as removal from the parent subregions list. These controls facilitate the fine grained
// control needed when splitting, merging and replacing mappings.
// If unmap is |No| then this method is defined to never fail. |remove_region| does not impact
// success or failure of the operation.
enum class DestroyUnmap : bool {
No,
Yes,
};
enum class DestroyRemoveFromParent : bool {
No,
Yes,
};
zx_status_t DestroyLockedObject(DestroyUnmap unmap, DestroyRemoveFromParent remove_region)
TA_REQ(region_lock()) TA_REQ(lock()) TA_REQ(object_->lock());
// Internal helper for performing a page fault after the object_ lock is acquired. Additionally
// object_ is passed in as the downcast specific type in object to allow the helper to assume
// that object->lock() is held in all paths, without needing to do its own AssertHeld's, after
// runtime casting, throughout.
template <typename T>
ktl::pair<zx_status_t, uint32_t> PageFaultLockedObject(vaddr_t va, uint pf_flags,
size_t additional_pages, T* object,
VmCowPages::DeferredOps* deferred,
MultiPageRequest* page_request)
TA_REQ(object->lock()) TA_REQ(object_->lock());
// Unmap a subset of the region of memory in the containing address space,
// returning it to the parent region to allocate. If all of the memory is unmapped,
// Destroy()s this mapping. If a subrange of the mapping is specified, the
// mapping may be split.
zx_status_t UnmapLocked(vaddr_t base, size_t size) TA_REQ(region_lock()) TA_REQ(lock());
// Change access permissions for this mapping. It is an error to specify a
// caching mode in the flags. This will persist the caching mode the
// mapping was created with. If a subrange of the mapping is specified, the
// mapping may be split.
zx_status_t ProtectLocked(vaddr_t base, size_t size, arch_mmu_flags_t new_arch_mmu_flags)
TA_REQ(lock());
// Helper for protect and unmap.
static zx_status_t ProtectOrUnmap(const fbl::RefPtr<VmAspace>& aspace, vaddr_t base, size_t size,
arch_mmu_flags_t new_arch_mmu_flags);
// Copies protection ranges for the given sub-range into |out_first_flags| and |out_ranges|.
// The sub-range [base, base + size) must be within [base_, base_ + size_).
zx_status_t CopyProtectionRangesLocked(vaddr_t base, size_t size,
arch_mmu_flags_t* out_first_flags,
btree::BTree<vaddr_t, arch_mmu_flags_t>* out_ranges) const
TA_REQ(lock()) TA_NO_THREAD_SAFETY_ANALYSIS;
// Merges a copy of the protection ranges of |right| into this mapping.
// Assumes |right| is immediately to the right of this mapping.
zx_status_t MergeProtectionRangesLocked(const VmMapping& right) TA_REQ(lock())
TA_REQ(object_->lock()) TA_REQ(right.lock()) TA_REQ(right.object_lock());
// Removes any protection range transitions within the range [base, end).
void ClearProtectionRangeTransitionsLocked(vaddr_t base, vaddr_t end) TA_REQ(lock())
TA_REQ(object_->lock());
// Removes all protection range transitions at or below |split_addr|, returning the flags
// that are active at |split_addr|.
arch_mmu_flags_t RemoveAfterSplitLocked(vaddr_t split_addr) TA_REQ(lock())
TA_REQ(object_->lock());
AttributionCounts GetAttributedMemoryLocked(Guard<CriticalMutex>& guard) const TA_REQ(lock());
// If MemoryPriority::HIGH, then disable dynamic reclamation within this region. If
// MemoryPriority::DEFAULT, move towards allowing reclamation.
//
// When called with SplitOnUnmap=true, we set the memory
// priority assuming the object has not been |Activate|'d yet.
template <bool SplitOnUnmap = false>
void SetMemoryPriorityLocked(VmAddressRegion::MemoryPriority priority) TA_REQ(lock())
TA_EXCL(object_->lock());
// Move towards allowing dynamic reclamation in the region. You may call this method with the
// object_ lock, in contrast to SetMemoryPriorityLocked.
//
// When called with SetMemoryPriorityLockedObject</*SplitOnUnmap=*/true>, we set the memory
// priority assuming the object has not been |Activate|'d yet.
template <bool SplitOnUnmap = false>
void SetMemoryPriorityDefaultLockedObject() TA_REQ(lock()) TA_REQ(object_->lock());
// Marks the address region as high priority. Only call this method if the mapped
// VmObject is already high priority (for example if you have already called
// SetMemoryPriorityLocked on a child).
//
// You may call this method with the object_ lock, in contrast to SetMemoryPriorityLocked.
//
// When called with SetMemoryPriorityLockedObject</*SplitOnUnmap=*/true>, we set the memory
// priority assuming the object has not been |Activate|'d yet.
template <bool SplitOnUnmap = false>
void SetMemoryPriorityHighAlreadyPositiveLockedObject() TA_REQ(lock()) TA_REQ(object_->lock());
void CommitHighMemoryPriority() override TA_EXCL(lock());
zx_status_t Activate() TA_REQ(region_lock()) TA_REQ(lock()) override;
// Fully locked version of Activate that can additionally control whether the region is installed
// into the parent subregion list and vmo mapping list or not. This control exists to facilitate
// the fine grained control needed for splitting, merging and replacing of mappings as when set to
// |No| this method is defined as never failing.
enum class ActivateInsertRegions : bool {
No,
Yes,
};
zx_status_t ActivateLocked(ActivateInsertRegions insert_region) TA_REQ(region_lock())
TA_REQ(lock()) TA_REQ(object_->lock());
// Wrapper for ActivateLocked that does not insert into the parent region, and therefore cannot
// fail.
void ActivateNoInsertLocked() TA_REQ(region_lock()) TA_REQ(lock()) TA_REQ(object_->lock()) {
[[maybe_unused]] zx_status_t status = ActivateLocked(ActivateInsertRegions::No);
ASSERT(status == ZX_OK);
}
// Takes a range relative to the vmo object_ and converts it into a virtual address range relative
// to aspace_. Returns true if a non zero sized intersection was found, false otherwise. If false
// is returned |base| and |virtual_len| hold undefined contents.
bool ObjectRangeToVaddrRange(uint64_t offset, uint64_t len, vaddr_t* base,
uint64_t* virtual_len) const TA_REQ(object_->lock());
// Attempts to merge this mapping with any neighbors. It is the responsibility of the caller to
// ensure a refptr to this is being held, as on return |this| may be in the dead state and have
// removed itself from the hierarchy, dropping a refptr.
void TryMergeNeighborsLocked() TA_REQ(region_lock()) TA_REQ(lock());
// Attempts to merge this and the given mapping into a new one. This only succeeds if the
// candidate is placed just after |this|, both in the aspace and the vmo. See implementation for
// the full requirements for merging to succeed. The candidate and this must be held as a RefPtr
// by the caller so that this function does not trigger any VmMapping destructor by dropping the
// last reference when removing from the parent vmar. If merging is successfully the newly created
// and installed mapping is returned, otherwise a nullptr is returned.
fbl::RefPtr<VmMapping> TryMergeRightNeighborLocked(VmMapping* right_candidate)
TA_REQ(region_lock()) TA_REQ(lock());
// For a VmMapping |state_| is only modified either with the object_ lock held, or if there is no
// |object_|. Therefore it is safe to read state if just the object lock is held.
LifeCycleState get_state_locked_object() const
TA_REQ(object_->lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return state_;
}
// Returns the minimum of the requested map length, the size of the VMO or, if
// FAULT_BEYOND_STREAM_SIZE is set, the page containing the stream size. MapRange can be trimmed
// to these lengths as it should not be considered an error to call MapRange past the VMO size in
// a resizable VMO or past the page containing the stream size in a FAULT_BEYOND_STREAM_SIZE VMO.
uint64_t TrimmedObjectRangeLocked(uint64_t offset, uint64_t len) const TA_REQ(lock())
TA_REQ(object_->lock());
const btree::BTree<vaddr_t, arch_mmu_flags_t>& rest_protection_ranges_locked() const
TA_REQ(lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return rest_protection_ranges_;
}
arch_mmu_flags_t first_region_arch_mmu_flags_locked() const
TA_REQ(lock()) TA_NO_THREAD_SAFETY_ANALYSIS {
return first_region_arch_mmu_flags_;
}
// Whether this mapping may be merged with other adjacent mappings. A mergeable mapping is just a
// region that can be represented by any VmMapping object, not specifically this one.
Mergeable mergeable_ TA_GUARDED(lock()) = Mergeable::NO;
// TODO(https://fxbug.dev/42106188): Tracks whether this mapping has been transitioned into a
// private clone to allow for writes to safely be done without modifying a VMO that the mapping
// does not have permission to.
const bool private_clone_ = false;
// Tracks whether the object_ has been reset (i.e. is null) or not. object_ is always non-null at
// construction and only ever performs a single transition, which is to the null value, prior to
// destruction. The object_.reset, and hence the modification of this value, happens under both
// the aspace lock() and the object_->lock(). See PageFault for usage of this.
RelaxedAtomic<bool> object_reset_ = false;
// The protection flags of this mapping are tracked as a series of contiguous
// virtual address ranges. Since the base address of the mapping is known (`base_`),
// we only need to store the flags for the first range, and then the start address
// and flags for any subsequent ranges where the protections change.
//
// `first_region_arch_mmu_flags_` holds the MMU flags for the interval starting
// exactly at `base_`. If `protection_ranges_` is empty, these flags apply to the
// entire mapping: [base_, base_ + size_).
//
// `protection_ranges_` is a B-Tree that records any transitions in protection.
// Each entry consists of a `vaddr_t` key and an `arch_mmu_flags_t` value.
// A node (addr, flags) means that the range starting at `addr` has the protections
// `flags`, up until the address of the next node in the tree, or `base_ + size_`
// if it is the last node.
//
// This can be read with either lock held, but requires both locks to write it.
arch_mmu_flags_t first_region_arch_mmu_flags_ TA_GUARDED(lock()) TA_GUARDED(object_->lock());
btree::BTree<vaddr_t, arch_mmu_flags_t> rest_protection_ranges_ TA_GUARDED(lock())
TA_GUARDED(object_->lock());
// pointer and region of the object we are mapping.
// The object_ cannot be marked const, as it gets reset(), but logically it is a constant value
// from construction until it gets reset, and then it stays null until the mapping is destructed.
fbl::RefPtr<VmObject> object_ TA_GUARDED(lock());
const uint64_t object_offset_ = 0;
class CurrentlyFaulting;
// Pointer to a CurrentlyFaulting object if the mapping is presently handling a page fault. This
// is protected specifically by the object lock so that AspaceUnmapLockedObject can inspect it.
CurrentlyFaulting* currently_faulting_ TA_GUARDED(object_->lock()) = nullptr;
};
// Interface for walking a VmAspace-rooted VmAddressRegion/VmMapping tree.
// Override this class and pass an instance to VmAddressRegion::EnumerateChildren().
// VmAddressRegion::EnumerateChildren() will call the On* methods in depth-first pre-order.
// ZX_ERR_NEXT and ZX_ERR_STOP can be used to control iteration, with any other status becoming the
// return value of this method. The root VmAspace's lock is held during the traversal and passed in
// to the callbacks as |guard|. A callback is permitted to temporarily drop the lock, using
// |CallUnlocked|, although doing so invalidates the pointers and to use them without the lock held,
// of after it is reacquired, they should first be turned into a RefPtr, with the caveat that they
// might now refer to a dead, aka unmapped, object.
class VmEnumerator {
public:
// |depth| will be 0 for the root VmAddressRegion.
virtual zx_status_t OnVmAddressRegion(VmAddressRegion* vmar, uint depth,
Guard<CriticalMutex>& guard) TA_REQ(vmar->lock()) {
return ZX_ERR_NEXT;
}
// |vmar| is the parent of |map|.
virtual zx_status_t OnVmMapping(VmMapping* map, VmAddressRegion* vmar, uint depth,
Guard<CriticalMutex>& guard) TA_REQ(map->lock())
TA_REQ(vmar->lock()) {
return ZX_ERR_NEXT;
}
protected:
VmEnumerator() = default;
~VmEnumerator() = default;
};
// Now that all the sub-classes are defined finish declaring some inline VmAddressRegionOrMapping
// methods.
inline fbl::RefPtr<VmAddressRegion> VmAddressRegionOrMapping::as_vm_address_region() {
canary_.Assert();
if (is_mapping()) {
return nullptr;
}
return fbl::RefPtr<VmAddressRegion>(static_cast<VmAddressRegion*>(this));
}
inline VmAddressRegion* VmAddressRegionOrMapping::as_vm_address_region_ptr() {
canary_.Assert();
if (unlikely(is_mapping())) {
return nullptr;
}
return static_cast<VmAddressRegion*>(this);
}
inline const VmAddressRegion* VmAddressRegionOrMapping::as_vm_address_region_ptr() const {
canary_.Assert();
if (unlikely(is_mapping())) {
return nullptr;
}
return static_cast<const VmAddressRegion*>(this);
}
inline fbl::RefPtr<VmAddressRegion> VmAddressRegionOrMapping::downcast_as_vm_address_region(
fbl::RefPtr<VmAddressRegionOrMapping>* region_or_map) {
DEBUG_ASSERT(region_or_map);
if ((*region_or_map)->is_mapping()) {
return nullptr;
}
return fbl::RefPtr<VmAddressRegion>::Downcast(ktl::move(*region_or_map));
}
inline fbl::RefPtr<VmMapping> VmAddressRegionOrMapping::as_vm_mapping() {
canary_.Assert();
if (!is_mapping()) {
return nullptr;
}
return fbl::RefPtr<VmMapping>(static_cast<VmMapping*>(this));
}
inline VmMapping* VmAddressRegionOrMapping::as_vm_mapping_ptr() {
canary_.Assert();
if (unlikely(!is_mapping())) {
return nullptr;
}
return static_cast<VmMapping*>(this);
}
inline const VmMapping* VmAddressRegionOrMapping::as_vm_mapping_ptr() const {
canary_.Assert();
if (unlikely(!is_mapping())) {
return nullptr;
}
return static_cast<const VmMapping*>(this);
}
inline fbl::RefPtr<VmMapping> VmAddressRegionOrMapping::downcast_as_vm_mapping(
fbl::RefPtr<VmAddressRegionOrMapping>* region_or_map) {
DEBUG_ASSERT(region_or_map);
if (!(*region_or_map)->is_mapping()) {
return nullptr;
}
return fbl::RefPtr<VmMapping>::Downcast(ktl::move(*region_or_map));
}
#endif // ZIRCON_KERNEL_VM_INCLUDE_VM_VM_ADDRESS_REGION_H_