| // SPDX-License-Identifier: MIT |
| |
| /* |
| * Copyright (C) 2022 Advanced Micro Devices, Inc. |
| */ |
| |
| #include <linux/dma-fence.h> |
| #include <linux/dma-fence-array.h> |
| #include <linux/dma-fence-chain.h> |
| #include <linux/dma-fence-unwrap.h> |
| |
| #include "selftest.h" |
| |
| #define CHAIN_SZ (4 << 10) |
| |
| struct mock_fence { |
| struct dma_fence base; |
| spinlock_t lock; |
| }; |
| |
| static const char *mock_name(struct dma_fence *f) |
| { |
| return "mock"; |
| } |
| |
| static const struct dma_fence_ops mock_ops = { |
| .get_driver_name = mock_name, |
| .get_timeline_name = mock_name, |
| }; |
| |
| static struct dma_fence *mock_fence(void) |
| { |
| struct mock_fence *f; |
| |
| f = kmalloc(sizeof(*f), GFP_KERNEL); |
| if (!f) |
| return NULL; |
| |
| spin_lock_init(&f->lock); |
| dma_fence_init(&f->base, &mock_ops, &f->lock, |
| dma_fence_context_alloc(1), 1); |
| |
| return &f->base; |
| } |
| |
| static struct dma_fence *mock_array(unsigned int num_fences, ...) |
| { |
| struct dma_fence_array *array; |
| struct dma_fence **fences; |
| va_list valist; |
| int i; |
| |
| fences = kcalloc(num_fences, sizeof(*fences), GFP_KERNEL); |
| if (!fences) |
| goto error_put; |
| |
| va_start(valist, num_fences); |
| for (i = 0; i < num_fences; ++i) |
| fences[i] = va_arg(valist, typeof(*fences)); |
| va_end(valist); |
| |
| array = dma_fence_array_create(num_fences, fences, |
| dma_fence_context_alloc(1), |
| 1, false); |
| if (!array) |
| goto error_free; |
| return &array->base; |
| |
| error_free: |
| kfree(fences); |
| |
| error_put: |
| va_start(valist, num_fences); |
| for (i = 0; i < num_fences; ++i) |
| dma_fence_put(va_arg(valist, typeof(*fences))); |
| va_end(valist); |
| return NULL; |
| } |
| |
| static struct dma_fence *mock_chain(struct dma_fence *prev, |
| struct dma_fence *fence) |
| { |
| struct dma_fence_chain *f; |
| |
| f = dma_fence_chain_alloc(); |
| if (!f) { |
| dma_fence_put(prev); |
| dma_fence_put(fence); |
| return NULL; |
| } |
| |
| dma_fence_chain_init(f, prev, fence, 1); |
| return &f->base; |
| } |
| |
| static int sanitycheck(void *arg) |
| { |
| struct dma_fence *f, *chain, *array; |
| int err = 0; |
| |
| f = mock_fence(); |
| if (!f) |
| return -ENOMEM; |
| |
| dma_fence_enable_sw_signaling(f); |
| |
| array = mock_array(1, f); |
| if (!array) |
| return -ENOMEM; |
| |
| chain = mock_chain(NULL, array); |
| if (!chain) |
| return -ENOMEM; |
| |
| dma_fence_put(chain); |
| return err; |
| } |
| |
| static int unwrap_array(void *arg) |
| { |
| struct dma_fence *fence, *f1, *f2, *array; |
| struct dma_fence_unwrap iter; |
| int err = 0; |
| |
| f1 = mock_fence(); |
| if (!f1) |
| return -ENOMEM; |
| |
| dma_fence_enable_sw_signaling(f1); |
| |
| f2 = mock_fence(); |
| if (!f2) { |
| dma_fence_put(f1); |
| return -ENOMEM; |
| } |
| |
| dma_fence_enable_sw_signaling(f2); |
| |
| array = mock_array(2, f1, f2); |
| if (!array) |
| return -ENOMEM; |
| |
| dma_fence_unwrap_for_each(fence, &iter, array) { |
| if (fence == f1) { |
| f1 = NULL; |
| } else if (fence == f2) { |
| f2 = NULL; |
| } else { |
| pr_err("Unexpected fence!\n"); |
| err = -EINVAL; |
| } |
| } |
| |
| if (f1 || f2) { |
| pr_err("Not all fences seen!\n"); |
| err = -EINVAL; |
| } |
| |
| dma_fence_put(array); |
| return err; |
| } |
| |
| static int unwrap_chain(void *arg) |
| { |
| struct dma_fence *fence, *f1, *f2, *chain; |
| struct dma_fence_unwrap iter; |
| int err = 0; |
| |
| f1 = mock_fence(); |
| if (!f1) |
| return -ENOMEM; |
| |
| dma_fence_enable_sw_signaling(f1); |
| |
| f2 = mock_fence(); |
| if (!f2) { |
| dma_fence_put(f1); |
| return -ENOMEM; |
| } |
| |
| dma_fence_enable_sw_signaling(f2); |
| |
| chain = mock_chain(f1, f2); |
| if (!chain) |
| return -ENOMEM; |
| |
| dma_fence_unwrap_for_each(fence, &iter, chain) { |
| if (fence == f1) { |
| f1 = NULL; |
| } else if (fence == f2) { |
| f2 = NULL; |
| } else { |
| pr_err("Unexpected fence!\n"); |
| err = -EINVAL; |
| } |
| } |
| |
| if (f1 || f2) { |
| pr_err("Not all fences seen!\n"); |
| err = -EINVAL; |
| } |
| |
| dma_fence_put(chain); |
| return err; |
| } |
| |
| static int unwrap_chain_array(void *arg) |
| { |
| struct dma_fence *fence, *f1, *f2, *array, *chain; |
| struct dma_fence_unwrap iter; |
| int err = 0; |
| |
| f1 = mock_fence(); |
| if (!f1) |
| return -ENOMEM; |
| |
| dma_fence_enable_sw_signaling(f1); |
| |
| f2 = mock_fence(); |
| if (!f2) { |
| dma_fence_put(f1); |
| return -ENOMEM; |
| } |
| |
| dma_fence_enable_sw_signaling(f2); |
| |
| array = mock_array(2, f1, f2); |
| if (!array) |
| return -ENOMEM; |
| |
| chain = mock_chain(NULL, array); |
| if (!chain) |
| return -ENOMEM; |
| |
| dma_fence_unwrap_for_each(fence, &iter, chain) { |
| if (fence == f1) { |
| f1 = NULL; |
| } else if (fence == f2) { |
| f2 = NULL; |
| } else { |
| pr_err("Unexpected fence!\n"); |
| err = -EINVAL; |
| } |
| } |
| |
| if (f1 || f2) { |
| pr_err("Not all fences seen!\n"); |
| err = -EINVAL; |
| } |
| |
| dma_fence_put(chain); |
| return err; |
| } |
| |
| static int unwrap_merge(void *arg) |
| { |
| struct dma_fence *fence, *f1, *f2, *f3; |
| struct dma_fence_unwrap iter; |
| int err = 0; |
| |
| f1 = mock_fence(); |
| if (!f1) |
| return -ENOMEM; |
| |
| dma_fence_enable_sw_signaling(f1); |
| |
| f2 = mock_fence(); |
| if (!f2) { |
| err = -ENOMEM; |
| goto error_put_f1; |
| } |
| |
| dma_fence_enable_sw_signaling(f2); |
| |
| f3 = dma_fence_unwrap_merge(f1, f2); |
| if (!f3) { |
| err = -ENOMEM; |
| goto error_put_f2; |
| } |
| |
| dma_fence_unwrap_for_each(fence, &iter, f3) { |
| if (fence == f1) { |
| dma_fence_put(f1); |
| f1 = NULL; |
| } else if (fence == f2) { |
| dma_fence_put(f2); |
| f2 = NULL; |
| } else { |
| pr_err("Unexpected fence!\n"); |
| err = -EINVAL; |
| } |
| } |
| |
| if (f1 || f2) { |
| pr_err("Not all fences seen!\n"); |
| err = -EINVAL; |
| } |
| |
| dma_fence_put(f3); |
| error_put_f2: |
| dma_fence_put(f2); |
| error_put_f1: |
| dma_fence_put(f1); |
| return err; |
| } |
| |
| static int unwrap_merge_complex(void *arg) |
| { |
| struct dma_fence *fence, *f1, *f2, *f3, *f4, *f5; |
| struct dma_fence_unwrap iter; |
| int err = -ENOMEM; |
| |
| f1 = mock_fence(); |
| if (!f1) |
| return -ENOMEM; |
| |
| dma_fence_enable_sw_signaling(f1); |
| |
| f2 = mock_fence(); |
| if (!f2) |
| goto error_put_f1; |
| |
| dma_fence_enable_sw_signaling(f2); |
| |
| f3 = dma_fence_unwrap_merge(f1, f2); |
| if (!f3) |
| goto error_put_f2; |
| |
| /* The resulting array has the fences in reverse */ |
| f4 = dma_fence_unwrap_merge(f2, f1); |
| if (!f4) |
| goto error_put_f3; |
| |
| /* Signaled fences should be filtered, the two arrays merged. */ |
| f5 = dma_fence_unwrap_merge(f3, f4, dma_fence_get_stub()); |
| if (!f5) |
| goto error_put_f4; |
| |
| err = 0; |
| dma_fence_unwrap_for_each(fence, &iter, f5) { |
| if (fence == f1) { |
| dma_fence_put(f1); |
| f1 = NULL; |
| } else if (fence == f2) { |
| dma_fence_put(f2); |
| f2 = NULL; |
| } else { |
| pr_err("Unexpected fence!\n"); |
| err = -EINVAL; |
| } |
| } |
| |
| if (f1 || f2) { |
| pr_err("Not all fences seen!\n"); |
| err = -EINVAL; |
| } |
| |
| dma_fence_put(f5); |
| error_put_f4: |
| dma_fence_put(f4); |
| error_put_f3: |
| dma_fence_put(f3); |
| error_put_f2: |
| dma_fence_put(f2); |
| error_put_f1: |
| dma_fence_put(f1); |
| return err; |
| } |
| |
| int dma_fence_unwrap(void) |
| { |
| static const struct subtest tests[] = { |
| SUBTEST(sanitycheck), |
| SUBTEST(unwrap_array), |
| SUBTEST(unwrap_chain), |
| SUBTEST(unwrap_chain_array), |
| SUBTEST(unwrap_merge), |
| SUBTEST(unwrap_merge_complex), |
| }; |
| |
| return subtests(tests, NULL); |
| } |