From 4356370caf36bc9f7ec9a7a376b9876d53b92878 Mon Sep 17 00:00:00 2001 From: Tejas Upadhyay Date: Thu, 3 Sep 2026 21:45:55 +0530 Subject: [PATCH] drm/xe: Link LRC BO and its execution queue with safe lifetime rules Introduce an execution queue back-pointer (`q`) within `struct xe_bo`, primarily for Logical Ring Context (LRC) Buffer Objects. This back-pointer allows the driver to identify and execute targeted corrective actions on a specific queue if its associated LRC BO encounters errors like memory corruption or eviction. Because this back-pointer takes no reference on its target execution queue, strict lifetime and serialization rules are implemented to prevent concurrent readers from encountering use-after-free or dangling pointer bugs: - Encapsulate tracking logic inside xe_exec_queue_set_lrc_bo_backpointer() and xe_exec_queue_clear_lrc_bo_backpointer(). - Explicitly wrap all back-pointer writes and clears under the BO's dma_resv lock via xe_bo_lock(). Readers must hold this same lock across both the pointer read and its subsequent xe_exec_queue_get_unless_zero() call to guarantee serialization against teardown. - Defer publishing the back-pointer until the very end of xe_exec_queue_create(). This ensures that early initialization failure paths (which bypass the kref mechanism and immediately free the queue structure) never leak a transient pointer to concurrent readers. - Clear the back-pointer at the absolute top of __xe_exec_queue_fini(). This strips the pointer before q->ops->fini() destroys the hardware backend, ensuring that any reader holding the BO lock either observes a fully functional queue or NULL. For multi-queue engines, secondary LRC BOs safely point to the primary queue, which is guaranteed to outlive the teardown pass due to active references held by its secondaries. V4 (MattB): - Add LRC BO's execution queue safe lifetime rules V3 (Sashiko): - Use 8-byte placeholder structure compatibility for non-LRC BO cases. - Wrap assignments and clears securely under dma_resv locks. V2 (Matt B): - Add native support handling multi-queue configuration tracking. Assisted-by: Copilot:claude-opus-4.6 Reviewed-by: Andi Shyti Reviewed-by: Himal Prasad Ghimiray Signed-off-by: Tejas Upadhyay Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260903161553.528932-19-tejas.upadhyay@intel.com --- drivers/gpu/drm/xe/xe_bo_types.h | 8 ++++ drivers/gpu/drm/xe/xe_exec_queue.c | 64 ++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_bo_types.h b/drivers/gpu/drm/xe/xe_bo_types.h index e45f24301050..0eb93052c1d5 100644 --- a/drivers/gpu/drm/xe/xe_bo_types.h +++ b/drivers/gpu/drm/xe/xe_bo_types.h @@ -20,6 +20,7 @@ struct xe_device; struct xe_mem_pool_node; struct xe_vm; +struct xe_exec_queue; #define XE_BO_MAX_PLACEMENTS 3 @@ -42,6 +43,13 @@ struct xe_bo { u32 flags; /** @vm: VM this BO is attached to, for extobj this will be NULL */ struct xe_vm *vm; + /** + * @q: Queue this BO is attached to, mostly for LRC BO, NULL otherwise. + * Protected by the BO dma_resv: readers must hold it across both the + * read and xe_exec_queue_get_unless_zero(). The BO holds no reference + * on the queue. + */ + struct xe_exec_queue *q; /** @tile: Tile this BO is attached to (kernel BO only) */ struct xe_tile *tile; /** @placements: valid placements for this BO */ diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c index c4213bb9c137..f3ea4807ac33 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue.c +++ b/drivers/gpu/drm/xe/xe_exec_queue.c @@ -322,10 +322,66 @@ struct xe_lrc *xe_exec_queue_lrc(struct xe_exec_queue *q) return q->lrc[0]; } +/* + * Publish the queue back-pointer in the LRC BOs. + * + * The BO holds no reference on the queue; the queue owns the LRCs, and + * therefore the BOs, instead. The back-pointer is made safe by two rules: + * + * - It is published only once the queue is fully constructed and can no + * longer be destroyed by an error path that bypasses the kref (see + * xe_exec_queue_create()), so a reader that successfully takes a + * reference can never be handed a queue that is freed without going + * through xe_exec_queue_destroy(). + * + * - It is written and cleared under the BO dma_resv. Readers must hold + * the same lock across both the read and + * xe_exec_queue_get_unless_zero(), which serializes them against + * xe_exec_queue_clear_lrc_bo_backpointer() below. + * + * For a multi-queue group the LRC BOs point at the primary queue, which is + * kept alive by the reference every secondary holds on it. + */ +static void xe_exec_queue_set_lrc_bo_backpointer(struct xe_exec_queue *q) +{ + struct xe_exec_queue *primary = xe_exec_queue_multi_queue_primary(q); + int i; + + for (i = 0; i < q->width; ++i) { + struct xe_bo *bo = q->lrc[i]->bo; + + xe_bo_lock(bo, false); + bo->q = primary; + xe_bo_unlock(bo); + } +} + +/* + * Drop the queue back-pointer before anything belonging to the queue is + * torn down. This must happen before q->ops->fini(), otherwise a reader + * could take a reference and then operate on an already destroyed backend. + */ +static void xe_exec_queue_clear_lrc_bo_backpointer(struct xe_exec_queue *q) +{ + int i; + + for (i = 0; i < q->width; ++i) { + struct xe_bo *bo = q->lrc[i] ? q->lrc[i]->bo : NULL; + + if (!bo) + continue; + + xe_bo_lock(bo, false); + bo->q = NULL; + xe_bo_unlock(bo); + } +} + static void __xe_exec_queue_fini(struct xe_exec_queue *q) { int i; + xe_exec_queue_clear_lrc_bo_backpointer(q); q->ops->fini(q); for (i = 0; i < q->width; ++i) @@ -450,6 +506,14 @@ struct xe_exec_queue *xe_exec_queue_create(struct xe_device *xe, struct xe_vm *v goto err_post_init; } + /* + * Publish the LRC BO back-pointers last: past this point the queue can + * only be destroyed through xe_exec_queue_destroy(), so a concurrent + * reader that takes a reference via bo->q cannot race with the + * kref-bypassing error paths below. + */ + xe_exec_queue_set_lrc_bo_backpointer(q); + return q; err_post_init: