blob: 429be63f83557785c208216db45729eaf2b53e21 [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
#include "vm/vm_object.h"
#include <align.h>
#include <assert.h>
#include <inttypes.h>
#include <lib/console.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <fbl/ref_ptr.h>
#include <kernel/mutex.h>
#include <ktl/algorithm.h>
#include <ktl/utility.h>
#include <vm/physmap.h>
#include <vm/vm.h>
#include <vm/vm_address_region.h>
#include <vm/vm_object_paged.h>
#include "vm_priv.h"
#include <ktl/enforce.h>
#define LOCAL_TRACE VM_GLOBAL_TRACE(0)
VmObject::GlobalList VmObject::all_vmos_ = {};
VmObject::VmObject(uint32_t options) : options_(options) { LTRACEF("%p\n", this); }
VmObject::~VmObject() {
canary_.Assert();
LTRACEF("%p\n", this);
DEBUG_ASSERT(!InGlobalList());
DEBUG_ASSERT(mapping_list_.is_empty());
DEBUG_ASSERT(children_list_.is_empty());
}
void VmObject::AddToGlobalList() {
Guard<CriticalMutex> guard{AllVmosLock::Get()};
all_vmos_.push_back(this);
}
void VmObject::RemoveFromGlobalList() {
Guard<CriticalMutex> guard{AllVmosLock::Get()};
DEBUG_ASSERT(InGlobalList());
all_vmos_.erase(*this);
}
void VmObject::get_name(char* out_name, size_t len) const {
canary_.Assert();
Guard<CriticalMutex> guard{lock()};
name_.get(len, out_name);
}
void VmObject::get_name_locked(char* out_name, size_t len) const {
canary_.Assert();
name_.get(len, out_name);
}
zx_status_t VmObject::set_name(const char* name, size_t len) {
canary_.Assert();
Guard<CriticalMutex> guard{lock()};
return name_.set(name, len);
}
void VmObject::set_user_id(uint64_t user_id) {
canary_.Assert();
DEBUG_ASSERT(user_id_ == 0);
user_id_ = user_id;
}
uint64_t VmObject::user_id() const {
canary_.Assert();
return user_id_;
}
zx_status_t VmObject::AddMappingLocked(VmMapping* r) {
canary_.Assert();
auto result = mapping_list_.insert({r->object_offset(), r}, r);
if (!result) {
return ZX_ERR_NO_MEMORY;
}
mapping_list_len_++;
return ZX_OK;
}
void VmObject::RemoveMappingLocked(VmMapping* r) {
canary_.Assert();
auto it = mapping_list_.find({r->object_offset(), r});
ASSERT(it);
mapping_list_.erase(it);
DEBUG_ASSERT(mapping_list_len_ > 0);
mapping_list_len_--;
}
uint32_t VmObject::num_mappings() const {
canary_.Assert();
Guard<CriticalMutex> guard{lock()};
return num_mappings_locked();
}
bool VmObject::IsMappedByUser() const {
canary_.Assert();
Guard<CriticalMutex> guard{lock()};
for (auto [key, mapping] : mapping_list_) {
if (mapping->aspace()->is_user()) {
return true;
}
}
return false;
}
uint32_t VmObject::share_count() const {
canary_.Assert();
Guard<CriticalMutex> guard{lock()};
if (mapping_list_len_ < 2) {
return 1;
}
// Find the number of unique VmAspaces that we're mapped into.
// Use this buffer to hold VmAspace pointers.
static constexpr int kAspaceBuckets = 64;
uintptr_t aspaces[kAspaceBuckets];
unsigned int num_mappings = 0; // Number of mappings we've visited
unsigned int num_aspaces = 0; // Unique aspaces we've seen
for (const auto& [key, m] : mapping_list_) {
uintptr_t as = reinterpret_cast<uintptr_t>(m->aspace().get());
// Simple O(n^2) should be fine.
for (unsigned int i = 0; i < num_aspaces; i++) {
if (aspaces[i] == as) {
goto found;
}
}
if (num_aspaces < kAspaceBuckets) {
aspaces[num_aspaces++] = as;
} else {
// Maxed out the buffer. Estimate the remaining number of aspaces.
num_aspaces +=
// The number of mappings we haven't visited yet
(mapping_list_len_ - num_mappings)
// Scaled down by the ratio of unique aspaces we've seen so far.
* num_aspaces / num_mappings;
break;
}
found:
num_mappings++;
}
DEBUG_ASSERT_MSG(num_aspaces <= mapping_list_len_,
"num_aspaces %u should be <= mapping_list_len_ %" PRIu32, num_aspaces,
mapping_list_len_);
// TODO: Cache this value as long as the set of mappings doesn't change.
// Or calculate it when adding/removing a new mapping under an aspace
// not in the list.
return num_aspaces;
}
void VmObject::SetChildObserver(VmObjectChildObserver* child_observer) {
Guard<Mutex> guard{ChildObserverLock::Get()};
child_observer_ = child_observer;
}
bool VmObject::AddChildLocked(VmObject* child) {
canary_.Assert();
children_list_.push_front(child);
children_list_len_++;
return children_list_len_ == 1;
}
bool VmObject::AddChild(VmObject* child) {
Guard<CriticalMutex> guard{ChildListLock::Get()};
return AddChildLocked(child);
}
void VmObject::DropChildLocked(VmObject* c) {
canary_.Assert();
DEBUG_ASSERT(children_list_len_ > 0);
children_list_.erase(*c);
--children_list_len_;
}
void VmObject::RemoveChild(VmObject* o, Guard<CriticalMutex>::Adoptable adopt) {
canary_.Assert();
// The observer may call back into this object so we must release the shared lock to prevent any
// self-deadlock. We explicitly release the lock prior to acquiring the ChildObserverLock as
// otherwise we have lock ordering issue, since we already allow the shared lock to be acquired
// whilst holding the ChildObserverLock.
{
Guard<CriticalMutex> guard{AdoptLock, ChildListLock::Get(), ktl::move(adopt)};
AssertHeld(*ChildListLock::Get());
DropChildLocked(o);
if (children_list_len_ != 0) {
return;
}
}
{
Guard<Mutex> observer_guard{ChildObserverLock::Get()};
// Signal the dispatcher that there are no more child VMOS
if (child_observer_ != nullptr) {
child_observer_->OnZeroChild();
}
}
}
uint32_t VmObject::num_children() const {
canary_.Assert();
Guard<CriticalMutex> guard{ChildListLock::Get()};
return children_list_len_;
}
// static
void VmObject::CacheOpPhys(paddr_t pa, uint64_t len, CacheOpType type,
ArchVmICacheConsistencyManager& cm) {
DEBUG_ASSERT(is_physmap_phys_addr(pa));
DEBUG_ASSERT(len > 0);
const vaddr_t va = reinterpret_cast<vaddr_t>(paddr_to_physmap(pa));
switch (type) {
case CacheOpType::Invalidate:
arch_invalidate_cache_range(va, len);
break;
case CacheOpType::Clean:
arch_clean_cache_range(va, len);
break;
case CacheOpType::CleanInvalidate:
arch_clean_invalidate_cache_range(va, len);
break;
case CacheOpType::Sync:
cm.SyncAddr(va, len);
break;
}
}
zx_status_t VmObject::GetPageBlocking(uint64_t offset, uint pf_flags, list_node* alloc_list,
vm_page_t** page, paddr_t* pa) {
zx_status_t status = ZX_OK;
// TOOD(https://fxbug.dev/42175933): Enforce no locks held as this might wait whilst holding a
// lock.
__UNINITIALIZED MultiPageRequest page_request;
do {
status = GetPage(offset, pf_flags, alloc_list, &page_request, page, pa);
if (status == ZX_ERR_SHOULD_WAIT) {
zx_status_t st = page_request.Wait();
if (st != ZX_OK) {
return st;
}
}
} while (status == ZX_ERR_SHOULD_WAIT);
return status;
}
ktl::optional<ktl::pair<uint64_t, uint64_t>> VmObject::GetMaximalMappedRange() const {
canary_.Assert();
uint64_t min_mapped, max_mapped;
Guard<CriticalMutex> guard{lock()};
if (unlikely(mapping_list_len_ == 0)) {
return ktl::nullopt;
}
auto& first_mapping = *(*mapping_list_.begin()).second;
first_mapping.assert_object_lock();
min_mapped = first_mapping.object_offset();
mapping_list_.walk(
[&](VmMappingObserver::State state) -> zx_status_t {
max_mapped = state.max_offset;
return ZX_ERR_STOP;
},
[&](VmMappingObserver::State state, auto first, auto last) -> zx_status_t {
max_mapped = state.max_offset;
return ZX_ERR_STOP;
});
return ktl::pair(min_mapped, max_mapped);
}
void VmObject::RangeChangeUpdateMappingsLocked(uint64_t offset, uint64_t len, RangeChangeOp op) {
canary_.Assert();
DEBUG_ASSERT(len != 0);
DEBUG_ASSERT(IsPageRounded(offset));
DEBUG_ASSERT(IsPageRounded(len));
const uint64_t last_offset = offset + len;
mapping_list_.walk(
[&](VmMappingObserver::State state) {
// If maximum offset of the subtree is below the start of our range, skip it.
if (state.max_offset <= offset) {
return ZX_ERR_NEXT;
}
// If the minimum offset of the subtree is above the end of our range then we are done.
if (last_offset <= state.min_offset) {
return ZX_ERR_STOP;
}
// Otherwise descend into the subtree.
return ZX_OK;
},
[&](VmMappingObserver::State state, auto first, auto last) {
// Before even iterating this leaf node check if anything is in range.
if (state.max_offset <= offset) {
return ZX_ERR_NEXT;
}
if (last_offset <= state.min_offset) {
return ZX_ERR_STOP;
}
last++;
for (; first != last; first++) {
// Node might be a viable candidate, perform the range update. The mapping will itself
// check for the precise intersection, if any, first and so it would be duplicate work to
// precisely check for overlap here.
const VmMapping& m = *(*first).second;
m.assert_object_lock();
if (op == RangeChangeOp::Unmap) {
m.AspaceUnmapLockedObject(offset, len, VmMapping::UnmapOptions::kNone);
} else if (op == RangeChangeOp::UnmapZeroPage) {
m.AspaceUnmapLockedObject(offset, len, VmMapping::UnmapOptions::OnlyHasZeroPages);
} else if (op == RangeChangeOp::UnmapAndHarvest) {
m.AspaceUnmapLockedObject(offset, len, VmMapping::UnmapOptions::Harvest);
} else if (op == RangeChangeOp::RemoveWrite) {
m.AspaceRemoveWriteLockedObject(offset, len);
} else if (op == RangeChangeOp::DebugUnpin) {
m.AspaceDebugUnpinLockedObject(offset, len);
} else {
panic("Unknown RangeChangeOp %d\n", static_cast<int>(op));
}
}
return ZX_ERR_NEXT;
});
}
// TODO(https://fxbug.dev/408878701): add option to dump by koid.
static int cmd_vm_object(int argc, const cmd_args* argv, uint32_t flags) {
auto usage_msg = [argv]() {
printf("usage:\n");
printf("%s dump <address>\n", argv[0].str);
printf("%s dump_pages <address>\n", argv[0].str);
return ZX_ERR_INTERNAL;
};
if (argc < 2) {
printf("not enough arguments\n");
return usage_msg();
}
if (!strcmp(argv[1].str, "dump")) {
if (argc < 3) {
printf("not enough arguments\n");
return usage_msg();
}
VmObject* o = reinterpret_cast<VmObject*>(argv[2].u);
if (o == NULL) {
printf("invalid address\n");
return usage_msg();
}
o->Dump(0, false);
} else if (!strcmp(argv[1].str, "dump_pages")) {
if (argc < 3) {
printf("not enough arguments\n");
return usage_msg();
}
VmObject* o = reinterpret_cast<VmObject*>(argv[2].u);
if (o == NULL) {
printf("invalid address\n");
return usage_msg();
}
o->Dump(0, true);
} else {
printf("unknown command\n");
return usage_msg();
}
return ZX_OK;
}
STATIC_COMMAND_START
STATIC_COMMAND("vm_object", "vm object debug commands", &cmd_vm_object)
STATIC_COMMAND_END(vm_object)
extern "C" {
void* cpp_vm_object_get_ref_counted(const VmObject* vmo);
void cpp_vm_object_free(VmObject* vmo);
void* cpp_vm_object_get_ref_counted(const VmObject* vmo) {
return const_cast<fbl::RefCountedUpgradeable<VmObject>*>(
static_cast<const fbl::RefCountedUpgradeable<VmObject>*>(vmo));
}
void cpp_vm_object_free(VmObject* vmo) { delete vmo; }
}