xref: /linux/drivers/char/misc.c (revision 8ab44b4003381cf4bae7ccdfe81059aa9ce76033)
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>
430e82d5b6SMatthias Kaehlcke #include <linux/mutex.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>
515a0e3ad6STejun Heo #include <linux/gfp.h>
521da177e4SLinus Torvalds 
531da177e4SLinus Torvalds /*
541da177e4SLinus Torvalds  * Head entry for the doubly linked miscdevice list
551da177e4SLinus Torvalds  */
561da177e4SLinus Torvalds static LIST_HEAD(misc_list);
570e82d5b6SMatthias Kaehlcke static DEFINE_MUTEX(misc_mtx);
581da177e4SLinus Torvalds 
591da177e4SLinus Torvalds /*
601da177e4SLinus Torvalds  * Assigned numbers, used for dynamic minors
611da177e4SLinus Torvalds  */
621da177e4SLinus Torvalds #define DYNAMIC_MINORS 64 /* like dynamic majors */
631f2f38d8SThadeu Lima de Souza Cascardo static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);
641da177e4SLinus Torvalds 
651da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
661da177e4SLinus Torvalds static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
671da177e4SLinus Torvalds {
680e82d5b6SMatthias Kaehlcke 	mutex_lock(&misc_mtx);
6946c65b71SPavel Emelianov 	return seq_list_start(&misc_list, *pos);
701da177e4SLinus Torvalds }
711da177e4SLinus Torvalds 
721da177e4SLinus Torvalds static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
731da177e4SLinus Torvalds {
7446c65b71SPavel Emelianov 	return seq_list_next(v, &misc_list, pos);
751da177e4SLinus Torvalds }
761da177e4SLinus Torvalds 
771da177e4SLinus Torvalds static void misc_seq_stop(struct seq_file *seq, void *v)
781da177e4SLinus Torvalds {
790e82d5b6SMatthias Kaehlcke 	mutex_unlock(&misc_mtx);
801da177e4SLinus Torvalds }
811da177e4SLinus Torvalds 
821da177e4SLinus Torvalds static int misc_seq_show(struct seq_file *seq, void *v)
831da177e4SLinus Torvalds {
8446c65b71SPavel Emelianov 	const struct miscdevice *p = list_entry(v, struct miscdevice, list);
851da177e4SLinus Torvalds 
861da177e4SLinus Torvalds 	seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
871da177e4SLinus Torvalds 	return 0;
881da177e4SLinus Torvalds }
891da177e4SLinus Torvalds 
901da177e4SLinus Torvalds 
9188e9d34cSJames Morris static const struct seq_operations misc_seq_ops = {
921da177e4SLinus Torvalds 	.start = misc_seq_start,
931da177e4SLinus Torvalds 	.next  = misc_seq_next,
941da177e4SLinus Torvalds 	.stop  = misc_seq_stop,
951da177e4SLinus Torvalds 	.show  = misc_seq_show,
961da177e4SLinus Torvalds };
971da177e4SLinus Torvalds 
981da177e4SLinus Torvalds static int misc_seq_open(struct inode *inode, struct file *file)
991da177e4SLinus Torvalds {
1001da177e4SLinus Torvalds 	return seq_open(file, &misc_seq_ops);
1011da177e4SLinus Torvalds }
1021da177e4SLinus Torvalds 
10362322d25SArjan van de Ven static const struct file_operations misc_proc_fops = {
1041da177e4SLinus Torvalds 	.owner	 = THIS_MODULE,
1051da177e4SLinus Torvalds 	.open    = misc_seq_open,
1061da177e4SLinus Torvalds 	.read    = seq_read,
1071da177e4SLinus Torvalds 	.llseek  = seq_lseek,
1081da177e4SLinus Torvalds 	.release = seq_release,
1091da177e4SLinus Torvalds };
1101da177e4SLinus Torvalds #endif
1111da177e4SLinus Torvalds 
1121da177e4SLinus Torvalds static int misc_open(struct inode *inode, struct file *file)
1131da177e4SLinus Torvalds {
1141da177e4SLinus Torvalds 	int minor = iminor(inode);
1151da177e4SLinus Torvalds 	struct miscdevice *c;
1161da177e4SLinus Torvalds 	int err = -ENODEV;
117e84f9e57SAl Viro 	const struct file_operations *new_fops = NULL;
1181da177e4SLinus Torvalds 
1190e82d5b6SMatthias Kaehlcke 	mutex_lock(&misc_mtx);
1201da177e4SLinus Torvalds 
1211da177e4SLinus Torvalds 	list_for_each_entry(c, &misc_list, list) {
1221da177e4SLinus Torvalds 		if (c->minor == minor) {
1231da177e4SLinus Torvalds 			new_fops = fops_get(c->fops);
1241da177e4SLinus Torvalds 			break;
1251da177e4SLinus Torvalds 		}
1261da177e4SLinus Torvalds 	}
1271da177e4SLinus Torvalds 
1281da177e4SLinus Torvalds 	if (!new_fops) {
1290e82d5b6SMatthias Kaehlcke 		mutex_unlock(&misc_mtx);
1301da177e4SLinus Torvalds 		request_module("char-major-%d-%d", MISC_MAJOR, minor);
1310e82d5b6SMatthias Kaehlcke 		mutex_lock(&misc_mtx);
1321da177e4SLinus Torvalds 
1331da177e4SLinus Torvalds 		list_for_each_entry(c, &misc_list, list) {
1341da177e4SLinus Torvalds 			if (c->minor == minor) {
1351da177e4SLinus Torvalds 				new_fops = fops_get(c->fops);
1361da177e4SLinus Torvalds 				break;
1371da177e4SLinus Torvalds 			}
1381da177e4SLinus Torvalds 		}
1391da177e4SLinus Torvalds 		if (!new_fops)
1401da177e4SLinus Torvalds 			goto fail;
1411da177e4SLinus Torvalds 	}
1421da177e4SLinus Torvalds 
1430b509d8dSTom Van Braeckel 	/*
1440b509d8dSTom Van Braeckel 	 * Place the miscdevice in the file's
1450b509d8dSTom Van Braeckel 	 * private_data so it can be used by the
1460b509d8dSTom Van Braeckel 	 * file operations, including f_op->open below
1470b509d8dSTom Van Braeckel 	 */
1480b509d8dSTom Van Braeckel 	file->private_data = c;
1490b509d8dSTom Van Braeckel 
1501da177e4SLinus Torvalds 	err = 0;
151e84f9e57SAl Viro 	replace_fops(file, new_fops);
1520b509d8dSTom Van Braeckel 	if (file->f_op->open)
1531da177e4SLinus Torvalds 		err = file->f_op->open(inode, file);
1541da177e4SLinus Torvalds fail:
1550e82d5b6SMatthias Kaehlcke 	mutex_unlock(&misc_mtx);
1561da177e4SLinus Torvalds 	return err;
1571da177e4SLinus Torvalds }
1581da177e4SLinus Torvalds 
159ca8eca68Sgregkh@suse.de static struct class *misc_class;
1601da177e4SLinus Torvalds 
16162322d25SArjan van de Ven static const struct file_operations misc_fops = {
1621da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
1631da177e4SLinus Torvalds 	.open		= misc_open,
1646038f373SArnd Bergmann 	.llseek		= noop_llseek,
1651da177e4SLinus Torvalds };
1661da177e4SLinus Torvalds 
1671da177e4SLinus Torvalds /**
1681da177e4SLinus Torvalds  *	misc_register	-	register a miscellaneous device
1691da177e4SLinus Torvalds  *	@misc: device structure
1701da177e4SLinus Torvalds  *
1711da177e4SLinus Torvalds  *	Register a miscellaneous device with the kernel. If the minor
1721da177e4SLinus Torvalds  *	number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
1731da177e4SLinus Torvalds  *	and placed in the minor field of the structure. For other cases
1741da177e4SLinus Torvalds  *	the minor number requested is used.
1751da177e4SLinus Torvalds  *
1761da177e4SLinus Torvalds  *	The structure passed is linked into the kernel and may not be
17703190c67SMartin Kepplinger  *	destroyed until it has been unregistered. By default, an open()
17803190c67SMartin Kepplinger  *	syscall to the device sets file->private_data to point to the
17903190c67SMartin Kepplinger  *	structure. Drivers don't need open in fops for this.
1801da177e4SLinus Torvalds  *
1811da177e4SLinus Torvalds  *	A zero is returned on success and a negative errno code for
1821da177e4SLinus Torvalds  *	failure.
1831da177e4SLinus Torvalds  */
1841da177e4SLinus Torvalds 
1851da177e4SLinus Torvalds int misc_register(struct miscdevice *misc)
1861da177e4SLinus Torvalds {
1871da177e4SLinus Torvalds 	dev_t dev;
1887c69ef79SGreg Kroah-Hartman 	int err = 0;
189b575f712SVladimir Zapolskiy 	bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
1901da177e4SLinus Torvalds 
1915d469ec0SNeil Horman 	INIT_LIST_HEAD(&misc->list);
1925d469ec0SNeil Horman 
1930e82d5b6SMatthias Kaehlcke 	mutex_lock(&misc_mtx);
1941da177e4SLinus Torvalds 
195b575f712SVladimir Zapolskiy 	if (is_dynamic) {
1961f2f38d8SThadeu Lima de Souza Cascardo 		int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
1975b884a95SVarsha Rao 
1981f2f38d8SThadeu Lima de Souza Cascardo 		if (i >= DYNAMIC_MINORS) {
199684116caSElad Wexler 			err = -EBUSY;
200684116caSElad Wexler 			goto out;
2011da177e4SLinus Torvalds 		}
2021f2f38d8SThadeu Lima de Souza Cascardo 		misc->minor = DYNAMIC_MINORS - i - 1;
2031f2f38d8SThadeu Lima de Souza Cascardo 		set_bit(i, misc_minors);
2043c94ce6fSDae S. Kim 	} else {
2053c94ce6fSDae S. Kim 		struct miscdevice *c;
2063c94ce6fSDae S. Kim 
2073c94ce6fSDae S. Kim 		list_for_each_entry(c, &misc_list, list) {
2083c94ce6fSDae S. Kim 			if (c->minor == misc->minor) {
209684116caSElad Wexler 				err = -EBUSY;
210684116caSElad Wexler 				goto out;
2113c94ce6fSDae S. Kim 			}
2123c94ce6fSDae S. Kim 		}
2131da177e4SLinus Torvalds 	}
2141da177e4SLinus Torvalds 
2151da177e4SLinus Torvalds 	dev = MKDEV(MISC_MAJOR, misc->minor);
2161da177e4SLinus Torvalds 
217bd735995STakashi Iwai 	misc->this_device =
218bd735995STakashi Iwai 		device_create_with_groups(misc_class, misc->parent, dev,
219bd735995STakashi Iwai 					  misc, misc->groups, "%s", misc->name);
22094fbcdedSGreg Kroah-Hartman 	if (IS_ERR(misc->this_device)) {
221b575f712SVladimir Zapolskiy 		if (is_dynamic) {
2221f2f38d8SThadeu Lima de Souza Cascardo 			int i = DYNAMIC_MINORS - misc->minor - 1;
223b575f712SVladimir Zapolskiy 
2244ae717daSThadeu Lima de Souza Cascardo 			if (i < DYNAMIC_MINORS && i >= 0)
2251f2f38d8SThadeu Lima de Souza Cascardo 				clear_bit(i, misc_minors);
226b575f712SVladimir Zapolskiy 			misc->minor = MISC_DYNAMIC_MINOR;
227b575f712SVladimir Zapolskiy 		}
22894fbcdedSGreg Kroah-Hartman 		err = PTR_ERR(misc->this_device);
2291da177e4SLinus Torvalds 		goto out;
2301da177e4SLinus Torvalds 	}
2311da177e4SLinus Torvalds 
2321da177e4SLinus Torvalds 	/*
2331da177e4SLinus Torvalds 	 * Add it to the front, so that later devices can "override"
2341da177e4SLinus Torvalds 	 * earlier defaults
2351da177e4SLinus Torvalds 	 */
2361da177e4SLinus Torvalds 	list_add(&misc->list, &misc_list);
2371da177e4SLinus Torvalds  out:
2380e82d5b6SMatthias Kaehlcke 	mutex_unlock(&misc_mtx);
2391da177e4SLinus Torvalds 	return err;
2401da177e4SLinus Torvalds }
2411da177e4SLinus Torvalds 
2421da177e4SLinus Torvalds /**
243b844eba2SRafael J. Wysocki  *	misc_deregister - unregister a miscellaneous device
2441da177e4SLinus Torvalds  *	@misc: device to unregister
2451da177e4SLinus Torvalds  *
2461da177e4SLinus Torvalds  *	Unregister a miscellaneous device that was previously
247f368ed60SGreg Kroah-Hartman  *	successfully registered with misc_register().
2481da177e4SLinus Torvalds  */
2491da177e4SLinus Torvalds 
250f368ed60SGreg Kroah-Hartman void misc_deregister(struct miscdevice *misc)
2511da177e4SLinus Torvalds {
2521f2f38d8SThadeu Lima de Souza Cascardo 	int i = DYNAMIC_MINORS - misc->minor - 1;
2531da177e4SLinus Torvalds 
254b329becfSAkinobu Mita 	if (WARN_ON(list_empty(&misc->list)))
255f368ed60SGreg Kroah-Hartman 		return;
2561da177e4SLinus Torvalds 
2570e82d5b6SMatthias Kaehlcke 	mutex_lock(&misc_mtx);
2581da177e4SLinus Torvalds 	list_del(&misc->list);
25994fbcdedSGreg Kroah-Hartman 	device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
2604ae717daSThadeu Lima de Souza Cascardo 	if (i < DYNAMIC_MINORS && i >= 0)
2611f2f38d8SThadeu Lima de Souza Cascardo 		clear_bit(i, misc_minors);
2620e82d5b6SMatthias Kaehlcke 	mutex_unlock(&misc_mtx);
2631da177e4SLinus Torvalds }
2641da177e4SLinus Torvalds 
2651da177e4SLinus Torvalds EXPORT_SYMBOL(misc_register);
266b844eba2SRafael J. Wysocki EXPORT_SYMBOL(misc_deregister);
2671da177e4SLinus Torvalds 
2682c9ede55SAl Viro static char *misc_devnode(struct device *dev, umode_t *mode)
269d4056405SKay Sievers {
270d4056405SKay Sievers 	struct miscdevice *c = dev_get_drvdata(dev);
271d4056405SKay Sievers 
272e454cea2SKay Sievers 	if (mode && c->mode)
273e454cea2SKay Sievers 		*mode = c->mode;
274e454cea2SKay Sievers 	if (c->nodename)
275e454cea2SKay Sievers 		return kstrdup(c->nodename, GFP_KERNEL);
276d4056405SKay Sievers 	return NULL;
277d4056405SKay Sievers }
278d4056405SKay Sievers 
2791da177e4SLinus Torvalds static int __init misc_init(void)
2801da177e4SLinus Torvalds {
2811b502217SDenis V. Lunev 	int err;
2821037b278SSudip Mukherjee 	struct proc_dir_entry *ret;
2831da177e4SLinus Torvalds 
2841037b278SSudip Mukherjee 	ret = proc_create("misc", 0, NULL, &misc_proc_fops);
285ca8eca68Sgregkh@suse.de 	misc_class = class_create(THIS_MODULE, "misc");
2861b502217SDenis V. Lunev 	err = PTR_ERR(misc_class);
2871da177e4SLinus Torvalds 	if (IS_ERR(misc_class))
2881b502217SDenis V. Lunev 		goto fail_remove;
289573fc113SChristoph Hellwig 
2901b502217SDenis V. Lunev 	err = -EIO;
2911b502217SDenis V. Lunev 	if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
2921b502217SDenis V. Lunev 		goto fail_printk;
293e454cea2SKay Sievers 	misc_class->devnode = misc_devnode;
2941da177e4SLinus Torvalds 	return 0;
2951b502217SDenis V. Lunev 
2961b502217SDenis V. Lunev fail_printk:
297*8ab44b40SVarsha Rao 	pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
2981b502217SDenis V. Lunev 	class_destroy(misc_class);
2991b502217SDenis V. Lunev fail_remove:
3001037b278SSudip Mukherjee 	if (ret)
3011b502217SDenis V. Lunev 		remove_proc_entry("misc", NULL);
3021b502217SDenis V. Lunev 	return err;
3031da177e4SLinus Torvalds }
3041da177e4SLinus Torvalds subsys_initcall(misc_init);
305