| /* |
| * Copyright © 2024 The Fuchsia Authors |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| * IN THE SOFTWARE. |
| */ |
| |
| #include "vk_zircon_syncobj.h" |
| |
| #include <string.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/port.h> |
| |
| #include "vk_device.h" |
| #include "vk_log.h" |
| #include "vk_util.h" |
| |
| static struct vk_zircon_syncobj * |
| to_zircon_syncobj(struct vk_sync *sync) |
| { |
| assert(vk_sync_type_is_zircon_syncobj(sync->type)); |
| return container_of(sync, struct vk_zircon_syncobj, base); |
| } |
| |
| static VkResult vk_zircon_syncobj_init(struct vk_device *device, |
| struct vk_sync *sync, |
| uint64_t initial_value) { |
| struct vk_zircon_syncobj *sobj = to_zircon_syncobj(sync); |
| zx_handle_t handle = (zx_handle_t) initial_value; |
| |
| assert((sync->flags & VK_SYNC_IS_TIMELINE) == 0); |
| |
| /* |initial_value| is overloaded to convey zx_handle_t handles. */ |
| /* If it's set and not 1, it's a zx_handle_t. */ |
| if (initial_value && initial_value != 1) { |
| assert((initial_value >> 32) == 0); |
| /* Close existing handle if it exists. */ |
| if (sobj->semaphore != ZX_HANDLE_INVALID) { |
| if (zx_handle_close(sobj->semaphore) != ZX_OK) { |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to close handle: %m"); |
| } |
| } |
| |
| /* Duplicate and store |handle| into |sobj->semaphore|. */ |
| zx_status_t status = |
| zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, |
| (zx_handle_t *) &sobj->semaphore); |
| if(status != ZX_OK) { |
| if(status == ZX_ERR_BAD_HANDLE) |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to initialize, bad Zircon handle: %m"); |
| else |
| return vk_errorf(device, VK_ERROR_UNKNOWN, |
| "Unable to initialize: %m"); |
| } |
| |
| /* Close original |handle| to invalidate it. */ |
| if (zx_handle_close(handle) != ZX_OK) { |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to close handle: %m"); |
| } |
| } else { |
| zx_status_t status = zx_event_create(0, (zx_handle_t *) &sobj->semaphore); |
| if (status != ZX_OK) { |
| assert(status == ZX_ERR_NO_MEMORY); |
| return vk_errorf(device, VK_ERROR_OUT_OF_HOST_MEMORY, |
| "Unable to create semaphore: %m"); |
| } |
| } |
| |
| if (initial_value == 1) { |
| zx_status_t status = |
| zx_object_signal((zx_handle_t) sobj->semaphore, 0u, ZX_EVENT_SIGNALED); |
| if(status != ZX_OK) { |
| return vk_errorf(device, VK_ERROR_UNKNOWN, |
| "Unable to signal semaphore: %m"); |
| } |
| } |
| |
| return VK_SUCCESS; |
| } |
| |
| void |
| vk_zircon_syncobj_finish(struct vk_device *device, |
| struct vk_sync *sync) |
| { |
| struct vk_zircon_syncobj *sobj = to_zircon_syncobj(sync); |
| if (sobj->semaphore == ZX_HANDLE_INVALID) { |
| vk_logi(VK_LOG_OBJS(device), "Attempt to close invalid Zircon event handle.\n"); |
| return; |
| } |
| zx_status_t status = zx_handle_close(sobj->semaphore); |
| if (status == ZX_ERR_BAD_HANDLE) { |
| vk_loge(VK_LOG_OBJS(device), "Attempt to close bad Zircon event handle.\n"); |
| return; |
| } |
| assert(status == ZX_OK); |
| sobj->semaphore = ZX_HANDLE_INVALID; |
| } |
| |
| static VkResult |
| vk_zircon_syncobj_signal(struct vk_device *device, |
| struct vk_sync *sync, |
| uint64_t value) |
| { |
| struct vk_zircon_syncobj *sobj = to_zircon_syncobj(sync); |
| if (sobj->semaphore == ZX_HANDLE_INVALID) { |
| return vk_errorf(device, VK_ERROR_UNKNOWN, |
| "Attempt to signal invalid Zircon event handle: %m"); |
| } |
| |
| zx_status_t status = zx_object_signal(sobj->semaphore, 0u, ZX_EVENT_SIGNALED); |
| switch(status) { |
| case ZX_OK: |
| return VK_SUCCESS; |
| case ZX_ERR_BAD_HANDLE: |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to signal semaphore, bad Zircon handle: %m"); |
| default: |
| return vk_errorf(device, VK_ERROR_UNKNOWN, "Unable to signal semaphore: %m"); |
| } |
| } |
| |
| static VkResult |
| vk_zircon_syncobj_reset(struct vk_device *device, |
| struct vk_sync *sync) |
| { |
| struct vk_zircon_syncobj *sobj = to_zircon_syncobj(sync); |
| if (sobj->semaphore == ZX_HANDLE_INVALID) { |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Attempt to reset invalid Zircon event handle: %m"); |
| } |
| |
| zx_status_t status = zx_object_signal(sobj->semaphore, ZX_EVENT_SIGNALED, 0u); |
| switch(status) { |
| case ZX_OK: |
| return VK_SUCCESS; |
| case ZX_ERR_BAD_HANDLE: |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to reset semaphore, bad Zircon handle: %m"); |
| default: |
| return vk_errorf(device, VK_ERROR_UNKNOWN, "Unable to reset semaphore: %m"); |
| } |
| } |
| |
| static VkResult |
| vk_zircon_syncobj_wait_many(struct vk_device *device, |
| uint32_t wait_count, |
| const struct vk_sync_wait *waits, |
| enum vk_sync_wait_flags wait_flags, |
| uint64_t abs_timeout_ns) |
| { |
| assert((wait_flags & VK_SYNC_WAIT_PENDING) == 0); |
| if(!wait_count) return VK_SUCCESS; |
| |
| /* Syncobj timeouts are signed. */ |
| abs_timeout_ns = MIN2(abs_timeout_ns, (uint64_t)ZX_TIME_INFINITE); |
| |
| /* Create port on which to monitor signaled semaphores. */ |
| zx_handle_t port = ZX_HANDLE_INVALID; |
| zx_status_t status = zx_port_create(0, &port); |
| if(status != ZX_OK) { |
| return vk_errorf(device, VK_ERROR_UNKNOWN, "Unable to create port: %m"); |
| } |
| |
| /* Enqueue all semaphores (events) on the port. */ |
| for (uint32_t i = 0; i < wait_count; i++) { |
| uint64_t semaphore = to_zircon_syncobj(waits[i].sync)->semaphore; |
| status = zx_object_wait_async(semaphore, port, i /* key */, ZX_EVENT_SIGNALED, |
| 0 /* options */); |
| if (status != ZX_OK) { |
| return vk_errorf(device, VK_ERROR_UNKNOWN, "Unable to queue semaphore on port: %m"); |
| } |
| } |
| |
| status = ZX_OK; |
| const bool kWaitAll = (wait_flags & VK_SYNC_WAIT_ANY) == 0; |
| zx_port_packet_t packet = {0}; |
| if(kWaitAll) { |
| const size_t kMaxMasks = 16; |
| uint64_t masks[kMaxMasks]; |
| uint64_t *masks_base = NULL; |
| uint64_t *masks_ptr = masks; |
| const uint32_t num_masks = ((wait_count - 1) / 64) + 1; |
| if(num_masks > kMaxMasks) { |
| masks_base = (uint64_t *) malloc(num_masks * sizeof(uint64_t)); |
| if(!masks_base) { |
| return vk_errorf(device, VK_ERROR_OUT_OF_HOST_MEMORY, "Masks alloc failed: %m"); |
| } |
| masks_ptr = masks_base; |
| } |
| memset(masks_ptr, 0xFF, num_masks * sizeof(uint64_t)); |
| |
| /* Clear all unused bits of the last mask. */ |
| *(masks_ptr + num_masks - 1) >>= (64 - (wait_count % 64)); |
| int cleared_masks = 0; |
| while(cleared_masks < num_masks) { |
| status = zx_port_wait(port, abs_timeout_ns, &packet); |
| const uint32_t mask_index = packet.key / 64; |
| switch(status) { |
| case ZX_OK: |
| /* Knock out the bit for the current signal. */ |
| *(masks_ptr + mask_index) &= ~(1 << (packet.key % 64)); |
| for(cleared_masks = 0; cleared_masks < num_masks; cleared_masks++) { |
| if(*(masks_ptr + cleared_masks)) { |
| break; |
| } |
| } |
| break; |
| default: |
| cleared_masks = num_masks; |
| break; |
| } |
| } |
| if(masks_base) free(masks_base); |
| } else { |
| /* Wait for any event signal. */ |
| status = zx_port_wait(port, abs_timeout_ns, &packet); |
| } |
| |
| switch(status) { |
| case ZX_OK: |
| return VK_SUCCESS; |
| case ZX_ERR_TIMED_OUT: |
| return vk_errorf(device, VK_TIMEOUT, "zx_port_wait timed out: %m"); |
| default: |
| return vk_errorf(device, VK_ERROR_UNKNOWN, "zx_port_wait failed: %m"); |
| } |
| } |
| |
| static VkResult |
| vk_zircon_syncobj_import_zircon_handle(struct vk_device *device, |
| struct vk_sync *sync, |
| uint32_t handle) |
| { |
| struct vk_zircon_syncobj *sobj = to_zircon_syncobj(sync); |
| zx_handle_t semaphore = ZX_HANDLE_INVALID; |
| |
| zx_status_t status = |
| zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, &semaphore); |
| if(status != ZX_OK) { |
| if(status == ZX_ERR_BAD_HANDLE) |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to import, bad Zircon handle: %m"); |
| else |
| return vk_errorf(device, VK_ERROR_UNKNOWN, |
| "Unable to import handle: %m"); |
| } |
| |
| if (zx_handle_close(handle) != ZX_OK) { |
| return vk_errorf(device, VK_ERROR_UNKNOWN, |
| "Unable to import handle: %m"); |
| } |
| |
| status = zx_handle_close((zx_handle_t) sobj->semaphore); |
| switch(status) { |
| case ZX_OK: |
| sobj->semaphore = (uint64_t) semaphore; |
| return VK_SUCCESS; |
| case ZX_ERR_BAD_HANDLE: |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to import, bad Zircon handle: %m"); |
| default: |
| return vk_errorf(device, VK_ERROR_UNKNOWN, |
| "Unable to import handle: %m"); |
| } |
| } |
| |
| static VkResult |
| vk_zircon_syncobj_export_zircon_handle(struct vk_device *device, |
| struct vk_sync *sync, |
| uint32_t* handle_out) |
| { |
| struct vk_zircon_syncobj *sobj = to_zircon_syncobj(sync); |
| |
| if (sobj->semaphore == ZX_HANDLE_INVALID) { |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Attempt to export invalid Zircon event handle: %m"); |
| } |
| |
| zx_status_t status = zx_handle_duplicate(sobj->semaphore, |
| ZX_RIGHT_SAME_RIGHTS, handle_out); |
| switch(status) { |
| case ZX_OK: |
| return VK_SUCCESS; |
| case ZX_ERR_BAD_HANDLE: |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to export, bad Zircon handle: %m"); |
| default: |
| return vk_errorf(device, VK_ERROR_UNKNOWN, |
| "Unable to export handle: %m"); |
| } |
| } |
| |
| static VkResult |
| vk_zircon_syncobj_move(struct vk_device *device, |
| struct vk_sync *dst, |
| struct vk_sync *src) |
| { |
| struct vk_zircon_syncobj *dst_sobj = to_zircon_syncobj(dst); |
| struct vk_zircon_syncobj *src_sobj = to_zircon_syncobj(src); |
| |
| if(src_sobj->semaphore == ZX_HANDLE_INVALID) { |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to move invalid handle: %m"); |
| } |
| |
| zx_handle_t handle_out = ZX_HANDLE_INVALID; |
| zx_status_t status = zx_handle_duplicate(src_sobj->semaphore, |
| ZX_RIGHT_SAME_RIGHTS, &handle_out); |
| if(status != ZX_OK) { |
| if(status == ZX_ERR_BAD_HANDLE) |
| return vk_errorf(device, VK_ERROR_INVALID_EXTERNAL_HANDLE, |
| "Unable to move, bad Zircon handle: %m"); |
| else |
| return vk_errorf(device, VK_ERROR_UNKNOWN, |
| "Unable to move handle: %m"); |
| } |
| |
| if (zx_handle_close((zx_handle_t) src_sobj->semaphore) != ZX_OK) { |
| return vk_errorf(device, VK_ERROR_UNKNOWN, |
| "Unable to close (move) src semaphore handle: %m"); |
| } |
| |
| src_sobj->semaphore = ZX_HANDLE_INVALID; |
| |
| *dst_sobj = *src_sobj; |
| dst_sobj->semaphore = (uint64_t) handle_out; |
| |
| return VK_SUCCESS; |
| } |
| |
| struct vk_sync_type |
| vk_zircon_syncobj_get_type(void) |
| { |
| struct vk_sync_type type = { |
| .size = sizeof(struct vk_zircon_syncobj), |
| .features = VK_SYNC_FEATURE_BINARY | |
| VK_SYNC_FEATURE_GPU_WAIT | |
| VK_SYNC_FEATURE_CPU_RESET | |
| VK_SYNC_FEATURE_CPU_SIGNAL | |
| VK_SYNC_FEATURE_CPU_WAIT | |
| VK_SYNC_FEATURE_WAIT_PENDING | |
| VK_SYNC_FEATURE_WAIT_ANY, |
| .init = vk_zircon_syncobj_init, |
| .finish = vk_zircon_syncobj_finish, |
| .move = vk_zircon_syncobj_move, |
| .signal = vk_zircon_syncobj_signal, |
| .reset = vk_zircon_syncobj_reset, |
| .wait_many = vk_zircon_syncobj_wait_many, |
| .import_zircon_handle = vk_zircon_syncobj_import_zircon_handle, |
| .export_zircon_handle = vk_zircon_syncobj_export_zircon_handle, |
| }; |
| |
| return type; |
| } |