| // SPDX-License-Identifier: MIT |
| /* |
| * Copyright © 2021 Intel Corporation |
| */ |
| |
| #include "xe_hw_fence.h" |
| |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| |
| #include "xe_bo.h" |
| #include "xe_device.h" |
| #include "xe_gt.h" |
| #include "xe_hw_engine.h" |
| #include "xe_macros.h" |
| #include "xe_map.h" |
| #include "xe_trace.h" |
| |
| static struct kmem_cache *xe_hw_fence_slab; |
| |
| int __init xe_hw_fence_module_init(void) |
| { |
| xe_hw_fence_slab = kmem_cache_create("xe_hw_fence", |
| sizeof(struct xe_hw_fence), 0, |
| SLAB_HWCACHE_ALIGN, NULL); |
| if (!xe_hw_fence_slab) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| void xe_hw_fence_module_exit(void) |
| { |
| rcu_barrier(); |
| kmem_cache_destroy(xe_hw_fence_slab); |
| } |
| |
| static struct xe_hw_fence *fence_alloc(void) |
| { |
| return kmem_cache_zalloc(xe_hw_fence_slab, GFP_KERNEL); |
| } |
| |
| static void fence_free(struct rcu_head *rcu) |
| { |
| struct xe_hw_fence *fence = |
| container_of(rcu, struct xe_hw_fence, dma.rcu); |
| |
| if (!WARN_ON_ONCE(!fence)) |
| kmem_cache_free(xe_hw_fence_slab, fence); |
| } |
| |
| static void hw_fence_irq_run_cb(struct irq_work *work) |
| { |
| struct xe_hw_fence_irq *irq = container_of(work, typeof(*irq), work); |
| struct xe_hw_fence *fence, *next; |
| bool tmp; |
| |
| tmp = dma_fence_begin_signalling(); |
| spin_lock(&irq->lock); |
| if (irq->enabled) { |
| list_for_each_entry_safe(fence, next, &irq->pending, irq_link) { |
| struct dma_fence *dma_fence = &fence->dma; |
| |
| trace_xe_hw_fence_try_signal(fence); |
| if (dma_fence_is_signaled_locked(dma_fence)) { |
| trace_xe_hw_fence_signal(fence); |
| list_del_init(&fence->irq_link); |
| dma_fence_put(dma_fence); |
| } |
| } |
| } |
| spin_unlock(&irq->lock); |
| dma_fence_end_signalling(tmp); |
| } |
| |
| void xe_hw_fence_irq_init(struct xe_hw_fence_irq *irq) |
| { |
| spin_lock_init(&irq->lock); |
| init_irq_work(&irq->work, hw_fence_irq_run_cb); |
| INIT_LIST_HEAD(&irq->pending); |
| irq->enabled = true; |
| } |
| |
| void xe_hw_fence_irq_finish(struct xe_hw_fence_irq *irq) |
| { |
| struct xe_hw_fence *fence, *next; |
| unsigned long flags; |
| int err; |
| bool tmp; |
| |
| if (XE_WARN_ON(!list_empty(&irq->pending))) { |
| tmp = dma_fence_begin_signalling(); |
| spin_lock_irqsave(&irq->lock, flags); |
| list_for_each_entry_safe(fence, next, &irq->pending, irq_link) { |
| list_del_init(&fence->irq_link); |
| err = dma_fence_signal_locked(&fence->dma); |
| dma_fence_put(&fence->dma); |
| XE_WARN_ON(err); |
| } |
| spin_unlock_irqrestore(&irq->lock, flags); |
| dma_fence_end_signalling(tmp); |
| } |
| } |
| |
| void xe_hw_fence_irq_run(struct xe_hw_fence_irq *irq) |
| { |
| irq_work_queue(&irq->work); |
| } |
| |
| void xe_hw_fence_irq_stop(struct xe_hw_fence_irq *irq) |
| { |
| spin_lock_irq(&irq->lock); |
| irq->enabled = false; |
| spin_unlock_irq(&irq->lock); |
| } |
| |
| void xe_hw_fence_irq_start(struct xe_hw_fence_irq *irq) |
| { |
| spin_lock_irq(&irq->lock); |
| irq->enabled = true; |
| spin_unlock_irq(&irq->lock); |
| |
| irq_work_queue(&irq->work); |
| } |
| |
| void xe_hw_fence_ctx_init(struct xe_hw_fence_ctx *ctx, struct xe_gt *gt, |
| struct xe_hw_fence_irq *irq, const char *name) |
| { |
| ctx->gt = gt; |
| ctx->irq = irq; |
| ctx->dma_fence_ctx = dma_fence_context_alloc(1); |
| ctx->next_seqno = XE_FENCE_INITIAL_SEQNO; |
| snprintf(ctx->name, sizeof(ctx->name), "%s", name); |
| } |
| |
| void xe_hw_fence_ctx_finish(struct xe_hw_fence_ctx *ctx) |
| { |
| } |
| |
| static struct xe_hw_fence *to_xe_hw_fence(struct dma_fence *fence); |
| |
| static struct xe_hw_fence_irq *xe_hw_fence_irq(struct xe_hw_fence *fence) |
| { |
| return container_of(fence->dma.lock, struct xe_hw_fence_irq, lock); |
| } |
| |
| static const char *xe_hw_fence_get_driver_name(struct dma_fence *dma_fence) |
| { |
| struct xe_hw_fence *fence = to_xe_hw_fence(dma_fence); |
| |
| return dev_name(fence->xe->drm.dev); |
| } |
| |
| static const char *xe_hw_fence_get_timeline_name(struct dma_fence *dma_fence) |
| { |
| struct xe_hw_fence *fence = to_xe_hw_fence(dma_fence); |
| |
| return fence->name; |
| } |
| |
| static bool xe_hw_fence_signaled(struct dma_fence *dma_fence) |
| { |
| struct xe_hw_fence *fence = to_xe_hw_fence(dma_fence); |
| struct xe_device *xe = fence->xe; |
| u32 seqno = xe_map_rd(xe, &fence->seqno_map, 0, u32); |
| |
| return dma_fence->error || |
| !__dma_fence_is_later(dma_fence->seqno, seqno, dma_fence->ops); |
| } |
| |
| static bool xe_hw_fence_enable_signaling(struct dma_fence *dma_fence) |
| { |
| struct xe_hw_fence *fence = to_xe_hw_fence(dma_fence); |
| struct xe_hw_fence_irq *irq = xe_hw_fence_irq(fence); |
| |
| dma_fence_get(dma_fence); |
| list_add_tail(&fence->irq_link, &irq->pending); |
| |
| /* SW completed (no HW IRQ) so kick handler to signal fence */ |
| if (xe_hw_fence_signaled(dma_fence)) |
| xe_hw_fence_irq_run(irq); |
| |
| return true; |
| } |
| |
| static void xe_hw_fence_release(struct dma_fence *dma_fence) |
| { |
| struct xe_hw_fence *fence = to_xe_hw_fence(dma_fence); |
| |
| XE_WARN_ON(!list_empty(&fence->irq_link)); |
| call_rcu(&dma_fence->rcu, fence_free); |
| } |
| |
| static const struct dma_fence_ops xe_hw_fence_ops = { |
| .get_driver_name = xe_hw_fence_get_driver_name, |
| .get_timeline_name = xe_hw_fence_get_timeline_name, |
| .enable_signaling = xe_hw_fence_enable_signaling, |
| .signaled = xe_hw_fence_signaled, |
| .release = xe_hw_fence_release, |
| }; |
| |
| static struct xe_hw_fence *to_xe_hw_fence(struct dma_fence *fence) |
| { |
| if (XE_WARN_ON(fence->ops != &xe_hw_fence_ops)) |
| return NULL; |
| |
| return container_of(fence, struct xe_hw_fence, dma); |
| } |
| |
| /** |
| * xe_hw_fence_alloc() - Allocate an hw fence. |
| * |
| * Allocate but don't initialize an hw fence. |
| * |
| * Return: Pointer to the allocated fence or |
| * negative error pointer on error. |
| */ |
| struct dma_fence *xe_hw_fence_alloc(void) |
| { |
| struct xe_hw_fence *hw_fence = fence_alloc(); |
| |
| if (!hw_fence) |
| return ERR_PTR(-ENOMEM); |
| |
| return &hw_fence->dma; |
| } |
| |
| /** |
| * xe_hw_fence_free() - Free an hw fence. |
| * @fence: Pointer to the fence to free. |
| * |
| * Frees an hw fence that hasn't yet been |
| * initialized. |
| */ |
| void xe_hw_fence_free(struct dma_fence *fence) |
| { |
| fence_free(&fence->rcu); |
| } |
| |
| /** |
| * xe_hw_fence_init() - Initialize an hw fence. |
| * @fence: Pointer to the fence to initialize. |
| * @ctx: Pointer to the struct xe_hw_fence_ctx fence context. |
| * @seqno_map: Pointer to the map into where the seqno is blitted. |
| * |
| * Initializes a pre-allocated hw fence. |
| * After initialization, the fence is subject to normal |
| * dma-fence refcounting. |
| */ |
| void xe_hw_fence_init(struct dma_fence *fence, struct xe_hw_fence_ctx *ctx, |
| struct iosys_map seqno_map) |
| { |
| struct xe_hw_fence *hw_fence = |
| container_of(fence, typeof(*hw_fence), dma); |
| |
| hw_fence->xe = gt_to_xe(ctx->gt); |
| snprintf(hw_fence->name, sizeof(hw_fence->name), "%s", ctx->name); |
| hw_fence->seqno_map = seqno_map; |
| INIT_LIST_HEAD(&hw_fence->irq_link); |
| |
| dma_fence_init(fence, &xe_hw_fence_ops, &ctx->irq->lock, |
| ctx->dma_fence_ctx, ctx->next_seqno++); |
| |
| trace_xe_hw_fence_create(hw_fence); |
| } |