2019-05-27 09:55:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2009-12-09 14:39:56 +03:00
/*
* Media device node
*
* Copyright ( C ) 2010 Nokia Corporation
*
* Based on drivers / media / video / v4l2_dev . c code authored by
MAINTAINERS & files: Canonize the e-mails I use at files
From now on, I'll start using my @kernel.org as my development e-mail.
As such, let's remove the entries that point to the old
mchehab@s-opensource.com at MAINTAINERS file.
For the files written with a copyright with mchehab@s-opensource,
let's keep Samsung on their names, using mchehab+samsung@kernel.org,
in order to keep pointing to my employer, with sponsors the work.
For the files written before I join Samsung (on July, 4 2013),
let's just use mchehab@kernel.org.
For bug reports, we can simply point to just kernel.org, as
this will reach my mchehab+samsung inbox anyway.
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
Signed-off-by: Brian Warner <brian.warner@samsung.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
2018-04-25 12:34:48 +03:00
* Mauro Carvalho Chehab < mchehab @ kernel . org > ( version 2 )
2009-12-09 14:39:56 +03:00
* Alan Cox , < alan @ lxorguk . ukuu . org . uk > ( version 1 )
*
* Contacts : Laurent Pinchart < laurent . pinchart @ ideasonboard . com >
* Sakari Ailus < sakari . ailus @ iki . fi >
*
* - -
*
* Generic media device node infrastructure to register and unregister
* character devices using a dynamic major number and proper reference
* counting .
*/
2012-09-24 15:26:24 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2009-12-09 14:39:56 +03:00
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/kmod.h>
# include <linux/slab.h>
# include <linux/mm.h>
# include <linux/string.h>
# include <linux/types.h>
# include <linux/uaccess.h>
# include <media/media-devnode.h>
2016-04-28 01:28:26 +03:00
# include <media/media-device.h>
2009-12-09 14:39:56 +03:00
# define MEDIA_NUM_DEVICES 256
# define MEDIA_NAME "media"
static dev_t media_dev_t ;
/*
* Active devices
*/
static DEFINE_MUTEX ( media_devnode_lock ) ;
static DECLARE_BITMAP ( media_devnode_nums , MEDIA_NUM_DEVICES ) ;
/* Called when the last user of the media device exits. */
static void media_devnode_release ( struct device * cd )
{
2016-03-23 17:22:57 +03:00
struct media_devnode * devnode = to_media_devnode ( cd ) ;
2009-12-09 14:39:56 +03:00
mutex_lock ( & media_devnode_lock ) ;
/* Mark device node number as free */
2016-03-23 17:22:57 +03:00
clear_bit ( devnode - > minor , media_devnode_nums ) ;
2009-12-09 14:39:56 +03:00
mutex_unlock ( & media_devnode_lock ) ;
/* Release media_devnode and perform other cleanups as needed. */
2016-03-23 17:22:57 +03:00
if ( devnode - > release )
devnode - > release ( devnode ) ;
2016-04-28 01:28:26 +03:00
kfree ( devnode ) ;
2016-05-04 22:48:28 +03:00
pr_debug ( " %s: Media Devnode Deallocated \n " , __func__ ) ;
2009-12-09 14:39:56 +03:00
}
static struct bus_type media_bus_type = {
. name = MEDIA_NAME ,
} ;
static ssize_t media_read ( struct file * filp , char __user * buf ,
size_t sz , loff_t * off )
{
2016-03-23 17:22:57 +03:00
struct media_devnode * devnode = media_devnode_data ( filp ) ;
2009-12-09 14:39:56 +03:00
2016-03-23 17:22:57 +03:00
if ( ! devnode - > fops - > read )
2009-12-09 14:39:56 +03:00
return - EINVAL ;
2016-03-23 17:22:57 +03:00
if ( ! media_devnode_is_registered ( devnode ) )
2009-12-09 14:39:56 +03:00
return - EIO ;
2016-03-23 17:22:57 +03:00
return devnode - > fops - > read ( filp , buf , sz , off ) ;
2009-12-09 14:39:56 +03:00
}
static ssize_t media_write ( struct file * filp , const char __user * buf ,
size_t sz , loff_t * off )
{
2016-03-23 17:22:57 +03:00
struct media_devnode * devnode = media_devnode_data ( filp ) ;
2009-12-09 14:39:56 +03:00
2016-03-23 17:22:57 +03:00
if ( ! devnode - > fops - > write )
2009-12-09 14:39:56 +03:00
return - EINVAL ;
2016-03-23 17:22:57 +03:00
if ( ! media_devnode_is_registered ( devnode ) )
2009-12-09 14:39:56 +03:00
return - EIO ;
2016-03-23 17:22:57 +03:00
return devnode - > fops - > write ( filp , buf , sz , off ) ;
2009-12-09 14:39:56 +03:00
}
2017-07-03 10:02:56 +03:00
static __poll_t media_poll ( struct file * filp ,
2009-12-09 14:39:56 +03:00
struct poll_table_struct * poll )
{
2016-03-23 17:22:57 +03:00
struct media_devnode * devnode = media_devnode_data ( filp ) ;
2009-12-09 14:39:56 +03:00
2016-03-23 17:22:57 +03:00
if ( ! media_devnode_is_registered ( devnode ) )
2018-02-12 01:34:03 +03:00
return EPOLLERR | EPOLLHUP ;
2016-03-23 17:22:57 +03:00
if ( ! devnode - > fops - > poll )
2009-12-09 14:39:56 +03:00
return DEFAULT_POLLMASK ;
2016-03-23 17:22:57 +03:00
return devnode - > fops - > poll ( filp , poll ) ;
2009-12-09 14:39:56 +03:00
}
2013-01-22 19:27:55 +04:00
static long
__media_ioctl ( struct file * filp , unsigned int cmd , unsigned long arg ,
long ( * ioctl_func ) ( struct file * filp , unsigned int cmd ,
unsigned long arg ) )
2009-12-09 14:39:56 +03:00
{
2016-03-23 17:22:57 +03:00
struct media_devnode * devnode = media_devnode_data ( filp ) ;
2009-12-09 14:39:56 +03:00
2013-01-22 19:27:55 +04:00
if ( ! ioctl_func )
2009-12-09 14:39:56 +03:00
return - ENOTTY ;
2016-03-23 17:22:57 +03:00
if ( ! media_devnode_is_registered ( devnode ) )
2009-12-09 14:39:56 +03:00
return - EIO ;
2013-01-22 19:27:55 +04:00
return ioctl_func ( filp , cmd , arg ) ;
}
static long media_ioctl ( struct file * filp , unsigned int cmd , unsigned long arg )
{
2016-03-23 17:22:57 +03:00
struct media_devnode * devnode = media_devnode_data ( filp ) ;
2013-01-22 19:27:55 +04:00
2016-03-23 17:22:57 +03:00
return __media_ioctl ( filp , cmd , arg , devnode - > fops - > ioctl ) ;
2009-12-09 14:39:56 +03:00
}
2013-01-22 19:27:55 +04:00
# ifdef CONFIG_COMPAT
static long media_compat_ioctl ( struct file * filp , unsigned int cmd ,
unsigned long arg )
{
2016-03-23 17:22:57 +03:00
struct media_devnode * devnode = media_devnode_data ( filp ) ;
2013-01-22 19:27:55 +04:00
2016-03-23 17:22:57 +03:00
return __media_ioctl ( filp , cmd , arg , devnode - > fops - > compat_ioctl ) ;
2013-01-22 19:27:55 +04:00
}
# endif /* CONFIG_COMPAT */
2009-12-09 14:39:56 +03:00
/* Override for the open function */
static int media_open ( struct inode * inode , struct file * filp )
{
2016-03-23 17:22:57 +03:00
struct media_devnode * devnode ;
2009-12-09 14:39:56 +03:00
int ret ;
/* Check if the media device is available. This needs to be done with
* the media_devnode_lock held to prevent an open / unregister race :
* without the lock , the device could be unregistered and freed between
* the media_devnode_is_registered ( ) and get_device ( ) calls , leading to
* a crash .
*/
mutex_lock ( & media_devnode_lock ) ;
2016-03-23 17:22:57 +03:00
devnode = container_of ( inode - > i_cdev , struct media_devnode , cdev ) ;
2009-12-09 14:39:56 +03:00
/* return ENXIO if the media device has been removed
already or if it is not registered anymore . */
2016-03-23 17:22:57 +03:00
if ( ! media_devnode_is_registered ( devnode ) ) {
2009-12-09 14:39:56 +03:00
mutex_unlock ( & media_devnode_lock ) ;
return - ENXIO ;
}
/* and increase the device refcount */
2016-03-23 17:22:57 +03:00
get_device ( & devnode - > dev ) ;
2009-12-09 14:39:56 +03:00
mutex_unlock ( & media_devnode_lock ) ;
2016-03-23 17:22:57 +03:00
filp - > private_data = devnode ;
2009-12-09 14:39:56 +03:00
2016-03-23 17:22:57 +03:00
if ( devnode - > fops - > open ) {
ret = devnode - > fops - > open ( filp ) ;
2009-12-09 14:39:56 +03:00
if ( ret ) {
2016-03-23 17:22:57 +03:00
put_device ( & devnode - > dev ) ;
2016-01-28 02:49:33 +03:00
filp - > private_data = NULL ;
2009-12-09 14:39:56 +03:00
return ret ;
}
}
return 0 ;
}
/* Override for the release function */
static int media_release ( struct inode * inode , struct file * filp )
{
2016-03-23 17:22:57 +03:00
struct media_devnode * devnode = media_devnode_data ( filp ) ;
2009-12-09 14:39:56 +03:00
2016-03-23 17:22:57 +03:00
if ( devnode - > fops - > release )
devnode - > fops - > release ( filp ) ;
2009-12-09 14:39:56 +03:00
2016-03-21 16:30:28 +03:00
filp - > private_data = NULL ;
2009-12-09 14:39:56 +03:00
/* decrease the refcount unconditionally since the release()
return value is ignored . */
2016-03-23 17:22:57 +03:00
put_device ( & devnode - > dev ) ;
2016-05-04 22:48:28 +03:00
pr_debug ( " %s: Media Release \n " , __func__ ) ;
2014-09-03 22:18:27 +04:00
return 0 ;
2009-12-09 14:39:56 +03:00
}
static const struct file_operations media_devnode_fops = {
. owner = THIS_MODULE ,
. read = media_read ,
. write = media_write ,
. open = media_open ,
. unlocked_ioctl = media_ioctl ,
2013-01-22 19:27:55 +04:00
# ifdef CONFIG_COMPAT
. compat_ioctl = media_compat_ioctl ,
# endif /* CONFIG_COMPAT */
2009-12-09 14:39:56 +03:00
. release = media_release ,
. poll = media_poll ,
. llseek = no_llseek ,
} ;
2016-04-28 01:28:26 +03:00
int __must_check media_devnode_register ( struct media_device * mdev ,
struct media_devnode * devnode ,
2013-12-12 19:38:17 +04:00
struct module * owner )
2009-12-09 14:39:56 +03:00
{
int minor ;
int ret ;
/* Part 1: Find a free minor number */
mutex_lock ( & media_devnode_lock ) ;
2011-05-30 22:45:47 +04:00
minor = find_next_zero_bit ( media_devnode_nums , MEDIA_NUM_DEVICES , 0 ) ;
2009-12-09 14:39:56 +03:00
if ( minor = = MEDIA_NUM_DEVICES ) {
mutex_unlock ( & media_devnode_lock ) ;
2012-09-24 15:26:24 +04:00
pr_err ( " could not get a free minor \n " ) ;
2016-05-04 22:48:28 +03:00
kfree ( devnode ) ;
2009-12-09 14:39:56 +03:00
return - ENFILE ;
}
2011-05-30 22:45:47 +04:00
set_bit ( minor , media_devnode_nums ) ;
2009-12-09 14:39:56 +03:00
mutex_unlock ( & media_devnode_lock ) ;
2016-03-23 17:22:57 +03:00
devnode - > minor = minor ;
2016-04-28 01:28:26 +03:00
devnode - > media_dev = mdev ;
2009-12-09 14:39:56 +03:00
2016-05-04 22:48:28 +03:00
/* Part 1: Initialize dev now to use dev.kobj for cdev.kobj.parent */
devnode - > dev . bus = & media_bus_type ;
devnode - > dev . devt = MKDEV ( MAJOR ( media_dev_t ) , devnode - > minor ) ;
devnode - > dev . release = media_devnode_release ;
if ( devnode - > parent )
devnode - > dev . parent = devnode - > parent ;
dev_set_name ( & devnode - > dev , " media%d " , devnode - > minor ) ;
device_initialize ( & devnode - > dev ) ;
2017-03-17 21:48:18 +03:00
/* Part 2: Initialize the character device */
2016-03-23 17:22:57 +03:00
cdev_init ( & devnode - > cdev , & media_devnode_fops ) ;
devnode - > cdev . owner = owner ;
2019-02-21 16:22:41 +03:00
kobject_set_name ( & devnode - > cdev . kobj , " media%d " , devnode - > minor ) ;
2009-12-09 14:39:56 +03:00
2017-03-17 21:48:18 +03:00
/* Part 3: Add the media and char device */
ret = cdev_device_add ( & devnode - > cdev , & devnode - > dev ) ;
2009-12-09 14:39:56 +03:00
if ( ret < 0 ) {
2017-03-17 21:48:18 +03:00
pr_err ( " %s: cdev_device_add failed \n " , __func__ ) ;
2016-05-04 22:48:28 +03:00
goto cdev_add_error ;
2009-12-09 14:39:56 +03:00
}
/* Part 4: Activate this minor. The char device can now be used. */
2016-03-23 17:22:57 +03:00
set_bit ( MEDIA_FLAG_REGISTERED , & devnode - > flags ) ;
2009-12-09 14:39:56 +03:00
return 0 ;
2016-05-04 22:48:28 +03:00
cdev_add_error :
mutex_lock ( & media_devnode_lock ) ;
2016-03-23 17:22:57 +03:00
clear_bit ( devnode - > minor , media_devnode_nums ) ;
2016-05-04 22:48:28 +03:00
devnode - > media_dev = NULL ;
2016-03-21 14:33:12 +03:00
mutex_unlock ( & media_devnode_lock ) ;
2016-05-04 22:48:28 +03:00
put_device ( & devnode - > dev ) ;
2009-12-09 14:39:56 +03:00
return ret ;
}
2016-06-10 20:37:23 +03:00
void media_devnode_unregister_prepare ( struct media_devnode * devnode )
2009-12-09 14:39:56 +03:00
{
2016-03-23 17:22:57 +03:00
/* Check if devnode was ever registered at all */
if ( ! media_devnode_is_registered ( devnode ) )
2009-12-09 14:39:56 +03:00
return ;
mutex_lock ( & media_devnode_lock ) ;
2016-03-23 17:22:57 +03:00
clear_bit ( MEDIA_FLAG_REGISTERED , & devnode - > flags ) ;
2016-06-10 20:37:23 +03:00
mutex_unlock ( & media_devnode_lock ) ;
}
void media_devnode_unregister ( struct media_devnode * devnode )
{
mutex_lock ( & media_devnode_lock ) ;
2016-05-04 22:48:28 +03:00
/* Delete the cdev on this minor as well */
2017-03-17 21:48:18 +03:00
cdev_device_del ( & devnode - > cdev , & devnode - > dev ) ;
2016-05-04 22:48:28 +03:00
devnode - > media_dev = NULL ;
2019-04-02 03:43:18 +03:00
mutex_unlock ( & media_devnode_lock ) ;
2016-05-04 22:48:28 +03:00
put_device ( & devnode - > dev ) ;
2009-12-09 14:39:56 +03:00
}
/*
* Initialise media for linux
*/
static int __init media_devnode_init ( void )
{
int ret ;
2012-09-24 15:26:24 +04:00
pr_info ( " Linux media interface: v0.10 \n " ) ;
2009-12-09 14:39:56 +03:00
ret = alloc_chrdev_region ( & media_dev_t , 0 , MEDIA_NUM_DEVICES ,
MEDIA_NAME ) ;
if ( ret < 0 ) {
2012-09-24 15:26:24 +04:00
pr_warn ( " unable to allocate major \n " ) ;
2009-12-09 14:39:56 +03:00
return ret ;
}
ret = bus_register ( & media_bus_type ) ;
if ( ret < 0 ) {
unregister_chrdev_region ( media_dev_t , MEDIA_NUM_DEVICES ) ;
2012-09-24 15:26:24 +04:00
pr_warn ( " bus_register failed \n " ) ;
2009-12-09 14:39:56 +03:00
return - EIO ;
}
return 0 ;
}
static void __exit media_devnode_exit ( void )
{
bus_unregister ( & media_bus_type ) ;
unregister_chrdev_region ( media_dev_t , MEDIA_NUM_DEVICES ) ;
}
2012-03-12 17:02:47 +04:00
subsys_initcall ( media_devnode_init ) ;
2009-12-09 14:39:56 +03:00
module_exit ( media_devnode_exit )
MODULE_AUTHOR ( " Laurent Pinchart <laurent.pinchart@ideasonboard.com> " ) ;
MODULE_DESCRIPTION ( " Device node registration for media drivers " ) ;
MODULE_LICENSE ( " GPL " ) ;