2018-08-27 12:34:44 +03:00
// SPDX-License-Identifier: GPL-2.0
2018-09-11 16:42:04 +03:00
# include <linux/cred.h>
2018-08-27 12:34:44 +03:00
# include <linux/device.h>
# include <linux/dma-buf.h>
# include <linux/highmem.h>
2018-09-11 16:42:04 +03:00
# include <linux/init.h>
# include <linux/kernel.h>
2018-08-27 12:34:44 +03:00
# include <linux/memfd.h>
2018-09-11 16:42:04 +03:00
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/shmem_fs.h>
# include <linux/slab.h>
# include <linux/udmabuf.h>
2018-08-27 12:34:44 +03:00
2018-09-11 16:42:10 +03:00
static const u32 list_limit = 1024 ; /* udmabuf_create_list->count limit */
static const size_t size_limit_mb = 64 ; /* total dmabuf size, in megabytes */
2018-08-27 12:34:44 +03:00
struct udmabuf {
2018-09-11 16:42:06 +03:00
pgoff_t pagecount ;
2018-08-27 12:34:44 +03:00
struct page * * pages ;
} ;
2019-01-04 02:26:34 +03:00
static vm_fault_t udmabuf_vm_fault ( struct vm_fault * vmf )
2018-08-27 12:34:44 +03:00
{
struct vm_area_struct * vma = vmf - > vma ;
struct udmabuf * ubuf = vma - > vm_private_data ;
vmf - > page = ubuf - > pages [ vmf - > pgoff ] ;
get_page ( vmf - > page ) ;
return 0 ;
}
static const struct vm_operations_struct udmabuf_vm_ops = {
. fault = udmabuf_vm_fault ,
} ;
static int mmap_udmabuf ( struct dma_buf * buf , struct vm_area_struct * vma )
{
struct udmabuf * ubuf = buf - > priv ;
if ( ( vma - > vm_flags & ( VM_SHARED | VM_MAYSHARE ) ) = = 0 )
return - EINVAL ;
vma - > vm_ops = & udmabuf_vm_ops ;
vma - > vm_private_data = ubuf ;
return 0 ;
}
static struct sg_table * map_udmabuf ( struct dma_buf_attachment * at ,
enum dma_data_direction direction )
{
struct udmabuf * ubuf = at - > dmabuf - > priv ;
struct sg_table * sg ;
2018-09-11 16:42:05 +03:00
int ret ;
2018-08-27 12:34:44 +03:00
sg = kzalloc ( sizeof ( * sg ) , GFP_KERNEL ) ;
if ( ! sg )
2018-09-11 16:42:05 +03:00
return ERR_PTR ( - ENOMEM ) ;
ret = sg_alloc_table_from_pages ( sg , ubuf - > pages , ubuf - > pagecount ,
0 , ubuf - > pagecount < < PAGE_SHIFT ,
GFP_KERNEL ) ;
if ( ret < 0 )
goto err ;
2018-09-14 09:56:15 +03:00
if ( ! dma_map_sg ( at - > dev , sg - > sgl , sg - > nents , direction ) ) {
ret = - EINVAL ;
2018-09-11 16:42:05 +03:00
goto err ;
2018-09-14 09:56:15 +03:00
}
2018-08-27 12:34:44 +03:00
return sg ;
2018-09-11 16:42:05 +03:00
err :
2018-08-27 12:34:44 +03:00
sg_free_table ( sg ) ;
kfree ( sg ) ;
2018-09-11 16:42:05 +03:00
return ERR_PTR ( ret ) ;
2018-08-27 12:34:44 +03:00
}
static void unmap_udmabuf ( struct dma_buf_attachment * at ,
struct sg_table * sg ,
enum dma_data_direction direction )
{
2019-06-04 23:23:31 +03:00
dma_unmap_sg ( at - > dev , sg - > sgl , sg - > nents , direction ) ;
2018-08-27 12:34:44 +03:00
sg_free_table ( sg ) ;
kfree ( sg ) ;
}
static void release_udmabuf ( struct dma_buf * buf )
{
struct udmabuf * ubuf = buf - > priv ;
pgoff_t pg ;
for ( pg = 0 ; pg < ubuf - > pagecount ; pg + + )
put_page ( ubuf - > pages [ pg ] ) ;
kfree ( ubuf - > pages ) ;
kfree ( ubuf ) ;
}
static void * kmap_udmabuf ( struct dma_buf * buf , unsigned long page_num )
{
struct udmabuf * ubuf = buf - > priv ;
struct page * page = ubuf - > pages [ page_num ] ;
return kmap ( page ) ;
}
static void kunmap_udmabuf ( struct dma_buf * buf , unsigned long page_num ,
void * vaddr )
{
kunmap ( vaddr ) ;
}
2018-09-11 16:42:07 +03:00
static const struct dma_buf_ops udmabuf_ops = {
2018-08-27 12:34:44 +03:00
. map_dma_buf = map_udmabuf ,
. unmap_dma_buf = unmap_udmabuf ,
. release = release_udmabuf ,
. map = kmap_udmabuf ,
. unmap = kunmap_udmabuf ,
. mmap = mmap_udmabuf ,
} ;
# define SEALS_WANTED (F_SEAL_SHRINK)
# define SEALS_DENIED (F_SEAL_WRITE)
2018-09-11 16:42:08 +03:00
static long udmabuf_create ( const struct udmabuf_create_list * head ,
const struct udmabuf_create_item * list )
2018-08-27 12:34:44 +03:00
{
DEFINE_DMA_BUF_EXPORT_INFO ( exp_info ) ;
struct file * memfd = NULL ;
struct udmabuf * ubuf ;
struct dma_buf * buf ;
2018-09-11 16:42:11 +03:00
pgoff_t pgoff , pgcnt , pgidx , pgbuf = 0 , pglimit ;
2018-08-27 12:34:44 +03:00
struct page * page ;
int seals , ret = - EINVAL ;
u32 i , flags ;
2018-09-11 16:42:15 +03:00
ubuf = kzalloc ( sizeof ( * ubuf ) , GFP_KERNEL ) ;
2018-08-27 12:34:44 +03:00
if ( ! ubuf )
return - ENOMEM ;
2018-09-11 16:42:10 +03:00
pglimit = ( size_limit_mb * 1024 * 1024 ) > > PAGE_SHIFT ;
2018-08-27 12:34:44 +03:00
for ( i = 0 ; i < head - > count ; i + + ) {
if ( ! IS_ALIGNED ( list [ i ] . offset , PAGE_SIZE ) )
2018-09-11 16:42:11 +03:00
goto err ;
2018-08-27 12:34:44 +03:00
if ( ! IS_ALIGNED ( list [ i ] . size , PAGE_SIZE ) )
2018-09-11 16:42:11 +03:00
goto err ;
2018-08-27 12:34:44 +03:00
ubuf - > pagecount + = list [ i ] . size > > PAGE_SHIFT ;
2018-09-11 16:42:10 +03:00
if ( ubuf - > pagecount > pglimit )
2018-09-11 16:42:11 +03:00
goto err ;
2018-08-27 12:34:44 +03:00
}
2018-09-11 16:42:15 +03:00
ubuf - > pages = kmalloc_array ( ubuf - > pagecount , sizeof ( * ubuf - > pages ) ,
2018-08-27 12:34:44 +03:00
GFP_KERNEL ) ;
if ( ! ubuf - > pages ) {
ret = - ENOMEM ;
2018-09-11 16:42:11 +03:00
goto err ;
2018-08-27 12:34:44 +03:00
}
pgbuf = 0 ;
for ( i = 0 ; i < head - > count ; i + + ) {
2018-09-11 16:42:12 +03:00
ret = - EBADFD ;
2018-08-27 12:34:44 +03:00
memfd = fget ( list [ i ] . memfd ) ;
if ( ! memfd )
2018-09-11 16:42:11 +03:00
goto err ;
2018-08-27 12:34:44 +03:00
if ( ! shmem_mapping ( file_inode ( memfd ) - > i_mapping ) )
2018-09-11 16:42:11 +03:00
goto err ;
2018-08-27 12:34:44 +03:00
seals = memfd_fcntl ( memfd , F_GET_SEALS , 0 ) ;
2018-09-11 16:42:12 +03:00
if ( seals = = - EINVAL )
goto err ;
ret = - EINVAL ;
if ( ( seals & SEALS_WANTED ) ! = SEALS_WANTED | |
2018-08-27 12:34:44 +03:00
( seals & SEALS_DENIED ) ! = 0 )
2018-09-11 16:42:11 +03:00
goto err ;
2018-08-27 12:34:44 +03:00
pgoff = list [ i ] . offset > > PAGE_SHIFT ;
pgcnt = list [ i ] . size > > PAGE_SHIFT ;
for ( pgidx = 0 ; pgidx < pgcnt ; pgidx + + ) {
page = shmem_read_mapping_page (
file_inode ( memfd ) - > i_mapping , pgoff + pgidx ) ;
if ( IS_ERR ( page ) ) {
ret = PTR_ERR ( page ) ;
2018-09-11 16:42:11 +03:00
goto err ;
2018-08-27 12:34:44 +03:00
}
ubuf - > pages [ pgbuf + + ] = page ;
}
fput ( memfd ) ;
2018-09-11 16:42:11 +03:00
memfd = NULL ;
2018-08-27 12:34:44 +03:00
}
exp_info . ops = & udmabuf_ops ;
exp_info . size = ubuf - > pagecount < < PAGE_SHIFT ;
exp_info . priv = ubuf ;
2018-11-14 15:20:29 +03:00
exp_info . flags = O_RDWR ;
2018-08-27 12:34:44 +03:00
buf = dma_buf_export ( & exp_info ) ;
if ( IS_ERR ( buf ) ) {
ret = PTR_ERR ( buf ) ;
2018-09-11 16:42:11 +03:00
goto err ;
2018-08-27 12:34:44 +03:00
}
flags = 0 ;
if ( head - > flags & UDMABUF_FLAGS_CLOEXEC )
flags | = O_CLOEXEC ;
return dma_buf_fd ( buf , flags ) ;
2018-09-11 16:42:11 +03:00
err :
2018-08-27 12:34:44 +03:00
while ( pgbuf > 0 )
put_page ( ubuf - > pages [ - - pgbuf ] ) ;
2018-09-04 22:07:49 +03:00
if ( memfd )
fput ( memfd ) ;
2018-08-27 12:34:44 +03:00
kfree ( ubuf - > pages ) ;
kfree ( ubuf ) ;
return ret ;
}
static long udmabuf_ioctl_create ( struct file * filp , unsigned long arg )
{
struct udmabuf_create create ;
struct udmabuf_create_list head ;
struct udmabuf_create_item list ;
if ( copy_from_user ( & create , ( void __user * ) arg ,
2018-09-11 16:42:15 +03:00
sizeof ( create ) ) )
2018-08-27 12:34:44 +03:00
return - EFAULT ;
head . flags = create . flags ;
head . count = 1 ;
list . memfd = create . memfd ;
list . offset = create . offset ;
list . size = create . size ;
return udmabuf_create ( & head , & list ) ;
}
static long udmabuf_ioctl_create_list ( struct file * filp , unsigned long arg )
{
struct udmabuf_create_list head ;
struct udmabuf_create_item * list ;
int ret = - EINVAL ;
u32 lsize ;
if ( copy_from_user ( & head , ( void __user * ) arg , sizeof ( head ) ) )
return - EFAULT ;
2018-09-11 16:42:10 +03:00
if ( head . count > list_limit )
2018-08-27 12:34:44 +03:00
return - EINVAL ;
lsize = sizeof ( struct udmabuf_create_item ) * head . count ;
list = memdup_user ( ( void __user * ) ( arg + sizeof ( head ) ) , lsize ) ;
if ( IS_ERR ( list ) )
return PTR_ERR ( list ) ;
ret = udmabuf_create ( & head , list ) ;
kfree ( list ) ;
return ret ;
}
static long udmabuf_ioctl ( struct file * filp , unsigned int ioctl ,
unsigned long arg )
{
long ret ;
switch ( ioctl ) {
case UDMABUF_CREATE :
ret = udmabuf_ioctl_create ( filp , arg ) ;
break ;
case UDMABUF_CREATE_LIST :
ret = udmabuf_ioctl_create_list ( filp , arg ) ;
break ;
default :
2018-09-11 16:42:13 +03:00
ret = - ENOTTY ;
2018-08-27 12:34:44 +03:00
break ;
}
return ret ;
}
static const struct file_operations udmabuf_fops = {
. owner = THIS_MODULE ,
. unlocked_ioctl = udmabuf_ioctl ,
} ;
static struct miscdevice udmabuf_misc = {
. minor = MISC_DYNAMIC_MINOR ,
. name = " udmabuf " ,
. fops = & udmabuf_fops ,
} ;
static int __init udmabuf_dev_init ( void )
{
return misc_register ( & udmabuf_misc ) ;
}
static void __exit udmabuf_dev_exit ( void )
{
misc_deregister ( & udmabuf_misc ) ;
}
module_init ( udmabuf_dev_init )
module_exit ( udmabuf_dev_exit )
MODULE_AUTHOR ( " Gerd Hoffmann <kraxel@redhat.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;