blob: 355bd1f31707b585f82af498c8451b18772be796 [file] [log] [blame]
// 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;
}