// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ARCH_X86 #include #endif #include "ddk_priv.h" #include "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, user_out_handle* 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(); fbl::RefPtr bti_dispatcher; zx_status_t status = up->GetDispatcherWithRights(bti, ZX_RIGHT_MAP, &bti_dispatcher); if (status != ZX_OK) { return status; } auto align_log2_arg = static_cast(alignment_log2); // create a vm object fbl::RefPtr vmo; status = VmObjectPaged::CreateContiguous(PMM_ALLOC_FLAG_ANY, size, align_log2_arg, &vmo); if (status != ZX_OK) { return status; } // create a Vm Object dispatcher fbl::RefPtr dispatcher; zx_rights_t rights; zx_status_t result = VmObjectDispatcher::Create(ktl::move(vmo), &dispatcher, &rights); if (result != ZX_OK) { return result; } // create a handle and attach the dispatcher to it return out->make(ktl::move(dispatcher), rights); } // 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, user_out_handle* out) { LTRACEF("size 0x%zu\n", size); // 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. zx_status_t status; if ((status = validate_resource_mmio(hrsrc, paddr, size)) != ZX_OK) { return status; } size = ROUNDUP_PAGE_SIZE(size); // create a vm object fbl::RefPtr vmo; zx_status_t result = VmObjectPhysical::Create(paddr, size, &vmo); if (result != ZX_OK) { return result; } // create a Vm Object dispatcher fbl::RefPtr dispatcher; zx_rights_t rights; result = VmObjectDispatcher::Create(ktl::move(vmo), &dispatcher, &rights); if (result != ZX_OK) { return result; } // create a handle and attach the dispatcher to it return out->make(ktl::move(dispatcher), rights); } // zx_status_t zx_framebuffer_get_info zx_status_t sys_framebuffer_get_info(zx_handle_t handle, user_out_ptr format, user_out_ptr width, user_out_ptr height, user_out_ptr stride) { zx_status_t status; if ((status = validate_resource(handle, ZX_RSRC_KIND_ROOT)) < 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) { zx_status_t status; if ((status = validate_resource(hrsrc, ZX_RSRC_KIND_ROOT)) < 0) return status; if (vmo_handle == ZX_HANDLE_INVALID) { udisplay_clear_framebuffer_vmo(); return ZX_OK; } auto up = ProcessDispatcher::GetCurrent(); // lookup the dispatcher from handle fbl::RefPtr vmo; status = up->GetDispatcher(vmo_handle, &vmo); if (status != ZX_OK) return status; status = udisplay_set_framebuffer(vmo->vmo()); if (status != ZX_OK) return status; struct display_info di; memset(&di, 0, sizeof(struct display_info)); di.format = format; di.width = width; di.height = height; di.stride = stride; di.flags = DISPLAY_FLAG_HW_FRAMEBUFFER; udisplay_set_display_info(&di); return ZX_OK; } // zx_status_t zx_iommu_create zx_status_t sys_iommu_create(zx_handle_t resource, uint32_t type, user_in_ptr desc, size_t desc_size, user_out_handle* out) { // TODO: finer grained validation zx_status_t status; if ((status = validate_resource(resource, ZX_RSRC_KIND_ROOT)) < 0) { return status; } if (desc_size > ZX_IOMMU_MAX_DESC_LEN) { return ZX_ERR_INVALID_ARGS; } fbl::RefPtr dispatcher; zx_rights_t rights; { // Copy the descriptor into the kernel and try to create the dispatcher // using it. fbl::AllocChecker ac; ktl::unique_ptr copied_desc(new (&ac) uint8_t[desc_size]); if (!ac.check()) { return ZX_ERR_NO_MEMORY; } if ((status = desc.copy_array_from_user(copied_desc.get(), desc_size)) != ZX_OK) { return status; } status = IommuDispatcher::Create(type, ktl::unique_ptr(copied_desc.release()), desc_size, &dispatcher, &rights); if (status != ZX_OK) { return status; } } return out->make(ktl::move(dispatcher), rights); } #if ARCH_X86 #include #include // 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, 1); } #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; } #endif // zx_status_t zx_pc_firmware_tables zx_status_t sys_pc_firmware_tables(zx_handle_t hrsrc, user_out_ptr acpi_rsdp, user_out_ptr smbios) { // TODO(ZX-971): finer grained validation zx_status_t status; if ((status = validate_resource(hrsrc, ZX_RSRC_KIND_ROOT)) < 0) { return status; } #if ARCH_X86 if ((status = acpi_rsdp.copy_to_user(bootloader.acpi_rsdp)) != ZX_OK) { return status; } if ((status = smbios.copy_to_user(bootloader.smbios)) != ZX_OK) { return status; } return ZX_OK; #endif return ZX_ERR_NOT_SUPPORTED; } // zx_status_t zx_bti_create zx_status_t sys_bti_create(zx_handle_t iommu, uint32_t options, uint64_t bti_id, user_out_handle* out) { auto up = ProcessDispatcher::GetCurrent(); if (options != 0) { return ZX_ERR_INVALID_ARGS; } fbl::RefPtr iommu_dispatcher; // TODO(teisenbe): This should probably have a right on it. zx_status_t status = up->GetDispatcherWithRights(iommu, ZX_RIGHT_NONE, &iommu_dispatcher); if (status != ZX_OK) { return status; } fbl::RefPtr dispatcher; zx_rights_t rights; // TODO(teisenbe): Migrate BusTransactionInitiatorDispatcher::Create to // taking the iommu_dispatcher status = BusTransactionInitiatorDispatcher::Create(iommu_dispatcher->iommu(), bti_id, &dispatcher, &rights); if (status != ZX_OK) { return status; } return out->make(ktl::move(dispatcher), rights); } // 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 addrs, size_t addrs_count, user_out_handle* pmt) { auto up = ProcessDispatcher::GetCurrent(); fbl::RefPtr bti_dispatcher; zx_status_t status = up->GetDispatcherWithRights(handle, ZX_RIGHT_MAP, &bti_dispatcher); if (status != ZX_OK) { return status; } if (!IS_PAGE_ALIGNED(offset) || !IS_PAGE_ALIGNED(size)) { return ZX_ERR_INVALID_ARGS; } fbl::RefPtr vmo_dispatcher; zx_rights_t vmo_rights; status = up->GetDispatcherAndRights(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 mapped_addrs(&ac, addrs_count); if (!ac.check()) { return ZX_ERR_NO_MEMORY; } fbl::RefPtr new_pmt; zx_rights_t new_pmt_rights; status = bti_dispatcher->Pin(vmo_dispatcher->vmo(), offset, size, iommu_perms, &new_pmt, &new_pmt_rights); if (status != ZX_OK) { return status; } status = static_cast(new_pmt.get()) ->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; } return pmt->make(ktl::move(new_pmt), new_pmt_rights); } // zx_status_t zx_bti_release_quarantine zx_status_t sys_bti_release_quarantine(zx_handle_t handle) { auto up = ProcessDispatcher::GetCurrent(); fbl::RefPtr bti_dispatcher; zx_status_t status = up->GetDispatcherWithRights(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->RemoveHandle(handle); if (!handle_owner) return ZX_ERR_BAD_HANDLE; fbl::RefPtr dispatcher = handle_owner->dispatcher(); auto pmt_dispatcher = DownCastDispatcher(&dispatcher); if (!pmt_dispatcher) return ZX_ERR_WRONG_TYPE; pmt_dispatcher->MarkUnpinned(); 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, user_out_handle* 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; } } fbl::RefPtr dispatcher; zx_rights_t rights; zx_status_t result; if (options & ZX_INTERRUPT_VIRTUAL) { result = VirtualInterruptDispatcher::Create(&dispatcher, &rights, options); } else { result = InterruptEventDispatcher::Create(&dispatcher, &rights, src_num, options); } if (result != ZX_OK) return result; return out_handle->make(ktl::move(dispatcher), rights); } // 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) { return ZX_ERR_INVALID_ARGS; } zx_status_t status; auto up = ProcessDispatcher::GetCurrent(); fbl::RefPtr interrupt; status = up->GetDispatcherWithRights(handle, ZX_RIGHT_READ, &interrupt); if (status != ZX_OK) return status; fbl::RefPtr port; status = up->GetDispatcherWithRights(port_handle, ZX_RIGHT_WRITE, &port); if (status != ZX_OK) return status; if (!port->can_bind_to_interrupt()) { return ZX_ERR_WRONG_TYPE; } return interrupt->Bind(ktl::move(port), key); } // zx_status_t zx_interrupt_bind_vcpu zx_status_t sys_interrupt_bind_vcpu(zx_handle_t handle, zx_handle_t vcpu, uint32_t options) { LTRACEF("handle %x\n", handle); auto up = ProcessDispatcher::GetCurrent(); fbl::RefPtr interrupt_dispatcher; zx_status_t status = up->GetDispatcherWithRights(handle, ZX_RIGHT_READ, &interrupt_dispatcher); if (status != ZX_OK) { return status; } fbl::RefPtr vcpu_dispatcher; status = up->GetDispatcherWithRights(vcpu, ZX_RIGHT_WRITE, &vcpu_dispatcher); if (status != ZX_OK) { return status; } return interrupt_dispatcher->BindVcpu(ktl::move(vcpu_dispatcher)); } // 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 interrupt; status = up->GetDispatcherWithRights(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 out_timestamp) { LTRACEF("handle %x\n", handle); zx_status_t status; auto up = ProcessDispatcher::GetCurrent(); fbl::RefPtr interrupt; status = up->GetDispatcherWithRights(handle, ZX_RIGHT_WAIT, &interrupt); if (status != ZX_OK) return status; zx_time_t timestamp; status = interrupt->WaitForInterrupt(×tamp); 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 interrupt; status = up->GetDispatcher(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_time_t timestamp) { LTRACEF("handle %x\n", handle); if (options) { return ZX_ERR_INVALID_ARGS; } zx_status_t status; auto up = ProcessDispatcher::GetCurrent(); fbl::RefPtr interrupt; status = up->GetDispatcherWithRights(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 parameters, user_out_ptr 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(¶ms); 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; arch_smc_call(¶ms, &result); return out_smc_result.copy_to_user(result); }