From 2a053b2e60862cc18a9d46b085a7cff715a2850a Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 17 Dec 2024 17:53:21 -0800 Subject: [PATCH] anv/xe: Bind queue per anv_queue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Xe uAPI is designed to use bind queues such that binds without input dependencies (sync objects) do not block on binds with input dependencies. For example: - Bind A (sparse) is submitted with a list of input dependencies. - Bind B (immediate) is subsequently submitted without a list of input dependencies. If Bind A and Bind B share a single bind queue, Bind B will not be scheduled until Bind A completes. Using individual bind queues decouples Bind A and Bind B, allowing Bind B to make immediate progress. This change creates a separate bind queue for each ANV queue, enabling support for sparse bindings that may have input dependencies. v2: - Bail on bind queue creation failure (Linoel) - Only create bind queue if VK_QUEUE_SPARSE_BINDING_BIT is set (Jose) v3: - Add comment around submit->queue usage (Jose) Signed-off-by: Matthew Brost Reviewed-by: José Roberto de Souza Part-of: --- src/intel/vulkan/anv_private.h | 2 + src/intel/vulkan/xe/anv_kmd_backend.c | 4 ++ src/intel/vulkan/xe/anv_queue.c | 86 ++++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src/intel/vulkan/anv_private.h b/src/intel/vulkan/anv_private.h index fce45261fe4..cb3c83d9288 100644 --- a/src/intel/vulkan/anv_private.h +++ b/src/intel/vulkan/anv_private.h @@ -1340,6 +1340,8 @@ struct anv_queue { uint32_t exec_queue_id; /* Xe */ }; + uint32_t bind_queue_id; /* Xe */ + /** Context/Engine id which executes companion RCS command buffer */ uint32_t companion_rcs_id; diff --git a/src/intel/vulkan/xe/anv_kmd_backend.c b/src/intel/vulkan/xe/anv_kmd_backend.c index 93d9d74eebe..2e4b19458af 100644 --- a/src/intel/vulkan/xe/anv_kmd_backend.c +++ b/src/intel/vulkan/xe/anv_kmd_backend.c @@ -210,6 +210,10 @@ xe_vm_bind_op(struct anv_device *device, struct drm_xe_vm_bind args = { .vm_id = device->vm_id, .num_binds = submit->binds_len, + /* submit->queue will be set for sparse bindings which application is + * required to synchronize access. + */ + .exec_queue_id = submit->queue ? submit->queue->bind_queue_id : 0, .bind = {}, .num_syncs = num_syncs, .syncs = (uintptr_t)xe_syncs, diff --git a/src/intel/vulkan/xe/anv_queue.c b/src/intel/vulkan/xe/anv_queue.c index dcda9b8e939..b1b06d1e2df 100644 --- a/src/intel/vulkan/xe/anv_queue.c +++ b/src/intel/vulkan/xe/anv_queue.c @@ -49,6 +49,16 @@ anv_vk_priority_to_drm_sched_priority(VkQueueGlobalPriorityKHR vk_priority) } } +static void +destroy_engine(struct anv_device *device, uint32_t exec_queue_id) +{ + struct drm_xe_exec_queue_destroy destroy = { + .exec_queue_id = exec_queue_id, + }; + + intel_ioctl(device->fd, DRM_IOCTL_XE_EXEC_QUEUE_DESTROY, &destroy); +} + static VkResult create_engine(struct anv_device *device, struct anv_queue *queue, @@ -123,6 +133,22 @@ create_engine(struct anv_device *device, else queue->exec_queue_id = create.exec_queue_id; + if (!create_companion_rcs_engine && + queue_family->queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) { + struct drm_xe_engine_class_instance bind_instance = { + .engine_class = DRM_XE_ENGINE_CLASS_VM_BIND, + }; + create.num_placements = 1; + create.instances = (uintptr_t)&bind_instance; + create.extensions = 0; + ret = intel_ioctl(device->fd, DRM_IOCTL_XE_EXEC_QUEUE_CREATE, &create); + if (ret) { + destroy_engine(device, queue->exec_queue_id); + return vk_errorf(device, VK_ERROR_UNKNOWN, "Unable to create bind queue"); + } + queue->bind_queue_id = create.exec_queue_id; + } + return VK_SUCCESS; } @@ -179,25 +205,67 @@ anv_xe_wait_exec_queue_idle(struct anv_device *device, uint32_t exec_queue_id) } static void -destroy_engine(struct anv_device *device, uint32_t exec_queue_id) +bind_engine_idle(struct anv_device *device, uint32_t exec_queue_id) { - struct drm_xe_exec_queue_destroy destroy = { - .exec_queue_id = exec_queue_id, + struct drm_syncobj_create syncobj_create = {}; + struct drm_xe_sync xe_sync = { + .type = DRM_XE_SYNC_TYPE_SYNCOBJ, + .flags = DRM_XE_SYNC_FLAG_SIGNAL, }; + struct drm_xe_vm_bind args = { + .vm_id = device->vm_id, + .num_binds = 0, + .exec_queue_id = exec_queue_id, + .bind = {}, + .num_syncs = 1, + .syncs = (uintptr_t)&xe_sync, + }; + struct drm_syncobj_destroy syncobj_destroy = {}; + int ret = intel_ioctl(device->fd, DRM_IOCTL_SYNCOBJ_CREATE, &syncobj_create); - /* Application could submit a workload and before it is done, destroy the - * queue causing job timeouts in Xe KMD as it don't have permanent - * exec queues. + assert(ret == 0); + + xe_sync.handle = syncobj_create.handle; + /* Using the special args.num_binds == 0 handling to get syncobj + * signaled when the last DRM_IOCTL_XE_VM_BIND is completed. */ - anv_xe_wait_exec_queue_idle(device, exec_queue_id); - intel_ioctl(device->fd, DRM_IOCTL_XE_EXEC_QUEUE_DESTROY, &destroy); + ret = intel_ioctl(device->fd, DRM_IOCTL_XE_VM_BIND, &args); + if (ret) { + /* exec_queue could have been banned, that is why it is being destroyed + * so no assert() here + */ + goto error_bind; + } + + struct drm_syncobj_wait syncobj_wait = { + .count_handles = 1, + .timeout_nsec = INT64_MAX, + .handles = (uintptr_t)&syncobj_create.handle, + }; + ret = intel_ioctl(device->fd, DRM_IOCTL_SYNCOBJ_WAIT, &syncobj_wait); + assert(ret == 0); + +error_bind: + syncobj_destroy.handle = syncobj_create.handle, + intel_ioctl(device->fd, DRM_IOCTL_SYNCOBJ_DESTROY, &syncobj_destroy); } void anv_xe_destroy_engine(struct anv_device *device, struct anv_queue *queue) { + /* Application could submit a workload and before it is done, destroy the + * queue causing job timeouts in Xe KMD as it don't have permanent + * exec queues. + */ + anv_xe_wait_exec_queue_idle(device, queue->exec_queue_id); destroy_engine(device, queue->exec_queue_id); - if (queue->companion_rcs_id != 0) + if (queue->companion_rcs_id != 0) { + anv_xe_wait_exec_queue_idle(device, queue->companion_rcs_id); destroy_engine(device, queue->companion_rcs_id); + } + if (queue->bind_queue_id != 0) { + bind_engine_idle(device, queue->bind_queue_id); + destroy_engine(device, queue->bind_queue_id); + } }