When first user starts waiting on a not yet signaled fence of a chain
link, a dma_fence_chain callback is added to a user fence of that link.
When the user fence of that chain link is then signaled, the chain is
traversed in search for a first not signaled link and the callback is
rearmed on a user fence of that link.

Since chain fences may be exposed to user space, e.g. over drm_syncobj
IOCTLs, users may start waiting on any link of the chain, then many links
of a chain may have signaling enabled and their callbacks added to their
user fences.  Once an arbitrary user fence is signaled, all
dma_fence_chain callbacks added to it so far must be rearmed to another
user fence of the chain.  In extreme scenarios, when all N links of a
chain are awaited and then signaled in reverse order, the dma_fence_chain
callback may be called up to N * (N + 1) / 2 times (an arithmetic series).

To avoid that potential excessive accumulation of dma_fence_chain
callbacks, rearm a trimmed-down, signal only callback version to the base
fence of a previous link, if not yet signaled, otherwise just signal the
base fence of the current link instead of traversing the chain in search
for a first not signaled link and moving all callbacks collected so far to
a user fence of that link.

Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/12904
Suggested-by: Chris Wilson <chris.p.wil...@linux.intel.com>
Signed-off-by: Janusz Krzysztofik <janusz.krzyszto...@linux.intel.com>
---
 drivers/dma-buf/dma-fence-chain.c | 101 +++++++++++++++++++++++++-----
 1 file changed, 84 insertions(+), 17 deletions(-)

diff --git a/drivers/dma-buf/dma-fence-chain.c 
b/drivers/dma-buf/dma-fence-chain.c
index a8a90acf4f34d..90eff264ee05c 100644
--- a/drivers/dma-buf/dma-fence-chain.c
+++ b/drivers/dma-buf/dma-fence-chain.c
@@ -119,46 +119,113 @@ static const char 
*dma_fence_chain_get_timeline_name(struct dma_fence *fence)
         return "unbound";
 }
 
-static void dma_fence_chain_irq_work(struct irq_work *work)
+static void signal_irq_work(struct irq_work *work)
 {
        struct dma_fence_chain *chain;
 
        chain = container_of(work, typeof(*chain), work);
 
-       /* Try to rearm the callback */
-       if (!dma_fence_chain_enable_signaling(&chain->base))
-               /* Ok, we are done. No more unsignaled fences left */
-               dma_fence_signal(&chain->base);
+       dma_fence_signal(&chain->base);
        dma_fence_put(&chain->base);
 }
 
-static void dma_fence_chain_cb(struct dma_fence *f, struct dma_fence_cb *cb)
+static void signal_cb(struct dma_fence *f, struct dma_fence_cb *cb)
+{
+       struct dma_fence_chain *chain;
+
+       chain = container_of(cb, typeof(*chain), cb);
+       init_irq_work(&chain->work, signal_irq_work);
+       irq_work_queue(&chain->work);
+}
+
+static void rearm_irq_work(struct irq_work *work)
+{
+       struct dma_fence_chain *chain;
+       struct dma_fence *prev;
+
+       chain = container_of(work, typeof(*chain), work);
+
+       rcu_read_lock();
+       prev = rcu_dereference(chain->prev);
+       if (prev && dma_fence_add_callback(prev, &chain->cb, signal_cb))
+               prev = NULL;
+       rcu_read_unlock();
+       if (prev)
+               return;
+
+       /* Ok, we are done. No more unsignaled fences left */
+       signal_irq_work(work);
+}
+
+static inline bool fence_is_signaled__nested(struct dma_fence *fence)
+{
+       if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))
+               return true;
+
+       if (fence->ops->signaled && fence->ops->signaled(fence)) {
+               unsigned long flags;
+
+               spin_lock_irqsave_nested(fence->lock, flags, 
SINGLE_DEPTH_NESTING);
+               dma_fence_signal_locked(fence);
+               spin_unlock_irqrestore(fence->lock, flags);
+
+               return true;
+       }
+
+       return false;
+}
+
+static bool prev_is_signaled(struct dma_fence_chain *chain)
+{
+       struct dma_fence *prev;
+       bool result;
+
+       rcu_read_lock();
+       prev = rcu_dereference(chain->prev);
+       result = !prev || fence_is_signaled__nested(prev);
+       rcu_read_unlock();
+
+       return result;
+}
+
+static void rearm_or_signal_cb(struct dma_fence *f, struct dma_fence_cb *cb)
 {
        struct dma_fence_chain *chain;
 
        chain = container_of(cb, typeof(*chain), cb);
-       init_irq_work(&chain->work, dma_fence_chain_irq_work);
+       if (prev_is_signaled(chain)) {
+               /* Ok, we are done. No more unsignaled fences left */
+               init_irq_work(&chain->work, signal_irq_work);
+       } else {
+               /* Try to rearm the callback */
+               init_irq_work(&chain->work, rearm_irq_work);
+       }
+
        irq_work_queue(&chain->work);
-       dma_fence_put(f);
 }
 
 static bool dma_fence_chain_enable_signaling(struct dma_fence *fence)
 {
        struct dma_fence_chain *head = to_dma_fence_chain(fence);
+       int err = -ENOENT;
 
-       dma_fence_get(&head->base);
-       dma_fence_chain_for_each(fence, &head->base) {
-               struct dma_fence *f = dma_fence_chain_contained(fence);
+       if (WARN_ON(!head))
+               return false;
 
-               dma_fence_get(f);
-               if (!dma_fence_add_callback(f, &head->cb, dma_fence_chain_cb)) {
+       dma_fence_get(fence);
+       if (head->fence)
+               err = dma_fence_add_callback(head->fence, &head->cb, 
rearm_or_signal_cb);
+       if (err) {
+               if (prev_is_signaled(head)) {
                        dma_fence_put(fence);
-                       return true;
+               } else {
+                       init_irq_work(&head->work, rearm_irq_work);
+                       irq_work_queue(&head->work);
+                       err = 0;
                }
-               dma_fence_put(f);
        }
-       dma_fence_put(&head->base);
-       return false;
+
+       return !err;
 }
 
 static bool dma_fence_chain_signaled(struct dma_fence *fence)
-- 
2.50.1

Reply via email to