blob: a5f6fcbb7315b9151645c63d4c411bae9311f295 [file] [log] [blame] [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 "object/vm_object_dispatcher.h"
#include <assert.h>
#include <inttypes.h>
#include <lib/boot-options/boot-options.h>
#include <lib/counters.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <fbl/alloc_checker.h>
#include <ktl/algorithm.h>
#include <ktl/atomic.h>
#include <ktl/optional.h>
#include <vm/page_source.h>
#include <vm/vm_aspace.h>
#include <vm/vm_object.h>
#include <vm/vm_object_paged.h>
#include <ktl/enforce.h>
#define LOCAL_TRACE 0
KCOUNTER(dispatcher_vmo_create_count, "dispatcher.vmo.create")
KCOUNTER(dispatcher_vmo_destroy_count, "dispatcher.vmo.destroy")
zx::result<VmObjectDispatcher::CreateStats> VmObjectDispatcher::parse_create_syscall_flags(
uint32_t flags, size_t size) {
CreateStats res = {0, size};
if (flags & ZX_VMO_RESIZABLE) {
if (flags & ZX_VMO_UNBOUNDED) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
res.flags |= VmObjectPaged::kResizable;
flags &= ~ZX_VMO_RESIZABLE;
}
if (flags & ZX_VMO_DISCARDABLE) {
res.flags |= VmObjectPaged::kDiscardable;
flags &= ~ZX_VMO_DISCARDABLE;
}
if (flags & ZX_VMO_UNBOUNDED) {
flags &= ~ZX_VMO_UNBOUNDED;
res.size = VmObjectPaged::max_size();
} else {
if (zx_status_t status = VmObject::RoundSize(size, &res.size); status != ZX_OK) {
return zx::error(status);
}
}
if (flags) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(res);
}
zx_status_t VmObjectDispatcher::CreateWithSsm(fbl::RefPtr<VmObject> vmo,
fbl::RefPtr<StreamSizeManager> stream_size_manager,
InitialMutability initial_mutability,
KernelHandle<VmObjectDispatcher>* handle,
zx_rights_t* rights) {
fbl::AllocChecker ac;
KernelHandle new_handle(fbl::AdoptRef(
new (&ac) VmObjectDispatcher(ktl::move(vmo), stream_size_manager, initial_mutability)));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
new_handle.dispatcher()->vmo()->SetUserStreamSize(ktl::move(stream_size_manager));
new_handle.dispatcher()->vmo()->set_user_id(new_handle.dispatcher()->get_koid());
*rights =
default_rights() | (new_handle.dispatcher()->vmo()->is_resizable() ? ZX_RIGHT_RESIZE : 0);
*handle = ktl::move(new_handle);
return ZX_OK;
}
zx_status_t VmObjectDispatcher::Create(fbl::RefPtr<VmObject> vmo, uint64_t stream_size,
InitialMutability initial_mutability,
KernelHandle<VmObjectDispatcher>* handle,
zx_rights_t* rights) {
fbl::RefPtr<StreamSizeManager> ssm;
// If the initial stream size we want to track is exactly equal to the current VMO size then we
// can defer creating the stream size manager till later.
if (stream_size != vmo->size()) {
auto result = StreamSizeManager::Create(stream_size);
if (result.is_error()) {
return result.status_value();
}
ssm = ktl::move(*result);
uint64_t aligned_stream_size = RoundUpPageSize(stream_size);
// The stream_size cannot be larger than the VMO size, so this cannot overflow.
DEBUG_ASSERT(aligned_stream_size >= stream_size);
if (aligned_stream_size < vmo->size()) {
// The range beyond the (rounded up) stream size to the VMO size is dirty-untracked zero.
zx_status_t status =
vmo->ZeroRangeUntracked(aligned_stream_size, vmo->size() - aligned_stream_size);
if (status != ZX_OK) {
return status;
}
}
}
return CreateWithSsm(ktl::move(vmo), ktl::move(ssm), initial_mutability, handle, rights);
}
VmObjectDispatcher::VmObjectDispatcher(fbl::RefPtr<VmObject> vmo,
fbl::RefPtr<StreamSizeManager> stream_size_manager,
InitialMutability initial_mutability)
: SoloDispatcher(ZX_VMO_ZERO_CHILDREN),
vmo_(ktl::move(vmo)),
stream_size_mgr_(ktl::move(stream_size_manager)),
initial_mutability_(initial_mutability) {
kcounter_add(dispatcher_vmo_create_count, 1);
vmo_->SetChildObserver(this);
}
VmObjectDispatcher::~VmObjectDispatcher() {
kcounter_add(dispatcher_vmo_destroy_count, 1);
// Intentionally leave vmo_->user_id() set to our koid even though we're
// dying and the koid will no longer map to a Dispatcher. koids are never
// recycled, and it could be a useful breadcrumb.
}
void VmObjectDispatcher::OnZeroChild() {
Guard<CriticalMutex> guard{get_lock()};
// Double check the number of children now that we are holding the dispatcher lock and so are
// serialized with our calls to CreateChild. This allows us to atomically observer that there are
// indeed zero children, and then set the signal. The double check is needed since OnZeroChild is
// called without the VMO lock held, and so there is a race where before we could acquire the
// dispatcher lock a new child got created.
if (vmo_->num_children() == 0) {
UpdateStateLocked(0, ZX_VMO_ZERO_CHILDREN);
}
}
zx_status_t VmObjectDispatcher::get_name(char (&out_name)[ZX_MAX_NAME_LEN]) const {
canary_.Assert();
vmo_->get_name(out_name, ZX_MAX_NAME_LEN);
return ZX_OK;
}
zx_status_t VmObjectDispatcher::set_name(const char* name, size_t len) {
canary_.Assert();
return vmo_->set_name(name, len);
}
void VmObjectDispatcher::on_zero_handles() {
// Clear when handle count reaches zero rather in the destructor because we're retaining a
// VmObject that might call back into |this| via VmObjectChildObserver when it's destroyed.
vmo_->SetChildObserver(nullptr);
}
ktl::pair<zx_status_t, size_t> VmObjectDispatcher::Read(user_out_ptr<char> user_data,
uint64_t offset, size_t length) {
canary_.Assert();
return vmo_->ReadUser(user_data, offset, length, VmObjectReadWriteOptions::None);
}
ktl::pair<zx_status_t, size_t> VmObjectDispatcher::Write(
user_in_ptr<const char> user_data, uint64_t offset, size_t length,
VmObject::OnWriteBytesTransferredCallback on_bytes_transferred) {
canary_.Assert();
return vmo_->WriteUser(user_data, offset, length, VmObjectReadWriteOptions::None,
on_bytes_transferred);
}
zx_status_t VmObjectDispatcher::SetSize(uint64_t size) {
canary_.Assert();
// No stream size management for physical or contiguous VMOs.
if (!vmo()->is_paged() || vmo()->is_contiguous()) {
DEBUG_ASSERT(!vmo_->is_resizable());
return ZX_ERR_UNAVAILABLE;
}
// TODO(https://fxbug.dev/341218975) SetSize should only change stream size to maintain invariant
// that stream size isn't larger than VMO size.
auto ssm = stream_size_manager();
if (ssm.is_error()) {
return ssm.status_value();
}
StreamSizeManager::Operation op((*ssm).get());
Guard<Mutex> guard{AliasedLock, ssm->lock(), op.lock()};
ssm->BeginSetStreamSizeLocked(size, &op, &guard);
uint64_t size_aligned = RoundUpPageSize(size);
// Check for overflow when rounding up.
if (size_aligned < size) {
op.CancelLocked();
return ZX_ERR_OUT_OF_RANGE;
}
zx_status_t status = vmo_->Resize(size_aligned);
if (status != ZX_OK) {
op.CancelLocked();
return status;
}
uint64_t remaining = size_aligned - size;
if (remaining > 0) {
// TODO(https://fxbug.dev/42053728): Determine whether failure to ZeroRange here should undo
// this operation.
//
// Dropping the lock here is fine, as an `Operation` only needs to be locked when initializing,
// committing, or cancelling.
guard.CallUnlocked([&] { vmo_->ZeroRange(size, remaining); });
}
op.CommitLocked();
return status;
}
zx_status_t VmObjectDispatcher::GetSize(uint64_t* size) {
canary_.Assert();
*size = vmo_->size();
return ZX_OK;
}
zx_info_vmo_t VmoToInfoEntry(const VmObject* vmo, VmoOwnership ownership,
zx_rights_t handle_rights) {
zx_info_vmo_t entry = {};
entry.koid = vmo->user_id();
vmo->get_name(entry.name, sizeof(entry.name));
entry.size_bytes = vmo->size();
entry.parent_koid = vmo->parent_user_id();
entry.num_children = vmo->num_children();
entry.num_mappings = vmo->num_mappings();
entry.share_count = vmo->share_count();
entry.flags = (vmo->is_paged() ? ZX_INFO_VMO_TYPE_PAGED : ZX_INFO_VMO_TYPE_PHYSICAL) |
(vmo->is_resizable() ? ZX_INFO_VMO_RESIZABLE : 0) |
(vmo->is_discardable() ? ZX_INFO_VMO_DISCARDABLE : 0) |
(vmo->is_user_pager_backed() ? ZX_INFO_VMO_PAGER_BACKED : 0) |
(vmo->is_contiguous() ? ZX_INFO_VMO_CONTIGUOUS : 0);
// As an implementation detail, both ends of an IOBuffer keep a child reference to a shared parent
// which is dropped. Since references aren't normally attributed memory otherwise, we specifically
// request their attribution counts.
VmObject::AttributionCounts counts = ownership == VmoOwnership::kIoBuffer
? vmo->GetAttributedMemoryInReferenceOwner()
: vmo->GetAttributedMemory();
const vm::FractionalBytes total_scaled_bytes = counts.total_scaled_bytes();
entry.committed_bytes = counts.uncompressed_bytes;
entry.populated_bytes = counts.total_bytes();
entry.committed_private_bytes = counts.private_uncompressed_bytes;
entry.populated_private_bytes = counts.total_private_bytes();
entry.committed_scaled_bytes = counts.scaled_uncompressed_bytes.integral;
entry.populated_scaled_bytes = total_scaled_bytes.integral;
entry.committed_fractional_scaled_bytes = counts.scaled_uncompressed_bytes.fractional.raw_value();
entry.populated_fractional_scaled_bytes = total_scaled_bytes.fractional.raw_value();
entry.cache_policy = vmo->GetMappingCachePolicy();
switch (ownership) {
case VmoOwnership::kHandle:
entry.flags |= ZX_INFO_VMO_VIA_HANDLE;
entry.handle_rights = handle_rights;
break;
case VmoOwnership::kMapping:
entry.flags |= ZX_INFO_VMO_VIA_MAPPING;
break;
case VmoOwnership::kIoBuffer:
entry.flags |= ZX_INFO_VMO_VIA_IOB_HANDLE;
entry.handle_rights = handle_rights;
break;
}
if (vmo->child_type() == VmObject::ChildType::kCowClone) {
entry.flags |= ZX_INFO_VMO_IS_COW_CLONE;
}
entry.metadata_bytes = vmo->HeapAllocationBytes();
// Only events that change committed pages are different kinds of reclamation.
entry.committed_change_events = vmo->ReclamationEventCount();
return entry;
}
zx_info_vmo_t VmObjectDispatcher::GetVmoInfo(zx_rights_t rights) {
zx_info_vmo_t info = VmoToInfoEntry(vmo().get(), VmoOwnership::kHandle, rights);
if (initial_mutability_ == InitialMutability::kImmutable) {
info.flags |= ZX_INFO_VMO_IMMUTABLE;
}
return info;
}
zx_status_t VmObjectDispatcher::SetStreamSize(uint64_t stream_size) {
canary_.Assert();
// Set stream size is not supported for physical or contiguous VMOs.
if (vmo_->is_contiguous() || !vmo_->is_paged()) {
return ZX_ERR_NOT_SUPPORTED;
}
auto ssm = stream_size_manager();
if (ssm.is_error()) {
return ssm.status_value();
}
StreamSizeManager::Operation op((*ssm).get());
Guard<Mutex> guard{AliasedLock, ssm->lock(), op.lock()};
uint64_t vmo_size = vmo_->size();
uint64_t old_stream_size = ssm->GetStreamSize();
if (stream_size == old_stream_size) {
return ZX_OK;
}
// can't resize the stream beyond the VMO size
if (stream_size > vmo_size) {
return ZX_ERR_OUT_OF_RANGE;
}
ssm->BeginSetStreamSizeLocked(stream_size, &op, &guard);
// Zero the range from min(stream size, old stream size) to the end of the VMO.
uint64_t zero_start = ktl::min(stream_size, old_stream_size);
uint64_t aligned_stream_size = RoundUpPageSize(stream_size);
DEBUG_ASSERT(aligned_stream_size >= stream_size);
// Dropping the lock here is fine, as an `Operation` only needs to be locked when initializing,
// committing, or cancelling.
zx_status_t status = ZX_OK;
guard.CallUnlocked([&] {
status = vmo_->ZeroRange(zero_start, aligned_stream_size - zero_start);
if (status == ZX_OK) {
status = vmo_->ZeroRangeUntracked(aligned_stream_size, vmo_size - aligned_stream_size);
}
});
// Undo this operation of ZeroRange fails.
if (status != ZX_OK) {
op.CancelLocked();
return status;
}
// Ensure pages between min(stream size, old stream size) and the end of the VMO are unmapped
// before before committing new stream size.
VmObjectPaged* paged = DownCastVmObject<VmObjectPaged>(vmo_.get());
DEBUG_ASSERT(paged);
{
Guard<CriticalMutex> vmo_guard{paged->lock()};
paged->ForwardRangeChangeUpdateLocked(zero_start, vmo_size - zero_start,
VmCowPages::RangeChangeOp::Unmap);
op.CommitLocked();
}
return ZX_OK;
}
uint64_t VmObjectDispatcher::GetStreamSize() const {
canary_.Assert();
// Stream size is always reported as 0 for physical & contiguous VMOs.
if (vmo_->is_contiguous() || !vmo_->is_paged()) {
return 0;
}
// Retrieving the stream size needs to be a non-fallible operation, so we avoid allocating one
// if it doesn't exist, since the allocation could fail.
StreamSizeManager* ssm;
{
Guard<CriticalMutex> guard{get_lock()};
if (!stream_size_mgr_) {
return vmo_->size();
}
ssm = stream_size_mgr_.get();
}
return ssm->GetStreamSize();
}
zx_status_t VmObjectDispatcher::RangeOp(uint32_t op, uint64_t offset, uint64_t size,
user_inout_ptr<void> buffer, size_t buffer_size,
zx_rights_t rights) {
canary_.Assert();
LTRACEF("op %u offset %#" PRIx64 " size %#" PRIx64 " buffer %p buffer_size %zu rights %#x\n", op,
offset, size, buffer.get(), buffer_size, rights);
switch (op) {
case ZX_VMO_OP_COMMIT: {
if ((rights & ZX_RIGHT_WRITE) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
// TODO: handle partial commits
auto status = vmo_->CommitRange(offset, size);
return status;
}
case ZX_VMO_OP_DECOMMIT: {
if ((rights & ZX_RIGHT_WRITE) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
// TODO: handle partial decommits
auto status = vmo_->DecommitRange(offset, size);
return status;
}
case ZX_VMO_OP_LOCK: {
if ((rights & (ZX_RIGHT_READ | ZX_RIGHT_WRITE)) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
zx_vmo_lock_state_t lock_state = {};
zx_status_t status = vmo_->LockRange(offset, size, &lock_state);
if (status != ZX_OK) {
return status;
}
// If an error is encountered from this point on, the lock operation MUST be reverted
// before returning.
if (buffer_size < sizeof(zx_vmo_lock_state_t)) {
// Undo the lock before returning an error.
vmo_->UnlockRange(offset, size);
return ZX_ERR_INVALID_ARGS;
}
auto lock_state_out = buffer.reinterpret<zx_vmo_lock_state_t>();
if ((status = lock_state_out.copy_to_user(lock_state)) != ZX_OK) {
// Undo the lock before returning an error.
vmo_->UnlockRange(offset, size);
return status;
}
return status;
}
case ZX_VMO_OP_TRY_LOCK:
if ((rights & (ZX_RIGHT_READ | ZX_RIGHT_WRITE)) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
return vmo_->TryLockRange(offset, size);
case ZX_VMO_OP_UNLOCK:
if ((rights & (ZX_RIGHT_READ | ZX_RIGHT_WRITE)) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
return vmo_->UnlockRange(offset, size);
case ZX_VMO_OP_CACHE_SYNC:
if ((rights & ZX_RIGHT_READ) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
return vmo_->CacheOp(offset, size, VmObject::CacheOpType::Sync);
case ZX_VMO_OP_CACHE_INVALIDATE:
if (!BootOptions::Get()->enable_debugging_syscalls) {
return ZX_ERR_NOT_SUPPORTED;
}
// A straight invalidate op requires the write right since
// it may drop dirty cache lines, thus modifying the contents
// of the VMO.
if ((rights & ZX_RIGHT_WRITE) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
return vmo_->CacheOp(offset, size, VmObject::CacheOpType::Invalidate);
case ZX_VMO_OP_CACHE_CLEAN:
if ((rights & ZX_RIGHT_READ) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
return vmo_->CacheOp(offset, size, VmObject::CacheOpType::Clean);
case ZX_VMO_OP_CACHE_CLEAN_INVALIDATE:
if ((rights & ZX_RIGHT_READ) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
return vmo_->CacheOp(offset, size, VmObject::CacheOpType::CleanInvalidate);
case ZX_VMO_OP_ZERO:
if ((rights & ZX_RIGHT_WRITE) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
return vmo_->ZeroRange(offset, size);
case ZX_VMO_OP_ALWAYS_NEED:
return vmo_->HintRange(offset, size, VmObject::EvictionHint::AlwaysNeed);
case ZX_VMO_OP_DONT_NEED:
return vmo_->HintRange(offset, size, VmObject::EvictionHint::DontNeed);
case ZX_VMO_OP_PREFETCH:
if ((rights & ZX_RIGHT_READ) == 0) {
return ZX_ERR_ACCESS_DENIED;
}
return vmo_->PrefetchRange(offset, size);
default:
return ZX_ERR_INVALID_ARGS;
}
}
zx_status_t VmObjectDispatcher::SetMappingCachePolicy(uint32_t cache_policy) {
return vmo_->SetMappingCachePolicy(cache_policy);
}
zx_status_t VmObjectDispatcher::CreateChildInternal(uint32_t options, uint64_t offset,
uint64_t size, bool copy_name,
fbl::RefPtr<VmObject>* child_vmo) {
// Clones are not supported for discardable VMOs.
if (vmo_->is_discardable()) {
return ZX_ERR_NOT_SUPPORTED;
}
if (options & ZX_VMO_CHILD_SLICE) {
// No other flags are valid for slices.
options &= ~ZX_VMO_CHILD_SLICE;
if (options) {
return ZX_ERR_INVALID_ARGS;
}
return vmo_->CreateChildSlice(offset, size, copy_name, child_vmo);
}
Resizability resizable = Resizability::NonResizable;
if (options & ZX_VMO_CHILD_REFERENCE) {
options &= ~ZX_VMO_CHILD_REFERENCE;
if (options & ZX_VMO_CHILD_RESIZABLE) {
resizable = Resizability::Resizable;
options &= ~ZX_VMO_CHILD_RESIZABLE;
}
if (options) {
return ZX_ERR_INVALID_ARGS;
}
return vmo_->CreateChildReference(resizable, offset, size, copy_name, nullptr, child_vmo);
}
// Check for mutually-exclusive child type flags.
SnapshotType type;
if (options & ZX_VMO_CHILD_SNAPSHOT) {
options &= ~ZX_VMO_CHILD_SNAPSHOT;
type = SnapshotType::Full;
} else if (options & ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE) {
options &= ~ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE;
type = SnapshotType::OnWrite;
} else if (options & ZX_VMO_CHILD_SNAPSHOT_MODIFIED) {
options &= ~ZX_VMO_CHILD_SNAPSHOT_MODIFIED;
type = SnapshotType::Modified;
} else {
return ZX_ERR_INVALID_ARGS;
}
if (options & ZX_VMO_CHILD_RESIZABLE) {
resizable = Resizability::Resizable;
options &= ~ZX_VMO_CHILD_RESIZABLE;
}
if (options)
return ZX_ERR_INVALID_ARGS;
return vmo_->CreateClone(resizable, type, offset, size, copy_name, child_vmo);
}
zx_status_t VmObjectDispatcher::CreateChild(uint32_t options, uint64_t offset, uint64_t size,
bool copy_name, fbl::RefPtr<VmObject>* child_vmo) {
canary_.Assert();
LTRACEF("options 0x%x offset %#" PRIx64 " size %#" PRIx64 "\n", options, offset, size);
// To synchronize the zero child signal the dispatcher lock is used over the create child path to
// ensure we can atomically know that a child exists, and clear the zero child signal.
Guard<CriticalMutex> guard{get_lock()};
zx_status_t status = CreateChildInternal(options, offset, size, copy_name, child_vmo);
DEBUG_ASSERT((status == ZX_OK) == (*child_vmo != nullptr));
if (status == ZX_OK) {
// We know definitively that a child exists, as we have a refptr to it, and so we can clear the
// zero children signal.
UpdateStateLocked(ZX_VMO_ZERO_CHILDREN, 0);
}
return status;
}
zx::result<fbl::RefPtr<StreamSizeManager>> VmObjectDispatcher::stream_size_manager() {
Guard<CriticalMutex> guard{get_lock()};
if (unlikely(!stream_size_mgr_)) {
// Physical & contiguous VMOs can't have associated stream.
if (!vmo_->is_paged() || vmo_->is_contiguous()) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
auto result = StreamSizeManager::Create(vmo_->size());
if (result.is_error()) {
return result.take_error();
}
stream_size_mgr_ = ktl::move(*result);
vmo_->SetUserStreamSize(stream_size_mgr_);
}
return zx::ok(stream_size_mgr_);
}