blob: cbefeca1956176be82c14374846b1d8995fca3d7 [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_OBJECT_PAGED_H_
#define ZIRCON_KERNEL_VM_INCLUDE_VM_VM_OBJECT_PAGED_H_
#include <assert.h>
#include <lib/page/size.h>
#include <lib/user_copy/user_ptr.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <stdint.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <fbl/array.h>
#include <fbl/canary.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/macros.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <kernel/mutex.h>
#include <kernel/range_check.h>
#include <vm/page_source.h>
#include <vm/pmm.h>
#include <vm/vm.h>
#include <vm/vm_aspace.h>
#include <vm/vm_cow_pages.h>
#include <vm/vm_object.h>
class VmObjectPaged;
extern "C" void cpp_vm_object_paged_free(VmObjectPaged* vmo);
// the main VM object type, based on a copy-on-write set of pages.
class VmObjectPaged final : public VmObject, public VmDeferredDeleter<VmObjectPaged> {
public:
// |VmObject::options_| bitmask is extended with:
static constexpr uint32_t kResizable = (1u << 1);
static constexpr uint32_t kContiguous = (1u << 2);
static constexpr uint32_t kSlice = (1u << 3);
static constexpr uint32_t kDiscardable = (1u << 4);
static constexpr uint32_t kAlwaysPinned = (1u << 5);
static constexpr uint32_t kReference = (1u << 6);
static constexpr uint32_t kCanBlockOnPageRequests = (1u << 31);
Lock<CriticalMutex>* lock() const override TA_RET_CAP(cow_pages_->lock_ref()) {
return cow_pages_->lock();
}
Lock<CriticalMutex>& lock_ref() const override TA_RET_CAP(cow_pages_->lock_ref()) {
return cow_pages_->lock_ref();
}
uint64_t lock_order() const { return cow_pages_->lock_order(); }
VmObject* self_locked() TA_REQ(lock()) TA_ASSERT(self_locked()->lock()) { return this; }
const VmObject* self_locked() const TA_REQ(lock()) TA_ASSERT(self_locked()->lock()) {
return this;
}
static zx_status_t Create(uint32_t pmm_alloc_flags, uint32_t options, uint64_t size,
fbl::RefPtr<VmObjectPaged>* vmo);
// Create a VMO backed by a contiguous range of physical memory. The
// returned vmo has all of its pages committed, and does not allow
// decommitting them.
static zx_status_t CreateContiguous(uint32_t pmm_alloc_flags, uint64_t size,
uint8_t alignment_log2, fbl::RefPtr<VmObjectPaged>* vmo);
// Creates a VMO from wired pages.
//
// Creating a VMO using this method is destructive. Once the VMO is released, its
// pages will be released into the general purpose page pool, so it is not possible
// to create multiple VMOs for the same region using this method.
//
// |exclusive| indicates whether or not the created vmo should have exclusive access to
// the pages. If exclusive is true, then [data, data + size) will be unmapped from the
// kernel address space (unless they lie in the physmap).
static zx_status_t CreateFromWiredPages(const void* data, size_t size, bool exclusive,
fbl::RefPtr<VmObjectPaged>* vmo);
static zx_status_t CreateExternal(fbl::RefPtr<PageSource> src, uint32_t options, uint64_t size,
fbl::RefPtr<VmObjectPaged>* vmo);
zx_status_t Resize(uint64_t size) override;
uint64_t size_locked() const override TA_REQ(lock()) {
// If this VmObject has a limit to the pages it references from |cow_pages_|, then that limit
// determines the size of this object rather than the size of the whole |cow_pages_| object.
return ktl::min(cow_pages_locked()->size_locked(), cow_range_.len);
}
// Queries the user defined stream size, which is distinct from the VMO size. Stream size is
// byte-aligned and is not guaranteed to be in the range of the VMO. The lock does not not guard
// the user changing the value via a syscall, so multiple calls under the same lock acquisition
// can have different results.
ktl::optional<uint64_t> user_stream_size_locked() TA_REQ(lock()) {
if (!user_stream_size_) {
return ktl::nullopt;
}
return user_stream_size_->GetStreamSize();
}
// Calculates the minimum of the VMO size and the page-aligned user stream size.
ktl::optional<uint64_t> saturating_stream_size_locked() TA_REQ(lock()) {
if (!user_stream_size_) {
return ktl::nullopt;
}
uint64_t user_stream_size = user_stream_size_->GetStreamSize();
uint64_t vmo_size = size_locked();
// If user stream size is larger, trim to the VMO.
// TODO(https://fxbug.dev/380960681): remove check when stream size <= VMO size invariant is
// enforced.
if (user_stream_size > vmo_size) {
return vmo_size;
}
return RoundUpPageSize(user_stream_size);
}
bool is_contiguous() const override { return (options_ & kContiguous); }
bool is_resizable() const override { return (options_ & kResizable); }
bool is_discardable() const override { return (options_ & kDiscardable); }
bool is_user_pager_backed() const override {
return cow_pages_->is_root_source_user_pager_backed();
}
bool is_dirty_tracked() const override { return cow_pages_->is_dirty_tracked(); }
// Streams are not supported on physical or contiguous VMOs.
bool is_stream_compatible() const override { return is_paged() && !is_contiguous(); }
void mark_modified_locked() override TA_REQ(lock()) {
return cow_pages_locked()->mark_modified_locked();
}
ChildType child_type() const override {
// Slices are implemented as references internally so for the purposes of reporting the
// expected type back to the user the slice check must be done before the plain reference check.
if (is_slice()) {
return ChildType::kSlice;
}
if (is_reference()) {
return ChildType::kReference;
}
Guard<CriticalMutex> guard{ChildListLock::Get()};
return parent_ ? ChildType::kCowClone : ChildType::kNotChild;
}
bool is_slice() const { return options_ & kSlice; }
bool is_reference() const { return (options_ & kReference); }
uint64_t parent_user_id() const override TA_NO_THREAD_SAFETY_ANALYSIS {
Guard<CriticalMutex> guard{ChildListLock::Get()};
if (parent_) {
return parent_->user_id();
}
return 0;
}
uint64_t HeapAllocationBytes() const override {
Guard<CriticalMutex> guard{lock()};
return cow_pages_locked()->HeapAllocationBytesLocked();
}
uint64_t ReclamationEventCount() const override {
Guard<CriticalMutex> guard{lock()};
return cow_pages_locked()->ReclamationEventCountLocked();
}
AttributionCounts GetAttributedMemoryInRange(uint64_t offset_bytes,
uint64_t len_bytes) const override {
Guard<CriticalMutex> guard{lock()};
return GetAttributedMemoryInRangeLocked(offset_bytes, len_bytes);
}
AttributionCounts GetAttributedMemoryInReferenceOwner() const override {
DEBUG_ASSERT(is_reference());
Guard<CriticalMutex> guard{lock()};
auto cow_range = GetCowRange(0, size_locked());
DEBUG_ASSERT(cow_range.has_value());
return cow_pages_locked()->GetAttributedMemoryInRangeLocked(cow_range.value());
}
AttributionCounts GetAttributedMemoryRangeInReferenceOwner(uint64_t offset, uint64_t len) const {
DEBUG_ASSERT(is_reference());
Guard<CriticalMutex> guard{lock()};
return cow_pages_locked()->GetAttributedMemoryInRangeLocked(VmCowRange(offset, len));
}
// Does this VMO have any children that can see pages owned by this VMO (i.e. pages held by its
// VmCowPages page list).
bool children_cannot_access_pages() const {
// If this is a reference whose parent has gone away, then the reference_list_ can contain
// sibling references. Only consider actual children for this check.
DEBUG_ASSERT(!is_reference());
Guard<CriticalMutex> guard{lock()};
// There are two cases to consider here:
// - If there is a hidden node above this node in the parent chain, this node can only be a leaf
// in the VmCowPages hierarchy tree. So its page list can only be seen by any references/slices,
// hence the check for reference_list_.is_empty(). cow_pages_locked()->has_no_children_locked()
// will always be true for this case.
// - If there is no hidden node, then a CoW clone can exist below this node and can see its
// pages, so we need to check for all types of children, both children represented in the CoW
// tree, and also the reference list.
return reference_list_.is_empty() && cow_pages_locked()->has_no_children_locked();
}
zx_status_t CommitRange(uint64_t offset, uint64_t len) override {
return CommitRangeInternal(offset, len, /*pin=*/false, /*write=*/false);
}
zx_status_t CommitRangePinned(uint64_t offset, uint64_t len, bool write) override {
return CommitRangeInternal(offset, len, /*pin=*/true, write);
}
zx_status_t PrefetchRange(uint64_t offset, uint64_t len) override;
zx_status_t DecommitRange(uint64_t offset, uint64_t len) override;
zx_status_t ZeroRange(uint64_t offset, uint64_t len) override {
return ZeroRangeInternal(offset, len, /*dirty_track=*/true);
}
zx_status_t ZeroRangeUntracked(uint64_t offset, uint64_t len) override {
// We don't expect any committed pages to remain at the end of this call, so we should be
// operating on whole pages.
if (!IsPageRounded(offset) || !IsPageRounded(len)) {
return ZX_ERR_INVALID_ARGS;
}
return ZeroRangeInternal(offset, len, /*dirty_track=*/false);
}
void Unpin(uint64_t offset, uint64_t len) override {
__UNINITIALIZED VmCowPages::DeferredOps deferred(cow_pages_.get());
Guard<CriticalMutex> guard{lock()};
auto cow_range = GetCowRange(offset, len);
ASSERT(cow_range);
cow_pages_locked()->UnpinLocked(*cow_range, &deferred);
}
// See VmObject::DebugIsRangePinned
bool DebugIsRangePinned(uint64_t offset, uint64_t len) override {
Guard<CriticalMutex> guard{lock()};
if (auto cow_range = GetCowRange(offset, len)) {
return cow_pages_locked()->DebugIsRangePinnedLocked(*cow_range);
}
return false;
}
zx_status_t LockRange(uint64_t offset, uint64_t len,
zx_vmo_lock_state_t* lock_state_out) override;
zx_status_t TryLockRange(uint64_t offset, uint64_t len) override;
zx_status_t UnlockRange(uint64_t offset, uint64_t len) override;
zx_status_t Read(void* ptr, uint64_t offset, size_t len) override;
zx_status_t Write(const void* ptr, uint64_t offset, size_t len) override;
zx_status_t Lookup(uint64_t offset, uint64_t len, VmObject::LookupFunction lookup_fn) override;
zx_status_t LookupContiguous(uint64_t offset, uint64_t len, paddr_t* out_paddr) override;
ktl::pair<zx_status_t, size_t> ReadUser(user_out_ptr<char> ptr, uint64_t offset, size_t len,
VmObjectReadWriteOptions options) override;
ktl::pair<zx_status_t, size_t> WriteUser(
user_in_ptr<const char> ptr, uint64_t offset, size_t len, VmObjectReadWriteOptions options,
const OnWriteBytesTransferredCallback& on_bytes_transferred) override;
ktl::pair<zx_status_t, size_t> ReadUserVector(user_out_iovec_t vec, uint64_t offset, size_t len);
ktl::pair<zx_status_t, size_t> WriteUserVector(
user_in_iovec_t vec, uint64_t offset, size_t len,
const OnWriteBytesTransferredCallback& on_bytes_transferred);
zx_status_t TakePages(uint64_t offset, uint64_t len, VmPageSpliceList* pages) override;
zx_status_t SupplyPages(uint64_t offset, uint64_t len, VmPageSpliceList* pages,
SupplyOptions options) override;
zx_status_t FailPageRequests(uint64_t offset, uint64_t len, zx_status_t error_status) override {
Guard<CriticalMutex> guard{lock()};
if (auto cow_range = GetCowRange(offset, len)) {
return cow_pages_locked()->FailPageRequestsLocked(*cow_range, error_status);
}
return ZX_ERR_OUT_OF_RANGE;
}
zx_status_t DirtyPages(uint64_t offset, uint64_t len) override;
zx_status_t EnumerateDirtyRanges(uint64_t offset, uint64_t len,
DirtyRangeEnumerateFunction&& dirty_range_fn) override;
zx_status_t QueryPagerVmoStats(bool reset, zx_pager_vmo_stats_t* stats) override {
Guard<CriticalMutex> guard{lock()};
return cow_pages_locked()->QueryPagerVmoStatsLocked(reset, stats);
}
void ResetPagerVmoStats() {
Guard<CriticalMutex> guard{lock()};
cow_pages_locked()->ResetPagerVmoStatsLocked();
}
zx_status_t WritebackBegin(uint64_t offset, uint64_t len, bool is_zero_range) override {
Guard<CriticalMutex> guard{lock()};
if (auto cow_range = GetCowRange(offset, len)) {
return cow_pages_locked()->WritebackBeginLocked(*cow_range, is_zero_range);
}
return ZX_ERR_OUT_OF_RANGE;
}
zx_status_t WritebackEnd(uint64_t offset, uint64_t len) override {
Guard<CriticalMutex> guard{lock()};
if (auto cow_range = GetCowRange(offset, len)) {
return cow_pages_locked()->WritebackEndLocked(*cow_range);
}
return ZX_ERR_OUT_OF_RANGE;
}
// See VmObject::SetUserStreamSize
void SetUserStreamSize(fbl::RefPtr<StreamSizeManager> ssm) override {
Guard<CriticalMutex> guard{lock()};
user_stream_size_ = ktl::move(ssm);
}
void Dump(uint depth, bool verbose) override {
Guard<CriticalMutex> guard{lock()};
DumpLocked(depth, verbose);
}
uint32_t DebugLookupDepth() const override {
Guard<CriticalMutex> guard{lock()};
return cow_pages_locked()->DebugLookupDepthLocked();
}
zx_status_t GetPage(uint64_t offset, uint pf_flags, MultiPageRequest* page_request,
vm_page_t** page, paddr_t* pa) override;
// Gets a reference to a LookupCursor for the specified range in the VMO. The returned cursor and
// all the items returned by the cursor are only valid as long as the lock is contiguous held.
//
// The requested range must be fully inside the VMO, and if a cursor is returned the caller can
// assume that all offsets up to |max_len| can be queried. Pages returned by the cursor itself
// might be from this VMO or a parent, depending on actual cursor methods used.
//
// See |VmCowPages::LookupCursor|.
zx::result<VmCowPages::LookupCursor> GetLookupCursorLocked(uint64_t offset, uint64_t max_len)
TA_REQ(lock()) {
auto cow_range = GetCowRange(offset, max_len);
if (likely(cow_range)) {
return cow_pages_locked()->GetLookupCursorLocked(*cow_range);
}
return zx::error{ZX_ERR_OUT_OF_RANGE};
}
zx_status_t CreateClone(Resizability resizable, SnapshotType type, uint64_t offset, uint64_t size,
bool copy_name, fbl::RefPtr<VmObject>* child_vmo) override;
zx_status_t CacheOp(uint64_t offset, uint64_t len, CacheOpType type) override;
arch_mmu_flags_t GetMappingCachePolicyLocked() const TA_REQ(lock()) {
return self_locked()->GetMappingCachePolicyLocked();
}
zx_status_t SetMappingCachePolicy(arch_mmu_flags_t cache_policy) override;
void DetachSource() override { cow_pages_->DetachSource(); }
ktl::optional<zx_koid_t> GetPageSourceKoid() const override {
if (is_reference()) {
return ktl::nullopt;
}
return cow_pages_->GetPageSourceKoid();
}
zx_status_t CreateChildSlice(uint64_t offset, uint64_t size, bool copy_name,
fbl::RefPtr<VmObject>* child_vmo) override;
zx_status_t CreateChildReference(Resizability resizable, uint64_t offset, uint64_t size,
bool copy_name, bool* first_child,
fbl::RefPtr<VmObject>* child_vmo) override;
// Returns whether or not zero pages can be safely deduped from this VMO. Zero pages cannot be
// deduped if the VMO is in use for kernel mappings, or if the pages cannot be accessed from the
// physmap due to not being cached.
bool CanDedupZeroPagesLocked() TA_REQ(lock());
// Exposed for testing.
fbl::RefPtr<VmCowPages> DebugGetCowPages() const {
Guard<CriticalMutex> guard{lock()};
return cow_pages_;
}
vm_page_t* DebugGetPage(uint64_t offset) const {
Guard<CriticalMutex> guard{lock()};
if (auto cow_range = GetCowRange(offset, kPageSize)) {
return cow_pages_locked()->DebugGetPageLocked(cow_range->offset);
}
return nullptr;
}
// Apply the specified operation to all mappings in the given range.
//
// Preconditions
// * This range may not include pages mapped in the kernel address space without the
// VMAR_FLAG_DEBUG_DYNAMIC_KERNEL_MAPPING flag.
// * |range| must be page-aligned.
void RangeChangeUpdateLocked(VmCowRange range, RangeChangeOp op) TA_REQ(lock());
// Apply the specified operation to all mappings in the given range, forwarded to the original
// owner of the VmCowPages. In the case of references and slices, this ensures that all VMOs in
// the reference list of the original, cloned VMO are included.
//
// |offset| and |len| must be page-rounded.
void ForwardRangeChangeUpdateLocked(uint64_t offset, uint64_t len, RangeChangeOp op)
TA_REQ(lock());
// Hint how the specified range is intended to be used, so that the hint can be taken into
// consideration when reclaiming pages under memory pressure (if applicable).
zx_status_t HintRange(uint64_t offset, uint64_t len, EvictionHint hint) override;
void CommitHighPriorityPages(uint64_t offset, uint64_t len) override;
// See the comments on |PriorityChanger| for how to use this object.
PriorityChanger MakePriorityChanger(int64_t delta) {
return PriorityChanger(delta, cow_pages_.get());
}
void MaybeDeadTransition() {}
// Constructs and returns a |DeferredOps| that can be passed into other methods on this VMO that
// require one.
VmCowPages::DeferredOps MakeDeferredOps() { return VmCowPages::DeferredOps(cow_pages_.get()); }
private:
// private constructor (use Create())
VmObjectPaged(uint32_t options, fbl::RefPtr<VmCowPages> cow_pages);
VmObjectPaged(uint32_t options, fbl::RefPtr<VmCowPages> cow_pages, VmCowRange range);
static zx_status_t CreateCommon(uint32_t pmm_alloc_flags, uint32_t options, uint64_t size,
fbl::RefPtr<VmObjectPaged>* vmo);
static zx_status_t CreateWithSourceCommon(fbl::RefPtr<PageSource> src, uint32_t pmm_alloc_flags,
uint32_t options, uint64_t size,
fbl::RefPtr<VmObjectPaged>* obj);
// private destructor, only called from refptr
~VmObjectPaged() override;
friend fbl::RefPtr<VmObjectPaged>;
friend void ::cpp_vm_object_paged_free(VmObjectPaged* vmo);
DISALLOW_COPY_ASSIGN_AND_MOVE(VmObjectPaged);
// Helper for the destructor as by default destructors do not require locks to be held under the
// assumption the destructor runs when no other entities can see the object. This is not true here
// and locking is still relevant, so having the cleanup logic in a separate method allows for full
// static analysis to happen.
void DestructorHelper();
zx_status_t CreateChildReferenceCommon(uint32_t options, VmCowRange range, bool allow_uncached,
bool copy_name, bool* first_child,
fbl::RefPtr<VmObject>* child_vmo);
// Unified function that implements both CommitRange and CommitRangePinned
zx_status_t CommitRangeInternal(uint64_t offset, uint64_t len, bool pin, bool write);
// see GetAttributedMemoryInRange
AttributionCounts GetAttributedMemoryInRangeLocked(uint64_t offset_bytes,
uint64_t len_bytes) const TA_REQ(lock());
// internal read/write routine that takes a templated copy function to help share some code
template <typename T>
ktl::pair<zx_status_t, size_t> ReadWriteInternal(uint64_t offset, size_t len, bool write,
VmObjectReadWriteOptions options, T copyfunc);
// Zeroes a partial range in a page. The page to zero is looked up using page_base_offset, and
// will be committed if needed. The range of [zero_start_offset, zero_end_offset) is relative to
// the page and so [0, kPageSize) would zero the entire page.
zx_status_t ZeroPartialPage(uint64_t page_base_offset, uint64_t zero_start_offset,
uint64_t zero_end_offset);
// Internal helper for ZeroRange*.
zx_status_t ZeroRangeInternal(uint64_t offset, uint64_t len, bool dirty_track);
// Internal implementations that assume lock is already held.
void DumpLocked(uint depth, bool verbose) const TA_REQ(lock());
// Convenience wrapper that returns cow_pages_ whilst asserting that the lock is held.
VmCowPages* cow_pages_locked() const TA_REQ(lock()) TA_ASSERT(cow_pages_locked()->lock()) {
AssertHeld(cow_pages_->lock_ref());
return cow_pages_.get();
}
// Translate a range in this VmObject to a VmCowRange in |cow_pages_|.
//
// The translated range might extend beyond the end of the cow_pages_ object. This function will
// return |ktl::nullopt| if the translated range might have included pages in cow_pages_ that
// should not be referenced by this VmObject (e.g., if this VmObject is a slice reference).
ktl::optional<VmCowRange> GetCowRange(uint64_t offset, uint64_t len) const {
if (likely(InRange(offset, len, cow_range_.len))) {
return VmCowRange(offset + cow_range_.offset, len);
}
return ktl::nullopt;
}
// Similar to GetCowRange, but also checks for being within the range of the cow pages size.
ktl::optional<VmCowRange> GetCowRangeSizeCheckLocked(uint64_t offset, uint64_t len) const
TA_REQ(lock()) {
if (likely(InRange(offset, len, size_locked()))) {
return VmCowRange(offset + cow_range_.offset, len);
}
return ktl::nullopt;
}
// This is a debug only state that is used to simplify assertions and validations around blocking
// on page requests. If false no operations on this VMO will ever fill out the PageRequest
// that is passed in, and will never block in ops like Commit that say they might block. This
// creates a carve-out that is necessary as kernel internals need to call VMO operations that
// might block on VMOs that they know won't block, and not have assertions spuriously trip. This
// acts as the union of user pager backed VMOs, as well as VMOs that might wait on internal kernel
// page sources.
bool can_block_on_page_requests() const { return options_ & kCanBlockOnPageRequests; }
using ReferenceListNodeState = fbl::DoublyLinkedListNodeState<VmObjectPaged*>;
struct ReferenceListTraits {
static ReferenceListNodeState& node_state(VmObjectPaged& vmo) {
return vmo.reference_list_node_state_;
}
};
friend struct ReferenceListTraits;
ReferenceListNodeState reference_list_node_state_;
using ReferenceList = fbl::DoublyLinkedListCustomTraits<VmObjectPaged*, ReferenceListTraits>;
// list of every reference child
ReferenceList reference_list_ TA_GUARDED(lock());
// parent pointer (may be null). This is a raw pointer as we have no need to hold our parent alive
// once they want to go away.
VmObjectPaged* parent_ TA_GUARDED(ChildListLock::Get()) = nullptr;
const fbl::RefPtr<VmCowPages> cow_pages_;
// The range in |cow_pages_| that this VmObject references.
//
// This range can be less than the whole VmCowPage for a slice reference.
const VmCowRange cow_range_;
// A user supplied stream size that can be queried. By itself this has no semantic meaning and is
// only read and used specifically when requested by the user. See VmObject::SetUserStreamSize.
fbl::RefPtr<StreamSizeManager> user_stream_size_ TA_GUARDED(lock());
};
#endif // ZIRCON_KERNEL_VM_INCLUDE_VM_VM_OBJECT_PAGED_H_