[lavapipe] VK_FUCHSIA_external_semaphore implementation

Bug: 42086544

Change-Id: I4446777f0de0748a3cf58bfeed2d8715fa323f19
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/mesa/+/991013
Reviewed-by: Josh Gargus <jjosh@google.com>
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/mesa/+/1005916
Reviewed-by: John Rosasco <rosasco@google.com>
Commit-Queue: John Rosasco <rosasco@google.com>
diff --git a/src/gallium/frontends/lavapipe/lvp_device.c b/src/gallium/frontends/lavapipe/lvp_device.c
index e0191dc..9acd022 100644
--- a/src/gallium/frontends/lavapipe/lvp_device.c
+++ b/src/gallium/frontends/lavapipe/lvp_device.c
@@ -45,9 +45,10 @@
 #include "vulkan/vulkan_core.h"
 
 #if defined(VK_USE_PLATFORM_FUCHSIA)
-#include "lvp_fuchsia_memory.h"
 #include <zircon/process.h>
 #include <zircon/syscalls.h>
+#include "lvp_fuchsia_memory.h"
+#include "vulkan/runtime/vk_zircon_syncobj.h"
 #endif
 
 #if defined(VK_USE_PLATFORM_WAYLAND_KHR) || \
@@ -201,6 +202,7 @@
    .GOOGLE_hlsl_functionality1            = true,
 #if defined(PIPE_MEMORY_FUCHSIA)
    .FUCHSIA_external_memory               = true,
+   .FUCHSIA_external_semaphore            = true,
 #endif
 };
 
@@ -270,9 +272,15 @@
       device->drv_options[i] = device->pscreen->get_compiler_options(device->pscreen, PIPE_SHADER_IR_NIR, i);
 
    device->sync_timeline_type = vk_sync_timeline_get_type(&lvp_pipe_sync_type);
-   device->sync_types[0] = &lvp_pipe_sync_type;
-   device->sync_types[1] = &device->sync_timeline_type.sync;
-   device->sync_types[2] = NULL;
+   uint32_t st = 0;
+   device->sync_types[st++] = &lvp_pipe_sync_type;
+   device->sync_types[st++] = &device->sync_timeline_type.sync;
+#ifdef VK_USE_PLATFORM_FUCHSIA
+   device->zircon_sync_type = vk_zircon_syncobj_get_type();
+   device->sync_types[st++] = &device->zircon_sync_type;
+#endif
+   device->sync_types[st] = NULL;
+   assert(st < MAX_SYNC_TYPES);
    device->vk.supported_sync_types = device->sync_types;
 
    device->max_images = device->pscreen->get_shader_param(device->pscreen, PIPE_SHADER_FRAGMENT, PIPE_SHADER_CAP_MAX_SHADER_IMAGES);
@@ -2537,6 +2545,16 @@
    pExternalSemaphoreProperties->exportFromImportedHandleTypes = 0;
    pExternalSemaphoreProperties->compatibleHandleTypes = 0;
    pExternalSemaphoreProperties->externalSemaphoreFeatures = 0;
+
+#if VK_USE_PLATFORM_FUCHSIA
+   pExternalSemaphoreProperties->compatibleHandleTypes |=
+       VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_ZIRCON_EVENT_BIT_FUCHSIA;
+   pExternalSemaphoreProperties->exportFromImportedHandleTypes |=
+       VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_ZIRCON_EVENT_BIT_FUCHSIA;
+   pExternalSemaphoreProperties->externalSemaphoreFeatures |=
+       VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+       VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT;
+#endif
 }
 
 static const VkTimeDomainEXT lvp_time_domains[] = {
diff --git a/src/gallium/frontends/lavapipe/lvp_private.h b/src/gallium/frontends/lavapipe/lvp_private.h
index 45b058e..52cdbed 100644
--- a/src/gallium/frontends/lavapipe/lvp_private.h
+++ b/src/gallium/frontends/lavapipe/lvp_private.h
@@ -86,6 +86,7 @@
 #define MAX_PUSH_DESCRIPTORS 32
 #define MAX_DESCRIPTOR_UNIFORM_BLOCK_SIZE 4096
 #define MAX_PER_STAGE_DESCRIPTOR_UNIFORM_BLOCKS 8
+#define MAX_SYNC_TYPES   4
 
 #ifdef _WIN32
 #define lvp_printflike(a, b)
@@ -130,7 +131,10 @@
    uint32_t max_images;
 
    struct vk_sync_timeline_type sync_timeline_type;
-   const struct vk_sync_type *sync_types[3];
+#if VK_USE_PLATFORM_FUCHSIA
+   struct vk_sync_type zircon_sync_type;
+#endif
+   const struct vk_sync_type *sync_types[MAX_SYNC_TYPES];
 
    VkPhysicalDeviceLimits device_limits;
 
diff --git a/src/vulkan/runtime/BUILD.gn b/src/vulkan/runtime/BUILD.gn
index 6f18dbe..691983c 100644
--- a/src/vulkan/runtime/BUILD.gn
+++ b/src/vulkan/runtime/BUILD.gn
@@ -160,6 +160,8 @@
     "$target_gen_dir/vk_common_entrypoints.c",
     "$target_gen_dir/vk_dispatch_trampolines.c",
     "$target_gen_dir/vk_physical_device_features.c",
+    "vk_zircon_syncobj.c",
+    "vk_zircon_syncobj.h"
   ] + runtime_sources + runtime_headers
 
   configs = [ "//build/config:Wno-strict-prototypes" ]
diff --git a/src/vulkan/runtime/vk_device.h b/src/vulkan/runtime/vk_device.h
index 0072858..30092e1 100644
--- a/src/vulkan/runtime/vk_device.h
+++ b/src/vulkan/runtime/vk_device.h
@@ -295,7 +295,7 @@
                const VkDeviceCreateInfo *pCreateInfo,
                const VkAllocationCallbacks *alloc);
 
-#if !defined(USE_MAGMA)
+#if !defined(USE_MAGMA) && !defined(__Fuchsia__)
 static inline void
 vk_device_set_drm_fd(struct vk_device *device, int drm_fd)
 {
diff --git a/src/vulkan/runtime/vk_semaphore.c b/src/vulkan/runtime/vk_semaphore.c
index 5e9ab93..afee3c1 100644
--- a/src/vulkan/runtime/vk_semaphore.c
+++ b/src/vulkan/runtime/vk_semaphore.c
@@ -50,6 +50,9 @@
 #if defined(USE_MAGMA)
    if (type->import_magma_handle)
       handle_types |= VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_ZIRCON_EVENT_BIT_FUCHSIA;
+#elif defined(__Fuchsia__)
+   if (type->import_zircon_handle)
+      handle_types |= VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_ZIRCON_EVENT_BIT_FUCHSIA;
 #endif
 
    return handle_types;
@@ -70,6 +73,9 @@
 #if defined(USE_MAGMA)
    if (type->export_magma_handle)
       handle_types |= VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_ZIRCON_EVENT_BIT_FUCHSIA;
+#elif defined(__Fuchsia__)
+   if (type->export_zircon_handle)
+      handle_types |= VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_ZIRCON_EVENT_BIT_FUCHSIA;
 #endif
 
    return handle_types;
@@ -104,7 +110,9 @@
       if (req_features & ~(*t)->features)
          continue;
 
-      if (handle_types & ~vk_sync_semaphore_handle_types(*t, semaphore_type))
+      uint32_t semaphore_handle_types =
+          vk_sync_semaphore_handle_types(*t, semaphore_type);
+      if (handle_types & ~semaphore_handle_types)
          continue;
 
       return *t;
@@ -245,6 +253,7 @@
 
    const struct vk_sync_type *sync_type =
       get_semaphore_sync_type(pdevice, semaphore_type, handle_type);
+
    if (sync_type == NULL) {
       pExternalSemaphoreProperties->exportFromImportedHandleTypes = 0;
       pExternalSemaphoreProperties->compatibleHandleTypes = 0;
@@ -581,8 +590,6 @@
 #endif /* !defined(_WIN32) */
 
 #if defined(__Fuchsia__)
-#if defined(USE_MAGMA)
-
 VkResult
 vk_common_ImportSemaphoreZirconHandleFUCHSIA(VkDevice vk_device,
                                              const VkImportSemaphoreZirconHandleInfoFUCHSIA* info)
@@ -597,7 +604,11 @@
 
    if (info->flags & VK_SEMAPHORE_IMPORT_TEMPORARY_BIT_KHR) {
       if (semaphore->temporary) {
+#if defined(USE_MAGMA)
          result = vk_sync_import_magma_handle(device, semaphore->temporary, info->zirconHandle);
+#else
+         result = vk_sync_import_zircon_handle(device, semaphore->temporary, info->zirconHandle);
+#endif
       } else {
          const struct vk_sync_type *sync_type =
             get_semaphore_sync_type(device->physical, semaphore->type, info->handleType);
@@ -610,7 +621,11 @@
          semaphore->temporary->flags |= VK_SYNC_IS_SHAREABLE | VK_SYNC_IS_SHARED;
       }
    } else {
+#if defined(USE_MAGMA)
       result = vk_sync_import_magma_handle(device, &semaphore->permanent, info->zirconHandle);
+#else
+      result = vk_sync_import_zircon_handle(device, &semaphore->permanent, info->zirconHandle);
+#endif
    }
 
    return result;
@@ -631,7 +646,11 @@
 
    struct vk_sync *sync = vk_semaphore_get_active_sync(semaphore);
 
+#if defined(USE_MAGMA)
    VkResult result = vk_sync_export_magma_handle(device, sync, pZirconHandle);
+#else
+   VkResult result = vk_sync_export_zircon_handle(device, sync, pZirconHandle);
+#endif
    if (result != VK_SUCCESS)
       return result;
 
@@ -647,6 +666,4 @@
    return VK_SUCCESS;
 }
 
-#else  /* Zircon */
-#endif  /* defined(USE_MAGMA) */
 #endif  /* defined(__Fuchsia__) */
diff --git a/src/vulkan/runtime/vk_sync.c b/src/vulkan/runtime/vk_sync.c
index dcd4e42..42f7cfc 100644
--- a/src/vulkan/runtime/vk_sync.c
+++ b/src/vulkan/runtime/vk_sync.c
@@ -407,6 +407,37 @@
 
    return VK_SUCCESS;
 }
+#elif defined(__Fuchsia__)
+VkResult
+vk_sync_import_zircon_handle(struct vk_device *device,
+                             struct vk_sync *sync,
+                             uint32_t handle)
+{
+   VkResult result = sync->type->import_zircon_handle(device, sync, handle);
+   if (unlikely(result != VK_SUCCESS))
+      return result;
+
+   sync->flags |= VK_SYNC_IS_SHAREABLE |
+                  VK_SYNC_IS_SHARED;
+
+   return VK_SUCCESS;
+}
+
+VkResult
+vk_sync_export_zircon_handle(struct vk_device *device,
+                             struct vk_sync *sync,
+                             uint32_t* handle_out)
+{
+   assert(sync->flags & VK_SYNC_IS_SHAREABLE);
+
+   VkResult result = sync->type->export_zircon_handle(device, sync, handle_out);
+   if (unlikely(result != VK_SUCCESS))
+      return result;
+
+   sync->flags |= VK_SYNC_IS_SHARED;
+
+   return VK_SUCCESS;
+}
 #endif
 
 VkResult
diff --git a/src/vulkan/runtime/vk_sync.h b/src/vulkan/runtime/vk_sync.h
index 769109d..129ca8f 100644
--- a/src/vulkan/runtime/vk_sync.h
+++ b/src/vulkan/runtime/vk_sync.h
@@ -279,8 +279,16 @@
                                    uint32_t handle);
 
    VkResult (*export_magma_handle)(struct vk_device *device,
-                                  struct vk_sync *sync,
-                                  uint32_t* handle_out);
+                                   struct vk_sync *sync,
+                                   uint32_t* handle_out);
+#elif defined(__Fuchsia__)
+   VkResult (*import_zircon_handle)(struct vk_device *device,
+                                    struct vk_sync *sync,
+                                    uint32_t handle);
+
+   VkResult (*export_zircon_handle)(struct vk_device *device,
+                                    struct vk_sync *sync,
+                                    uint32_t* handle_out);
 #endif
 };
 
@@ -379,6 +387,14 @@
 VkResult MUST_CHECK vk_sync_export_magma_handle(struct vk_device *device,
                                                 struct vk_sync *sync,
                                                 uint32_t *handle_out);
+#elif defined(__Fuchsia__)
+VkResult MUST_CHECK vk_sync_import_zircon_handle(struct vk_device *device,
+                                                 struct vk_sync *sync,
+                                                 uint32_t handle);
+
+VkResult MUST_CHECK vk_sync_export_zircon_handle(struct vk_device *device,
+                                                 struct vk_sync *sync,
+                                                 uint32_t *handle_out);
 #endif
 
 VkResult MUST_CHECK vk_sync_move(struct vk_device *device,
diff --git a/src/vulkan/runtime/vk_zircon_syncobj.c b/src/vulkan/runtime/vk_zircon_syncobj.c
new file mode 100644
index 0000000..475a8a6
--- /dev/null
+++ b/src/vulkan/runtime/vk_zircon_syncobj.c
@@ -0,0 +1,373 @@
+/*
+ * 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;
+}
diff --git a/src/vulkan/runtime/vk_zircon_syncobj.h b/src/vulkan/runtime/vk_zircon_syncobj.h
new file mode 100644
index 0000000..de600fb
--- /dev/null
+++ b/src/vulkan/runtime/vk_zircon_syncobj.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+#ifndef VK_ZIRCON_SYNCOBJ_H
+#define VK_ZIRCON_SYNCOBJ_H
+
+#include "vk_sync.h"
+
+#include "util/macros.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct vk_zircon_syncobj {
+   struct vk_sync base;
+   uint64_t semaphore;
+};
+
+void vk_zircon_syncobj_finish(struct vk_device *device,
+                              struct vk_sync *sync);
+
+static inline bool
+vk_sync_type_is_zircon_syncobj(const struct vk_sync_type *type)
+{
+   return type->finish == vk_zircon_syncobj_finish;
+}
+
+static inline struct vk_zircon_syncobj *
+vk_sync_as_zircon_syncobj(struct vk_sync *sync)
+{
+   if (!vk_sync_type_is_zircon_syncobj(sync->type))
+      return NULL;
+
+   return container_of(sync, struct vk_zircon_syncobj, base);
+}
+
+struct vk_sync_type vk_zircon_syncobj_get_type(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* VK_ZIRCON_SYNCOBJ_H */