2005-04-17 02:20:36 +04:00
/*
* linux / drivers / char / misc . c
*
* Generic misc open routine by Johan Myreen
*
* Based on code from Linus
*
* Teemu Rantanen ' s Microsoft Busmouse support and Derrick Cole ' s
* changes incorporated into 0.97 pl4
* by Peter Cervasio ( pete % q106fm . uucp @ wupost . wustl . edu ) ( 08 SEP92 )
* See busmouse . c for particulars .
*
* Made things a lot mode modular - easy to compile in just one or two
* of the misc drivers , as they are now completely independent . Linus .
*
* Support for loadable modules . 8 - Sep - 95 Philip Blundell < pjb27 @ cam . ac . uk >
*
* Fixed a failing symbol register to free the device registration
* Alan Cox < alan @ lxorguk . ukuu . org . uk > 21 - Jan - 96
*
* Dynamic minors and / proc / mice by Alessandro Rubini . 26 - Mar - 96
*
* Renamed to misc and miscdevice to be more accurate . Alan Cox 26 - Mar - 96
*
* Handling of mouse minor numbers for kerneld :
* Idea by Jacques Gelinas < jack @ solucorp . qc . ca > ,
* adapted by Bjorn Ekwall < bj0rn @ blox . se >
* corrected by Alan Cox < alan @ lxorguk . ukuu . org . uk >
*
* Changes for kmod ( from kerneld ) :
* Cyrus Durgin < cider @ speakeasy . org >
*
* Added devfs support . Richard Gooch < rgooch @ atnf . csiro . au > 10 - Jan - 1998
*/
# include <linux/module.h>
# include <linux/fs.h>
# include <linux/errno.h>
# include <linux/miscdevice.h>
# include <linux/kernel.h>
# include <linux/major.h>
# include <linux/slab.h>
2007-05-08 11:32:08 +04:00
# include <linux/mutex.h>
2005-04-17 02:20:36 +04:00
# include <linux/proc_fs.h>
# include <linux/seq_file.h>
# include <linux/stat.h>
# include <linux/init.h>
# include <linux/device.h>
# include <linux/tty.h>
# include <linux/kmod.h>
2008-05-15 21:07:52 +04:00
# include <linux/smp_lock.h>
2005-04-17 02:20:36 +04:00
/*
* Head entry for the doubly linked miscdevice list
*/
static LIST_HEAD ( misc_list ) ;
2007-05-08 11:32:08 +04:00
static DEFINE_MUTEX ( misc_mtx ) ;
2005-04-17 02:20:36 +04:00
/*
* Assigned numbers , used for dynamic minors
*/
# define DYNAMIC_MINORS 64 /* like dynamic majors */
static unsigned char misc_minors [ DYNAMIC_MINORS / 8 ] ;
extern int pmu_device_init ( void ) ;
# ifdef CONFIG_PROC_FS
static void * misc_seq_start ( struct seq_file * seq , loff_t * pos )
{
2007-05-08 11:32:08 +04:00
mutex_lock ( & misc_mtx ) ;
2007-07-16 10:39:53 +04:00
return seq_list_start ( & misc_list , * pos ) ;
2005-04-17 02:20:36 +04:00
}
static void * misc_seq_next ( struct seq_file * seq , void * v , loff_t * pos )
{
2007-07-16 10:39:53 +04:00
return seq_list_next ( v , & misc_list , pos ) ;
2005-04-17 02:20:36 +04:00
}
static void misc_seq_stop ( struct seq_file * seq , void * v )
{
2007-05-08 11:32:08 +04:00
mutex_unlock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
}
static int misc_seq_show ( struct seq_file * seq , void * v )
{
2007-07-16 10:39:53 +04:00
const struct miscdevice * p = list_entry ( v , struct miscdevice , list ) ;
2005-04-17 02:20:36 +04:00
seq_printf ( seq , " %3i %s \n " , p - > minor , p - > name ? p - > name : " " ) ;
return 0 ;
}
static struct seq_operations misc_seq_ops = {
. start = misc_seq_start ,
. next = misc_seq_next ,
. stop = misc_seq_stop ,
. show = misc_seq_show ,
} ;
static int misc_seq_open ( struct inode * inode , struct file * file )
{
return seq_open ( file , & misc_seq_ops ) ;
}
2006-07-03 11:24:21 +04:00
static const struct file_operations misc_proc_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. open = misc_seq_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = seq_release ,
} ;
# endif
static int misc_open ( struct inode * inode , struct file * file )
{
int minor = iminor ( inode ) ;
struct miscdevice * c ;
int err = - ENODEV ;
2006-03-28 13:56:41 +04:00
const struct file_operations * old_fops , * new_fops = NULL ;
2005-04-17 02:20:36 +04:00
2008-05-15 21:07:52 +04:00
lock_kernel ( ) ;
2007-05-08 11:32:08 +04:00
mutex_lock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
list_for_each_entry ( c , & misc_list , list ) {
if ( c - > minor = = minor ) {
new_fops = fops_get ( c - > fops ) ;
break ;
}
}
if ( ! new_fops ) {
2007-05-08 11:32:08 +04:00
mutex_unlock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
request_module ( " char-major-%d-%d " , MISC_MAJOR , minor ) ;
2007-05-08 11:32:08 +04:00
mutex_lock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
list_for_each_entry ( c , & misc_list , list ) {
if ( c - > minor = = minor ) {
new_fops = fops_get ( c - > fops ) ;
break ;
}
}
if ( ! new_fops )
goto fail ;
}
err = 0 ;
old_fops = file - > f_op ;
file - > f_op = new_fops ;
if ( file - > f_op - > open ) {
err = file - > f_op - > open ( inode , file ) ;
if ( err ) {
fops_put ( file - > f_op ) ;
file - > f_op = fops_get ( old_fops ) ;
}
}
fops_put ( old_fops ) ;
fail :
2007-05-08 11:32:08 +04:00
mutex_unlock ( & misc_mtx ) ;
2008-05-15 21:07:52 +04:00
unlock_kernel ( ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
2005-03-23 20:53:09 +03:00
static struct class * misc_class ;
2005-04-17 02:20:36 +04:00
2006-07-03 11:24:21 +04:00
static const struct file_operations misc_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. open = misc_open ,
} ;
/**
* misc_register - register a miscellaneous device
* @ misc : device structure
*
* Register a miscellaneous device with the kernel . If the minor
* number is set to % MISC_DYNAMIC_MINOR a minor number is assigned
* and placed in the minor field of the structure . For other cases
* the minor number requested is used .
*
* The structure passed is linked into the kernel and may not be
* destroyed until it has been unregistered .
*
* A zero is returned on success and a negative errno code for
* failure .
*/
int misc_register ( struct miscdevice * misc )
{
struct miscdevice * c ;
dev_t dev ;
2005-06-21 08:15:16 +04:00
int err = 0 ;
2005-04-17 02:20:36 +04:00
2006-12-07 07:37:08 +03:00
INIT_LIST_HEAD ( & misc - > list ) ;
2007-05-08 11:32:08 +04:00
mutex_lock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
list_for_each_entry ( c , & misc_list , list ) {
if ( c - > minor = = misc - > minor ) {
2007-05-08 11:32:08 +04:00
mutex_unlock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
}
if ( misc - > minor = = MISC_DYNAMIC_MINOR ) {
int i = DYNAMIC_MINORS ;
while ( - - i > = 0 )
if ( ( misc_minors [ i > > 3 ] & ( 1 < < ( i & 7 ) ) ) = = 0 )
break ;
if ( i < 0 ) {
2007-05-08 11:32:08 +04:00
mutex_unlock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
misc - > minor = i ;
}
if ( misc - > minor < DYNAMIC_MINORS )
misc_minors [ misc - > minor > > 3 ] | = 1 < < ( misc - > minor & 7 ) ;
dev = MKDEV ( MISC_MAJOR , misc - > minor ) ;
2009-04-30 17:23:42 +04:00
misc - > this_device = device_create ( misc_class , misc - > parent , dev ,
misc , " %s " , misc - > name ) ;
2006-07-28 03:16:04 +04:00
if ( IS_ERR ( misc - > this_device ) ) {
err = PTR_ERR ( misc - > this_device ) ;
2005-04-17 02:20:36 +04:00
goto out ;
}
/*
* Add it to the front , so that later devices can " override "
* earlier defaults
*/
list_add ( & misc - > list , & misc_list ) ;
out :
2007-05-08 11:32:08 +04:00
mutex_unlock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
/**
2008-03-23 22:28:24 +03:00
* misc_deregister - unregister a miscellaneous device
2005-04-17 02:20:36 +04:00
* @ misc : device to unregister
*
* Unregister a miscellaneous device that was previously
* successfully registered with misc_register ( ) . Success
* is indicated by a zero return , a negative errno code
* indicates an error .
*/
2008-03-23 22:28:24 +03:00
int misc_deregister ( struct miscdevice * misc )
2005-04-17 02:20:36 +04:00
{
int i = misc - > minor ;
if ( list_empty ( & misc - > list ) )
return - EINVAL ;
2007-05-08 11:32:08 +04:00
mutex_lock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
list_del ( & misc - > list ) ;
2008-03-23 22:28:24 +03:00
device_destroy ( misc_class , MKDEV ( MISC_MAJOR , misc - > minor ) ) ;
2005-04-17 02:20:36 +04:00
if ( i < DYNAMIC_MINORS & & i > 0 ) {
misc_minors [ i > > 3 ] & = ~ ( 1 < < ( misc - > minor & 7 ) ) ;
}
2007-05-08 11:32:08 +04:00
mutex_unlock ( & misc_mtx ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
EXPORT_SYMBOL ( misc_register ) ;
2008-03-23 22:28:24 +03:00
EXPORT_SYMBOL ( misc_deregister ) ;
2005-04-17 02:20:36 +04:00
2009-04-30 17:23:42 +04:00
static char * misc_nodename ( struct device * dev )
{
struct miscdevice * c = dev_get_drvdata ( dev ) ;
if ( c - > devnode )
return kstrdup ( c - > devnode , GFP_KERNEL ) ;
return NULL ;
}
2005-04-17 02:20:36 +04:00
static int __init misc_init ( void )
{
2008-04-29 12:02:34 +04:00
int err ;
2005-04-17 02:20:36 +04:00
2008-04-29 12:02:34 +04:00
# ifdef CONFIG_PROC_FS
proc_create ( " misc " , 0 , NULL , & misc_proc_fops ) ;
2005-04-17 02:20:36 +04:00
# endif
2005-03-23 20:53:09 +03:00
misc_class = class_create ( THIS_MODULE , " misc " ) ;
2008-04-29 12:02:34 +04:00
err = PTR_ERR ( misc_class ) ;
2005-04-17 02:20:36 +04:00
if ( IS_ERR ( misc_class ) )
2008-04-29 12:02:34 +04:00
goto fail_remove ;
2005-09-07 02:16:58 +04:00
2008-04-29 12:02:34 +04:00
err = - EIO ;
if ( register_chrdev ( MISC_MAJOR , " misc " , & misc_fops ) )
goto fail_printk ;
2009-04-30 17:23:42 +04:00
misc_class - > nodename = misc_nodename ;
2005-04-17 02:20:36 +04:00
return 0 ;
2008-04-29 12:02:34 +04:00
fail_printk :
printk ( " unable to get major %d for misc devices \n " , MISC_MAJOR ) ;
class_destroy ( misc_class ) ;
fail_remove :
remove_proc_entry ( " misc " , NULL ) ;
return err ;
2005-04-17 02:20:36 +04:00
}
subsys_initcall ( misc_init ) ;