| // 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_address_region_dispatcher.h> |
| |
| #include <vm/vm_address_region.h> |
| #include <vm/vm_aspace.h> |
| #include <vm/vm_object.h> |
| |
| #include <zircon/rights.h> |
| |
| #include <fbl/alloc_checker.h> |
| |
| #include <assert.h> |
| #include <err.h> |
| #include <inttypes.h> |
| #include <trace.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| namespace { |
| |
| // Split out the syscall flags into vmar flags and mmu flags. Note that this |
| // does not validate that the requested protections in *flags* are valid. For |
| // that use is_valid_mapping_protection() |
| zx_status_t split_syscall_flags(uint32_t flags, uint32_t* vmar_flags, uint* arch_mmu_flags) { |
| // Figure out arch_mmu_flags |
| uint mmu_flags = ARCH_MMU_FLAG_PERM_USER; |
| switch (flags & (ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE)) { |
| case ZX_VM_FLAG_PERM_READ: |
| mmu_flags |= ARCH_MMU_FLAG_PERM_READ; |
| break; |
| case ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE: |
| mmu_flags |= ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE; |
| break; |
| } |
| |
| if (flags & ZX_VM_FLAG_PERM_EXECUTE) { |
| mmu_flags |= ARCH_MMU_FLAG_PERM_EXECUTE; |
| } |
| |
| // Mask out arch_mmu_flags options |
| flags &= ~(ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE | ZX_VM_FLAG_PERM_EXECUTE); |
| |
| // Figure out vmar flags |
| uint32_t vmar = 0; |
| if (flags & ZX_VM_FLAG_COMPACT) { |
| vmar |= VMAR_FLAG_COMPACT; |
| flags &= ~ZX_VM_FLAG_COMPACT; |
| } |
| if (flags & ZX_VM_FLAG_SPECIFIC) { |
| vmar |= VMAR_FLAG_SPECIFIC; |
| flags &= ~ZX_VM_FLAG_SPECIFIC; |
| } |
| if (flags & ZX_VM_FLAG_SPECIFIC_OVERWRITE) { |
| vmar |= VMAR_FLAG_SPECIFIC_OVERWRITE; |
| flags &= ~ZX_VM_FLAG_SPECIFIC_OVERWRITE; |
| } |
| if (flags & ZX_VM_FLAG_CAN_MAP_SPECIFIC) { |
| vmar |= VMAR_FLAG_CAN_MAP_SPECIFIC; |
| flags &= ~ZX_VM_FLAG_CAN_MAP_SPECIFIC; |
| } |
| if (flags & ZX_VM_FLAG_CAN_MAP_READ) { |
| vmar |= VMAR_FLAG_CAN_MAP_READ; |
| flags &= ~ZX_VM_FLAG_CAN_MAP_READ; |
| } |
| if (flags & ZX_VM_FLAG_CAN_MAP_WRITE) { |
| vmar |= VMAR_FLAG_CAN_MAP_WRITE; |
| flags &= ~ZX_VM_FLAG_CAN_MAP_WRITE; |
| } |
| if (flags & ZX_VM_FLAG_CAN_MAP_EXECUTE) { |
| vmar |= VMAR_FLAG_CAN_MAP_EXECUTE; |
| flags &= ~ZX_VM_FLAG_CAN_MAP_EXECUTE; |
| } |
| |
| if (flags != 0) |
| return ZX_ERR_INVALID_ARGS; |
| |
| *vmar_flags = vmar; |
| *arch_mmu_flags = mmu_flags; |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| zx_status_t VmAddressRegionDispatcher::Create(fbl::RefPtr<VmAddressRegion> vmar, |
| fbl::RefPtr<Dispatcher>* dispatcher, |
| zx_rights_t* rights) { |
| |
| // The initial rights should match the VMAR's creation permissions |
| zx_rights_t vmar_rights = ZX_DEFAULT_VMAR_RIGHTS; |
| uint32_t vmar_flags = vmar->flags(); |
| if (vmar_flags & VMAR_FLAG_CAN_MAP_READ) { |
| vmar_rights |= ZX_RIGHT_READ; |
| } |
| if (vmar_flags & VMAR_FLAG_CAN_MAP_WRITE) { |
| vmar_rights |= ZX_RIGHT_WRITE; |
| } |
| if (vmar_flags & VMAR_FLAG_CAN_MAP_EXECUTE) { |
| vmar_rights |= ZX_RIGHT_EXECUTE; |
| } |
| |
| fbl::AllocChecker ac; |
| auto disp = new (&ac) VmAddressRegionDispatcher(fbl::move(vmar)); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| |
| *rights = vmar_rights; |
| *dispatcher = fbl::AdoptRef<Dispatcher>(disp); |
| return ZX_OK; |
| } |
| |
| VmAddressRegionDispatcher::VmAddressRegionDispatcher(fbl::RefPtr<VmAddressRegion> vmar) |
| : vmar_(fbl::move(vmar)) {} |
| |
| VmAddressRegionDispatcher::~VmAddressRegionDispatcher() {} |
| |
| zx_status_t VmAddressRegionDispatcher::Allocate( |
| size_t offset, size_t size, uint32_t flags, |
| fbl::RefPtr<VmAddressRegionDispatcher>* new_dispatcher, |
| zx_rights_t* new_rights) { |
| |
| canary_.Assert(); |
| |
| uint32_t vmar_flags; |
| uint arch_mmu_flags; |
| zx_status_t status = split_syscall_flags(flags, &vmar_flags, &arch_mmu_flags); |
| if (status != ZX_OK) |
| return status; |
| |
| // Check if any MMU-related flags were requested (USER is always implied) |
| if (arch_mmu_flags != ARCH_MMU_FLAG_PERM_USER) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::RefPtr<VmAddressRegion> new_vmar; |
| status = vmar_->CreateSubVmar(offset, size, /* align_pow2 */ 0 , vmar_flags, |
| "useralloc", &new_vmar); |
| if (status != ZX_OK) |
| return status; |
| |
| // Create the dispatcher. |
| fbl::RefPtr<Dispatcher> dispatcher; |
| status = VmAddressRegionDispatcher::Create(fbl::move(new_vmar), |
| &dispatcher, new_rights); |
| if (status != ZX_OK) |
| return status; |
| |
| *new_dispatcher = |
| DownCastDispatcher<VmAddressRegionDispatcher>(&dispatcher); |
| return ZX_OK; |
| } |
| |
| zx_status_t VmAddressRegionDispatcher::Destroy() { |
| canary_.Assert(); |
| |
| return vmar_->Destroy(); |
| } |
| |
| zx_status_t VmAddressRegionDispatcher::Map(size_t vmar_offset, fbl::RefPtr<VmObject> vmo, |
| uint64_t vmo_offset, size_t len, uint32_t flags, |
| fbl::RefPtr<VmMapping>* out) { |
| canary_.Assert(); |
| |
| if (!is_valid_mapping_protection(flags)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| // Split flags into vmar_flags and arch_mmu_flags |
| uint32_t vmar_flags; |
| uint arch_mmu_flags; |
| zx_status_t status = split_syscall_flags(flags, &vmar_flags, &arch_mmu_flags); |
| if (status != ZX_OK) |
| return status; |
| |
| fbl::RefPtr<VmMapping> result(nullptr); |
| status = vmar_->CreateVmMapping(vmar_offset, len, /* align_pow2 */ 0, |
| vmar_flags, fbl::move(vmo), vmo_offset, |
| arch_mmu_flags, "useralloc", |
| &result); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| *out = fbl::move(result); |
| return ZX_OK; |
| } |
| |
| zx_status_t VmAddressRegionDispatcher::Protect(vaddr_t base, size_t len, uint32_t flags) { |
| canary_.Assert(); |
| |
| if (!IS_PAGE_ALIGNED(base)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (!is_valid_mapping_protection(flags)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| uint32_t vmar_flags; |
| uint arch_mmu_flags; |
| zx_status_t status = split_syscall_flags(flags, &vmar_flags, &arch_mmu_flags); |
| if (status != ZX_OK) |
| return status; |
| |
| // This request does not allow any VMAR flags to be set |
| if (vmar_flags) |
| return ZX_ERR_INVALID_ARGS; |
| |
| return vmar_->Protect(base, len, arch_mmu_flags); |
| } |
| |
| zx_status_t VmAddressRegionDispatcher::Unmap(vaddr_t base, size_t len) { |
| canary_.Assert(); |
| |
| if (!IS_PAGE_ALIGNED(base)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| return vmar_->Unmap(base, len); |
| } |
| |
| bool VmAddressRegionDispatcher::is_valid_mapping_protection(uint32_t flags) { |
| if (!(flags & ZX_VM_FLAG_PERM_READ)) { |
| // No way to express non-readable mappings that are also writeable or |
| // executable. |
| if (flags & (ZX_VM_FLAG_PERM_WRITE | ZX_VM_FLAG_PERM_EXECUTE)) { |
| return false; |
| } |
| } |
| return true; |
| } |