xref: /linux/drivers/char/misc.c (revision 62322d2554d2f9680c8ace7bbf1f97d8fa84ad1a)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  * linux/drivers/char/misc.c
31da177e4SLinus Torvalds  *
41da177e4SLinus Torvalds  * Generic misc open routine by Johan Myreen
51da177e4SLinus Torvalds  *
61da177e4SLinus Torvalds  * Based on code from Linus
71da177e4SLinus Torvalds  *
81da177e4SLinus Torvalds  * Teemu Rantanen's Microsoft Busmouse support and Derrick Cole's
91da177e4SLinus Torvalds  *   changes incorporated into 0.97pl4
101da177e4SLinus Torvalds  *   by Peter Cervasio (pete%q106fm.uucp@wupost.wustl.edu) (08SEP92)
111da177e4SLinus Torvalds  *   See busmouse.c for particulars.
121da177e4SLinus Torvalds  *
131da177e4SLinus Torvalds  * Made things a lot mode modular - easy to compile in just one or two
141da177e4SLinus Torvalds  * of the misc drivers, as they are now completely independent. Linus.
151da177e4SLinus Torvalds  *
161da177e4SLinus Torvalds  * Support for loadable modules. 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk>
171da177e4SLinus Torvalds  *
181da177e4SLinus Torvalds  * Fixed a failing symbol register to free the device registration
191da177e4SLinus Torvalds  *		Alan Cox <alan@lxorguk.ukuu.org.uk> 21-Jan-96
201da177e4SLinus Torvalds  *
211da177e4SLinus Torvalds  * Dynamic minors and /proc/mice by Alessandro Rubini. 26-Mar-96
221da177e4SLinus Torvalds  *
231da177e4SLinus Torvalds  * Renamed to misc and miscdevice to be more accurate. Alan Cox 26-Mar-96
241da177e4SLinus Torvalds  *
251da177e4SLinus Torvalds  * Handling of mouse minor numbers for kerneld:
261da177e4SLinus Torvalds  *  Idea by Jacques Gelinas <jack@solucorp.qc.ca>,
271da177e4SLinus Torvalds  *  adapted by Bjorn Ekwall <bj0rn@blox.se>
281da177e4SLinus Torvalds  *  corrected by Alan Cox <alan@lxorguk.ukuu.org.uk>
291da177e4SLinus Torvalds  *
301da177e4SLinus Torvalds  * Changes for kmod (from kerneld):
311da177e4SLinus Torvalds  *	Cyrus Durgin <cider@speakeasy.org>
321da177e4SLinus Torvalds  *
331da177e4SLinus Torvalds  * Added devfs support. Richard Gooch <rgooch@atnf.csiro.au>  10-Jan-1998
341da177e4SLinus Torvalds  */
351da177e4SLinus Torvalds 
361da177e4SLinus Torvalds #include <linux/module.h>
371da177e4SLinus Torvalds 
381da177e4SLinus Torvalds #include <linux/fs.h>
391da177e4SLinus Torvalds #include <linux/errno.h>
401da177e4SLinus Torvalds #include <linux/miscdevice.h>
411da177e4SLinus Torvalds #include <linux/kernel.h>
421da177e4SLinus Torvalds #include <linux/major.h>
431da177e4SLinus Torvalds #include <linux/slab.h>
441da177e4SLinus Torvalds #include <linux/proc_fs.h>
451da177e4SLinus Torvalds #include <linux/seq_file.h>
461da177e4SLinus Torvalds #include <linux/stat.h>
471da177e4SLinus Torvalds #include <linux/init.h>
481da177e4SLinus Torvalds #include <linux/device.h>
491da177e4SLinus Torvalds #include <linux/tty.h>
501da177e4SLinus Torvalds #include <linux/kmod.h>
511da177e4SLinus Torvalds 
521da177e4SLinus Torvalds /*
531da177e4SLinus Torvalds  * Head entry for the doubly linked miscdevice list
541da177e4SLinus Torvalds  */
551da177e4SLinus Torvalds static LIST_HEAD(misc_list);
561da177e4SLinus Torvalds static DECLARE_MUTEX(misc_sem);
571da177e4SLinus Torvalds 
581da177e4SLinus Torvalds /*
591da177e4SLinus Torvalds  * Assigned numbers, used for dynamic minors
601da177e4SLinus Torvalds  */
611da177e4SLinus Torvalds #define DYNAMIC_MINORS 64 /* like dynamic majors */
621da177e4SLinus Torvalds static unsigned char misc_minors[DYNAMIC_MINORS / 8];
631da177e4SLinus Torvalds 
641da177e4SLinus Torvalds extern int pmu_device_init(void);
651da177e4SLinus Torvalds 
661da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
671da177e4SLinus Torvalds static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
681da177e4SLinus Torvalds {
691da177e4SLinus Torvalds 	struct miscdevice *p;
701da177e4SLinus Torvalds 	loff_t off = 0;
711da177e4SLinus Torvalds 
721da177e4SLinus Torvalds 	down(&misc_sem);
731da177e4SLinus Torvalds 	list_for_each_entry(p, &misc_list, list) {
741da177e4SLinus Torvalds 		if (*pos == off++)
751da177e4SLinus Torvalds 			return p;
761da177e4SLinus Torvalds 	}
771da177e4SLinus Torvalds 	return NULL;
781da177e4SLinus Torvalds }
791da177e4SLinus Torvalds 
801da177e4SLinus Torvalds static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
811da177e4SLinus Torvalds {
821da177e4SLinus Torvalds 	struct list_head *n = ((struct miscdevice *)v)->list.next;
831da177e4SLinus Torvalds 
841da177e4SLinus Torvalds 	++*pos;
851da177e4SLinus Torvalds 
861da177e4SLinus Torvalds 	return (n != &misc_list) ? list_entry(n, struct miscdevice, list)
871da177e4SLinus Torvalds 		 : NULL;
881da177e4SLinus Torvalds }
891da177e4SLinus Torvalds 
901da177e4SLinus Torvalds static void misc_seq_stop(struct seq_file *seq, void *v)
911da177e4SLinus Torvalds {
921da177e4SLinus Torvalds 	up(&misc_sem);
931da177e4SLinus Torvalds }
941da177e4SLinus Torvalds 
951da177e4SLinus Torvalds static int misc_seq_show(struct seq_file *seq, void *v)
961da177e4SLinus Torvalds {
971da177e4SLinus Torvalds 	const struct miscdevice *p = v;
981da177e4SLinus Torvalds 
991da177e4SLinus Torvalds 	seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
1001da177e4SLinus Torvalds 	return 0;
1011da177e4SLinus Torvalds }
1021da177e4SLinus Torvalds 
1031da177e4SLinus Torvalds 
1041da177e4SLinus Torvalds static struct seq_operations misc_seq_ops = {
1051da177e4SLinus Torvalds 	.start = misc_seq_start,
1061da177e4SLinus Torvalds 	.next  = misc_seq_next,
1071da177e4SLinus Torvalds 	.stop  = misc_seq_stop,
1081da177e4SLinus Torvalds 	.show  = misc_seq_show,
1091da177e4SLinus Torvalds };
1101da177e4SLinus Torvalds 
1111da177e4SLinus Torvalds static int misc_seq_open(struct inode *inode, struct file *file)
1121da177e4SLinus Torvalds {
1131da177e4SLinus Torvalds 	return seq_open(file, &misc_seq_ops);
1141da177e4SLinus Torvalds }
1151da177e4SLinus Torvalds 
116*62322d25SArjan van de Ven static const struct file_operations misc_proc_fops = {
1171da177e4SLinus Torvalds 	.owner	 = THIS_MODULE,
1181da177e4SLinus Torvalds 	.open    = misc_seq_open,
1191da177e4SLinus Torvalds 	.read    = seq_read,
1201da177e4SLinus Torvalds 	.llseek  = seq_lseek,
1211da177e4SLinus Torvalds 	.release = seq_release,
1221da177e4SLinus Torvalds };
1231da177e4SLinus Torvalds #endif
1241da177e4SLinus Torvalds 
1251da177e4SLinus Torvalds static int misc_open(struct inode * inode, struct file * file)
1261da177e4SLinus Torvalds {
1271da177e4SLinus Torvalds 	int minor = iminor(inode);
1281da177e4SLinus Torvalds 	struct miscdevice *c;
1291da177e4SLinus Torvalds 	int err = -ENODEV;
13099ac48f5SArjan van de Ven 	const struct file_operations *old_fops, *new_fops = NULL;
1311da177e4SLinus Torvalds 
1321da177e4SLinus Torvalds 	down(&misc_sem);
1331da177e4SLinus Torvalds 
1341da177e4SLinus Torvalds 	list_for_each_entry(c, &misc_list, list) {
1351da177e4SLinus Torvalds 		if (c->minor == minor) {
1361da177e4SLinus Torvalds 			new_fops = fops_get(c->fops);
1371da177e4SLinus Torvalds 			break;
1381da177e4SLinus Torvalds 		}
1391da177e4SLinus Torvalds 	}
1401da177e4SLinus Torvalds 
1411da177e4SLinus Torvalds 	if (!new_fops) {
1421da177e4SLinus Torvalds 		up(&misc_sem);
1431da177e4SLinus Torvalds 		request_module("char-major-%d-%d", MISC_MAJOR, minor);
1441da177e4SLinus Torvalds 		down(&misc_sem);
1451da177e4SLinus Torvalds 
1461da177e4SLinus Torvalds 		list_for_each_entry(c, &misc_list, list) {
1471da177e4SLinus Torvalds 			if (c->minor == minor) {
1481da177e4SLinus Torvalds 				new_fops = fops_get(c->fops);
1491da177e4SLinus Torvalds 				break;
1501da177e4SLinus Torvalds 			}
1511da177e4SLinus Torvalds 		}
1521da177e4SLinus Torvalds 		if (!new_fops)
1531da177e4SLinus Torvalds 			goto fail;
1541da177e4SLinus Torvalds 	}
1551da177e4SLinus Torvalds 
1561da177e4SLinus Torvalds 	err = 0;
1571da177e4SLinus Torvalds 	old_fops = file->f_op;
1581da177e4SLinus Torvalds 	file->f_op = new_fops;
1591da177e4SLinus Torvalds 	if (file->f_op->open) {
1601da177e4SLinus Torvalds 		err=file->f_op->open(inode,file);
1611da177e4SLinus Torvalds 		if (err) {
1621da177e4SLinus Torvalds 			fops_put(file->f_op);
1631da177e4SLinus Torvalds 			file->f_op = fops_get(old_fops);
1641da177e4SLinus Torvalds 		}
1651da177e4SLinus Torvalds 	}
1661da177e4SLinus Torvalds 	fops_put(old_fops);
1671da177e4SLinus Torvalds fail:
1681da177e4SLinus Torvalds 	up(&misc_sem);
1691da177e4SLinus Torvalds 	return err;
1701da177e4SLinus Torvalds }
1711da177e4SLinus Torvalds 
1721da177e4SLinus Torvalds /*
1731da177e4SLinus Torvalds  * TODO for 2.7:
174ca8eca68Sgregkh@suse.de  *  - add a struct kref to struct miscdevice and make all usages of
1751da177e4SLinus Torvalds  *    them dynamic.
1761da177e4SLinus Torvalds  */
177ca8eca68Sgregkh@suse.de static struct class *misc_class;
1781da177e4SLinus Torvalds 
179*62322d25SArjan van de Ven static const struct file_operations misc_fops = {
1801da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
1811da177e4SLinus Torvalds 	.open		= misc_open,
1821da177e4SLinus Torvalds };
1831da177e4SLinus Torvalds 
1841da177e4SLinus Torvalds 
1851da177e4SLinus Torvalds /**
1861da177e4SLinus Torvalds  *	misc_register	-	register a miscellaneous device
1871da177e4SLinus Torvalds  *	@misc: device structure
1881da177e4SLinus Torvalds  *
1891da177e4SLinus Torvalds  *	Register a miscellaneous device with the kernel. If the minor
1901da177e4SLinus Torvalds  *	number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
1911da177e4SLinus Torvalds  *	and placed in the minor field of the structure. For other cases
1921da177e4SLinus Torvalds  *	the minor number requested is used.
1931da177e4SLinus Torvalds  *
1941da177e4SLinus Torvalds  *	The structure passed is linked into the kernel and may not be
1951da177e4SLinus Torvalds  *	destroyed until it has been unregistered.
1961da177e4SLinus Torvalds  *
1971da177e4SLinus Torvalds  *	A zero is returned on success and a negative errno code for
1981da177e4SLinus Torvalds  *	failure.
1991da177e4SLinus Torvalds  */
2001da177e4SLinus Torvalds 
2011da177e4SLinus Torvalds int misc_register(struct miscdevice * misc)
2021da177e4SLinus Torvalds {
2031da177e4SLinus Torvalds 	struct miscdevice *c;
2041da177e4SLinus Torvalds 	dev_t dev;
2057c69ef79SGreg Kroah-Hartman 	int err = 0;
2061da177e4SLinus Torvalds 
2071da177e4SLinus Torvalds 	down(&misc_sem);
2081da177e4SLinus Torvalds 	list_for_each_entry(c, &misc_list, list) {
2091da177e4SLinus Torvalds 		if (c->minor == misc->minor) {
2101da177e4SLinus Torvalds 			up(&misc_sem);
2111da177e4SLinus Torvalds 			return -EBUSY;
2121da177e4SLinus Torvalds 		}
2131da177e4SLinus Torvalds 	}
2141da177e4SLinus Torvalds 
2151da177e4SLinus Torvalds 	if (misc->minor == MISC_DYNAMIC_MINOR) {
2161da177e4SLinus Torvalds 		int i = DYNAMIC_MINORS;
2171da177e4SLinus Torvalds 		while (--i >= 0)
2181da177e4SLinus Torvalds 			if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
2191da177e4SLinus Torvalds 				break;
2201da177e4SLinus Torvalds 		if (i<0) {
2211da177e4SLinus Torvalds 			up(&misc_sem);
2221da177e4SLinus Torvalds 			return -EBUSY;
2231da177e4SLinus Torvalds 		}
2241da177e4SLinus Torvalds 		misc->minor = i;
2251da177e4SLinus Torvalds 	}
2261da177e4SLinus Torvalds 
2271da177e4SLinus Torvalds 	if (misc->minor < DYNAMIC_MINORS)
2281da177e4SLinus Torvalds 		misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
2291da177e4SLinus Torvalds 	dev = MKDEV(MISC_MAJOR, misc->minor);
2301da177e4SLinus Torvalds 
23153f46542SGreg Kroah-Hartman 	misc->class = class_device_create(misc_class, NULL, dev, misc->dev,
232ca8eca68Sgregkh@suse.de 					  "%s", misc->name);
2331da177e4SLinus Torvalds 	if (IS_ERR(misc->class)) {
2341da177e4SLinus Torvalds 		err = PTR_ERR(misc->class);
2351da177e4SLinus Torvalds 		goto out;
2361da177e4SLinus Torvalds 	}
2371da177e4SLinus Torvalds 
2381da177e4SLinus Torvalds 	/*
2391da177e4SLinus Torvalds 	 * Add it to the front, so that later devices can "override"
2401da177e4SLinus Torvalds 	 * earlier defaults
2411da177e4SLinus Torvalds 	 */
2421da177e4SLinus Torvalds 	list_add(&misc->list, &misc_list);
2431da177e4SLinus Torvalds  out:
2441da177e4SLinus Torvalds 	up(&misc_sem);
2451da177e4SLinus Torvalds 	return err;
2461da177e4SLinus Torvalds }
2471da177e4SLinus Torvalds 
2481da177e4SLinus Torvalds /**
2491da177e4SLinus Torvalds  *	misc_deregister - unregister a miscellaneous device
2501da177e4SLinus Torvalds  *	@misc: device to unregister
2511da177e4SLinus Torvalds  *
2521da177e4SLinus Torvalds  *	Unregister a miscellaneous device that was previously
2531da177e4SLinus Torvalds  *	successfully registered with misc_register(). Success
2541da177e4SLinus Torvalds  *	is indicated by a zero return, a negative errno code
2551da177e4SLinus Torvalds  *	indicates an error.
2561da177e4SLinus Torvalds  */
2571da177e4SLinus Torvalds 
2581da177e4SLinus Torvalds int misc_deregister(struct miscdevice * misc)
2591da177e4SLinus Torvalds {
2601da177e4SLinus Torvalds 	int i = misc->minor;
2611da177e4SLinus Torvalds 
2621da177e4SLinus Torvalds 	if (list_empty(&misc->list))
2631da177e4SLinus Torvalds 		return -EINVAL;
2641da177e4SLinus Torvalds 
2651da177e4SLinus Torvalds 	down(&misc_sem);
2661da177e4SLinus Torvalds 	list_del(&misc->list);
267ca8eca68Sgregkh@suse.de 	class_device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
2681da177e4SLinus Torvalds 	if (i < DYNAMIC_MINORS && i>0) {
2691da177e4SLinus Torvalds 		misc_minors[i>>3] &= ~(1 << (misc->minor & 7));
2701da177e4SLinus Torvalds 	}
2711da177e4SLinus Torvalds 	up(&misc_sem);
2721da177e4SLinus Torvalds 	return 0;
2731da177e4SLinus Torvalds }
2741da177e4SLinus Torvalds 
2751da177e4SLinus Torvalds EXPORT_SYMBOL(misc_register);
2761da177e4SLinus Torvalds EXPORT_SYMBOL(misc_deregister);
2771da177e4SLinus Torvalds 
2781da177e4SLinus Torvalds static int __init misc_init(void)
2791da177e4SLinus Torvalds {
2801da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
2811da177e4SLinus Torvalds 	struct proc_dir_entry *ent;
2821da177e4SLinus Torvalds 
2831da177e4SLinus Torvalds 	ent = create_proc_entry("misc", 0, NULL);
2841da177e4SLinus Torvalds 	if (ent)
2851da177e4SLinus Torvalds 		ent->proc_fops = &misc_proc_fops;
2861da177e4SLinus Torvalds #endif
287ca8eca68Sgregkh@suse.de 	misc_class = class_create(THIS_MODULE, "misc");
2881da177e4SLinus Torvalds 	if (IS_ERR(misc_class))
2891da177e4SLinus Torvalds 		return PTR_ERR(misc_class);
290573fc113SChristoph Hellwig 
2911da177e4SLinus Torvalds 	if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) {
2921da177e4SLinus Torvalds 		printk("unable to get major %d for misc devices\n",
2931da177e4SLinus Torvalds 		       MISC_MAJOR);
294ca8eca68Sgregkh@suse.de 		class_destroy(misc_class);
2951da177e4SLinus Torvalds 		return -EIO;
2961da177e4SLinus Torvalds 	}
2971da177e4SLinus Torvalds 	return 0;
2981da177e4SLinus Torvalds }
2991da177e4SLinus Torvalds subsys_initcall(misc_init);
300