vulkan/drm_syncobj: Add a vk_drm_syncobj_copy_payloads helper

This intentionally matches vk_device::copy_sync_payloads.

Reviewed-by: Lars-Ivar Hesselberg Simonsen <lars-ivar.simonsen@arm.com>
Reviewed-by: Yiwei Zhang <zzyiwei@chromium.org>
Reviewed-by: Adam Jackson <ajax@redhat.com>
Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36827>
This commit is contained in:
Faith Ekstrand 2025-08-14 13:34:33 -04:00 committed by Marge Bot
parent f4d00c1dbf
commit 4827ba625d
3 changed files with 205 additions and 5 deletions

View file

@ -226,11 +226,7 @@ struct vk_device {
* queue. Instead, the driver is expected to simply copy the sync payloads
* from the wait set, merge them together into one, and apply that to the
* signals. After this function returns, all of the signals are now
* equivalent to all of the waits.
*
* This function MUST reset all binary syncs it waits on, unless that same
* sync is also used as a signal. Otherwise, we risk breaking timeline
* semaphore negotiation invariants.
* equivalent to the union all of the waits.
*/
VkResult (*copy_sync_payloads)(struct vk_device *device,
uint32_t wait_count,

View file

@ -28,11 +28,13 @@
#include "drm-uapi/drm.h"
#include "util/libsync.h"
#include "util/os_time.h"
#include "util/u_sync_provider.h"
#include "vk_device.h"
#include "vk_log.h"
#include "vk_physical_device.h"
#include "vk_util.h"
static struct vk_drm_syncobj *
@ -462,6 +464,194 @@ vk_drm_syncobj_move(struct vk_device *device,
}
}
static VkResult
vk_drm_copy_sync_file_payloads(struct vk_device *device,
uint32_t wait_count,
const struct vk_sync_wait *waits,
uint32_t signal_count,
const struct vk_sync_signal *signals)
{
VkResult result = VK_SUCCESS;
int merged = -1;
for (uint32_t i = 0; i < wait_count; i++) {
assert(!(waits[i].sync->flags & VK_SYNC_IS_TIMELINE));
assert(waits[i].wait_value == 0);
int wait_fd = -1;
result = vk_drm_syncobj_export_sync_file(device, waits[i].sync, &wait_fd);
if (result != VK_SUCCESS)
goto fail;
/* -1 means it's already signaled, so nothing to merge. */
if (wait_fd == -1)
continue;
if (merged == -1) {
merged = wait_fd;
} else {
int ret = sync_merge("vk_drm_syncobj", merged, wait_fd);
close(wait_fd);
if (ret < 0) {
result = vk_errorf(device, VK_ERROR_UNKNOWN,
"SYNC_IOC_MERGE failed: %m");
goto fail;
}
close(merged);
merged = ret;
}
}
/* merged == -1 could either mean that we had no waits or it could mean
* that they were all already complete. In either case there's nothing to
* wait on so we can just signal everything.
*/
if (merged == -1)
return vk_drm_syncobj_signal_many(device, signal_count, signals);
for (uint32_t i = 0; i < signal_count; i++) {
assert(!(signals[i].sync->flags & VK_SYNC_IS_TIMELINE));
assert(signals[i].signal_value == 0);
result = vk_drm_syncobj_import_sync_file(device, signals[i].sync, merged);
if (result != VK_SUCCESS)
goto fail;
}
fail:
if (merged >= 0)
close(merged);
return result;
}
static VkResult
vk_drm_syncobj_transfer_payloads(struct vk_device *device,
uint32_t wait_count,
const struct vk_sync_wait *waits,
uint32_t signal_count,
const struct vk_sync_signal *signals)
{
if (wait_count == 1) {
/* If we only have one wait, we can transfer directly into each of the
* signal syncs.
*/
struct vk_drm_syncobj *wait_sobj = to_drm_syncobj(waits[0].sync);
const uint64_t wait_value = waits[0].wait_value;
for (uint32_t i = 0; i < signal_count; i++) {
struct vk_drm_syncobj *signal_sobj = to_drm_syncobj(signals[i].sync);
const uint64_t signal_value = signals[i].signal_value;
/* It's possible that we're waiting and signaling the same syncobj */
if (signal_sobj == wait_sobj) {
if (wait_sobj->base.flags & VK_SYNC_IS_TIMELINE) {
/* We have to be signaling a higher value */
assert(signal_value > wait_value);
} else {
/* Don't copy into ourself */
continue;
}
}
int err = device->sync->transfer(device->sync,
signal_sobj->syncobj, signal_value,
wait_sobj->syncobj, wait_value, 0);
if (err) {
return vk_errorf(device, VK_ERROR_UNKNOWN,
"DRM_IOCTL_SYNCOBJ_TRANSFER failed: %m");
}
}
return VK_SUCCESS;
} else {
/* This is the annoying case where we have to do an actual many-to-many
* transfer. This requires us to go through an intermediary syncobj.
*
* We'll build up tmp_syncobj as a timeline and then transfer from it
* as a binary. The behavior of dma_fence_chain in the kernel is that
* waiting on a whole chain waits on everything.
*/
uint32_t tmp_syncobj;
int err = device->sync->create(device->sync, 0, &tmp_syncobj);
if (err) {
return vk_errorf(device, VK_ERROR_UNKNOWN,
"DRM_IOCTL_SYNCOBJ_CREATE failed: %m");
}
for (uint32_t i = 0; i < wait_count; i++) {
struct vk_drm_syncobj *wait_sobj = to_drm_syncobj(waits[i].sync);
const uint64_t wait_value = waits[i].wait_value;
err = device->sync->transfer(device->sync, tmp_syncobj, i + 1,
wait_sobj->syncobj, wait_value, 0);
if (err) {
err = device->sync->destroy(device->sync, tmp_syncobj);
assert(err == 0);
return vk_errorf(device, VK_ERROR_UNKNOWN,
"DRM_IOCTL_SYNCOBJ_TRANSFER failed: %m");
}
}
for (uint32_t i = 0; i < signal_count; i++) {
struct vk_drm_syncobj *signal_sobj = to_drm_syncobj(signals[i].sync);
const uint64_t signal_value = signals[i].signal_value;
int err = device->sync->transfer(device->sync,
signal_sobj->syncobj, signal_value,
tmp_syncobj, 0, 0);
if (err) {
err = device->sync->destroy(device->sync, tmp_syncobj);
assert(err == 0);
return vk_errorf(device, VK_ERROR_UNKNOWN,
"DRM_IOCTL_SYNCOBJ_TRANSFER failed: %m");
}
}
err = device->sync->destroy(device->sync, tmp_syncobj);
assert(err == 0);
return VK_SUCCESS;
}
}
static bool
vk_device_has_timeline_syncobj(struct vk_device *device)
{
/* This is annoyingly complex but nothing compared to calling an ioctl. */
for (const struct vk_sync_type *const *t =
device->physical->supported_sync_types; *t; t++) {
if (vk_sync_type_is_drm_syncobj(*t) &&
((*t)->features & VK_SYNC_FEATURE_TIMELINE))
return true;
}
return false;
}
VkResult
vk_drm_syncobj_copy_payloads(struct vk_device *device,
uint32_t wait_count,
const struct vk_sync_wait *waits,
uint32_t signal_count,
const struct vk_sync_signal *signals)
{
/* First check if there's even anything to signal */
if (signal_count == 0)
return VK_SUCCESS;
/* If there's nothing to wait on, just signal everything */
if (wait_count == 0)
return vk_drm_syncobj_signal_many(device, signal_count, signals);
if (vk_device_has_timeline_syncobj(device)) {
return vk_drm_syncobj_transfer_payloads(device, wait_count, waits,
signal_count, signals);
} else {
return vk_drm_copy_sync_file_payloads(device, wait_count, waits,
signal_count, signals);
}
}
struct vk_sync_type
vk_drm_syncobj_get_type_from_provider(struct util_sync_provider *sync)
{

View file

@ -54,6 +54,20 @@ vk_sync_as_drm_syncobj(struct vk_sync *sync)
return container_of(sync, struct vk_drm_syncobj, base);
}
/** Signal the signal syncobjs with the union/merge of the wait syncobjs
*
* This is an implementation of vk_device::copy_sync_payloads for drivers
* which use DRM syncobjs as their only core synchronization primitive.
* Internally, it either uses DRM_SYNCOBJ_TRANSFER if the driver supports
* timelines or sync file import/export if the driver only supports binary
* sync objects.
*/
VkResult vk_drm_syncobj_copy_payloads(struct vk_device *device,
uint32_t wait_count,
const struct vk_sync_wait *waits,
uint32_t signal_count,
const struct vk_sync_signal *signals);
struct util_sync_provider;
struct vk_sync_type vk_drm_syncobj_get_type_from_provider(struct util_sync_provider *sync);
struct vk_sync_type vk_drm_syncobj_get_type(int drm_fd);