/* SPDX-License-Identifier: MIT */ /* * Copyright © 2019 Intel Corporation */ #include #include #include #include #include #include #include #include "selftest.h" static struct kmem_cache *slab_fences; static struct mock_fence { struct dma_fence base; struct spinlock 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)); } struct wait_cb { struct dma_fence_cb cb; struct task_struct *task; }; static void mock_wakeup(struct dma_fence *f, struct dma_fence_cb *cb) { wake_up_process(container_of(cb, struct wait_cb, cb)->task); } static long mock_wait(struct dma_fence *f, bool intr, long timeout) { const int state = intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE; struct wait_cb cb = { .task = current }; if (dma_fence_add_callback(f, &cb.cb, mock_wakeup)) return timeout; while (timeout) { set_current_state(state); if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &f->flags)) break; if (signal_pending_state(state, current)) break; timeout = schedule_timeout(timeout); } __set_current_state(TASK_RUNNING); if (!dma_fence_remove_callback(f, &cb.cb)) return timeout; if (signal_pending_state(state, current)) return -ERESTARTSYS; return -ETIME; } static const struct dma_fence_ops mock_ops = { .get_driver_name = mock_name, .get_timeline_name = mock_name, .wait = mock_wait, .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 int sanitycheck(void *arg) { struct dma_fence *f; f = mock_fence(); if (!f) return -ENOMEM; dma_fence_enable_sw_signaling(f); dma_fence_signal(f); dma_fence_put(f); return 0; } static int test_signaling(void *arg) { struct dma_fence *f; int err = -EINVAL; f = mock_fence(); if (!f) return -ENOMEM; dma_fence_enable_sw_signaling(f); if (dma_fence_is_signaled(f)) { pr_err("Fence unexpectedly signaled on creation\n"); goto err_free; } if (dma_fence_signal(f)) { pr_err("Fence reported being already signaled\n"); goto err_free; } if (!dma_fence_is_signaled(f)) { pr_err("Fence not reporting signaled\n"); goto err_free; } if (!dma_fence_signal(f)) { pr_err("Fence reported not being already signaled\n"); goto err_free; } err = 0; err_free: dma_fence_put(f); return err; } struct simple_cb { struct dma_fence_cb cb; bool seen; }; static void simple_callback(struct dma_fence *f, struct dma_fence_cb *cb) { smp_store_mb(container_of(cb, struct simple_cb, cb)->seen, true); } static int test_add_callback(void *arg) { struct simple_cb cb = {}; struct dma_fence *f; int err = -EINVAL; f = mock_fence(); if (!f) return -ENOMEM; if (dma_fence_add_callback(f, &cb.cb, simple_callback)) { pr_err("Failed to add callback, fence already signaled!\n"); goto err_free; } dma_fence_signal(f); if (!cb.seen) { pr_err("Callback failed!\n"); goto err_free; } err = 0; err_free: dma_fence_put(f); return err; } static int test_late_add_callback(void *arg) { struct simple_cb cb = {}; struct dma_fence *f; int err = -EINVAL; f = mock_fence(); if (!f) return -ENOMEM; dma_fence_enable_sw_signaling(f); dma_fence_signal(f); if (!dma_fence_add_callback(f, &cb.cb, simple_callback)) { pr_err("Added callback, but fence was already signaled!\n"); goto err_free; } dma_fence_signal(f); if (cb.seen) { pr_err("Callback called after failed attachment !\n"); goto err_free; } err = 0; err_free: dma_fence_put(f); return err; } static int test_rm_callback(void *arg) { struct simple_cb cb = {}; struct dma_fence *f; int err = -EINVAL; f = mock_fence(); if (!f) return -ENOMEM; if (dma_fence_add_callback(f, &cb.cb, simple_callback)) { pr_err("Failed to add callback, fence already signaled!\n"); goto err_free; } if (!dma_fence_remove_callback(f, &cb.cb)) { pr_err("Failed to remove callback!\n"); goto err_free; } dma_fence_signal(f); if (cb.seen) { pr_err("Callback still signaled after removal!\n"); goto err_free; } err = 0; err_free: dma_fence_put(f); return err; } static int test_late_rm_callback(void *arg) { struct simple_cb cb = {}; struct dma_fence *f; int err = -EINVAL; f = mock_fence(); if (!f) return -ENOMEM; if (dma_fence_add_callback(f, &cb.cb, simple_callback)) { pr_err("Failed to add callback, fence already signaled!\n"); goto err_free; } dma_fence_signal(f); if (!cb.seen) { pr_err("Callback failed!\n"); goto err_free; } if (dma_fence_remove_callback(f, &cb.cb)) { pr_err("Callback removal succeed after being executed!\n"); goto err_free; } err = 0; err_free: dma_fence_put(f); return err; } static int test_status(void *arg) { struct dma_fence *f; int err = -EINVAL; f = mock_fence(); if (!f) return -ENOMEM; dma_fence_enable_sw_signaling(f); if (dma_fence_get_status(f)) { pr_err("Fence unexpectedly has signaled status on creation\n"); goto err_free; } dma_fence_signal(f); if (!dma_fence_get_status(f)) { pr_err("Fence not reporting signaled status\n"); goto err_free; } err = 0; err_free: dma_fence_put(f); return err; } static int test_error(void *arg) { struct dma_fence *f; int err = -EINVAL; f = mock_fence(); if (!f) return -ENOMEM; dma_fence_enable_sw_signaling(f); dma_fence_set_error(f, -EIO); if (dma_fence_get_status(f)) { pr_err("Fence unexpectedly has error status before signal\n"); goto err_free; } dma_fence_signal(f); if (dma_fence_get_status(f) != -EIO) { pr_err("Fence not reporting error status, got %d\n", dma_fence_get_status(f)); goto err_free; } err = 0; err_free: dma_fence_put(f); return err; } static int test_wait(void *arg) { struct dma_fence *f; int err = -EINVAL; f = mock_fence(); if (!f) return -ENOMEM; dma_fence_enable_sw_signaling(f); if (dma_fence_wait_timeout(f, false, 0) != -ETIME) { pr_err("Wait reported complete before being signaled\n"); goto err_free; } dma_fence_signal(f); if (dma_fence_wait_timeout(f, false, 0) != 0) { pr_err("Wait reported incomplete after being signaled\n"); goto err_free; } err = 0; err_free: dma_fence_signal(f); dma_fence_put(f); return err; } struct wait_timer { struct timer_list timer; struct dma_fence *f; }; static void wait_timer(struct timer_list *timer) { struct wait_timer *wt = from_timer(wt, timer, timer); dma_fence_signal(wt->f); } static int test_wait_timeout(void *arg) { struct wait_timer wt; int err = -EINVAL; timer_setup_on_stack(&wt.timer, wait_timer, 0); wt.f = mock_fence(); if (!wt.f) return -ENOMEM; dma_fence_enable_sw_signaling(wt.f); if (dma_fence_wait_timeout(wt.f, false, 1) != -ETIME) { pr_err("Wait reported complete before being signaled\n"); goto err_free; } mod_timer(&wt.timer, jiffies + 1); if (dma_fence_wait_timeout(wt.f, false, 2) == -ETIME) { if (timer_pending(&wt.timer)) { pr_notice("Timer did not fire within the jiffie!\n"); err = 0; /* not our fault! */ } else { pr_err("Wait reported incomplete after timeout\n"); } goto err_free; } err = 0; err_free: del_timer_sync(&wt.timer); destroy_timer_on_stack(&wt.timer); dma_fence_signal(wt.f); dma_fence_put(wt.f); return err; } static int test_stub(void *arg) { struct dma_fence *f[64]; int err = -EINVAL; int i; for (i = 0; i < ARRAY_SIZE(f); i++) { f[i] = dma_fence_get_stub(); if (!dma_fence_is_signaled(f[i])) { pr_err("Obtained unsignaled stub fence!\n"); goto err; } } err = 0; err: while (i--) dma_fence_put(f[i]); return err; } /* Now off to the races! */ struct race_thread { struct dma_fence __rcu **fences; struct task_struct *task; bool before; int id; }; static void __wait_for_callbacks(struct dma_fence *f) { spin_lock_irq(f->lock); spin_unlock_irq(f->lock); } static int thread_signal_callback(void *arg) { const struct race_thread *t = arg; unsigned long pass = 0; unsigned long miss = 0; int err = 0; while (!err && !kthread_should_stop()) { struct dma_fence *f1, *f2; struct simple_cb cb; f1 = mock_fence(); if (!f1) { err = -ENOMEM; break; } dma_fence_enable_sw_signaling(f1); rcu_assign_pointer(t->fences[t->id], f1); smp_wmb(); rcu_read_lock(); do { f2 = dma_fence_get_rcu_safe(&t->fences[!t->id]); } while (!f2 && !kthread_should_stop()); rcu_read_unlock(); if (t->before) dma_fence_signal(f1); smp_store_mb(cb.seen, false); if (!f2 || dma_fence_add_callback(f2, &cb.cb, simple_callback)) { miss++; cb.seen = true; } if (!t->before) dma_fence_signal(f1); if (!cb.seen) { dma_fence_wait(f2, false); __wait_for_callbacks(f2); } if (!READ_ONCE(cb.seen)) { pr_err("Callback not seen on thread %d, pass %lu (%lu misses), signaling %s add_callback; fence signaled? %s\n", t->id, pass, miss, t->before ? "before" : "after", dma_fence_is_signaled(f2) ? "yes" : "no"); err = -EINVAL; } dma_fence_put(f2); rcu_assign_pointer(t->fences[t->id], NULL); smp_wmb(); dma_fence_put(f1); pass++; } pr_info("%s[%d] completed %lu passes, %lu misses\n", __func__, t->id, pass, miss); return err; } static int race_signal_callback(void *arg) { struct dma_fence __rcu *f[2] = {}; int ret = 0; int pass; for (pass = 0; !ret && pass <= 1; pass++) { struct race_thread t[2]; int i; for (i = 0; i < ARRAY_SIZE(t); i++) { t[i].fences = f; t[i].id = i; t[i].before = pass; t[i].task = kthread_run(thread_signal_callback, &t[i], "dma-fence:%d", i); get_task_struct(t[i].task); } msleep(50); for (i = 0; i < ARRAY_SIZE(t); i++) { int err; err = kthread_stop_put(t[i].task); if (err && !ret) ret = err; } } return ret; } int dma_fence(void) { static const struct subtest tests[] = { SUBTEST(sanitycheck), SUBTEST(test_signaling), SUBTEST(test_add_callback), SUBTEST(test_late_add_callback), SUBTEST(test_rm_callback), SUBTEST(test_late_rm_callback), SUBTEST(test_status), SUBTEST(test_error), SUBTEST(test_wait), SUBTEST(test_wait_timeout), SUBTEST(test_stub), SUBTEST(race_signal_callback), }; int ret; pr_info("sizeof(dma_fence)=%zu\n", sizeof(struct dma_fence)); 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; }