blob: f98b7c9db38a835aaa0d8183c66cb322f9e34b02 [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 <align.h>
#include <lib/fit/defer.h>
#include <lib/user_copy/user_ptr.h>
#include <platform.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <zircon/syscalls/iommu.h>
#include <zircon/syscalls/pci.h>
#include <zircon/syscalls/smc.h>
#include <zircon/types.h>
#include <new>
#include <dev/interrupt.h>
#include <dev/iommu.h>
#include <fbl/inline_array.h>
#include <object/bus_transaction_initiator_dispatcher.h>
#include <object/handle.h>
#include <object/interrupt_dispatcher.h>
#include <object/interrupt_event_dispatcher.h>
#include <object/iommu_dispatcher.h>
#include <object/msi_dispatcher.h>
#include <object/msi_interrupt_dispatcher.h>
#include <object/process_dispatcher.h>
#include <object/resource.h>
#include <object/vcpu_dispatcher.h>
#include <object/virtual_interrupt_dispatcher.h>
#include <object/vm_object_dispatcher.h>
#include <vm/vm.h>
#include <vm/vm_object_paged.h>
#include <vm/vm_object_physical.h>
#if ARCH_X86
#include <platform/pc/bootloader.h>
#include <platform/pc/smbios.h>
#endif
#include <lib/syscalls/forward.h>
#include "ddk_priv.h"
#define LOCAL_TRACE 0
// zx_status_t zx_vmo_create_contiguous
zx_status_t sys_vmo_create_contiguous(zx_handle_t bti, size_t size, uint32_t alignment_log2,
zx_handle_t* out) {
LTRACEF("size 0x%zu\n", size);
if (size == 0) {
return ZX_ERR_INVALID_ARGS;
}
if (alignment_log2 == 0) {
alignment_log2 = PAGE_SIZE_SHIFT;
}
// catch obviously wrong values
if (alignment_log2 < PAGE_SIZE_SHIFT || alignment_log2 >= (8 * sizeof(uint64_t))) {
return ZX_ERR_INVALID_ARGS;
}
auto up = ProcessDispatcher::GetCurrent();
zx_status_t status = up->EnforceBasicPolicy(ZX_POL_NEW_VMO);
if (status != ZX_OK) {
return status;
}
fbl::RefPtr<BusTransactionInitiatorDispatcher> bti_dispatcher;
status = up->handle_table().GetDispatcherWithRights(*up, bti, ZX_RIGHT_MAP, &bti_dispatcher);
if (status != ZX_OK) {
return status;
}
auto align_log2_arg = static_cast<uint8_t>(alignment_log2);
uint64_t vmo_size = 0;
status = VmObject::RoundSize(size, &vmo_size);
if (status != ZX_OK) {
return status;
}
// create a vm object
fbl::RefPtr<VmObjectPaged> vmo;
status = VmObjectPaged::CreateContiguous(PMM_ALLOC_FLAG_ANY, vmo_size, align_log2_arg, &vmo);
if (status != ZX_OK) {
return status;
}
// create a Vm Object dispatcher
KernelHandle<VmObjectDispatcher> kernel_handle;
zx_rights_t rights;
status = VmObjectDispatcher::Create(ktl::move(vmo), size,
VmObjectDispatcher::InitialMutability::kMutable,
&kernel_handle, &rights);
if (status != ZX_OK) {
return status;
}
// create a handle and attach the dispatcher to it
return up->MakeAndAddHandle(ktl::move(kernel_handle), rights, out);
}
// zx_status_t zx_vmo_create_physical
zx_status_t sys_vmo_create_physical(zx_handle_t hrsrc, zx_paddr_t paddr, size_t size,
zx_handle_t* out) {
LTRACEF("size 0x%zu\n", size);
auto up = ProcessDispatcher::GetCurrent();
zx_status_t status = up->EnforceBasicPolicy(ZX_POL_NEW_VMO);
if (status != ZX_OK) {
return status;
}
// Memory should be subtracted from the PhysicalAspace allocators, so it's
// safe to assume that if the caller has access to a resource for this specified
// region of MMIO space then it is safe to allow the vmo to be created.
if ((status = validate_resource_mmio(hrsrc, paddr, size)) != ZX_OK) {
return status;
}
status = VmObject::RoundSize(size, &size);
if (status != ZX_OK) {
return status;
}
// create a vm object
fbl::RefPtr<VmObjectPhysical> vmo;
status = VmObjectPhysical::Create(paddr, size, &vmo);
if (status != ZX_OK) {
return status;
}
// create a Vm Object dispatcher
KernelHandle<VmObjectDispatcher> kernel_handle;
zx_rights_t rights;
status = VmObjectDispatcher::Create(ktl::move(vmo), size,
VmObjectDispatcher::InitialMutability::kMutable,
&kernel_handle, &rights);
if (status != ZX_OK) {
return status;
}
// create a handle and attach the dispatcher to it
return up->MakeAndAddHandle(ktl::move(kernel_handle), rights, out);
}
// zx_status_t zx_framebuffer_get_info
zx_status_t sys_framebuffer_get_info(zx_handle_t handle, user_out_ptr<uint32_t> format,
user_out_ptr<uint32_t> width, user_out_ptr<uint32_t> height,
user_out_ptr<uint32_t> stride) {
zx_status_t status;
if ((status = validate_resource_kind_base(handle, ZX_RSRC_KIND_SYSTEM,
ZX_RSRC_SYSTEM_FRAMEBUFFER_BASE)) < 0) {
return status;
}
#if ARCH_X86
if (!bootloader.fb.base) {
return ZX_ERR_INVALID_ARGS;
}
status = format.copy_to_user(bootloader.fb.format);
if (status != ZX_OK) {
return status;
}
status = width.copy_to_user(bootloader.fb.width);
if (status != ZX_OK) {
return status;
}
status = height.copy_to_user(bootloader.fb.height);
if (status != ZX_OK) {
return status;
}
status = stride.copy_to_user(bootloader.fb.stride);
if (status != ZX_OK) {
return status;
}
return ZX_OK;
#else
return ZX_ERR_NOT_SUPPORTED;
#endif
}
// zx_status_t zx_framebuffer_set_range
zx_status_t sys_framebuffer_set_range(zx_handle_t hrsrc, zx_handle_t vmo_handle, uint32_t len,
uint32_t format, uint32_t width, uint32_t height,
uint32_t stride) {
return ZX_ERR_NOT_SUPPORTED;
}
// zx_status_t zx_iommu_create
zx_status_t sys_iommu_create(zx_handle_t resource, uint32_t type, user_in_ptr<const void> desc,
size_t desc_size, zx_handle_t* out) {
zx_status_t status;
if ((status = validate_resource_kind_base(resource, ZX_RSRC_KIND_SYSTEM,
ZX_RSRC_SYSTEM_IOMMU_BASE)) < 0) {
return status;
}
if (desc_size > ZX_IOMMU_MAX_DESC_LEN) {
return ZX_ERR_INVALID_ARGS;
}
KernelHandle<IommuDispatcher> handle;
zx_rights_t rights;
{
// Copy the descriptor into the kernel and try to create the dispatcher
// using it.
fbl::AllocChecker ac;
ktl::unique_ptr<uint8_t[]> copied_desc(new (&ac) uint8_t[desc_size]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if ((status = desc.reinterpret<const uint8_t>().copy_array_from_user(copied_desc.get(),
desc_size)) != ZX_OK) {
return status;
}
status = IommuDispatcher::Create(type, ktl::unique_ptr<const uint8_t[]>(copied_desc.release()),
desc_size, &handle, &rights);
if (status != ZX_OK) {
return status;
}
}
return ProcessDispatcher::GetCurrent()->MakeAndAddHandle(ktl::move(handle), rights, out);
}
#if ARCH_X86
#include <arch/x86/descriptor.h>
#include <arch/x86/ioport.h>
// zx_status_t zx_ioports_request
zx_status_t sys_ioports_request(zx_handle_t hrsrc, uint16_t io_addr, uint32_t len) {
zx_status_t status;
if ((status = validate_resource_ioport(hrsrc, io_addr, len)) != ZX_OK) {
return status;
}
LTRACEF("addr 0x%x len 0x%x\n", io_addr, len);
return IoBitmap::GetCurrent()->SetIoBitmap(io_addr, len, /*enable=*/true);
}
// zx_status_t zx_ioports_release
zx_status_t sys_ioports_release(zx_handle_t hrsrc, uint16_t io_addr, uint32_t len) {
zx_status_t status;
if ((status = validate_resource_ioport(hrsrc, io_addr, len)) != ZX_OK) {
return status;
}
LTRACEF("addr 0x%x len 0x%x\n", io_addr, len);
return IoBitmap::GetCurrent()->SetIoBitmap(io_addr, len, /*enable=*/false);
}
#else
// zx_status_t zx_ioports_request
zx_status_t sys_ioports_request(zx_handle_t hrsrc, uint16_t io_addr, uint32_t len) {
// doesn't make sense on non-x86
return ZX_ERR_NOT_SUPPORTED;
}
// zx_status_t zx_ioports_release
zx_status_t sys_ioports_release(zx_handle_t hrsrc, uint16_t io_addr, uint32_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
#endif
// zx_status_t zx_msi_allocate
zx_status_t sys_msi_allocate(zx_handle_t msi, uint32_t count, zx_handle_t* out) {
zx_status_t status;
if ((status = validate_resource_kind_base(msi, ZX_RSRC_KIND_SYSTEM, ZX_RSRC_SYSTEM_MSI_BASE)) !=
ZX_OK) {
return status;
}
fbl::RefPtr<MsiAllocation> alloc;
if ((status = MsiAllocation::Create(count, &alloc)) != ZX_OK) {
return status;
}
zx_rights_t rights;
KernelHandle<MsiDispatcher> alloc_handle;
if ((status = MsiDispatcher::Create(ktl::move(alloc), &alloc_handle, &rights)) != ZX_OK) {
return status;
}
return ProcessDispatcher::GetCurrent()->MakeAndAddHandle(ktl::move(alloc_handle), rights, out);
}
// zx_status_t zx_msi_create
zx_status_t sys_msi_create(zx_handle_t msi_alloc, uint32_t options, uint32_t msi_id,
zx_handle_t vmo, size_t vmo_offset, zx_handle_t* out) {
auto* up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<MsiDispatcher> msi_alloc_disp;
zx_status_t status;
if ((status = up->handle_table().GetDispatcher(*up, msi_alloc, &msi_alloc_disp)) != ZX_OK) {
return status;
}
fbl::RefPtr<VmObjectDispatcher> vmo_disp;
if ((status = up->handle_table().GetDispatcherWithRights(*up, vmo, ZX_RIGHT_MAP, &vmo_disp)) !=
ZX_OK) {
return status;
}
zx_rights_t rights;
KernelHandle<InterruptDispatcher> msi_handle;
if ((status = MsiInterruptDispatcher::Create(
msi_alloc_disp->msi_allocation(), /* msi_id= */ msi_id, vmo_disp->vmo(),
/* cap_offset= */ vmo_offset, /* options= */ options, &rights, &msi_handle)) != ZX_OK) {
return status;
}
return up->MakeAndAddHandle(ktl::move(msi_handle), rights, out);
}
// zx_status_t zx_bti_create
zx_status_t sys_bti_create(zx_handle_t iommu, uint32_t options, uint64_t bti_id, zx_handle_t* out) {
auto up = ProcessDispatcher::GetCurrent();
if (options != 0) {
return ZX_ERR_INVALID_ARGS;
}
fbl::RefPtr<IommuDispatcher> iommu_dispatcher;
// TODO(teisenbe): This should probably have a right on it.
zx_status_t status =
up->handle_table().GetDispatcherWithRights(*up, iommu, ZX_RIGHT_NONE, &iommu_dispatcher);
if (status != ZX_OK) {
return status;
}
KernelHandle<BusTransactionInitiatorDispatcher> handle;
zx_rights_t rights;
status = BusTransactionInitiatorDispatcher::Create(ktl::move(iommu_dispatcher), bti_id, &handle,
&rights);
if (status != ZX_OK) {
return status;
}
return up->MakeAndAddHandle(ktl::move(handle), rights, out);
}
// zx_status_t zx_bti_pin
zx_status_t sys_bti_pin(zx_handle_t handle, uint32_t options, zx_handle_t vmo, uint64_t offset,
uint64_t size, user_out_ptr<zx_paddr_t> addrs, size_t addrs_count,
zx_handle_t* pmt) {
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<BusTransactionInitiatorDispatcher> bti_dispatcher;
zx_status_t status =
up->handle_table().GetDispatcherWithRights(*up, handle, ZX_RIGHT_MAP, &bti_dispatcher);
if (status != ZX_OK) {
return status;
}
// Address count is currently limited to the amount of addresses that can fit on 64 pages. This
// is large enough for all current usage of bti_pin, but protects against the case of an
// arbitrarily large array being allocated on the heap.
constexpr size_t kMaxAddrs = (PAGE_SIZE * 64) / sizeof(dev_vaddr_t);
if (!IS_PAGE_ALIGNED(offset) || !IS_PAGE_ALIGNED(size) || addrs_count > kMaxAddrs) {
return ZX_ERR_INVALID_ARGS;
}
fbl::RefPtr<VmObjectDispatcher> vmo_dispatcher;
zx_rights_t vmo_rights;
status = up->handle_table().GetDispatcherAndRights(*up, vmo, &vmo_dispatcher, &vmo_rights);
if (status != ZX_OK) {
return status;
}
if (!(vmo_rights & ZX_RIGHT_MAP)) {
return ZX_ERR_ACCESS_DENIED;
}
// Convert requested permissions and check against VMO rights
uint32_t iommu_perms = 0;
bool compress_results = false;
bool contiguous = false;
if (options & ZX_BTI_PERM_READ) {
if (!(vmo_rights & ZX_RIGHT_READ)) {
return ZX_ERR_ACCESS_DENIED;
}
iommu_perms |= IOMMU_FLAG_PERM_READ;
options &= ~ZX_BTI_PERM_READ;
}
if (options & ZX_BTI_PERM_WRITE) {
if (!(vmo_rights & ZX_RIGHT_WRITE)) {
return ZX_ERR_ACCESS_DENIED;
}
iommu_perms |= IOMMU_FLAG_PERM_WRITE;
options &= ~ZX_BTI_PERM_WRITE;
}
if (options & ZX_BTI_PERM_EXECUTE) {
// Note: We check ZX_RIGHT_READ instead of ZX_RIGHT_EXECUTE
// here because the latter applies to execute permission of
// the host CPU, whereas ZX_BTI_PERM_EXECUTE applies to
// transactions initiated by the bus device.
if (!(vmo_rights & ZX_RIGHT_READ)) {
return ZX_ERR_ACCESS_DENIED;
}
iommu_perms |= IOMMU_FLAG_PERM_EXECUTE;
options &= ~ZX_BTI_PERM_EXECUTE;
}
if (!((options & ZX_BTI_COMPRESS) && (options & ZX_BTI_CONTIGUOUS))) {
if (options & ZX_BTI_COMPRESS) {
compress_results = true;
options &= ~ZX_BTI_COMPRESS;
}
if (options & ZX_BTI_CONTIGUOUS && vmo_dispatcher->vmo()->is_contiguous()) {
contiguous = true;
options &= ~ZX_BTI_CONTIGUOUS;
}
}
if (options) {
return ZX_ERR_INVALID_ARGS;
}
constexpr size_t kAddrsLenLimitForStack = 4;
fbl::AllocChecker ac;
fbl::InlineArray<dev_vaddr_t, kAddrsLenLimitForStack> mapped_addrs(&ac, addrs_count);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
KernelHandle<PinnedMemoryTokenDispatcher> new_pmt_handle;
zx_rights_t new_pmt_rights;
status = bti_dispatcher->Pin(vmo_dispatcher->vmo(), offset, size, iommu_perms, &new_pmt_handle,
&new_pmt_rights);
if (status != ZX_OK) {
return status;
}
// If anything goes wrong from here on out, we _must_ remember to unpin the
// PMT we are holding. Failure to do this means that the PMT will hit
// on-zero-handles while it still has pages pinned and end up in the BTI's
// quarantine list. This is definitely not correct as the user never got
// access to the PMT handle in order to unpin the data.
//
// Notice that we're holding a RefPtr to the dispatcher rather than a
// reference to the |new_pmt_handle|. Just before we return, |new_pmt_handle|
// will be moved in order to make a zx_handle_t. |new_pmt_handle| will
// not be valid after the move so we keep a RefPtr to the dispatcher instead.
auto cleanup = fit::defer([disp = new_pmt_handle.dispatcher()]() { disp->Unpin(); });
status = new_pmt_handle.dispatcher()->EncodeAddrs(compress_results, contiguous,
mapped_addrs.get(), addrs_count);
if (status != ZX_OK) {
return status;
}
static_assert(sizeof(dev_vaddr_t) == sizeof(zx_paddr_t), "mismatched types");
if ((status = addrs.copy_array_to_user(mapped_addrs.get(), addrs_count)) != ZX_OK) {
return status;
}
zx_status_t res = up->MakeAndAddHandle(ktl::move(new_pmt_handle), new_pmt_rights, pmt);
if (res == ZX_OK) {
cleanup.cancel();
}
return res;
}
// zx_status_t zx_bti_release_quarantine
zx_status_t sys_bti_release_quarantine(zx_handle_t handle) {
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<BusTransactionInitiatorDispatcher> bti_dispatcher;
zx_status_t status =
up->handle_table().GetDispatcherWithRights(*up, handle, ZX_RIGHT_WRITE, &bti_dispatcher);
if (status != ZX_OK) {
return status;
}
bti_dispatcher->ReleaseQuarantine();
return ZX_OK;
}
// Having a single-purpose syscall like this is a bit of an anti-pattern in our
// syscall API, but we feel there is benefit in this over trying to extend the
// semantics of handle closing in sys_handle_close and process death. In
// particular, PMTs are the only objects in the system that track the lifetime
// of something external to the process model (external hardware DMA
// capabilities).
// zx_status_t zx_pmt_unpin
zx_status_t sys_pmt_unpin(zx_handle_t handle) {
auto up = ProcessDispatcher::GetCurrent();
HandleOwner handle_owner = up->handle_table().RemoveHandle(*up, handle);
if (!handle_owner) {
return ZX_ERR_BAD_HANDLE;
}
fbl::RefPtr<Dispatcher> dispatcher = handle_owner->dispatcher();
auto pmt_dispatcher = DownCastDispatcher<PinnedMemoryTokenDispatcher>(&dispatcher);
if (!pmt_dispatcher) {
return ZX_ERR_WRONG_TYPE;
}
pmt_dispatcher->Unpin();
return ZX_OK;
}
// zx_status_t zx_interrupt_create
zx_status_t sys_interrupt_create(zx_handle_t src_obj, uint32_t src_num, uint32_t options,
zx_handle_t* out_handle) {
LTRACEF("options 0x%x\n", options);
// resource not required for virtual interrupts
if (!(options & ZX_INTERRUPT_VIRTUAL)) {
zx_status_t status;
if ((status = validate_resource_irq(src_obj, src_num)) != ZX_OK) {
return status;
}
}
KernelHandle<InterruptDispatcher> handle;
zx_rights_t rights;
zx_status_t result;
if (options & ZX_INTERRUPT_VIRTUAL) {
result = VirtualInterruptDispatcher::Create(&handle, &rights, options);
} else {
result = InterruptEventDispatcher::Create(&handle, &rights, src_num, options);
}
if (result != ZX_OK) {
return result;
}
return ProcessDispatcher::GetCurrent()->MakeAndAddHandle(ktl::move(handle), rights, out_handle);
}
// zx_status_t zx_interrupt_bind
zx_status_t sys_interrupt_bind(zx_handle_t handle, zx_handle_t port_handle, uint64_t key,
uint32_t options) {
LTRACEF("handle %x\n", handle);
if ((options != ZX_INTERRUPT_BIND) && (options != ZX_INTERRUPT_UNBIND)) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<InterruptDispatcher> interrupt;
status = up->handle_table().GetDispatcherWithRights(*up, handle, ZX_RIGHT_READ, &interrupt);
if (status != ZX_OK) {
return status;
}
fbl::RefPtr<PortDispatcher> port;
status = up->handle_table().GetDispatcherWithRights(*up, port_handle, ZX_RIGHT_WRITE, &port);
if (status != ZX_OK) {
return status;
}
if (!port->can_bind_to_interrupt()) {
return ZX_ERR_WRONG_TYPE;
}
if (options == ZX_INTERRUPT_BIND) {
return interrupt->Bind(ktl::move(port), key);
} else {
return interrupt->Unbind(ktl::move(port));
}
}
// zx_status_t zx_interrupt_ack
zx_status_t sys_interrupt_ack(zx_handle_t inth) {
LTRACEF("handle %x\n", inth);
zx_status_t status;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<InterruptDispatcher> interrupt;
status = up->handle_table().GetDispatcherWithRights(*up, inth, ZX_RIGHT_WRITE, &interrupt);
if (status != ZX_OK) {
return status;
}
return interrupt->Ack();
}
// zx_status_t zx_interrupt_wait
zx_status_t sys_interrupt_wait(zx_handle_t handle, user_out_ptr<zx_instant_boot_t> out_timestamp) {
LTRACEF("handle %x\n", handle);
zx_status_t status;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<InterruptDispatcher> interrupt;
status = up->handle_table().GetDispatcherWithRights(*up, handle, ZX_RIGHT_WAIT, &interrupt);
if (status != ZX_OK) {
return status;
}
zx_instant_boot_t timestamp;
status = interrupt->WaitForInterrupt(&timestamp);
if (status == ZX_OK && out_timestamp) {
status = out_timestamp.copy_to_user(timestamp);
}
return status;
}
// zx_status_t zx_interrupt_destroy
zx_status_t sys_interrupt_destroy(zx_handle_t handle) {
LTRACEF("handle %x\n", handle);
zx_status_t status;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<InterruptDispatcher> interrupt;
status = up->handle_table().GetDispatcher(*up, handle, &interrupt);
if (status != ZX_OK) {
return status;
}
return interrupt->Destroy();
}
// zx_status_t zx_interrupt_trigger
zx_status_t sys_interrupt_trigger(zx_handle_t handle, uint32_t options,
zx_instant_boot_t timestamp) {
LTRACEF("handle %x\n", handle);
if (options) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<InterruptDispatcher> interrupt;
status = up->handle_table().GetDispatcherWithRights(*up, handle, ZX_RIGHT_SIGNAL, &interrupt);
if (status != ZX_OK) {
return status;
}
return interrupt->Trigger(timestamp);
}
// zx_status_t zx_smc_call
zx_status_t sys_smc_call(zx_handle_t handle, user_in_ptr<const zx_smc_parameters_t> parameters,
user_out_ptr<zx_smc_result_t> out_smc_result) {
if (!parameters || !out_smc_result) {
return ZX_ERR_INVALID_ARGS;
}
zx_smc_parameters_t params;
zx_status_t status = parameters.copy_from_user(&params);
if (status != ZX_OK) {
return status;
}
uint32_t service_call_num = ARM_SMC_GET_SERVICE_CALL_NUM_FROM_FUNC_ID(params.func_id);
if ((status = validate_resource_smc(handle, service_call_num)) != ZX_OK) {
return status;
}
zx_smc_result_t result;
status = arch_smc_call(&params, &result);
if (status != ZX_OK) {
return status;
}
return out_smc_result.copy_to_user(result);
}