blob: 417fd9c4c112cc7f9ffa0b9bf2d338a39e65929f [file] [edit]
// Copyright 2018 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_PMM_NODE_H_
#define ZIRCON_KERNEL_VM_INCLUDE_VM_PMM_NODE_H_
#include <lib/memalloc/range.h>
#include <lib/page/size.h>
#include <fbl/canary.h>
#include <fbl/intrusive_double_list.h>
#include <kernel/event.h>
#include <kernel/lockdep.h>
#include <kernel/mutex.h>
#include <ktl/span.h>
#include <vm/evictor.h>
#include <vm/page_queues.h>
#include <vm/physical_page_borrowing_config.h>
#include <vm/pmm_arena.h>
#include <vm/pmm_checker.h>
// Forward declaration; defined in <lib/memalloc/range.h>
namespace memalloc {
struct Range;
}
// flags for allocation routines below
#define PMM_ALLOC_FLAG_ANY (0 << 0) // no restrictions on which arena to allocate from
// The caller is able to wait and retry this allocation and so pmm allocation functions are allowed
// to return ZX_ERR_SHOULD_WAIT, as opposed to ZX_ERR_NO_MEMORY, to indicate that the caller should
// wait and try again. This is intended for the PMM to tell callers who are able to wait that memory
// is low. The caller should not infer anything about memory state if it is told to wait, as the PMM
// may tell it to wait for any reason.
#define PMM_ALLOC_FLAG_CAN_WAIT (1 << 0)
// This enum is used to specify whether a page, when freed, should have its reuse
// (i.e. reallocation) delayed. This feature exists to both improve the PMM checker's ability to
// detect "bad DMAs" and to reduce the impact when they do occur. When allocating a page, reusing
// the most recently freed page often has performance benefits. However, it can amplify the impact
// of use-after-free bugs. This feature enables part of the kernel to express a preference on
// whether a page should be eligible for immediate reuse or not. It's a hint.
//
// |Default| means no preference. When specified, the page may or may not be immediately reused.
// In some build/runtime configurations (e.g. kasan) delayed reuse is the default behavior.
//
// |Yes| indicates that the PMM should delay the reuse of the page by placing it on the "cold" end
// up the free list, thereby maximizing the amount of time before which it is reallocated.
enum class PmmOptDelayReuse : bool { Default, Yes };
class FreeLoanedPagesHolder;
class VmCompression;
// per numa node collection of pmm arenas and worker threads
class PmmNode {
public:
// This constructor may be called early in the boot sequence so make sure it does not do any "real
// work" or depend on any globals.
PmmNode() = default;
~PmmNode() = default;
DISALLOW_COPY_ASSIGN_AND_MOVE(PmmNode);
zx_status_t Init(ktl::span<const memalloc::Range> ranges);
// See pmm_end_handoff().
void EndHandoff();
vm_page_t* PaddrToPage(paddr_t addr) TA_NO_THREAD_SAFETY_ANALYSIS;
static constexpr int kIndexZeroBits = 3;
// Returns compressed representation a page_t*, with the following characteristics:
// - zeros in the last kIndexZeroBits bits, used by clients to store metadata.
// - The value 0 is never returned, it can be used as "no page" marker.
//
// Note: This method needs to traverse (up to) all the memory pools so it's cost is
// low but not trivial.
uint32_t PageToIndex(const vm_page_t* page) TA_NO_THREAD_SAFETY_ANALYSIS;
// Converts the number returned by PageToIndex() back to a page_t* pointer.
// It does not check for invalid indexes such as 0.
//
// Note: This method is faster than PageToIndex(), about the cost of some basic math
// and bit manipulation.
vm_page_t* IndexToPage(uint32_t index) TA_NO_THREAD_SAFETY_ANALYSIS;
// Converts the number returned by PageToIndex() back to a paddr_t.
// It does not check for invalid indexes such as 0 or kIndexReserved0.
//
// Note: This method is faster than IndexToPage()->paddr() as the vm_page_t itself does not have
// to be de-referenced, saving a memory load.
paddr_t IndexToPaddr(uint32_t index) TA_NO_THREAD_SAFETY_ANALYSIS;
// main allocator routines
zx::result<vm_page_t*> AllocPage(uint alloc_flags);
zx_status_t AllocPages(size_t count, uint alloc_flags, VmPageDoublyLinkedList* list);
zx_status_t AllocRange(paddr_t address, size_t count, VmPageDoublyLinkedList* list);
zx_status_t AllocContiguous(size_t count, uint alloc_flags, uint8_t alignment_log2, paddr_t* pa,
VmPageDoublyLinkedList* list);
void FreePage(vm_page* page, PmmOptDelayReuse delay_reuse = PmmOptDelayReuse::Default);
void FreeList(VmPageDoublyLinkedList* list,
PmmOptDelayReuse delay_reuse = PmmOptDelayReuse::Default);
// Calls the provided function, passing |page| back into it, serialized with any other calls to
// |AllocLoanedPage|, |BeginFreeLoanedPage| and |FinishFreeLoanedPages|. This allows caller to
// know that while the |with_page| callback is running there are no in progress calls to these
// methods, and that the page is not presently in holding object, i.e. it is either fully owned by
// the PmmNode, or fully owned by an object.
void WithLoanedPage(vm_page_t* page, fit::inline_function<void(vm_page_t*)> with_page);
// Allocates a single page from the loaned pages list. The allocated page will always have
// is_loaned() being true, and must be returned by either FreeLoanedPage or FreeLoanedList. If
// there are not loaned pages available ZX_ERR_UNAVAILABLE is returned, as an absence of loaned
// pages does not constitute an out of memory scenario.
// The provided callback must transition the page into a state such that it has a valid backlink,
// i.e. it is in the OBJECT state with an owner set, prior to returning.
// During the execution of the callback the page contents must *not* be modified.
zx::result<vm_page_t*> AllocLoanedPage(fit::inline_function<void(vm_page_t*), 32> allocated);
// Begins freeing a loaned page that was previously allocated by AllocLoanPage by moving into a
// holding object. It is an error to attempt to free a non loaned page. When this method is called
// the |page| must have a valid backlink (i.e. be in the OBJECT state with an owner set). This
// backlink should be removed by the |release_page| callback, which is invoked under the loaned
// pages lock, prior to transition the page into the holding state. The caller *must*, at some
// point in the future, complete the page freeing process by passing the provided |flph| into a
// |FinishFreeLoanedPages| call.
void BeginFreeLoanedPage(vm_page_t* page, fit::inline_function<void(vm_page_t*)> release_page,
FreeLoanedPagesHolder& flph);
// Completes the freeing of any loaned pages in |flph|, after which |flph| is allowed to be
// destructed. Once this method is called on a given |flph| that object is effectively 'dead' and
// is not allowed to be passed to any PmmNode methods.
void FinishFreeLoanedPages(FreeLoanedPagesHolder& flph);
// Begins freeing multiple pages that were allocated by AllocLoanedPage by moving into a holding
// object. It is an error to attempt to free any non loaned pages. When this method is called all
// pages in the array must have a valid backlink (i.e. be in the OBJECT state with an owner set),
// and the |release_list| method must remove the backlink from all pages, and place them in the
// provided list in the same order.
// The caller *must*, at some point in the future, complete the page freeing process by passing
// the provided |flph| into a |FinishFreeLoanedPages| call.
void BeginFreeLoanedArray(
vm_page_t** pages, size_t count,
fit::inline_function<void(vm_page_t**, size_t, VmPageDoublyLinkedList*)> release_list,
FreeLoanedPagesHolder& flph);
void UnwirePage(vm_page* page);
// Frees all pages in the given list and places them in the loaned state available to be returned
// from AllocLoanedPage.
//
// |delay_reuse| controls whether the newly loaned pages are eligile for immediate or delayed
// reuse.
void BeginLoan(VmPageDoublyLinkedList* page_list,
PmmOptDelayReuse delay_reuse = PmmOptDelayReuse::Default);
// Marks a page that had been previously provided to BeginLoan as cancelled. This page may be in
// the FREE_LOANED state, or presently in use.
//
// This call prevents the page from being reused for any new purpose until EndLoan(). For
// presently-FREE_LOANED pages, this removes the pages from free_loaned_list_. For presently-used
// pages, this specifies that the page will not be added to free_loaned_list_ when later freed.
// Once this page is FREE_LOANED (to be ensured by the caller via PhysicalPageProvider reclaim of
// the pages), the loan can be ended with EndLoan().
void CancelLoan(vm_page_t* page);
// Allocates the page to the caller as a regular non-loaned page. Must currently be:
// * Loaned (via BeginLoan).
// * Have had its loan cancelled (via CancelLoan).
// * Be in the FREE_LOANED state.
void EndLoan(vm_page_t* page);
// See |pmm_set_free_memory_signal|
bool SetFreeMemorySignal(uint64_t free_lower_bound, uint64_t free_upper_bound,
uint64_t delay_allocations_pages, Event* event);
// Waits the system to exit low memory state and then attempts to allocate.
//
// To prevent herding problem, and because allocation compete for the `PmmNode::lock_` anyway,
// only one thread is woken up a time, only if the previous thread successfully allocated.
//
// In normal conditions, when the system is in low memory state, this method will return
// `ZX_ERR_TIMED_OUT` if the system didn't transition fast enough. If we run into a TOC to TOU,
// for the system race `ZX_ERR_SHOULD_WAIT` will be returned, that is the system transitioned
// out and back into low memory state before we managed to perform the allocation.
//
// If `BootOptions::Get()->pmm_alloc_random_wait` is true, then the system
// may return spurious `ZX_ERR_SHOULD_WAIT`, in such cases, if the system is
// not in a low memory state, a thread is woken up anyway, so forward
// progress can be made.
//
// If |suspendable| is true, the wait will terminate early with
// `ZX_ERR_INTERNAL_INTR_RETRY` if the thread is suspended. If false, suspension is ignored and
// the wait continues.
zx::result<vm_page_t*> WaitForSinglePageAllocation(Deadline deadline, bool suspendable);
void StopReturningShouldWait();
uint64_t CountFreePages() const;
uint64_t CountLoanedFreePages() const;
uint64_t CountLoanCancelledPages() const;
// Not actually used and cancelled is still not free, since the page can't be allocated in that
// state.
uint64_t CountLoanedNotFreePages() const;
uint64_t CountLoanedPages() const;
uint64_t CountTotalBytes() const;
// printf free and overall state of the internal arenas
// NOTE: both functions skip mutexes and can be called inside timer or crash context
// though the data they return may be questionable
void DumpFree() const TA_NO_THREAD_SAFETY_ANALYSIS;
void Dump(bool is_panic) const TA_NO_THREAD_SAFETY_ANALYSIS;
// Returns the number of active arenas.
size_t NumArenas() const {
Guard<Mutex> guard{&lock_};
return active_arenas().size();
}
// Copies |count| pmm_arena_info_t objects into |buffer| starting with the |i|-th arena ordered by
// base address. For example, passing an |i| of 1 would skip the 1st arena.
//
// The objects will be sorted in ascending order by arena base address.
//
// Returns ZX_ERR_OUT_OF_RANGE if |count| is 0 or |i| and |count| specificy an invalid range.
//
// Returns ZX_ERR_BUFFER_TOO_SMALL if the buffer is too small.
zx_status_t GetArenaInfo(size_t count, uint64_t i, pmm_arena_info_t* buffer, size_t buffer_size);
// add new pages to the free queue. used when boostrapping a PmmArena
void AddFreePages(VmPageDoublyLinkedList* list);
PageQueues* GetPageQueues() { return &page_queues_; }
// Retrieve any page compression instance. If this returns non-null then it's return value will
// not change and the result can be cached.
VmCompression* GetPageCompression() {
Guard<Mutex> guard{&compression_lock_};
return page_compression_.get();
}
// Set the page compression instance. Returns an error if one has already been set.
zx_status_t SetPageCompression(fbl::RefPtr<VmCompression> compression);
// Fill all free pages (both non-loaned and loaned) with a pattern and arm the checker. See
// |PmmChecker|.
//
// This is a no-op if the checker is not enabled. See |EnableFreePageFilling|
void FillFreePagesAndArm();
// Synchronously walk the PMM's free list (and free loaned list) and validate each page. This is
// an incredibly expensive operation and should only be used for debugging purposes.
void CheckAllFreePages();
#if __has_feature(address_sanitizer)
// Synchronously walk the PMM's free list (and free loaned list) and poison each page.
void PoisonAllFreePages();
#endif
// Enable the free fill checker with the specified fill size and action, and begin filling freed
// pages (including freed loaned pages) going forward. See |PmmChecker| for definition of fill
// size.
//
// Note, pages freed piror to calling this method will remain unfilled. To fill them, call
// |FillFreePagesAndArm|.
//
// Returns true if the checker was enabled with the requested fill_size, or |false| otherwise.
bool EnableFreePageFilling(size_t fill_size, CheckFailAction action);
// Return a pointer to this object's free fill checker.
//
// For test and diagnostic purposes.
PmmChecker* Checker() { return &checker_; }
static int64_t get_alloc_failed_count();
// See |pmm_has_alloc_failed_no_mem|.
static bool has_alloc_failed_no_mem();
Evictor* GetEvictor() { return &evictor_; }
// If randomly waiting on allocations is enabled, this re-seeds from the global prng, otherwise it
// does nothing.
void SeedRandomShouldWait();
// Tell this PmmNode that we've failed a user-visible allocation. Calling this method will
// (optionally) trigger an asynchronous OOM response. To improve diagnostics some information
// about the source of the failure can be provided.
struct AllocFailure {
enum class Type : uint8_t {
None,
Pmm,
Heap,
Handle,
Other,
};
static const char* TypeToString(Type type);
Type type = Type::None;
size_t size = 0;
uint64_t free_count = 0;
};
void ReportAllocFailure(AllocFailure failure) TA_EXCL(lock_);
// Retrieves information given to |ReportAllocFailure|. Due to book keeping limitations this will
// only return information from the first failure.
AllocFailure GetFirstAllocFailure() TA_EXCL(lock_);
private:
zx_status_t InitArena(const PmmArenaSelection& selected);
// An initialization subroutine to update PMM bookkeeping to reflect that a
// given range is to be regarded as reserved (e.g., wired). This is used for
// both special pre-PMM allocations (e.g., the kernel memory image) as well as
// holes in RAM contained within arenas (indicated by type
// memalloc::Type::kReserved).
//
// `range` is expected to be page-aligned and to not intersect with past
// ranges provided to this method.
void InitReservedRange(const memalloc::Range& range);
void FreePageHelperLocked(vm_page* page, bool already_filled) TA_REQ(lock_);
void FreeLoanedPageHelperLocked(vm_page* page, bool already_filled) TA_REQ(loaned_list_lock_);
void FreeListLocked(VmPageDoublyLinkedList* list, bool already_filled,
PmmOptDelayReuse delay_reuse) TA_REQ(lock_);
template <typename F>
void FreeLoanedListLocked(VmPageDoublyLinkedList* list, bool already_filled,
PmmOptDelayReuse delay_reuse, F validator) TA_REQ(loaned_list_lock_);
void SignalFreeMemoryChangeLocked() TA_REQ(lock_);
void TripFreePagesLevelLocked() TA_REQ(lock_);
void UpdateMemAvailStateLocked() TA_REQ(lock_);
void SetMemAvailStateLocked(uint8_t mem_avail_state) TA_REQ(lock_);
void IncrementFreeCountLocked(uint64_t amount) TA_REQ(lock_) {
free_count_.fetch_add(amount, ktl::memory_order_relaxed);
if (mem_signal_ && free_count_.load(ktl::memory_order_relaxed) > mem_signal_upper_bound_) {
SignalFreeMemoryChangeLocked();
}
}
void DecrementFreeCountLocked(uint64_t amount) TA_REQ(lock_) {
[[maybe_unused]] uint64_t count = free_count_.fetch_sub(amount, ktl::memory_order_relaxed);
DEBUG_ASSERT(count >= amount);
if (should_wait_ == ShouldWaitState::OnceLevelTripped &&
free_count_.load(ktl::memory_order_relaxed) < should_wait_free_pages_level_) {
TripFreePagesLevelLocked();
}
if (mem_signal_ && free_count_.load(ktl::memory_order_relaxed) < mem_signal_lower_bound_) {
SignalFreeMemoryChangeLocked();
}
}
void IncrementFreeLoanedCountLocked(uint64_t amount) TA_REQ(loaned_list_lock_) {
free_loaned_count_.fetch_add(amount, ktl::memory_order_relaxed);
}
void DecrementFreeLoanedCountLocked(uint64_t amount) TA_REQ(loaned_list_lock_) {
DEBUG_ASSERT(free_loaned_count_.load(ktl::memory_order_relaxed) >= amount);
free_loaned_count_.fetch_sub(amount, ktl::memory_order_relaxed);
}
void IncrementLoanedCountLocked(uint64_t amount) TA_REQ(loaned_list_lock_) {
loaned_count_.fetch_add(amount, ktl::memory_order_relaxed);
}
void DecrementLoanedCountLocked(uint64_t amount) TA_REQ(loaned_list_lock_) {
DEBUG_ASSERT(loaned_count_.load(ktl::memory_order_relaxed) >= amount);
loaned_count_.fetch_sub(amount, ktl::memory_order_relaxed);
}
void IncrementLoanCancelledCountLocked(uint64_t amount) TA_REQ(loaned_list_lock_) {
loan_cancelled_count_.fetch_add(amount, ktl::memory_order_relaxed);
}
void DecrementLoanCancelledCountLocked(uint64_t amount) TA_REQ(loaned_list_lock_) {
DEBUG_ASSERT(loan_cancelled_count_.load(ktl::memory_order_relaxed) >= amount);
loan_cancelled_count_.fetch_sub(amount, ktl::memory_order_relaxed);
}
bool ShouldDelayAllocationLocked() TA_REQ(lock_);
void AllocPageHelperLocked(vm_page_t* page) TA_REQ(lock_);
void AllocLoanedPageHelperLocked(vm_page_t* page) TA_REQ(loaned_list_lock_);
// This method should be called when the PMM fails to allocate in a user-visible way and will
// (optionally) trigger an asynchronous OOM response.
void ReportAllocFailureLocked(AllocFailure failure) TA_REQ(lock_);
fbl::Canary<fbl::magic("PNOD")> canary_;
mutable DECLARE_MUTEX(PmmNode) lock_;
uint64_t arena_cumulative_size_ TA_GUARDED(lock_) = 0;
// This is both an atomic and guarded by lock_ as we would like modifications to require the lock,
// as logic in the system relies on the free_count_ not changing whilst the lock is held, but also
// be an atomic so it can be correctly read without the lock.
ktl::atomic<uint64_t> free_count_ TA_GUARDED(lock_) = 0;
ktl::atomic<uint64_t> free_loaned_count_ TA_GUARDED(loaned_list_lock_) = 0;
ktl::atomic<uint64_t> loaned_count_ TA_GUARDED(loaned_list_lock_) = 0;
ktl::atomic<uint64_t> loan_cancelled_count_ TA_GUARDED(loaned_list_lock_) = 0;
// Free pages where !loaned.
VmPageDoublyLinkedList free_list_ TA_GUARDED(lock_);
// Free pages where loaned && !loan_cancelled.
mutable DECLARE_MUTEX(PmmNode) loaned_list_lock_;
VmPageDoublyLinkedList free_loaned_list_ TA_GUARDED(loaned_list_lock_);
// The pages comprising the memory temporarily used during phys hand-off,
// populated on Init(). It is the responsibility of EndHandoff() to free this
// list.
VmPageDoublyLinkedList phys_handoff_temporary_list_;
// The pages comprising the page-aligned regions of memory that we expect to
// turn into VMOs to hand-off to userspace - as determined by
// PhysHandoff::IsPhysVmoType() - populated and marked as wired on Init().
//
// It is expected that this memory will be unwired and turned into VMOs by the
// end of the phys hand-off phase, and it is the responsibility of
// PmmNode::EndHandoff() to ensure afterward that this list is empty.
VmPageDoublyLinkedList phys_handoff_vmo_list_ TA_GUARDED(lock_);
// The pages intended to be permanently reserved.
VmPageDoublyLinkedList permanently_reserved_list_ TA_GUARDED(lock_);
// Controls the behavior of requests that have the PMM_ALLOC_FLAG_CAN_WAIT.
enum class ShouldWaitState {
// The PMM_ALLOC_FLAG_CAN_WAIT should never be followed and we will always attempt to perform
// the allocation, or fail with ZX_ERR_NO_MEMORY. This state is permanent and cannot be left.
Never,
// Allocations do not need to be delayed, but the should_wait_free_pages_level_ should be
// monitored and once tripped should be delayed.
OnceLevelTripped,
// State indicates that the level got tripped, and we should delay any allocations until the
// level is reset.
UntilReset,
};
ShouldWaitState should_wait_ TA_GUARDED(lock_) = ShouldWaitState::OnceLevelTripped;
// Below this number of free pages the PMM will transition into delaying allocations.
uint64_t should_wait_free_pages_level_ TA_GUARDED(lock_) = 0;
// The event acts a gate keeper for waking up threads waiting for allocations one at time.
// The event gets signalled when there MAY be pages available.
AutounsignalEvent may_allocate_evt_{true};
// A record of the first time an allocation failure is reported to aid in diagnostics.
AllocFailure first_alloc_failure_ TA_GUARDED(lock_);
// If mem_signal_ is not null, then once the available free memory falls outside of the defined
// lower and upper bound the signal is raised. This is a one-shot signal and is cleared after
// firing.
Event* mem_signal_ TA_GUARDED(lock_) = nullptr;
uint64_t mem_signal_lower_bound_ TA_GUARDED(lock_) = 0;
uint64_t mem_signal_upper_bound_ TA_GUARDED(lock_) = 0;
PageQueues page_queues_;
Evictor evictor_;
// The page_compression_ is a lazily initialized RefPtr to keep the PmmNode constructor simple, at
// the cost needing to hold a lock to read the RefPtr. To avoid unnecessarily contending on the
// main pmm lock_, use a separate one.
DECLARE_MUTEX(PmmNode) compression_lock_;
fbl::RefPtr<VmCompression> page_compression_ TA_GUARDED(compression_lock_);
// Indicates whether pages should have a pattern filled into them when they are freed. This value
// can only transition from false->true, and never back to false again. Once this value is set,
// the fill size in checker_ may no longer be changed, and it becomes safe to call FillPattern
// even without the lock held.
// This is an atomic to allow for reading this outside of the lock, but modifications only happen
// with the lock held.
ktl::atomic<bool> free_fill_enabled_ TA_GUARDED(lock_) TA_GUARDED(loaned_list_lock_) = false;
// Indicates whether it is known that all pages in the free list have had a pattern filled into
// them. This value can only transition from false->true, and never back to false again. Once this
// value is set the action and armed state in checker_ may no longer be changed, and it becomes
// safe to call AssertPattern even without the lock held.
bool all_free_pages_filled_ TA_GUARDED(loaned_list_lock_) TA_GUARDED(lock_) = false;
PmmChecker checker_;
// This method is racy as it allows us to read free_fill_enabled_ without holding the lock. If we
// receive a value of 'true', then as there is no mechanism to re-set it to false, we know it is
// still true. If we receive the value of 'false', then it could still become 'true' later.
// The intent of this method is to allow for filling the free pattern outside of the lock in most
// cases, and in the unlikely event of a race during the checker being armed, the pattern can
// resort to being filled inside the lock.
bool IsFreeFillEnabledRacy() const TA_NO_THREAD_SAFETY_ANALYSIS {
// Read with acquire semantics to ensure that any modifications to checker_ are visible before
// changes to free_fill_enabled_. See EnableFreePageFilling for where the release is performed.
return free_fill_enabled_.load(ktl::memory_order_acquire);
}
// The free_fill_enabled_ and all_free_pages_filled_ members require both locks to modify, but can
// be safely read with either lock held. These methods provide a convenient way to do so with
// either of the locks held.
bool FreeFillEnabledLocked() const TA_REQ(lock_) TA_NO_THREAD_SAFETY_ANALYSIS {
return free_fill_enabled_;
}
bool FreeFillEnabledLoanedLocked() const TA_REQ(loaned_list_lock_) TA_NO_THREAD_SAFETY_ANALYSIS {
return free_fill_enabled_;
}
bool FreePagesFilledLocked() const TA_REQ(lock_) TA_NO_THREAD_SAFETY_ANALYSIS {
return all_free_pages_filled_;
}
bool FreePagesFilledLoanedLocked() const TA_REQ(loaned_list_lock_) TA_NO_THREAD_SAFETY_ANALYSIS {
return all_free_pages_filled_;
}
// The rng state for random waiting on allocations. This allows us to use rand_r, which requires
// no further thread synchronization, unlike rand().
uintptr_t random_should_wait_seed_ TA_GUARDED(lock_) = 0;
// Arenas are allocated from the node itself to avoid any boot allocations. Walking linearly
// through them at run time should also be fairly efficient.
static constexpr size_t kArenaCount = 16;
// Bit constants to fold a vm_page_t pointer into a uint32 and back. The format from LSB
// to MSB is : zero-bits | arena-index | page-index |. Where arena-index is 4 bits wide and
// zero-bits is 3 bits wide. This limits the number of pages per arena to 2^25.
static constexpr int kArenaBits = 4;
static_assert(kArenaCount == (1ul << kArenaBits));
static constexpr size_t kMaxPagesPerArena = 1u << (32u - (kArenaBits + kIndexZeroBits));
static constexpr uint32_t kArenaMask = (1u << kArenaBits) - 1u;
size_t used_arena_count_ TA_GUARDED(lock_) = 0;
PmmArena arenas_[kArenaCount] TA_GUARDED(lock_);
// Return the span of arenas from the built-in array that are known to be active. Used in loops
// that iterate across all arenas.
ktl::span<PmmArena> active_arenas() TA_REQ(lock_) {
return ktl::span<PmmArena>(arenas_, used_arena_count_);
}
ktl::span<const PmmArena> active_arenas() const TA_REQ(lock_) {
return ktl::span<const PmmArena>(arenas_, used_arena_count_);
}
};
// We don't need to hold the arena lock while executing this, since it is
// only accesses values that are set once during system initialization.
inline vm_page_t* PmmNode::PaddrToPage(paddr_t addr) TA_NO_THREAD_SAFETY_ANALYSIS {
for (auto& a : active_arenas()) {
if (a.address_in_arena(addr)) {
size_t index = (addr - a.base()) / kPageSize;
return a.get_page(index);
}
}
return nullptr;
}
// Same locking requirements as PaddrToPage().
inline vm_page_t* PmmNode::IndexToPage(uint32_t index) TA_NO_THREAD_SAFETY_ANALYSIS {
index = index >> kIndexZeroBits;
uint32_t arena_ix = index & kArenaMask;
uint32_t page_ix = (index >> kArenaBits);
return active_arenas()[arena_ix].get_page(page_ix - 1);
}
// Same locking requirements as PaddrToPage().
inline paddr_t PmmNode::IndexToPaddr(uint32_t index) TA_NO_THREAD_SAFETY_ANALYSIS {
index = index >> kIndexZeroBits;
uint32_t arena_ix = index & kArenaMask;
uint32_t page_ix = (index >> kArenaBits);
return active_arenas()[arena_ix].base() + (static_cast<uint64_t>(page_ix - 1) * kPageSize);
}
// Same locking requirements as PaddrToPage().
inline uint32_t PmmNode::PageToIndex(const vm_page_t* page) TA_NO_THREAD_SAFETY_ANALYSIS {
uint32_t arena_ix = 0;
for (auto& a : active_arenas()) {
if (a.page_belongs_to_arena(page)) {
auto page_ix = static_cast<uint32_t>(a.get_index(page) + 1);
return ((page_ix << kArenaBits) | arena_ix) << kIndexZeroBits;
}
arena_ix += 1;
}
return 0;
}
// Object for managing freeing of loaned pages via a temporary holding object. Can be instantiated
// on the stack and then passed into different PmmNode methods, the object itself has no publicly
// available methods.
// This object is not thread safe, and multiple threads must not pass the same instance of this
// object into PmmNode methods.
// A given FreeLoanedPagesHolder, as described in |FinishFreeLoanedPages|, may only be used for a
// single call to |FinishFreeLoanedPages|, after which it is 'dead', and may not be passed to any
// other PmmNode methods.
class FreeLoanedPagesHolder {
public:
FreeLoanedPagesHolder() = default;
~FreeLoanedPagesHolder() {
ASSERT(pages_.is_empty());
ASSERT(waiters_.is_empty());
}
private:
// A given FreeLoanedPagesHolder interval is only allowed to be used once to return pages to the
// PMM, this tracks whether this has happened or not.
// Only permitting a single instance of freeing simplifies any need to reason about a single
// FreeLoanedPagesHolder repeatedly having pages moved into it and free'd to the PMM concurrently
// with attempts to wait on it.
// Although the lock cannot be annotated, this member is guarded by the relevant
// PmmNode::loaned_list_lock_.
bool used_ = false;
// List of pages presently owned by this object. Every page in this list is defined to be in the
// ALLOC state with |owner| set to this object.
// Although the lock cannot be annotated, this member is guarded by the relevant
// PmmNode::loaned_list_lock_.
VmPageDoublyLinkedList pages_;
struct Waiter : fbl::SinglyLinkedListable<Waiter*> {
Event event;
};
// Maintain a list of waiters to be notified once pages_ have been freed. The Waiter object itself
// is stack allocated in the WithLoanedPage method and registered into this list. Having this be a
// list of Events of single waiting thread, instead of a single Event with a list of waiting
// threads, allows the waiters to retain a reference to the FLPH object while waiting. This
// ensures that once FinishFreeLoanedPages performs the signal on the waiters, the FLPH object can
// be safely destroyed.
fbl::SinglyLinkedList<Waiter*> waiters_;
friend PmmNode;
};
#endif // ZIRCON_KERNEL_VM_INCLUDE_VM_PMM_NODE_H_