text: // SPDX-License-Identifier: GPL-2.0-only
  /*
   * fence-chain: chain fences together in a timeline
   *
   * Copyright (C) 2018 Advanced Micro Devices, Inc.
   * Authors:
   *	Christian König <christian.koenig@amd.com>
   */
  
  #include <linux/dma-fence-chain.h>
  
  static bool dma_fence_chain_enable_signaling(struct dma_fence *fence);
  
  /**
   * dma_fence_chain_get_prev - use RCU to get a reference to the previous fence
   * @chain: chain node to get the previous node from
   *
   * Use dma_fence_get_rcu_safe to get a reference to the previous fence of the
   * chain node.
   */
  static struct dma_fence *dma_fence_chain_get_prev(struct dma_fence_chain *chain)
  {
  	struct dma_fence *prev;
  
  	rcu_read_lock();
  	prev = dma_fence_get_rcu_safe(&chain->prev);
  	rcu_read_unlock();
  	return prev;
  }
  
  /**
   * dma_fence_chain_walk - chain walking function
   * @fence: current chain node
   *
   * Walk the chain to the next node. Returns the next fence or NULL if we are at
   * the end of the chain. Garbage collects chain nodes which are already
   * signaled.
   */
  struct dma_fence *dma_fence_chain_walk(struct dma_fence *fence)
  {
  	struct dma_fence_chain *chain, *prev_chain;
  	struct dma_fence *prev, *replacement, *tmp;
  
  	chain = to_dma_fence_chain(fence);
  	if (!chain) {
  		dma_fence_put(fence);
  		return NULL;
  	}
  
  	while ((prev = dma_fence_chain_get_prev(chain))) {
  
  		prev_chain = to_dma_fence_chain(prev);
  		if (prev_chain) {
  			if (!dma_fence_is_signaled(prev_chain->fence))
  				break;
  
  			replacement = dma_fence_chain_get_prev(prev_chain);
  		} else {
  			if (!dma_fence_is_signaled(prev))
  				break;
  
  			replacement = NULL;
  		}
  
  		tmp = cmpxchg((void **)&chain->prev, (void *)prev, (void *)replacement);
  		if (tmp == prev)
  			dma_fence_put(tmp);
  		else
  			dma_fence_put(replacement);
  		dma_fence_put(prev);
  	}
  
  	dma_fence_put(fence);
  	return prev;
  }
  EXPORT_SYMBOL(dma_fence_chain_walk);
  
  /**
   * dma_fence_chain_find_seqno - find fence chain node by seqno
   * @pfence: pointer to the chain node where to start
   * @seqno: the sequence number to search for
   *
   * Advance the fence pointer to the chain node which will signal this sequence
   * number. If no sequence number is provided then this is a no-op.
   *
   * Returns EINVAL if the fence is not a chain node or the sequence number has
   * not yet advanced far enough.
   */
  int dma_fence_chain_find_seqno(struct dma_fence **pfence, uint64_t seqno)
  {
  	struct dma_fence_chain *chain;
  
  	if (!seqno)
  		return 0;
  
  	chain = to_dma_fence_chain(*pfence);
  	if (!chain || chain->base.seqno < seqno)
  		return -EINVAL;
  
  	dma_fence_chain_for_each(*pfence, &chain->base) {
  		if ((*pfence)->context != chain->base.context ||
  		    to_dma_fence_chain(*pfence)->prev_seqno < seqno)
  			break;
  	}
  	dma_fence_put(&chain->base);
  
  	return 0;
  }
  EXPORT_SYMBOL(dma_fence_chain_find_seqno);
  
  static const char *dma_fence_chain_get_driver_name(struct dma_fence *fence)
  {
          return "dma_fence_chain";
  }
  
  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)
  {
  	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_put(&chain->base);
  }
  
  static void dma_fence_chain_cb(struct dma_fence *f, struct dma_fence_cb *cb)
  {
  	struct dma_fence_chain *chain;
  
  	chain = container_of(cb, typeof(*chain), cb);
  	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);
  
  	dma_fence_get(&head->base);
  	dma_fence_chain_for_each(fence, &head->base) {
  		struct dma_fence_chain *chain = to_dma_fence_chain(fence);
  		struct dma_fence *f = chain ? chain->fence : fence;
  
  		dma_fence_get(f);
  		if (!dma_fence_add_callback(f, &head->cb, dma_fence_chain_cb)) {
  			dma_fence_put(fence);
  			return true;
  		}
  		dma_fence_put(f);
  	}
  	dma_fence_put(&head->base);
  	return false;
  }
  
  static bool dma_fence_chain_signaled(struct dma_fence *fence)
  {
  	dma_fence_chain_for_each(fence, fence) {
  		struct dma_fence_chain *chain = to_dma_fence_chain(fence);
  		struct dma_fence *f = chain ? chain->fence : fence;
  
  		if (!dma_fence_is_signaled(f)) {
  			dma_fence_put(fence);
  			return false;
  		}
  	}
  
  	return true;
  }
  
  static void dma_fence_chain_release(struct dma_fence *fence)
  {
  	struct dma_fence_chain *chain = to_dma_fence_chain(fence);
  	struct dma_fence *prev;
  
  	/* Manually unlink the chain as much as possible to avoid recursion
  	 * and potential stack overflow.
  	 */
  	while ((prev = rcu_dereference_protected(chain->prev, true))) {
  		struct dma_fence_chain *prev_chain;
  
  		if (kref_read(&prev->refcount) > 1)
  		       break;
  
  		prev_chain = to_dma_fence_chain(prev);
  		if (!prev_chain)
  			break;
  
  		/* No need for atomic operations since we hold the last
  		 * reference to prev_chain.
  		 */
  		chain->prev = prev_chain->prev;
  		RCU_INIT_POINTER(prev_chain->prev, NULL);
  		dma_fence_put(prev);
  	}
  	dma_fence_put(prev);
  
  	dma_fence_put(chain->fence);
  	dma_fence_free(fence);
  }
  
  const struct dma_fence_ops dma_fence_chain_ops = {
  	.use_64bit_seqno = true,
  	.get_driver_name = dma_fence_chain_get_driver_name,
  	.get_timeline_name = dma_fence_chain_get_timeline_name,
  	.enable_signaling = dma_fence_chain_enable_signaling,
  	.signaled = dma_fence_chain_signaled,
  	.release = dma_fence_chain_release,
  };
  EXPORT_SYMBOL(dma_fence_chain_ops);
  
  /**
   * dma_fence_chain_init - initialize a fence chain
   * @chain: the chain node to initialize
   * @prev: the previous fence
   * @fence: the current fence
   *
   * Initialize a new chain node and either start a new chain or add the node to
   * the existing chain of the previous fence.
   */
  void dma_fence_chain_init(struct dma_fence_chain *chain,
  			  struct dma_fence *prev,
  			  struct dma_fence *fence,
  			  uint64_t seqno)
  {
  	struct dma_fence_chain *prev_chain = to_dma_fence_chain(prev);
  	uint64_t context;
  
  	spin_lock_init(&chain->lock);
  	rcu_assign_pointer(chain->prev, prev);
  	chain->fence = fence;
  	chain->prev_seqno = 0;
  	init_irq_work(&chain->work, dma_fence_chain_irq_work);
  
  	/* Try to reuse the context of the previous chain node. */
  	if (prev_chain && __dma_fence_is_later(seqno, prev->seqno, prev->ops)) {
  		context = prev->context;
  		chain->prev_seqno = prev->seqno;
  	} else {
  		context = dma_fence_context_alloc(1);
  		/* Make sure that we always have a valid sequence number. */
  		if (prev_chain)
  			seqno = max(prev->seqno, seqno);
  	}
  
  	dma_fence_init(&chain->base, &dma_fence_chain_ops,
  		       &chain->lock, context, seqno);
  }
  EXPORT_SYMBOL(dma_fence_chain_init);
  