2016-04-28 16:46:54 +03:00
/*
* drivers / dma - buf / sync_file . c
*
* Copyright ( C ) 2012 Google , Inc .
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# include <linux/export.h>
# include <linux/file.h>
# include <linux/fs.h>
# include <linux/kernel.h>
# include <linux/poll.h>
# include <linux/sched.h>
# include <linux/slab.h>
# include <linux/uaccess.h>
# include <linux/anon_inodes.h>
2016-04-28 16:46:57 +03:00
# include <linux/sync_file.h>
# include <uapi/linux/sync_file.h>
2016-04-28 16:46:54 +03:00
static const struct file_operations sync_file_fops ;
static struct sync_file * sync_file_alloc ( int size )
{
struct sync_file * sync_file ;
sync_file = kzalloc ( size , GFP_KERNEL ) ;
if ( ! sync_file )
return NULL ;
sync_file - > file = anon_inode_getfile ( " sync_file " , & sync_file_fops ,
sync_file , 0 ) ;
if ( IS_ERR ( sync_file - > file ) )
goto err ;
kref_init ( & sync_file - > kref ) ;
init_waitqueue_head ( & sync_file - > wq ) ;
return sync_file ;
err :
kfree ( sync_file ) ;
return NULL ;
}
static void fence_check_cb_func ( struct fence * f , struct fence_cb * cb )
{
struct sync_file_cb * check ;
struct sync_file * sync_file ;
check = container_of ( cb , struct sync_file_cb , cb ) ;
sync_file = check - > sync_file ;
if ( atomic_dec_and_test ( & sync_file - > status ) )
wake_up_all ( & sync_file - > wq ) ;
}
/**
2016-04-28 16:46:55 +03:00
* sync_file_create ( ) - creates a sync file
2016-04-28 16:46:54 +03:00
* @ fence : fence to add to the sync_fence
*
* Creates a sync_file containg @ fence . Once this is called , the sync_file
2016-04-28 16:46:55 +03:00
* takes ownership of @ fence . The sync_file can be released with
* fput ( sync_file - > file ) . Returns the sync_file or NULL in case of error .
2016-04-28 16:46:54 +03:00
*/
struct sync_file * sync_file_create ( struct fence * fence )
{
struct sync_file * sync_file ;
sync_file = sync_file_alloc ( offsetof ( struct sync_file , cbs [ 1 ] ) ) ;
if ( ! sync_file )
return NULL ;
sync_file - > num_fences = 1 ;
atomic_set ( & sync_file - > status , 1 ) ;
2016-06-03 18:46:31 +03:00
snprintf ( sync_file - > name , sizeof ( sync_file - > name ) , " %s-%s%llu-%d " ,
2016-04-28 16:46:54 +03:00
fence - > ops - > get_driver_name ( fence ) ,
fence - > ops - > get_timeline_name ( fence ) , fence - > context ,
fence - > seqno ) ;
sync_file - > cbs [ 0 ] . fence = fence ;
sync_file - > cbs [ 0 ] . sync_file = sync_file ;
if ( fence_add_callback ( fence , & sync_file - > cbs [ 0 ] . cb ,
fence_check_cb_func ) )
atomic_dec ( & sync_file - > status ) ;
return sync_file ;
}
EXPORT_SYMBOL ( sync_file_create ) ;
/**
* sync_file_fdget ( ) - get a sync_file from an fd
* @ fd : fd referencing a fence
*
* Ensures @ fd references a valid sync_file , increments the refcount of the
* backing file . Returns the sync_file or NULL in case of error .
*/
static struct sync_file * sync_file_fdget ( int fd )
{
struct file * file = fget ( fd ) ;
if ( ! file )
return NULL ;
if ( file - > f_op ! = & sync_file_fops )
goto err ;
return file - > private_data ;
err :
fput ( file ) ;
return NULL ;
}
static void sync_file_add_pt ( struct sync_file * sync_file , int * i ,
struct fence * fence )
{
sync_file - > cbs [ * i ] . fence = fence ;
sync_file - > cbs [ * i ] . sync_file = sync_file ;
if ( ! fence_add_callback ( fence , & sync_file - > cbs [ * i ] . cb ,
fence_check_cb_func ) ) {
fence_get ( fence ) ;
( * i ) + + ;
}
}
/**
* sync_file_merge ( ) - merge two sync_files
* @ name : name of new fence
* @ a : sync_file a
* @ b : sync_file b
*
* Creates a new sync_file which contains copies of all the fences in both
* @ a and @ b . @ a and @ b remain valid , independent sync_file . Returns the
* new merged sync_file or NULL in case of error .
*/
static struct sync_file * sync_file_merge ( const char * name , struct sync_file * a ,
struct sync_file * b )
{
int num_fences = a - > num_fences + b - > num_fences ;
struct sync_file * sync_file ;
int i , i_a , i_b ;
unsigned long size = offsetof ( struct sync_file , cbs [ num_fences ] ) ;
sync_file = sync_file_alloc ( size ) ;
if ( ! sync_file )
return NULL ;
atomic_set ( & sync_file - > status , num_fences ) ;
/*
* Assume sync_file a and b are both ordered and have no
* duplicates with the same context .
*
* If a sync_file can only be created with sync_file_merge
* and sync_file_create , this is a reasonable assumption .
*/
for ( i = i_a = i_b = 0 ; i_a < a - > num_fences & & i_b < b - > num_fences ; ) {
struct fence * pt_a = a - > cbs [ i_a ] . fence ;
struct fence * pt_b = b - > cbs [ i_b ] . fence ;
if ( pt_a - > context < pt_b - > context ) {
sync_file_add_pt ( sync_file , & i , pt_a ) ;
i_a + + ;
} else if ( pt_a - > context > pt_b - > context ) {
sync_file_add_pt ( sync_file , & i , pt_b ) ;
i_b + + ;
} else {
if ( pt_a - > seqno - pt_b - > seqno < = INT_MAX )
sync_file_add_pt ( sync_file , & i , pt_a ) ;
else
sync_file_add_pt ( sync_file , & i , pt_b ) ;
i_a + + ;
i_b + + ;
}
}
for ( ; i_a < a - > num_fences ; i_a + + )
sync_file_add_pt ( sync_file , & i , a - > cbs [ i_a ] . fence ) ;
for ( ; i_b < b - > num_fences ; i_b + + )
sync_file_add_pt ( sync_file , & i , b - > cbs [ i_b ] . fence ) ;
if ( num_fences > i )
atomic_sub ( num_fences - i , & sync_file - > status ) ;
sync_file - > num_fences = i ;
strlcpy ( sync_file - > name , name , sizeof ( sync_file - > name ) ) ;
return sync_file ;
}
static void sync_file_free ( struct kref * kref )
{
struct sync_file * sync_file = container_of ( kref , struct sync_file ,
kref ) ;
int i ;
for ( i = 0 ; i < sync_file - > num_fences ; + + i ) {
fence_remove_callback ( sync_file - > cbs [ i ] . fence ,
& sync_file - > cbs [ i ] . cb ) ;
fence_put ( sync_file - > cbs [ i ] . fence ) ;
}
kfree ( sync_file ) ;
}
static int sync_file_release ( struct inode * inode , struct file * file )
{
struct sync_file * sync_file = file - > private_data ;
kref_put ( & sync_file - > kref , sync_file_free ) ;
return 0 ;
}
static unsigned int sync_file_poll ( struct file * file , poll_table * wait )
{
struct sync_file * sync_file = file - > private_data ;
int status ;
poll_wait ( file , & sync_file - > wq , wait ) ;
status = atomic_read ( & sync_file - > status ) ;
if ( ! status )
return POLLIN ;
if ( status < 0 )
return POLLERR ;
return 0 ;
}
static long sync_file_ioctl_merge ( struct sync_file * sync_file ,
2016-04-28 16:46:56 +03:00
unsigned long arg )
2016-04-28 16:46:54 +03:00
{
int fd = get_unused_fd_flags ( O_CLOEXEC ) ;
int err ;
struct sync_file * fence2 , * fence3 ;
struct sync_merge_data data ;
if ( fd < 0 )
return fd ;
if ( copy_from_user ( & data , ( void __user * ) arg , sizeof ( data ) ) ) {
err = - EFAULT ;
goto err_put_fd ;
}
if ( data . flags | | data . pad ) {
err = - EINVAL ;
goto err_put_fd ;
}
fence2 = sync_file_fdget ( data . fd2 ) ;
if ( ! fence2 ) {
err = - ENOENT ;
goto err_put_fd ;
}
data . name [ sizeof ( data . name ) - 1 ] = ' \0 ' ;
fence3 = sync_file_merge ( data . name , sync_file , fence2 ) ;
if ( ! fence3 ) {
err = - ENOMEM ;
goto err_put_fence2 ;
}
data . fence = fd ;
if ( copy_to_user ( ( void __user * ) arg , & data , sizeof ( data ) ) ) {
err = - EFAULT ;
goto err_put_fence3 ;
}
fd_install ( fd , fence3 - > file ) ;
fput ( fence2 - > file ) ;
return 0 ;
err_put_fence3 :
fput ( fence3 - > file ) ;
err_put_fence2 :
fput ( fence2 - > file ) ;
err_put_fd :
put_unused_fd ( fd ) ;
return err ;
}
static void sync_fill_fence_info ( struct fence * fence ,
2016-04-28 16:46:56 +03:00
struct sync_fence_info * info )
2016-04-28 16:46:54 +03:00
{
strlcpy ( info - > obj_name , fence - > ops - > get_timeline_name ( fence ) ,
sizeof ( info - > obj_name ) ) ;
strlcpy ( info - > driver_name , fence - > ops - > get_driver_name ( fence ) ,
sizeof ( info - > driver_name ) ) ;
if ( fence_is_signaled ( fence ) )
info - > status = fence - > status > = 0 ? 1 : fence - > status ;
else
info - > status = 0 ;
info - > timestamp_ns = ktime_to_ns ( fence - > timestamp ) ;
}
static long sync_file_ioctl_fence_info ( struct sync_file * sync_file ,
2016-04-28 16:46:56 +03:00
unsigned long arg )
2016-04-28 16:46:54 +03:00
{
struct sync_file_info info ;
struct sync_fence_info * fence_info = NULL ;
__u32 size ;
int ret , i ;
if ( copy_from_user ( & info , ( void __user * ) arg , sizeof ( info ) ) )
return - EFAULT ;
if ( info . flags | | info . pad )
return - EINVAL ;
/*
* Passing num_fences = 0 means that userspace doesn ' t want to
* retrieve any sync_fence_info . If num_fences = 0 we skip filling
* sync_fence_info and return the actual number of fences on
* info - > num_fences .
*/
if ( ! info . num_fences )
goto no_fences ;
if ( info . num_fences < sync_file - > num_fences )
return - EINVAL ;
size = sync_file - > num_fences * sizeof ( * fence_info ) ;
fence_info = kzalloc ( size , GFP_KERNEL ) ;
if ( ! fence_info )
return - ENOMEM ;
for ( i = 0 ; i < sync_file - > num_fences ; + + i )
sync_fill_fence_info ( sync_file - > cbs [ i ] . fence , & fence_info [ i ] ) ;
if ( copy_to_user ( u64_to_user_ptr ( info . sync_fence_info ) , fence_info ,
size ) ) {
ret = - EFAULT ;
goto out ;
}
no_fences :
strlcpy ( info . name , sync_file - > name , sizeof ( info . name ) ) ;
info . status = atomic_read ( & sync_file - > status ) ;
if ( info . status > = 0 )
info . status = ! info . status ;
info . num_fences = sync_file - > num_fences ;
if ( copy_to_user ( ( void __user * ) arg , & info , sizeof ( info ) ) )
ret = - EFAULT ;
else
ret = 0 ;
out :
kfree ( fence_info ) ;
return ret ;
}
static long sync_file_ioctl ( struct file * file , unsigned int cmd ,
2016-04-28 16:46:56 +03:00
unsigned long arg )
2016-04-28 16:46:54 +03:00
{
struct sync_file * sync_file = file - > private_data ;
switch ( cmd ) {
case SYNC_IOC_MERGE :
return sync_file_ioctl_merge ( sync_file , arg ) ;
case SYNC_IOC_FILE_INFO :
return sync_file_ioctl_fence_info ( sync_file , arg ) ;
default :
return - ENOTTY ;
}
}
static const struct file_operations sync_file_fops = {
. release = sync_file_release ,
. poll = sync_file_poll ,
. unlocked_ioctl = sync_file_ioctl ,
. compat_ioctl = sync_file_ioctl ,
} ;