diff mbox series

[05/10] dma-buf: Exercise dma-fence-chain under selftests

Message ID 20200403091300.14734-5-chris@chris-wilson.co.uk (mailing list archive)
State New, archived
Headers show
Series [01/10] drm/i915/selftests: Add request throughput measurement to perf | expand

Commit Message

Chris Wilson April 3, 2020, 9:12 a.m. UTC
A few very simple testcases to exercise the dma-fence-chain API.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
---
 drivers/dma-buf/Makefile             |   3 +-
 drivers/dma-buf/selftests.h          |   1 +
 drivers/dma-buf/st-dma-fence-chain.c | 713 +++++++++++++++++++++++++++
 3 files changed, 716 insertions(+), 1 deletion(-)
 create mode 100644 drivers/dma-buf/st-dma-fence-chain.c

Comments

Venkata Sandeep Dhanalakota April 8, 2020, 7:49 p.m. UTC | #1
On 20/04/03 10:12, Chris Wilson wrote:
> A few very simple testcases to exercise the dma-fence-chain API.
> 
> Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
> ---
>  drivers/dma-buf/Makefile             |   3 +-
>  drivers/dma-buf/selftests.h          |   1 +
>  drivers/dma-buf/st-dma-fence-chain.c | 713 +++++++++++++++++++++++++++
>  3 files changed, 716 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/dma-buf/st-dma-fence-chain.c
> 
> diff --git a/drivers/dma-buf/Makefile b/drivers/dma-buf/Makefile
> index 9c190026bfab..995e05f609ff 100644
> --- a/drivers/dma-buf/Makefile
> +++ b/drivers/dma-buf/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_UDMABUF)		+= udmabuf.o
>  
>  dmabuf_selftests-y := \
>  	selftest.o \
> -	st-dma-fence.o
> +	st-dma-fence.o \
> +	st-dma-fence-chain.o
>  
>  obj-$(CONFIG_DMABUF_SELFTESTS)	+= dmabuf_selftests.o
> diff --git a/drivers/dma-buf/selftests.h b/drivers/dma-buf/selftests.h
> index 5320386f02e5..55918ef9adab 100644
> --- a/drivers/dma-buf/selftests.h
> +++ b/drivers/dma-buf/selftests.h
> @@ -11,3 +11,4 @@
>   */
>  selftest(sanitycheck, __sanitycheck__) /* keep first (igt selfcheck) */
>  selftest(dma_fence, dma_fence)
> +selftest(dma_fence_chain, dma_fence_chain)
> diff --git a/drivers/dma-buf/st-dma-fence-chain.c b/drivers/dma-buf/st-dma-fence-chain.c
> new file mode 100644
> index 000000000000..bd08ba67b03b
> --- /dev/null
> +++ b/drivers/dma-buf/st-dma-fence-chain.c
> @@ -0,0 +1,713 @@
> +// SPDX-License-Identifier: MIT
> +
> +/*
> + * Copyright © 2019 Intel Corporation
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/dma-fence.h>
> +#include <linux/dma-fence-chain.h>
> +#include <linux/kernel.h>
> +#include <linux/kthread.h>
> +#include <linux/mm.h>
> +#include <linux/sched/signal.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/random.h>
> +
> +#include "selftest.h"
> +
> +static struct kmem_cache *slab_fences;
> +
> +static inline struct mock_fence {
> +	struct dma_fence base;
> +	spinlock_t lock;
> +} *to_mock_fence(struct dma_fence *f) {
> +	return container_of(f, struct mock_fence, base);
> +}
> +
> +static const char *mock_name(struct dma_fence *f)
> +{
> +	return "mock";
> +}
> +
> +static void mock_fence_release(struct dma_fence *f)
> +{
> +	kmem_cache_free(slab_fences, to_mock_fence(f));
> +}
> +
> +static const struct dma_fence_ops mock_ops = {
> +	.get_driver_name = mock_name,
> +	.get_timeline_name = mock_name,
> +	.release = mock_fence_release,
> +};
> +
> +static struct dma_fence *mock_fence(void)
> +{
> +	struct mock_fence *f;
> +
> +	f = kmem_cache_alloc(slab_fences, GFP_KERNEL);
> +	if (!f)
> +		return NULL;
> +
> +	spin_lock_init(&f->lock);
> +	dma_fence_init(&f->base, &mock_ops, &f->lock, 0, 0);
> +
> +	return &f->base;
> +}
> +
> +static inline struct mock_chain {
> +	struct dma_fence_chain base;
> +} *to_mock_chain(struct dma_fence *f) {
> +	return container_of(f, struct mock_chain, base.base);
> +}
> +
> +static struct dma_fence *mock_chain(struct dma_fence *prev,
> +				    struct dma_fence *fence,
> +				    u64 seqno)
> +{
> +	struct mock_chain *f;
> +
> +	f = kmalloc(sizeof(*f), GFP_KERNEL);
> +	if (!f)
> +		return NULL;
> +
> +	dma_fence_chain_init(&f->base,
> +			     dma_fence_get(prev),
> +			     dma_fence_get(fence),
> +			     seqno);
> +
> +	return &f->base.base;
> +}
> +
> +static int sanitycheck(void *arg)
> +{
> +	struct dma_fence *f, *chain;
> +	int err = 0;
> +
> +	f = mock_fence();
> +	if (!f)
> +		return -ENOMEM;
> +
> +	chain = mock_chain(NULL, f, 1);
> +	if (!chain)
> +		err = -ENOMEM;
> +
> +	dma_fence_signal(f);
> +	dma_fence_put(f);
> +
> +	dma_fence_put(chain);
> +
> +	return err;
> +}
> +
> +struct fence_chains {
> +	unsigned int chain_length;
> +	struct dma_fence **fences;
> +	struct dma_fence **chains;
> +
> +	struct dma_fence *tail;
> +};
> +
> +static uint64_t seqno_inc(unsigned int i)
> +{
> +	return i + 1;
> +}
> +
> +static int fence_chains_init(struct fence_chains *fc, unsigned int count,
> +			     uint64_t (*seqno_fn)(unsigned int))
> +{
> +	unsigned int i;
> +	int err = 0;
> +
> +	fc->chains = kvmalloc_array(count, sizeof(*fc->chains),
> +				    GFP_KERNEL | __GFP_ZERO);
> +	if (!fc->chains)
> +		return -ENOMEM;
> +
> +	fc->fences = kvmalloc_array(count, sizeof(*fc->fences),
> +				    GFP_KERNEL | __GFP_ZERO);
> +	if (!fc->fences) {
> +		err = -ENOMEM;
> +		goto err_chains;
> +	}
> +
> +	fc->tail = NULL;
> +	for (i = 0; i < count; i++) {
> +		fc->fences[i] = mock_fence();
> +		if (!fc->fences[i]) {
> +			err = -ENOMEM;
> +			goto unwind;
> +		}
> +
> +		fc->chains[i] = mock_chain(fc->tail,
> +					   fc->fences[i],
> +					   seqno_fn(i));
> +		if (!fc->chains[i]) {
> +			err = -ENOMEM;
> +			goto unwind;
> +		}
> +
> +		fc->tail = fc->chains[i];
> +	}
> +
> +	fc->chain_length = i;
> +	return 0;
> +
> +unwind:
> +	for (i = 0; i < count; i++) {
> +		dma_fence_put(fc->fences[i]);
> +		dma_fence_put(fc->chains[i]);
> +	}
> +	kvfree(fc->fences);
> +err_chains:
> +	kvfree(fc->chains);
> +	return err;
> +}
> +
> +static void fence_chains_fini(struct fence_chains *fc)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < fc->chain_length; i++) {
> +		dma_fence_signal(fc->fences[i]);
> +		dma_fence_put(fc->fences[i]);
> +	}
> +	kvfree(fc->fences);
> +
> +	for (i = 0; i < fc->chain_length; i++)
> +		dma_fence_put(fc->chains[i]);
> +	kvfree(fc->chains);
> +}
> +
> +static int find_seqno(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct dma_fence *fence;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	fence = dma_fence_get(fc.tail);
> +	err = dma_fence_chain_find_seqno(&fence, 0);
> +	dma_fence_put(fence);
> +	if (err) {
> +		pr_err("Reported %d for find_seqno(0)!\n", err);
> +		goto err;
> +	}
> +
> +	for (i = 0; i < fc.chain_length; i++) {
> +		fence = dma_fence_get(fc.tail);
> +		err = dma_fence_chain_find_seqno(&fence, i + 1);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Reported %d for find_seqno(%d:%d)!\n",
> +			       err, fc.chain_length + 1, i + 1);
> +			goto err;
> +		}
> +		if (fence != fc.chains[i]) {
> +			pr_err("Incorrect fence reported by find_seqno(%d:%d)\n",
> +			       fc.chain_length + 1, i + 1);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, i + 1);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Error reported for finding self\n");
> +			goto err;
> +		}
> +		if (fence != fc.chains[i]) {
> +			pr_err("Incorrect fence reported by find self\n");
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, i + 2);
> +		dma_fence_put(fence);
> +		if (!err) {
> +			pr_err("Error not reported for future fence: find_seqno(%d:%d)!\n",
> +			       i + 1, i + 2);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, i);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Error reported for previous fence!\n");
> +			goto err;
> +		}
> +		if (i > 0 && fence != fc.chains[i - 1]) {
> +			pr_err("Incorrect fence reported by find_seqno(%d:%d)\n",
> +			       i + 1, i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int find_signaled(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct dma_fence *fence;
> +	int err;
> +
> +	err = fence_chains_init(&fc, 2, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	dma_fence_signal(fc.fences[0]);
> +
> +	fence = dma_fence_get(fc.tail);
> +	err = dma_fence_chain_find_seqno(&fence, 1);
> +	dma_fence_put(fence);
> +	if (err) {
> +		pr_err("Reported %d for find_seqno()!\n", err);
> +		goto err;
> +	}
> +
> +	if (fence && fence != fc.chains[0]) {
> +		pr_err("Incorrect chain-fence.seqno:%lld reported for completed seqno:1\n",
> +		       fence->seqno);
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, 1);
> +		dma_fence_put(fence);
> +		if (err)
> +			pr_err("Reported %d for finding self!\n", err);
> +
> +		err = -EINVAL;
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int find_out_of_order(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct dma_fence *fence;
> +	int err;
> +
> +	err = fence_chains_init(&fc, 3, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	dma_fence_signal(fc.fences[1]);
> +
> +	fence = dma_fence_get(fc.tail);
> +	err = dma_fence_chain_find_seqno(&fence, 2);
> +	dma_fence_put(fence);
> +	if (err) {
> +		pr_err("Reported %d for find_seqno()!\n", err);
> +		goto err;
> +	}
> +
> +	if (fence && fence != fc.chains[1]) {
> +		pr_err("Incorrect chain-fence.seqno:%lld reported for completed seqno:2\n",
> +		       fence->seqno);
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, 2);
> +		dma_fence_put(fence);
> +		if (err)
> +			pr_err("Reported %d for finding self!\n", err);
> +
> +		err = -EINVAL;
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static uint64_t seqno_inc2(unsigned int i)
> +{
> +	return 2 * i + 2;
> +}
> +
> +static int find_gap(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct dma_fence *fence;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64, seqno_inc2);
> +	if (err)
> +		return err;
> +
> +	for (i = 0; i < fc.chain_length; i++) {
> +		fence = dma_fence_get(fc.tail);
> +		err = dma_fence_chain_find_seqno(&fence, 2 * i + 1);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Reported %d for find_seqno(%d:%d)!\n",
> +			       err, fc.chain_length + 1, 2 * i + 1);
> +			goto err;
> +		}
> +		if (fence != fc.chains[i]) {
> +			pr_err("Incorrect fence.seqno:%lld reported by find_seqno(%d:%d)\n",
> +			       fence->seqno,
> +			       fc.chain_length + 1,
> +			       2 * i + 1);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, 2 * i + 2);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Error reported for finding self\n");
> +			goto err;
> +		}
> +		if (fence != fc.chains[i]) {
> +			pr_err("Incorrect fence reported by find self\n");
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +struct find_race {
> +	struct fence_chains fc;
> +	atomic_t children;
> +};
> +
> +static int __find_race(void *arg)
> +{
> +	struct find_race *data = arg;
> +	int err = 0;
> +
> +	while (!kthread_should_stop()) {
> +		struct dma_fence *fence = dma_fence_get(data->fc.tail);
> +		int seqno;
> +
> +		seqno = prandom_u32_max(data->fc.chain_length) + 1;
> +
> +		err = dma_fence_chain_find_seqno(&fence, seqno);
> +		if (err) {
> +			pr_err("Failed to find fence seqno:%d\n",
> +			       seqno);
> +			dma_fence_put(fence);
> +			break;
> +		}
> +		if (!fence)
> +			goto signal;
> +
> +		err = dma_fence_chain_find_seqno(&fence, seqno);
> +		if (err) {
> +			pr_err("Reported an invalid fence for find-self:%d\n",
> +			       seqno);
> +			dma_fence_put(fence);
> +			break;
> +		}
> +
> +		if (fence->seqno < seqno) {
> +			pr_err("Reported an earlier fence.seqno:%lld for seqno:%d\n",
> +			       fence->seqno, seqno);
> +			err = -EINVAL;
> +			dma_fence_put(fence);
> +			break;
> +		}
> +
> +		dma_fence_put(fence);
> +
> +signal:
> +		seqno = prandom_u32_max(data->fc.chain_length - 1);
> +		dma_fence_signal(data->fc.fences[seqno]);
> +		cond_resched();
> +	}
> +
> +	if (atomic_dec_and_test(&data->children))
> +		wake_up_var(&data->children);
> +	return err;
> +}
> +
> +static int find_race(void *arg)
> +{
> +	struct find_race data;
> +	int ncpus = num_online_cpus();
> +	struct task_struct **threads;
> +	unsigned long count;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&data.fc, 64 << 10, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	threads = kmalloc_array(ncpus, sizeof(*threads), GFP_KERNEL);
> +	if (!threads) {
> +		err = -ENOMEM;
> +		goto err;
> +	}
> +
> +	atomic_set(&data.children, 0);
> +	for (i = 0; i < ncpus; i++) {
> +		threads[i] = kthread_run(__find_race, &data, "dmabuf/%d", i);
> +		if (IS_ERR(threads[i])) {
> +			ncpus = i;
> +			break;
> +		}
> +		atomic_inc(&data.children);
> +		get_task_struct(threads[i]);
> +	}
> +
> +	wait_var_event_timeout(&data.children,
> +			       !atomic_read(&data.children),
> +			       5 * HZ);
> +
> +	for (i = 0; i < ncpus; i++) {
> +		int ret;
> +
> +		ret = kthread_stop(threads[i]);
> +		if (ret && !err)
> +			err = ret;
> +		put_task_struct(threads[i]);
> +	}
> +	kfree(threads);
> +
> +	count = 0;
> +	for (i = 0; i < data.fc.chain_length; i++)
> +		if (dma_fence_is_signaled(data.fc.fences[i]))
> +			count++;
> +	pr_info("Completed %lu cycles\n", count);
> +
> +err:
> +	fence_chains_fini(&data.fc);
> +	return err;
> +}
> +
> +static int signal_forward(void *arg)
> +{
> +	struct fence_chains fc;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	for (i = 0; i < fc.chain_length; i++) {
> +		dma_fence_signal(fc.fences[i]);
> +
> +		if (!dma_fence_is_signaled(fc.chains[i])) {
> +			pr_err("chain[%d] not signaled!\n", i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		if (i + 1 < fc.chain_length &&
> +		    dma_fence_is_signaled(fc.chains[i + 1])) {
> +			pr_err("chain[%d] is signaled!\n", i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int signal_backward(void *arg)
> +{
> +	struct fence_chains fc;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	for (i = fc.chain_length; i--; ) {
> +		dma_fence_signal(fc.fences[i]);
> +
> +		if (i > 0 && dma_fence_is_signaled(fc.chains[i])) {
> +			pr_err("chain[%d] is signaled!\n", i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +	for (i = 0; i < fc.chain_length; i++) {
> +		if (!dma_fence_is_signaled(fc.chains[i])) {
> +			pr_err("chain[%d] was not signaled!\n", i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int __wait_fence_chains(void *arg)
> +{
> +	struct fence_chains *fc = arg;
> +
> +	if (dma_fence_wait(fc->tail, false))
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +static int wait_forward(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct task_struct *tsk;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64 << 10, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
> +	if (IS_ERR(tsk)) {
> +		err = PTR_ERR(tsk);
> +		goto err;
> +	}
> +	get_task_struct(tsk);
> +	yield_to(tsk, true);
> +
> +	for (i = 0; i < fc.chain_length; i++)
> +		dma_fence_signal(fc.fences[i]);
> +
> +	err = kthread_stop(tsk);
> +	put_task_struct(tsk);
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int wait_backward(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct task_struct *tsk;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64 << 10, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
> +	if (IS_ERR(tsk)) {
> +		err = PTR_ERR(tsk);
> +		goto err;
> +	}
> +	get_task_struct(tsk);
> +	yield_to(tsk, true);
> +
> +	for (i = fc.chain_length; i--; )
> +		dma_fence_signal(fc.fences[i]);
> +
> +	err = kthread_stop(tsk);
> +	put_task_struct(tsk);
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static void randomise_fences(struct fence_chains *fc)
> +{
> +	unsigned int count = fc->chain_length;
> +
> +	/* Fisher-Yates shuffle courtesy of Knuth */
> +	while (--count) {
> +		unsigned int swp;
> +
> +		swp = prandom_u32_max(count + 1);
> +		if (swp == count)
> +			continue;
> +
> +		swap(fc->fences[count], fc->fences[swp]);
> +	}
> +}
> +
> +static int wait_random(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct task_struct *tsk;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64 << 10, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	randomise_fences(&fc);
> +
> +	tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
> +	if (IS_ERR(tsk)) {
> +		err = PTR_ERR(tsk);
> +		goto err;
> +	}
> +	get_task_struct(tsk);
> +	yield_to(tsk, true);
> +
> +	for (i = 0; i < fc.chain_length; i++)
> +		dma_fence_signal(fc.fences[i]);
> +
> +	err = kthread_stop(tsk);
> +	put_task_struct(tsk);
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +int dma_fence_chain(void)
> +{
> +	static const struct subtest tests[] = {
> +		SUBTEST(sanitycheck),
> +		SUBTEST(find_seqno),
> +		SUBTEST(find_signaled),
> +		SUBTEST(find_out_of_order),
> +		SUBTEST(find_gap),
> +		SUBTEST(find_race),
> +		SUBTEST(signal_forward),
> +		SUBTEST(signal_backward),
> +		SUBTEST(wait_forward),
> +		SUBTEST(wait_backward),
> +		SUBTEST(wait_random),
> +	};
> +	int ret;
> +
> +	pr_info("sizeof(dma_fence_chain)=%zu\n",
> +		sizeof(struct dma_fence_chain));
> +
> +	slab_fences = KMEM_CACHE(mock_fence,
> +				 SLAB_TYPESAFE_BY_RCU |
> +				 SLAB_HWCACHE_ALIGN);
> +	if (!slab_fences)
> +		return -ENOMEM;
> +
> +	ret = subtests(tests, NULL);
> +
> +	kmem_cache_destroy(slab_fences);
> +	return ret;
> +}
I think it covers all api required for fence-chain.
Reviewed-by: Venkata Sandeep Dhanalakota <venkata.s.dhanalakota@intel.com>
> -- 
> 2.20.1
> 
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx
Lionel Landwerlin April 10, 2020, 4:11 p.m. UTC | #2
On 03/04/2020 12:12, Chris Wilson wrote:
> A few very simple testcases to exercise the dma-fence-chain API.
>
> Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
> ---
>   drivers/dma-buf/Makefile             |   3 +-
>   drivers/dma-buf/selftests.h          |   1 +
>   drivers/dma-buf/st-dma-fence-chain.c | 713 +++++++++++++++++++++++++++
>   3 files changed, 716 insertions(+), 1 deletion(-)
>   create mode 100644 drivers/dma-buf/st-dma-fence-chain.c
>
> diff --git a/drivers/dma-buf/Makefile b/drivers/dma-buf/Makefile
> index 9c190026bfab..995e05f609ff 100644
> --- a/drivers/dma-buf/Makefile
> +++ b/drivers/dma-buf/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_UDMABUF)		+= udmabuf.o
>   
>   dmabuf_selftests-y := \
>   	selftest.o \
> -	st-dma-fence.o
> +	st-dma-fence.o \
> +	st-dma-fence-chain.o
>   
>   obj-$(CONFIG_DMABUF_SELFTESTS)	+= dmabuf_selftests.o
> diff --git a/drivers/dma-buf/selftests.h b/drivers/dma-buf/selftests.h
> index 5320386f02e5..55918ef9adab 100644
> --- a/drivers/dma-buf/selftests.h
> +++ b/drivers/dma-buf/selftests.h
> @@ -11,3 +11,4 @@
>    */
>   selftest(sanitycheck, __sanitycheck__) /* keep first (igt selfcheck) */
>   selftest(dma_fence, dma_fence)
> +selftest(dma_fence_chain, dma_fence_chain)
> diff --git a/drivers/dma-buf/st-dma-fence-chain.c b/drivers/dma-buf/st-dma-fence-chain.c
> new file mode 100644
> index 000000000000..bd08ba67b03b
> --- /dev/null
> +++ b/drivers/dma-buf/st-dma-fence-chain.c
> @@ -0,0 +1,713 @@
> +// SPDX-License-Identifier: MIT
> +
> +/*
> + * Copyright © 2019 Intel Corporation
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/dma-fence.h>
> +#include <linux/dma-fence-chain.h>
> +#include <linux/kernel.h>
> +#include <linux/kthread.h>
> +#include <linux/mm.h>
> +#include <linux/sched/signal.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/random.h>
> +
> +#include "selftest.h"
> +
> +static struct kmem_cache *slab_fences;
> +
> +static inline struct mock_fence {
> +	struct dma_fence base;
> +	spinlock_t lock;
> +} *to_mock_fence(struct dma_fence *f) {
> +	return container_of(f, struct mock_fence, base);
> +}
> +
> +static const char *mock_name(struct dma_fence *f)
> +{
> +	return "mock";
> +}
> +
> +static void mock_fence_release(struct dma_fence *f)
> +{
> +	kmem_cache_free(slab_fences, to_mock_fence(f));
> +}
> +
> +static const struct dma_fence_ops mock_ops = {
> +	.get_driver_name = mock_name,
> +	.get_timeline_name = mock_name,
> +	.release = mock_fence_release,
> +};
> +
> +static struct dma_fence *mock_fence(void)
> +{
> +	struct mock_fence *f;
> +
> +	f = kmem_cache_alloc(slab_fences, GFP_KERNEL);
> +	if (!f)
> +		return NULL;
> +
> +	spin_lock_init(&f->lock);
> +	dma_fence_init(&f->base, &mock_ops, &f->lock, 0, 0);
> +
> +	return &f->base;
> +}
> +
> +static inline struct mock_chain {
> +	struct dma_fence_chain base;
> +} *to_mock_chain(struct dma_fence *f) {
> +	return container_of(f, struct mock_chain, base.base);
> +}
> +
> +static struct dma_fence *mock_chain(struct dma_fence *prev,
> +				    struct dma_fence *fence,
> +				    u64 seqno)
> +{
> +	struct mock_chain *f;
> +
> +	f = kmalloc(sizeof(*f), GFP_KERNEL);
> +	if (!f)
> +		return NULL;
> +
> +	dma_fence_chain_init(&f->base,
> +			     dma_fence_get(prev),
> +			     dma_fence_get(fence),
> +			     seqno);
> +
> +	return &f->base.base;
> +}
> +
> +static int sanitycheck(void *arg)
> +{
> +	struct dma_fence *f, *chain;
> +	int err = 0;
> +
> +	f = mock_fence();
> +	if (!f)
> +		return -ENOMEM;
> +
> +	chain = mock_chain(NULL, f, 1);
> +	if (!chain)
> +		err = -ENOMEM;
> +
> +	dma_fence_signal(f);
> +	dma_fence_put(f);
> +
> +	dma_fence_put(chain);
> +
> +	return err;
> +}
> +
> +struct fence_chains {
> +	unsigned int chain_length;
> +	struct dma_fence **fences;
> +	struct dma_fence **chains;
> +
> +	struct dma_fence *tail;
> +};
> +
> +static uint64_t seqno_inc(unsigned int i)
> +{
> +	return i + 1;
> +}
> +
> +static int fence_chains_init(struct fence_chains *fc, unsigned int count,
> +			     uint64_t (*seqno_fn)(unsigned int))
> +{
> +	unsigned int i;
> +	int err = 0;
> +
> +	fc->chains = kvmalloc_array(count, sizeof(*fc->chains),
> +				    GFP_KERNEL | __GFP_ZERO);
> +	if (!fc->chains)
> +		return -ENOMEM;
> +
> +	fc->fences = kvmalloc_array(count, sizeof(*fc->fences),
> +				    GFP_KERNEL | __GFP_ZERO);
> +	if (!fc->fences) {
> +		err = -ENOMEM;
> +		goto err_chains;
> +	}
> +
> +	fc->tail = NULL;
> +	for (i = 0; i < count; i++) {
> +		fc->fences[i] = mock_fence();
> +		if (!fc->fences[i]) {
> +			err = -ENOMEM;
> +			goto unwind;
> +		}
> +
> +		fc->chains[i] = mock_chain(fc->tail,
> +					   fc->fences[i],
> +					   seqno_fn(i));
> +		if (!fc->chains[i]) {
> +			err = -ENOMEM;
> +			goto unwind;
> +		}
> +
> +		fc->tail = fc->chains[i];
> +	}
> +
> +	fc->chain_length = i;
> +	return 0;
> +
> +unwind:
> +	for (i = 0; i < count; i++) {
> +		dma_fence_put(fc->fences[i]);
> +		dma_fence_put(fc->chains[i]);
> +	}
> +	kvfree(fc->fences);
> +err_chains:
> +	kvfree(fc->chains);
> +	return err;
> +}
> +
> +static void fence_chains_fini(struct fence_chains *fc)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < fc->chain_length; i++) {
> +		dma_fence_signal(fc->fences[i]);
> +		dma_fence_put(fc->fences[i]);
> +	}
> +	kvfree(fc->fences);
> +
> +	for (i = 0; i < fc->chain_length; i++)
> +		dma_fence_put(fc->chains[i]);
> +	kvfree(fc->chains);
> +}
> +
> +static int find_seqno(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct dma_fence *fence;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	fence = dma_fence_get(fc.tail);
> +	err = dma_fence_chain_find_seqno(&fence, 0);
> +	dma_fence_put(fence);
> +	if (err) {
> +		pr_err("Reported %d for find_seqno(0)!\n", err);
> +		goto err;
> +	}
> +
> +	for (i = 0; i < fc.chain_length; i++) {
> +		fence = dma_fence_get(fc.tail);
> +		err = dma_fence_chain_find_seqno(&fence, i + 1);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Reported %d for find_seqno(%d:%d)!\n",
> +			       err, fc.chain_length + 1, i + 1);
> +			goto err;
> +		}
> +		if (fence != fc.chains[i]) {
> +			pr_err("Incorrect fence reported by find_seqno(%d:%d)\n",
> +			       fc.chain_length + 1, i + 1);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, i + 1);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Error reported for finding self\n");
> +			goto err;
> +		}
> +		if (fence != fc.chains[i]) {
> +			pr_err("Incorrect fence reported by find self\n");
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, i + 2);
> +		dma_fence_put(fence);
> +		if (!err) {
> +			pr_err("Error not reported for future fence: find_seqno(%d:%d)!\n",
> +			       i + 1, i + 2);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, i);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Error reported for previous fence!\n");
> +			goto err;
> +		}
> +		if (i > 0 && fence != fc.chains[i - 1]) {
> +			pr_err("Incorrect fence reported by find_seqno(%d:%d)\n",
> +			       i + 1, i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int find_signaled(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct dma_fence *fence;
> +	int err;
> +
> +	err = fence_chains_init(&fc, 2, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	dma_fence_signal(fc.fences[0]);
> +
> +	fence = dma_fence_get(fc.tail);
> +	err = dma_fence_chain_find_seqno(&fence, 1);
> +	dma_fence_put(fence);
> +	if (err) {
> +		pr_err("Reported %d for find_seqno()!\n", err);
> +		goto err;
> +	}
> +
> +	if (fence && fence != fc.chains[0]) {
> +		pr_err("Incorrect chain-fence.seqno:%lld reported for completed seqno:1\n",
> +		       fence->seqno);
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, 1);
> +		dma_fence_put(fence);
> +		if (err)
> +			pr_err("Reported %d for finding self!\n", err);
> +
> +		err = -EINVAL;
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int find_out_of_order(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct dma_fence *fence;
> +	int err;
> +
> +	err = fence_chains_init(&fc, 3, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	dma_fence_signal(fc.fences[1]);
> +
> +	fence = dma_fence_get(fc.tail);
> +	err = dma_fence_chain_find_seqno(&fence, 2);
> +	dma_fence_put(fence);
> +	if (err) {
> +		pr_err("Reported %d for find_seqno()!\n", err);
> +		goto err;
> +	}
> +


I don't think this test is right.


You have 3 dma-fence-chains :

1 2 & 3


You signaled the underlying fence of chains[1] (seqno 2) above, but 
chains[0] (seqno 1) is still not signaled.

The principle behind fence chain is that a given seqno is signaled 
whenever every seqno prior to that are signaled.

Therefore when you look for seqno 2, you should be given anything that 
hasn't completed before 2.

In this case that would be chains[0] which is seqno 1.


-Lionel


> +	if (fence && fence != fc.chains[1]) {
> +		pr_err("Incorrect chain-fence.seqno:%lld reported for completed seqno:2\n",
> +		       fence->seqno);
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, 2);
> +		dma_fence_put(fence);
> +		if (err)
> +			pr_err("Reported %d for finding self!\n", err);
> +
> +		err = -EINVAL;
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static uint64_t seqno_inc2(unsigned int i)
> +{
> +	return 2 * i + 2;
> +}
> +
> +static int find_gap(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct dma_fence *fence;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64, seqno_inc2);
> +	if (err)
> +		return err;
> +
> +	for (i = 0; i < fc.chain_length; i++) {
> +		fence = dma_fence_get(fc.tail);
> +		err = dma_fence_chain_find_seqno(&fence, 2 * i + 1);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Reported %d for find_seqno(%d:%d)!\n",
> +			       err, fc.chain_length + 1, 2 * i + 1);
> +			goto err;
> +		}
> +		if (fence != fc.chains[i]) {
> +			pr_err("Incorrect fence.seqno:%lld reported by find_seqno(%d:%d)\n",
> +			       fence->seqno,
> +			       fc.chain_length + 1,
> +			       2 * i + 1);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		dma_fence_get(fence);
> +		err = dma_fence_chain_find_seqno(&fence, 2 * i + 2);
> +		dma_fence_put(fence);
> +		if (err) {
> +			pr_err("Error reported for finding self\n");
> +			goto err;
> +		}
> +		if (fence != fc.chains[i]) {
> +			pr_err("Incorrect fence reported by find self\n");
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +struct find_race {
> +	struct fence_chains fc;
> +	atomic_t children;
> +};
> +
> +static int __find_race(void *arg)
> +{
> +	struct find_race *data = arg;
> +	int err = 0;
> +
> +	while (!kthread_should_stop()) {
> +		struct dma_fence *fence = dma_fence_get(data->fc.tail);
> +		int seqno;
> +
> +		seqno = prandom_u32_max(data->fc.chain_length) + 1;
> +
> +		err = dma_fence_chain_find_seqno(&fence, seqno);
> +		if (err) {
> +			pr_err("Failed to find fence seqno:%d\n",
> +			       seqno);
> +			dma_fence_put(fence);
> +			break;
> +		}
> +		if (!fence)
> +			goto signal;
> +
> +		err = dma_fence_chain_find_seqno(&fence, seqno);
> +		if (err) {
> +			pr_err("Reported an invalid fence for find-self:%d\n",
> +			       seqno);
> +			dma_fence_put(fence);
> +			break;
> +		}
> +
> +		if (fence->seqno < seqno) {
> +			pr_err("Reported an earlier fence.seqno:%lld for seqno:%d\n",
> +			       fence->seqno, seqno);
> +			err = -EINVAL;
> +			dma_fence_put(fence);
> +			break;
> +		}
> +
> +		dma_fence_put(fence);
> +
> +signal:
> +		seqno = prandom_u32_max(data->fc.chain_length - 1);
> +		dma_fence_signal(data->fc.fences[seqno]);
> +		cond_resched();
> +	}
> +
> +	if (atomic_dec_and_test(&data->children))
> +		wake_up_var(&data->children);
> +	return err;
> +}
> +
> +static int find_race(void *arg)
> +{
> +	struct find_race data;
> +	int ncpus = num_online_cpus();
> +	struct task_struct **threads;
> +	unsigned long count;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&data.fc, 64 << 10, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	threads = kmalloc_array(ncpus, sizeof(*threads), GFP_KERNEL);
> +	if (!threads) {
> +		err = -ENOMEM;
> +		goto err;
> +	}
> +
> +	atomic_set(&data.children, 0);
> +	for (i = 0; i < ncpus; i++) {
> +		threads[i] = kthread_run(__find_race, &data, "dmabuf/%d", i);
> +		if (IS_ERR(threads[i])) {
> +			ncpus = i;
> +			break;
> +		}
> +		atomic_inc(&data.children);
> +		get_task_struct(threads[i]);
> +	}
> +
> +	wait_var_event_timeout(&data.children,
> +			       !atomic_read(&data.children),
> +			       5 * HZ);
> +
> +	for (i = 0; i < ncpus; i++) {
> +		int ret;
> +
> +		ret = kthread_stop(threads[i]);
> +		if (ret && !err)
> +			err = ret;
> +		put_task_struct(threads[i]);
> +	}
> +	kfree(threads);
> +
> +	count = 0;
> +	for (i = 0; i < data.fc.chain_length; i++)
> +		if (dma_fence_is_signaled(data.fc.fences[i]))
> +			count++;
> +	pr_info("Completed %lu cycles\n", count);
> +
> +err:
> +	fence_chains_fini(&data.fc);
> +	return err;
> +}
> +
> +static int signal_forward(void *arg)
> +{
> +	struct fence_chains fc;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	for (i = 0; i < fc.chain_length; i++) {
> +		dma_fence_signal(fc.fences[i]);
> +
> +		if (!dma_fence_is_signaled(fc.chains[i])) {
> +			pr_err("chain[%d] not signaled!\n", i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +
> +		if (i + 1 < fc.chain_length &&
> +		    dma_fence_is_signaled(fc.chains[i + 1])) {
> +			pr_err("chain[%d] is signaled!\n", i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int signal_backward(void *arg)
> +{
> +	struct fence_chains fc;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	for (i = fc.chain_length; i--; ) {
> +		dma_fence_signal(fc.fences[i]);
> +
> +		if (i > 0 && dma_fence_is_signaled(fc.chains[i])) {
> +			pr_err("chain[%d] is signaled!\n", i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +	for (i = 0; i < fc.chain_length; i++) {
> +		if (!dma_fence_is_signaled(fc.chains[i])) {
> +			pr_err("chain[%d] was not signaled!\n", i);
> +			err = -EINVAL;
> +			goto err;
> +		}
> +	}
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int __wait_fence_chains(void *arg)
> +{
> +	struct fence_chains *fc = arg;
> +
> +	if (dma_fence_wait(fc->tail, false))
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +static int wait_forward(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct task_struct *tsk;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64 << 10, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
> +	if (IS_ERR(tsk)) {
> +		err = PTR_ERR(tsk);
> +		goto err;
> +	}
> +	get_task_struct(tsk);
> +	yield_to(tsk, true);
> +
> +	for (i = 0; i < fc.chain_length; i++)
> +		dma_fence_signal(fc.fences[i]);
> +
> +	err = kthread_stop(tsk);
> +	put_task_struct(tsk);
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static int wait_backward(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct task_struct *tsk;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64 << 10, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
> +	if (IS_ERR(tsk)) {
> +		err = PTR_ERR(tsk);
> +		goto err;
> +	}
> +	get_task_struct(tsk);
> +	yield_to(tsk, true);
> +
> +	for (i = fc.chain_length; i--; )
> +		dma_fence_signal(fc.fences[i]);
> +
> +	err = kthread_stop(tsk);
> +	put_task_struct(tsk);
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +static void randomise_fences(struct fence_chains *fc)
> +{
> +	unsigned int count = fc->chain_length;
> +
> +	/* Fisher-Yates shuffle courtesy of Knuth */
> +	while (--count) {
> +		unsigned int swp;
> +
> +		swp = prandom_u32_max(count + 1);
> +		if (swp == count)
> +			continue;
> +
> +		swap(fc->fences[count], fc->fences[swp]);
> +	}
> +}
> +
> +static int wait_random(void *arg)
> +{
> +	struct fence_chains fc;
> +	struct task_struct *tsk;
> +	int err;
> +	int i;
> +
> +	err = fence_chains_init(&fc, 64 << 10, seqno_inc);
> +	if (err)
> +		return err;
> +
> +	randomise_fences(&fc);
> +
> +	tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
> +	if (IS_ERR(tsk)) {
> +		err = PTR_ERR(tsk);
> +		goto err;
> +	}
> +	get_task_struct(tsk);
> +	yield_to(tsk, true);
> +
> +	for (i = 0; i < fc.chain_length; i++)
> +		dma_fence_signal(fc.fences[i]);
> +
> +	err = kthread_stop(tsk);
> +	put_task_struct(tsk);
> +
> +err:
> +	fence_chains_fini(&fc);
> +	return err;
> +}
> +
> +int dma_fence_chain(void)
> +{
> +	static const struct subtest tests[] = {
> +		SUBTEST(sanitycheck),
> +		SUBTEST(find_seqno),
> +		SUBTEST(find_signaled),
> +		SUBTEST(find_out_of_order),
> +		SUBTEST(find_gap),
> +		SUBTEST(find_race),
> +		SUBTEST(signal_forward),
> +		SUBTEST(signal_backward),
> +		SUBTEST(wait_forward),
> +		SUBTEST(wait_backward),
> +		SUBTEST(wait_random),
> +	};
> +	int ret;
> +
> +	pr_info("sizeof(dma_fence_chain)=%zu\n",
> +		sizeof(struct dma_fence_chain));
> +
> +	slab_fences = KMEM_CACHE(mock_fence,
> +				 SLAB_TYPESAFE_BY_RCU |
> +				 SLAB_HWCACHE_ALIGN);
> +	if (!slab_fences)
> +		return -ENOMEM;
> +
> +	ret = subtests(tests, NULL);
> +
> +	kmem_cache_destroy(slab_fences);
> +	return ret;
> +}
diff mbox series

Patch

diff --git a/drivers/dma-buf/Makefile b/drivers/dma-buf/Makefile
index 9c190026bfab..995e05f609ff 100644
--- a/drivers/dma-buf/Makefile
+++ b/drivers/dma-buf/Makefile
@@ -9,6 +9,7 @@  obj-$(CONFIG_UDMABUF)		+= udmabuf.o
 
 dmabuf_selftests-y := \
 	selftest.o \
-	st-dma-fence.o
+	st-dma-fence.o \
+	st-dma-fence-chain.o
 
 obj-$(CONFIG_DMABUF_SELFTESTS)	+= dmabuf_selftests.o
diff --git a/drivers/dma-buf/selftests.h b/drivers/dma-buf/selftests.h
index 5320386f02e5..55918ef9adab 100644
--- a/drivers/dma-buf/selftests.h
+++ b/drivers/dma-buf/selftests.h
@@ -11,3 +11,4 @@ 
  */
 selftest(sanitycheck, __sanitycheck__) /* keep first (igt selfcheck) */
 selftest(dma_fence, dma_fence)
+selftest(dma_fence_chain, dma_fence_chain)
diff --git a/drivers/dma-buf/st-dma-fence-chain.c b/drivers/dma-buf/st-dma-fence-chain.c
new file mode 100644
index 000000000000..bd08ba67b03b
--- /dev/null
+++ b/drivers/dma-buf/st-dma-fence-chain.c
@@ -0,0 +1,713 @@ 
+// SPDX-License-Identifier: MIT
+
+/*
+ * Copyright © 2019 Intel Corporation
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-fence.h>
+#include <linux/dma-fence-chain.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/mm.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+
+#include "selftest.h"
+
+static struct kmem_cache *slab_fences;
+
+static inline struct mock_fence {
+	struct dma_fence base;
+	spinlock_t lock;
+} *to_mock_fence(struct dma_fence *f) {
+	return container_of(f, struct mock_fence, base);
+}
+
+static const char *mock_name(struct dma_fence *f)
+{
+	return "mock";
+}
+
+static void mock_fence_release(struct dma_fence *f)
+{
+	kmem_cache_free(slab_fences, to_mock_fence(f));
+}
+
+static const struct dma_fence_ops mock_ops = {
+	.get_driver_name = mock_name,
+	.get_timeline_name = mock_name,
+	.release = mock_fence_release,
+};
+
+static struct dma_fence *mock_fence(void)
+{
+	struct mock_fence *f;
+
+	f = kmem_cache_alloc(slab_fences, GFP_KERNEL);
+	if (!f)
+		return NULL;
+
+	spin_lock_init(&f->lock);
+	dma_fence_init(&f->base, &mock_ops, &f->lock, 0, 0);
+
+	return &f->base;
+}
+
+static inline struct mock_chain {
+	struct dma_fence_chain base;
+} *to_mock_chain(struct dma_fence *f) {
+	return container_of(f, struct mock_chain, base.base);
+}
+
+static struct dma_fence *mock_chain(struct dma_fence *prev,
+				    struct dma_fence *fence,
+				    u64 seqno)
+{
+	struct mock_chain *f;
+
+	f = kmalloc(sizeof(*f), GFP_KERNEL);
+	if (!f)
+		return NULL;
+
+	dma_fence_chain_init(&f->base,
+			     dma_fence_get(prev),
+			     dma_fence_get(fence),
+			     seqno);
+
+	return &f->base.base;
+}
+
+static int sanitycheck(void *arg)
+{
+	struct dma_fence *f, *chain;
+	int err = 0;
+
+	f = mock_fence();
+	if (!f)
+		return -ENOMEM;
+
+	chain = mock_chain(NULL, f, 1);
+	if (!chain)
+		err = -ENOMEM;
+
+	dma_fence_signal(f);
+	dma_fence_put(f);
+
+	dma_fence_put(chain);
+
+	return err;
+}
+
+struct fence_chains {
+	unsigned int chain_length;
+	struct dma_fence **fences;
+	struct dma_fence **chains;
+
+	struct dma_fence *tail;
+};
+
+static uint64_t seqno_inc(unsigned int i)
+{
+	return i + 1;
+}
+
+static int fence_chains_init(struct fence_chains *fc, unsigned int count,
+			     uint64_t (*seqno_fn)(unsigned int))
+{
+	unsigned int i;
+	int err = 0;
+
+	fc->chains = kvmalloc_array(count, sizeof(*fc->chains),
+				    GFP_KERNEL | __GFP_ZERO);
+	if (!fc->chains)
+		return -ENOMEM;
+
+	fc->fences = kvmalloc_array(count, sizeof(*fc->fences),
+				    GFP_KERNEL | __GFP_ZERO);
+	if (!fc->fences) {
+		err = -ENOMEM;
+		goto err_chains;
+	}
+
+	fc->tail = NULL;
+	for (i = 0; i < count; i++) {
+		fc->fences[i] = mock_fence();
+		if (!fc->fences[i]) {
+			err = -ENOMEM;
+			goto unwind;
+		}
+
+		fc->chains[i] = mock_chain(fc->tail,
+					   fc->fences[i],
+					   seqno_fn(i));
+		if (!fc->chains[i]) {
+			err = -ENOMEM;
+			goto unwind;
+		}
+
+		fc->tail = fc->chains[i];
+	}
+
+	fc->chain_length = i;
+	return 0;
+
+unwind:
+	for (i = 0; i < count; i++) {
+		dma_fence_put(fc->fences[i]);
+		dma_fence_put(fc->chains[i]);
+	}
+	kvfree(fc->fences);
+err_chains:
+	kvfree(fc->chains);
+	return err;
+}
+
+static void fence_chains_fini(struct fence_chains *fc)
+{
+	unsigned int i;
+
+	for (i = 0; i < fc->chain_length; i++) {
+		dma_fence_signal(fc->fences[i]);
+		dma_fence_put(fc->fences[i]);
+	}
+	kvfree(fc->fences);
+
+	for (i = 0; i < fc->chain_length; i++)
+		dma_fence_put(fc->chains[i]);
+	kvfree(fc->chains);
+}
+
+static int find_seqno(void *arg)
+{
+	struct fence_chains fc;
+	struct dma_fence *fence;
+	int err;
+	int i;
+
+	err = fence_chains_init(&fc, 64, seqno_inc);
+	if (err)
+		return err;
+
+	fence = dma_fence_get(fc.tail);
+	err = dma_fence_chain_find_seqno(&fence, 0);
+	dma_fence_put(fence);
+	if (err) {
+		pr_err("Reported %d for find_seqno(0)!\n", err);
+		goto err;
+	}
+
+	for (i = 0; i < fc.chain_length; i++) {
+		fence = dma_fence_get(fc.tail);
+		err = dma_fence_chain_find_seqno(&fence, i + 1);
+		dma_fence_put(fence);
+		if (err) {
+			pr_err("Reported %d for find_seqno(%d:%d)!\n",
+			       err, fc.chain_length + 1, i + 1);
+			goto err;
+		}
+		if (fence != fc.chains[i]) {
+			pr_err("Incorrect fence reported by find_seqno(%d:%d)\n",
+			       fc.chain_length + 1, i + 1);
+			err = -EINVAL;
+			goto err;
+		}
+
+		dma_fence_get(fence);
+		err = dma_fence_chain_find_seqno(&fence, i + 1);
+		dma_fence_put(fence);
+		if (err) {
+			pr_err("Error reported for finding self\n");
+			goto err;
+		}
+		if (fence != fc.chains[i]) {
+			pr_err("Incorrect fence reported by find self\n");
+			err = -EINVAL;
+			goto err;
+		}
+
+		dma_fence_get(fence);
+		err = dma_fence_chain_find_seqno(&fence, i + 2);
+		dma_fence_put(fence);
+		if (!err) {
+			pr_err("Error not reported for future fence: find_seqno(%d:%d)!\n",
+			       i + 1, i + 2);
+			err = -EINVAL;
+			goto err;
+		}
+
+		dma_fence_get(fence);
+		err = dma_fence_chain_find_seqno(&fence, i);
+		dma_fence_put(fence);
+		if (err) {
+			pr_err("Error reported for previous fence!\n");
+			goto err;
+		}
+		if (i > 0 && fence != fc.chains[i - 1]) {
+			pr_err("Incorrect fence reported by find_seqno(%d:%d)\n",
+			       i + 1, i);
+			err = -EINVAL;
+			goto err;
+		}
+	}
+
+err:
+	fence_chains_fini(&fc);
+	return err;
+}
+
+static int find_signaled(void *arg)
+{
+	struct fence_chains fc;
+	struct dma_fence *fence;
+	int err;
+
+	err = fence_chains_init(&fc, 2, seqno_inc);
+	if (err)
+		return err;
+
+	dma_fence_signal(fc.fences[0]);
+
+	fence = dma_fence_get(fc.tail);
+	err = dma_fence_chain_find_seqno(&fence, 1);
+	dma_fence_put(fence);
+	if (err) {
+		pr_err("Reported %d for find_seqno()!\n", err);
+		goto err;
+	}
+
+	if (fence && fence != fc.chains[0]) {
+		pr_err("Incorrect chain-fence.seqno:%lld reported for completed seqno:1\n",
+		       fence->seqno);
+
+		dma_fence_get(fence);
+		err = dma_fence_chain_find_seqno(&fence, 1);
+		dma_fence_put(fence);
+		if (err)
+			pr_err("Reported %d for finding self!\n", err);
+
+		err = -EINVAL;
+	}
+
+err:
+	fence_chains_fini(&fc);
+	return err;
+}
+
+static int find_out_of_order(void *arg)
+{
+	struct fence_chains fc;
+	struct dma_fence *fence;
+	int err;
+
+	err = fence_chains_init(&fc, 3, seqno_inc);
+	if (err)
+		return err;
+
+	dma_fence_signal(fc.fences[1]);
+
+	fence = dma_fence_get(fc.tail);
+	err = dma_fence_chain_find_seqno(&fence, 2);
+	dma_fence_put(fence);
+	if (err) {
+		pr_err("Reported %d for find_seqno()!\n", err);
+		goto err;
+	}
+
+	if (fence && fence != fc.chains[1]) {
+		pr_err("Incorrect chain-fence.seqno:%lld reported for completed seqno:2\n",
+		       fence->seqno);
+
+		dma_fence_get(fence);
+		err = dma_fence_chain_find_seqno(&fence, 2);
+		dma_fence_put(fence);
+		if (err)
+			pr_err("Reported %d for finding self!\n", err);
+
+		err = -EINVAL;
+	}
+
+err:
+	fence_chains_fini(&fc);
+	return err;
+}
+
+static uint64_t seqno_inc2(unsigned int i)
+{
+	return 2 * i + 2;
+}
+
+static int find_gap(void *arg)
+{
+	struct fence_chains fc;
+	struct dma_fence *fence;
+	int err;
+	int i;
+
+	err = fence_chains_init(&fc, 64, seqno_inc2);
+	if (err)
+		return err;
+
+	for (i = 0; i < fc.chain_length; i++) {
+		fence = dma_fence_get(fc.tail);
+		err = dma_fence_chain_find_seqno(&fence, 2 * i + 1);
+		dma_fence_put(fence);
+		if (err) {
+			pr_err("Reported %d for find_seqno(%d:%d)!\n",
+			       err, fc.chain_length + 1, 2 * i + 1);
+			goto err;
+		}
+		if (fence != fc.chains[i]) {
+			pr_err("Incorrect fence.seqno:%lld reported by find_seqno(%d:%d)\n",
+			       fence->seqno,
+			       fc.chain_length + 1,
+			       2 * i + 1);
+			err = -EINVAL;
+			goto err;
+		}
+
+		dma_fence_get(fence);
+		err = dma_fence_chain_find_seqno(&fence, 2 * i + 2);
+		dma_fence_put(fence);
+		if (err) {
+			pr_err("Error reported for finding self\n");
+			goto err;
+		}
+		if (fence != fc.chains[i]) {
+			pr_err("Incorrect fence reported by find self\n");
+			err = -EINVAL;
+			goto err;
+		}
+	}
+
+err:
+	fence_chains_fini(&fc);
+	return err;
+}
+
+struct find_race {
+	struct fence_chains fc;
+	atomic_t children;
+};
+
+static int __find_race(void *arg)
+{
+	struct find_race *data = arg;
+	int err = 0;
+
+	while (!kthread_should_stop()) {
+		struct dma_fence *fence = dma_fence_get(data->fc.tail);
+		int seqno;
+
+		seqno = prandom_u32_max(data->fc.chain_length) + 1;
+
+		err = dma_fence_chain_find_seqno(&fence, seqno);
+		if (err) {
+			pr_err("Failed to find fence seqno:%d\n",
+			       seqno);
+			dma_fence_put(fence);
+			break;
+		}
+		if (!fence)
+			goto signal;
+
+		err = dma_fence_chain_find_seqno(&fence, seqno);
+		if (err) {
+			pr_err("Reported an invalid fence for find-self:%d\n",
+			       seqno);
+			dma_fence_put(fence);
+			break;
+		}
+
+		if (fence->seqno < seqno) {
+			pr_err("Reported an earlier fence.seqno:%lld for seqno:%d\n",
+			       fence->seqno, seqno);
+			err = -EINVAL;
+			dma_fence_put(fence);
+			break;
+		}
+
+		dma_fence_put(fence);
+
+signal:
+		seqno = prandom_u32_max(data->fc.chain_length - 1);
+		dma_fence_signal(data->fc.fences[seqno]);
+		cond_resched();
+	}
+
+	if (atomic_dec_and_test(&data->children))
+		wake_up_var(&data->children);
+	return err;
+}
+
+static int find_race(void *arg)
+{
+	struct find_race data;
+	int ncpus = num_online_cpus();
+	struct task_struct **threads;
+	unsigned long count;
+	int err;
+	int i;
+
+	err = fence_chains_init(&data.fc, 64 << 10, seqno_inc);
+	if (err)
+		return err;
+
+	threads = kmalloc_array(ncpus, sizeof(*threads), GFP_KERNEL);
+	if (!threads) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	atomic_set(&data.children, 0);
+	for (i = 0; i < ncpus; i++) {
+		threads[i] = kthread_run(__find_race, &data, "dmabuf/%d", i);
+		if (IS_ERR(threads[i])) {
+			ncpus = i;
+			break;
+		}
+		atomic_inc(&data.children);
+		get_task_struct(threads[i]);
+	}
+
+	wait_var_event_timeout(&data.children,
+			       !atomic_read(&data.children),
+			       5 * HZ);
+
+	for (i = 0; i < ncpus; i++) {
+		int ret;
+
+		ret = kthread_stop(threads[i]);
+		if (ret && !err)
+			err = ret;
+		put_task_struct(threads[i]);
+	}
+	kfree(threads);
+
+	count = 0;
+	for (i = 0; i < data.fc.chain_length; i++)
+		if (dma_fence_is_signaled(data.fc.fences[i]))
+			count++;
+	pr_info("Completed %lu cycles\n", count);
+
+err:
+	fence_chains_fini(&data.fc);
+	return err;
+}
+
+static int signal_forward(void *arg)
+{
+	struct fence_chains fc;
+	int err;
+	int i;
+
+	err = fence_chains_init(&fc, 64, seqno_inc);
+	if (err)
+		return err;
+
+	for (i = 0; i < fc.chain_length; i++) {
+		dma_fence_signal(fc.fences[i]);
+
+		if (!dma_fence_is_signaled(fc.chains[i])) {
+			pr_err("chain[%d] not signaled!\n", i);
+			err = -EINVAL;
+			goto err;
+		}
+
+		if (i + 1 < fc.chain_length &&
+		    dma_fence_is_signaled(fc.chains[i + 1])) {
+			pr_err("chain[%d] is signaled!\n", i);
+			err = -EINVAL;
+			goto err;
+		}
+	}
+
+err:
+	fence_chains_fini(&fc);
+	return err;
+}
+
+static int signal_backward(void *arg)
+{
+	struct fence_chains fc;
+	int err;
+	int i;
+
+	err = fence_chains_init(&fc, 64, seqno_inc);
+	if (err)
+		return err;
+
+	for (i = fc.chain_length; i--; ) {
+		dma_fence_signal(fc.fences[i]);
+
+		if (i > 0 && dma_fence_is_signaled(fc.chains[i])) {
+			pr_err("chain[%d] is signaled!\n", i);
+			err = -EINVAL;
+			goto err;
+		}
+	}
+
+	for (i = 0; i < fc.chain_length; i++) {
+		if (!dma_fence_is_signaled(fc.chains[i])) {
+			pr_err("chain[%d] was not signaled!\n", i);
+			err = -EINVAL;
+			goto err;
+		}
+	}
+
+err:
+	fence_chains_fini(&fc);
+	return err;
+}
+
+static int __wait_fence_chains(void *arg)
+{
+	struct fence_chains *fc = arg;
+
+	if (dma_fence_wait(fc->tail, false))
+		return -EIO;
+
+	return 0;
+}
+
+static int wait_forward(void *arg)
+{
+	struct fence_chains fc;
+	struct task_struct *tsk;
+	int err;
+	int i;
+
+	err = fence_chains_init(&fc, 64 << 10, seqno_inc);
+	if (err)
+		return err;
+
+	tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
+	if (IS_ERR(tsk)) {
+		err = PTR_ERR(tsk);
+		goto err;
+	}
+	get_task_struct(tsk);
+	yield_to(tsk, true);
+
+	for (i = 0; i < fc.chain_length; i++)
+		dma_fence_signal(fc.fences[i]);
+
+	err = kthread_stop(tsk);
+	put_task_struct(tsk);
+
+err:
+	fence_chains_fini(&fc);
+	return err;
+}
+
+static int wait_backward(void *arg)
+{
+	struct fence_chains fc;
+	struct task_struct *tsk;
+	int err;
+	int i;
+
+	err = fence_chains_init(&fc, 64 << 10, seqno_inc);
+	if (err)
+		return err;
+
+	tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
+	if (IS_ERR(tsk)) {
+		err = PTR_ERR(tsk);
+		goto err;
+	}
+	get_task_struct(tsk);
+	yield_to(tsk, true);
+
+	for (i = fc.chain_length; i--; )
+		dma_fence_signal(fc.fences[i]);
+
+	err = kthread_stop(tsk);
+	put_task_struct(tsk);
+
+err:
+	fence_chains_fini(&fc);
+	return err;
+}
+
+static void randomise_fences(struct fence_chains *fc)
+{
+	unsigned int count = fc->chain_length;
+
+	/* Fisher-Yates shuffle courtesy of Knuth */
+	while (--count) {
+		unsigned int swp;
+
+		swp = prandom_u32_max(count + 1);
+		if (swp == count)
+			continue;
+
+		swap(fc->fences[count], fc->fences[swp]);
+	}
+}
+
+static int wait_random(void *arg)
+{
+	struct fence_chains fc;
+	struct task_struct *tsk;
+	int err;
+	int i;
+
+	err = fence_chains_init(&fc, 64 << 10, seqno_inc);
+	if (err)
+		return err;
+
+	randomise_fences(&fc);
+
+	tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
+	if (IS_ERR(tsk)) {
+		err = PTR_ERR(tsk);
+		goto err;
+	}
+	get_task_struct(tsk);
+	yield_to(tsk, true);
+
+	for (i = 0; i < fc.chain_length; i++)
+		dma_fence_signal(fc.fences[i]);
+
+	err = kthread_stop(tsk);
+	put_task_struct(tsk);
+
+err:
+	fence_chains_fini(&fc);
+	return err;
+}
+
+int dma_fence_chain(void)
+{
+	static const struct subtest tests[] = {
+		SUBTEST(sanitycheck),
+		SUBTEST(find_seqno),
+		SUBTEST(find_signaled),
+		SUBTEST(find_out_of_order),
+		SUBTEST(find_gap),
+		SUBTEST(find_race),
+		SUBTEST(signal_forward),
+		SUBTEST(signal_backward),
+		SUBTEST(wait_forward),
+		SUBTEST(wait_backward),
+		SUBTEST(wait_random),
+	};
+	int ret;
+
+	pr_info("sizeof(dma_fence_chain)=%zu\n",
+		sizeof(struct dma_fence_chain));
+
+	slab_fences = KMEM_CACHE(mock_fence,
+				 SLAB_TYPESAFE_BY_RCU |
+				 SLAB_HWCACHE_ALIGN);
+	if (!slab_fences)
+		return -ENOMEM;
+
+	ret = subtests(tests, NULL);
+
+	kmem_cache_destroy(slab_fences);
+	return ret;
+}