2019-08-19 12:59:27 +03:00
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2019 Intel Corporation
*/
# include <linux/delay.h>
# include <linux/dma-fence.h>
# include <linux/kernel.h>
# include <linux/kthread.h>
# include <linux/sched/signal.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# 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 ;
2022-09-14 19:43:18 +03:00
dma_fence_enable_sw_signaling ( f ) ;
2019-08-19 12:59:27 +03:00
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 ;
2022-09-14 19:43:18 +03:00
dma_fence_enable_sw_signaling ( f ) ;
2019-08-19 12:59:27 +03:00
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 ;
2022-09-14 19:43:18 +03:00
dma_fence_enable_sw_signaling ( f ) ;
2019-08-19 12:59:27 +03:00
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 ;
2022-09-14 19:43:18 +03:00
dma_fence_enable_sw_signaling ( f ) ;
2019-08-19 12:59:27 +03:00
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 ;
2022-09-14 19:43:18 +03:00
dma_fence_enable_sw_signaling ( f ) ;
2019-08-19 12:59:27 +03:00
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 ;
2022-09-14 19:43:18 +03:00
dma_fence_enable_sw_signaling ( f ) ;
2019-08-19 12:59:27 +03:00
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 ;
2019-08-20 15:21:18 +03:00
timer_setup_on_stack ( & wt . timer , wait_timer , 0 ) ;
2019-08-19 12:59:27 +03:00
wt . f = mock_fence ( ) ;
if ( ! wt . f )
return - ENOMEM ;
2022-09-14 19:43:18 +03:00
dma_fence_enable_sw_signaling ( wt . f ) ;
2019-08-19 12:59:27 +03:00
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 ) ;
2019-08-20 15:21:18 +03:00
destroy_timer_on_stack ( & wt . timer ) ;
2019-08-19 12:59:27 +03:00
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 ;
}
2022-09-14 19:43:18 +03:00
dma_fence_enable_sw_signaling ( f1 ) ;
2019-08-19 12:59:27 +03:00
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 ) ;
2020-08-25 07:56:05 +03:00
if ( ! f2 | |
dma_fence_add_callback ( f2 , & cb . cb , simple_callback ) ) {
miss + + ;
cb . seen = true ;
}
2019-08-19 12:59:27 +03:00
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 ;
2023-09-08 02:40:48 +03:00
err = kthread_stop_put ( t [ i ] . task ) ;
2019-08-19 12:59:27 +03:00
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 ;
2019-08-19 22:57:40 +03:00
pr_info ( " sizeof(dma_fence)=%zu \n " , sizeof ( struct dma_fence ) ) ;
2019-08-19 12:59:27 +03:00
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 ;
}