| // 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_physical.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/alloc_checker.h> |
| #include <ktl/move.h> |
| #include <vm/vm.h> |
| |
| #include "vm_priv.h" |
| |
| #define LOCAL_TRACE VM_GLOBAL_TRACE(0) |
| |
| VmObjectPhysical::VmObjectPhysical(fbl::RefPtr<VmHierarchyState> state, paddr_t base, uint64_t size, |
| bool is_slice) |
| : VmObject(ktl::move(state)), size_(size), base_(base), is_slice_(is_slice) { |
| LTRACEF("%p, size %#" PRIx64 "\n", this, size_); |
| |
| DEBUG_ASSERT(IS_PAGE_ALIGNED(size_)); |
| |
| AddToGlobalList(); |
| } |
| |
| VmObjectPhysical::~VmObjectPhysical() { |
| canary_.Assert(); |
| LTRACEF("%p\n", this); |
| |
| { |
| Guard<Mutex> guard{&lock_}; |
| if (parent_) { |
| parent_->RemoveChild(this, guard.take()); |
| // Avoid recursing destructors when we delete our parent by using the deferred deletion |
| // method. |
| hierarchy_state_ptr_->DoDeferredDelete(ktl::move(parent_)); |
| } |
| } |
| |
| RemoveFromGlobalList(); |
| } |
| |
| zx_status_t VmObjectPhysical::Create(paddr_t base, uint64_t size, |
| fbl::RefPtr<VmObjectPhysical>* obj) { |
| if (!IS_PAGE_ALIGNED(base) || !IS_PAGE_ALIGNED(size) || size == 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // check that base + size is a valid range |
| paddr_t safe_base; |
| if (add_overflow(base, size - 1, &safe_base)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AllocChecker ac; |
| auto state = fbl::AdoptRef<VmHierarchyState>(new (&ac) VmHierarchyState); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| auto vmo = fbl::AdoptRef<VmObjectPhysical>( |
| new (&ac) VmObjectPhysical(ktl::move(state), base, size, /*is_slice=*/false)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // Physical VMOs should default to uncached access. |
| vmo->SetMappingCachePolicy(ARCH_MMU_FLAG_UNCACHED); |
| |
| *obj = ktl::move(vmo); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t VmObjectPhysical::CreateChildSlice(uint64_t offset, uint64_t size, bool copy_name, |
| fbl::RefPtr<VmObject>* child_vmo) { |
| canary_.Assert(); |
| |
| // Offset must be page aligned. |
| if (!IS_PAGE_ALIGNED(offset)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Make sure size is page aligned. |
| zx_status_t status = RoundSize(size, &size); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Forbid creating children of resizable VMOs. This restriction may be lifted in the future. |
| if (is_resizable()) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // Slice must be wholly contained. |
| uint64_t our_size; |
| { |
| // size_ is not an atomic variable and although it should not be changing, as we are not |
| // allowing this operation on resizable vmo's, we should still be holding the lock to |
| // correctly read size_. Unfortunately we must also drop then drop the lock in order to |
| // perform the allocation. |
| Guard<Mutex> guard{&lock_}; |
| our_size = size_; |
| } |
| if (!InRange(offset, size, our_size)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // To mimic a slice we can just create a physical vmo with the correct region. This works since |
| // nothing is resizable and the slice must be wholly contained. |
| fbl::AllocChecker ac; |
| auto vmo = fbl::AdoptRef<VmObjectPhysical>( |
| new (&ac) VmObjectPhysical(hierarchy_state_ptr_, base_ + offset, size, /*is_slice=*/true)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| bool notify_one_child; |
| { |
| Guard<Mutex> guard{&lock_}; |
| |
| // Inherit the current cache policy |
| vmo->mapping_cache_flags_ = mapping_cache_flags_; |
| // Initialize parent |
| vmo->parent_ = fbl::RefPtr(this); |
| // Initialize parent user id. |
| vmo->parent_user_id_ = user_id_locked(); |
| |
| // add the new vmo as a child. |
| notify_one_child = AddChildLocked(vmo.get()); |
| |
| if (copy_name) { |
| vmo->name_ = name_; |
| } |
| } |
| |
| if (notify_one_child) { |
| NotifyOneChild(); |
| } |
| |
| *child_vmo = ktl::move(vmo); |
| |
| return ZX_OK; |
| } |
| |
| void VmObjectPhysical::Dump(uint depth, bool verbose) { |
| canary_.Assert(); |
| |
| Guard<Mutex> guard{&lock_}; |
| for (uint i = 0; i < depth; ++i) { |
| printf(" "); |
| } |
| printf("object %p base %#" PRIxPTR " size %#" PRIx64 " ref %d\n", this, base_, size_, |
| ref_count_debug()); |
| } |
| |
| // get the physical address of a page at offset |
| zx_status_t VmObjectPhysical::GetPageLocked(uint64_t offset, uint pf_flags, list_node* free_list, |
| PageRequest* page_request, vm_page_t** _page, |
| paddr_t* _pa) { |
| canary_.Assert(); |
| |
| if (_page) { |
| *_page = nullptr; |
| } |
| |
| if (offset >= size_) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| uint64_t pa = base_ + ROUNDDOWN(offset, PAGE_SIZE); |
| if (pa > UINTPTR_MAX) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| *_pa = (paddr_t)pa; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t VmObjectPhysical::Lookup( |
| uint64_t offset, uint64_t len, |
| fbl::Function<zx_status_t(uint64_t offset, paddr_t pa)> lookup_fn) { |
| canary_.Assert(); |
| |
| if (unlikely(len == 0)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| Guard<Mutex> guard{&lock_}; |
| if (unlikely(!InRange(offset, len, size_))) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| uint64_t cur_offset = ROUNDDOWN(offset, PAGE_SIZE); |
| uint64_t end = offset + len; |
| uint64_t end_page_offset = ROUNDUP(end, PAGE_SIZE); |
| |
| for (size_t idx = 0; cur_offset < end_page_offset; cur_offset += PAGE_SIZE, ++idx) { |
| zx_status_t status = lookup_fn(cur_offset, base_ + cur_offset); |
| if (unlikely(status != ZX_ERR_NEXT)) { |
| if (status == ZX_ERR_STOP) { |
| return ZX_OK; |
| } |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t VmObjectPhysical::CommitRangePinned(uint64_t offset, uint64_t len) { |
| canary_.Assert(); |
| |
| if (unlikely(len == 0 || !IS_PAGE_ALIGNED(offset))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| Guard<Mutex> guard{&lock_}; |
| if (unlikely(!InRange(offset, len, size_))) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| // Physical VMOs are always committed and so are always pinned. |
| return ZX_OK; |
| } |
| |
| zx_status_t VmObjectPhysical::LookupContiguous(uint64_t offset, uint64_t len, paddr_t* out_paddr) { |
| canary_.Assert(); |
| if (unlikely(len == 0 || !IS_PAGE_ALIGNED(offset))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| Guard<Mutex> guard{&lock_}; |
| if (unlikely(!InRange(offset, len, size_))) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| if (out_paddr) { |
| *out_paddr = base_ + offset; |
| } |
| return ZX_OK; |
| } |
| |
| uint32_t VmObjectPhysical::GetMappingCachePolicy() const { |
| Guard<Mutex> guard{&lock_}; |
| |
| return mapping_cache_flags_; |
| } |
| |
| zx_status_t VmObjectPhysical::SetMappingCachePolicy(const uint32_t cache_policy) { |
| // Is it a valid cache flag? |
| if (cache_policy & ~ZX_CACHE_POLICY_MASK) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| Guard<Mutex> guard{&lock_}; |
| |
| // If the cache policy is already configured on this VMO and matches |
| // the requested policy then this is a no-op. This is a common practice |
| // in the serialio and magma drivers, but may change. |
| // TODO: revisit this when we shake out more of the future DDK protocol. |
| if (cache_policy == mapping_cache_flags_) { |
| return ZX_OK; |
| } |
| |
| // If this VMO is mapped already it is not safe to allow its caching policy to change |
| if (mapping_list_len_ != 0 || children_list_len_ != 0 || parent_) { |
| LTRACEF( |
| "Warning: trying to change cache policy while this vmo has mappings, children or a " |
| "parent!\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| mapping_cache_flags_ = cache_policy; |
| return ZX_OK; |
| } |