xref: /linux/drivers/char/misc.c (revision cdd5b5a9761fd66d17586e4f4ba6588c70e640ea)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  * linux/drivers/char/misc.c
41da177e4SLinus Torvalds  *
51da177e4SLinus Torvalds  * Generic misc open routine by Johan Myreen
61da177e4SLinus Torvalds  *
71da177e4SLinus Torvalds  * Based on code from Linus
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  * Teemu Rantanen's Microsoft Busmouse support and Derrick Cole's
101da177e4SLinus Torvalds  *   changes incorporated into 0.97pl4
111da177e4SLinus Torvalds  *   by Peter Cervasio (pete%q106fm.uucp@wupost.wustl.edu) (08SEP92)
121da177e4SLinus Torvalds  *   See busmouse.c for particulars.
131da177e4SLinus Torvalds  *
141da177e4SLinus Torvalds  * Made things a lot mode modular - easy to compile in just one or two
151da177e4SLinus Torvalds  * of the misc drivers, as they are now completely independent. Linus.
161da177e4SLinus Torvalds  *
171da177e4SLinus Torvalds  * Support for loadable modules. 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk>
181da177e4SLinus Torvalds  *
191da177e4SLinus Torvalds  * Fixed a failing symbol register to free the device registration
201da177e4SLinus Torvalds  *		Alan Cox <alan@lxorguk.ukuu.org.uk> 21-Jan-96
211da177e4SLinus Torvalds  *
221da177e4SLinus Torvalds  * Dynamic minors and /proc/mice by Alessandro Rubini. 26-Mar-96
231da177e4SLinus Torvalds  *
241da177e4SLinus Torvalds  * Renamed to misc and miscdevice to be more accurate. Alan Cox 26-Mar-96
251da177e4SLinus Torvalds  *
261da177e4SLinus Torvalds  * Handling of mouse minor numbers for kerneld:
271da177e4SLinus Torvalds  *  Idea by Jacques Gelinas <jack@solucorp.qc.ca>,
281da177e4SLinus Torvalds  *  adapted by Bjorn Ekwall <bj0rn@blox.se>
291da177e4SLinus Torvalds  *  corrected by Alan Cox <alan@lxorguk.ukuu.org.uk>
301da177e4SLinus Torvalds  *
311da177e4SLinus Torvalds  * Changes for kmod (from kerneld):
321da177e4SLinus Torvalds  *	Cyrus Durgin <cider@speakeasy.org>
331da177e4SLinus Torvalds  *
341da177e4SLinus Torvalds  * Added devfs support. Richard Gooch <rgooch@atnf.csiro.au>  10-Jan-1998
351da177e4SLinus Torvalds  */
361da177e4SLinus Torvalds 
371da177e4SLinus Torvalds #include <linux/module.h>
381da177e4SLinus Torvalds 
391da177e4SLinus Torvalds #include <linux/fs.h>
401da177e4SLinus Torvalds #include <linux/errno.h>
411da177e4SLinus Torvalds #include <linux/miscdevice.h>
421da177e4SLinus Torvalds #include <linux/kernel.h>
431da177e4SLinus Torvalds #include <linux/major.h>
440e82d5b6SMatthias Kaehlcke #include <linux/mutex.h>
451da177e4SLinus Torvalds #include <linux/proc_fs.h>
461da177e4SLinus Torvalds #include <linux/seq_file.h>
471da177e4SLinus Torvalds #include <linux/stat.h>
481da177e4SLinus Torvalds #include <linux/init.h>
491da177e4SLinus Torvalds #include <linux/device.h>
501da177e4SLinus Torvalds #include <linux/tty.h>
511da177e4SLinus Torvalds #include <linux/kmod.h>
525a0e3ad6STejun Heo #include <linux/gfp.h>
531da177e4SLinus Torvalds 
541da177e4SLinus Torvalds /*
551da177e4SLinus Torvalds  * Head entry for the doubly linked miscdevice list
561da177e4SLinus Torvalds  */
571da177e4SLinus Torvalds static LIST_HEAD(misc_list);
580e82d5b6SMatthias Kaehlcke static DEFINE_MUTEX(misc_mtx);
591da177e4SLinus Torvalds 
601da177e4SLinus Torvalds /*
611da177e4SLinus Torvalds  * Assigned numbers, used for dynamic minors
621da177e4SLinus Torvalds  */
63c62b1f97SSangmoon Kim #define DYNAMIC_MINORS 128 /* like dynamic majors */
64ab760791SD Scott Phillips static DEFINE_IDA(misc_minors_ida);
65ab760791SD Scott Phillips 
misc_minor_alloc(void)66ab760791SD Scott Phillips static int misc_minor_alloc(void)
67ab760791SD Scott Phillips {
68ab760791SD Scott Phillips 	int ret;
69ab760791SD Scott Phillips 
70ab760791SD Scott Phillips 	ret = ida_alloc_max(&misc_minors_ida, DYNAMIC_MINORS - 1, GFP_KERNEL);
71ab760791SD Scott Phillips 	if (ret >= 0) {
72ab760791SD Scott Phillips 		ret = DYNAMIC_MINORS - ret - 1;
73ab760791SD Scott Phillips 	} else {
74ab760791SD Scott Phillips 		ret = ida_alloc_range(&misc_minors_ida, MISC_DYNAMIC_MINOR + 1,
75ab760791SD Scott Phillips 				      MINORMASK, GFP_KERNEL);
76ab760791SD Scott Phillips 	}
77ab760791SD Scott Phillips 	return ret;
78ab760791SD Scott Phillips }
79ab760791SD Scott Phillips 
misc_minor_free(int minor)80ab760791SD Scott Phillips static void misc_minor_free(int minor)
81ab760791SD Scott Phillips {
82ab760791SD Scott Phillips 	if (minor < DYNAMIC_MINORS)
83ab760791SD Scott Phillips 		ida_free(&misc_minors_ida, DYNAMIC_MINORS - minor - 1);
84ab760791SD Scott Phillips 	else if (minor > MISC_DYNAMIC_MINOR)
85ab760791SD Scott Phillips 		ida_free(&misc_minors_ida, minor);
86ab760791SD Scott Phillips }
871da177e4SLinus Torvalds 
881da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
misc_seq_start(struct seq_file * seq,loff_t * pos)891da177e4SLinus Torvalds static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
901da177e4SLinus Torvalds {
910e82d5b6SMatthias Kaehlcke 	mutex_lock(&misc_mtx);
9246c65b71SPavel Emelianov 	return seq_list_start(&misc_list, *pos);
931da177e4SLinus Torvalds }
941da177e4SLinus Torvalds 
misc_seq_next(struct seq_file * seq,void * v,loff_t * pos)951da177e4SLinus Torvalds static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
961da177e4SLinus Torvalds {
9746c65b71SPavel Emelianov 	return seq_list_next(v, &misc_list, pos);
981da177e4SLinus Torvalds }
991da177e4SLinus Torvalds 
misc_seq_stop(struct seq_file * seq,void * v)1001da177e4SLinus Torvalds static void misc_seq_stop(struct seq_file *seq, void *v)
1011da177e4SLinus Torvalds {
1020e82d5b6SMatthias Kaehlcke 	mutex_unlock(&misc_mtx);
1031da177e4SLinus Torvalds }
1041da177e4SLinus Torvalds 
misc_seq_show(struct seq_file * seq,void * v)1051da177e4SLinus Torvalds static int misc_seq_show(struct seq_file *seq, void *v)
1061da177e4SLinus Torvalds {
10746c65b71SPavel Emelianov 	const struct miscdevice *p = list_entry(v, struct miscdevice, list);
1081da177e4SLinus Torvalds 
1091da177e4SLinus Torvalds 	seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
1101da177e4SLinus Torvalds 	return 0;
1111da177e4SLinus Torvalds }
1121da177e4SLinus Torvalds 
1131da177e4SLinus Torvalds 
11488e9d34cSJames Morris static const struct seq_operations misc_seq_ops = {
1151da177e4SLinus Torvalds 	.start = misc_seq_start,
1161da177e4SLinus Torvalds 	.next  = misc_seq_next,
1171da177e4SLinus Torvalds 	.stop  = misc_seq_stop,
1181da177e4SLinus Torvalds 	.show  = misc_seq_show,
1191da177e4SLinus Torvalds };
1201da177e4SLinus Torvalds #endif
1211da177e4SLinus Torvalds 
misc_open(struct inode * inode,struct file * file)1221da177e4SLinus Torvalds static int misc_open(struct inode *inode, struct file *file)
1231da177e4SLinus Torvalds {
1241da177e4SLinus Torvalds 	int minor = iminor(inode);
1253a5e6502SJakob Koschel 	struct miscdevice *c = NULL, *iter;
1261da177e4SLinus Torvalds 	int err = -ENODEV;
127e84f9e57SAl Viro 	const struct file_operations *new_fops = NULL;
1281da177e4SLinus Torvalds 
1290e82d5b6SMatthias Kaehlcke 	mutex_lock(&misc_mtx);
1301da177e4SLinus Torvalds 
1313a5e6502SJakob Koschel 	list_for_each_entry(iter, &misc_list, list) {
1323a5e6502SJakob Koschel 		if (iter->minor != minor)
1333a5e6502SJakob Koschel 			continue;
1343a5e6502SJakob Koschel 		c = iter;
1353a5e6502SJakob Koschel 		new_fops = fops_get(iter->fops);
1361da177e4SLinus Torvalds 		break;
1371da177e4SLinus Torvalds 	}
1381da177e4SLinus Torvalds 
1391da177e4SLinus Torvalds 	if (!new_fops) {
1400e82d5b6SMatthias Kaehlcke 		mutex_unlock(&misc_mtx);
1411da177e4SLinus Torvalds 		request_module("char-major-%d-%d", MISC_MAJOR, minor);
1420e82d5b6SMatthias Kaehlcke 		mutex_lock(&misc_mtx);
1431da177e4SLinus Torvalds 
1443a5e6502SJakob Koschel 		list_for_each_entry(iter, &misc_list, list) {
1453a5e6502SJakob Koschel 			if (iter->minor != minor)
1463a5e6502SJakob Koschel 				continue;
1473a5e6502SJakob Koschel 			c = iter;
1483a5e6502SJakob Koschel 			new_fops = fops_get(iter->fops);
1491da177e4SLinus Torvalds 			break;
1501da177e4SLinus Torvalds 		}
1511da177e4SLinus Torvalds 		if (!new_fops)
1521da177e4SLinus Torvalds 			goto fail;
1531da177e4SLinus Torvalds 	}
1541da177e4SLinus Torvalds 
1550b509d8dSTom Van Braeckel 	/*
1560b509d8dSTom Van Braeckel 	 * Place the miscdevice in the file's
1570b509d8dSTom Van Braeckel 	 * private_data so it can be used by the
1580b509d8dSTom Van Braeckel 	 * file operations, including f_op->open below
1590b509d8dSTom Van Braeckel 	 */
1600b509d8dSTom Van Braeckel 	file->private_data = c;
1610b509d8dSTom Van Braeckel 
1621da177e4SLinus Torvalds 	err = 0;
163e84f9e57SAl Viro 	replace_fops(file, new_fops);
1640b509d8dSTom Van Braeckel 	if (file->f_op->open)
1651da177e4SLinus Torvalds 		err = file->f_op->open(inode, file);
1661da177e4SLinus Torvalds fail:
1670e82d5b6SMatthias Kaehlcke 	mutex_unlock(&misc_mtx);
1681da177e4SLinus Torvalds 	return err;
1691da177e4SLinus Torvalds }
1701da177e4SLinus Torvalds 
misc_devnode(const struct device * dev,umode_t * mode)171*eafd52e6SIvan Orlov static char *misc_devnode(const struct device *dev, umode_t *mode)
172*eafd52e6SIvan Orlov {
173*eafd52e6SIvan Orlov 	const struct miscdevice *c = dev_get_drvdata(dev);
174*eafd52e6SIvan Orlov 
175*eafd52e6SIvan Orlov 	if (mode && c->mode)
176*eafd52e6SIvan Orlov 		*mode = c->mode;
177*eafd52e6SIvan Orlov 	if (c->nodename)
178*eafd52e6SIvan Orlov 		return kstrdup(c->nodename, GFP_KERNEL);
179*eafd52e6SIvan Orlov 	return NULL;
180*eafd52e6SIvan Orlov }
181*eafd52e6SIvan Orlov 
182*eafd52e6SIvan Orlov static const struct class misc_class = {
183*eafd52e6SIvan Orlov 	.name		= "misc",
184*eafd52e6SIvan Orlov 	.devnode	= misc_devnode,
185*eafd52e6SIvan Orlov };
1861da177e4SLinus Torvalds 
18762322d25SArjan van de Ven static const struct file_operations misc_fops = {
1881da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
1891da177e4SLinus Torvalds 	.open		= misc_open,
1906038f373SArnd Bergmann 	.llseek		= noop_llseek,
1911da177e4SLinus Torvalds };
1921da177e4SLinus Torvalds 
1931da177e4SLinus Torvalds /**
1941da177e4SLinus Torvalds  *	misc_register	-	register a miscellaneous device
1951da177e4SLinus Torvalds  *	@misc: device structure
1961da177e4SLinus Torvalds  *
1971da177e4SLinus Torvalds  *	Register a miscellaneous device with the kernel. If the minor
1981da177e4SLinus Torvalds  *	number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
1991da177e4SLinus Torvalds  *	and placed in the minor field of the structure. For other cases
2001da177e4SLinus Torvalds  *	the minor number requested is used.
2011da177e4SLinus Torvalds  *
2021da177e4SLinus Torvalds  *	The structure passed is linked into the kernel and may not be
20303190c67SMartin Kepplinger  *	destroyed until it has been unregistered. By default, an open()
20403190c67SMartin Kepplinger  *	syscall to the device sets file->private_data to point to the
20503190c67SMartin Kepplinger  *	structure. Drivers don't need open in fops for this.
2061da177e4SLinus Torvalds  *
2071da177e4SLinus Torvalds  *	A zero is returned on success and a negative errno code for
2081da177e4SLinus Torvalds  *	failure.
2091da177e4SLinus Torvalds  */
2101da177e4SLinus Torvalds 
misc_register(struct miscdevice * misc)2111da177e4SLinus Torvalds int misc_register(struct miscdevice *misc)
2121da177e4SLinus Torvalds {
2131da177e4SLinus Torvalds 	dev_t dev;
2147c69ef79SGreg Kroah-Hartman 	int err = 0;
215b575f712SVladimir Zapolskiy 	bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
2161da177e4SLinus Torvalds 
2175d469ec0SNeil Horman 	INIT_LIST_HEAD(&misc->list);
2185d469ec0SNeil Horman 
2190e82d5b6SMatthias Kaehlcke 	mutex_lock(&misc_mtx);
2201da177e4SLinus Torvalds 
221b575f712SVladimir Zapolskiy 	if (is_dynamic) {
222ab760791SD Scott Phillips 		int i = misc_minor_alloc();
2235b884a95SVarsha Rao 
224ab760791SD Scott Phillips 		if (i < 0) {
225684116caSElad Wexler 			err = -EBUSY;
226684116caSElad Wexler 			goto out;
2271da177e4SLinus Torvalds 		}
228ab760791SD Scott Phillips 		misc->minor = i;
2293c94ce6fSDae S. Kim 	} else {
2303c94ce6fSDae S. Kim 		struct miscdevice *c;
2313c94ce6fSDae S. Kim 
2323c94ce6fSDae S. Kim 		list_for_each_entry(c, &misc_list, list) {
2333c94ce6fSDae S. Kim 			if (c->minor == misc->minor) {
234684116caSElad Wexler 				err = -EBUSY;
235684116caSElad Wexler 				goto out;
2363c94ce6fSDae S. Kim 			}
2373c94ce6fSDae S. Kim 		}
2381da177e4SLinus Torvalds 	}
2391da177e4SLinus Torvalds 
2401da177e4SLinus Torvalds 	dev = MKDEV(MISC_MAJOR, misc->minor);
2411da177e4SLinus Torvalds 
242bd735995STakashi Iwai 	misc->this_device =
243*eafd52e6SIvan Orlov 		device_create_with_groups(&misc_class, misc->parent, dev,
244bd735995STakashi Iwai 					  misc, misc->groups, "%s", misc->name);
24594fbcdedSGreg Kroah-Hartman 	if (IS_ERR(misc->this_device)) {
246b575f712SVladimir Zapolskiy 		if (is_dynamic) {
247ab760791SD Scott Phillips 			misc_minor_free(misc->minor);
248b575f712SVladimir Zapolskiy 			misc->minor = MISC_DYNAMIC_MINOR;
249b575f712SVladimir Zapolskiy 		}
25094fbcdedSGreg Kroah-Hartman 		err = PTR_ERR(misc->this_device);
2511da177e4SLinus Torvalds 		goto out;
2521da177e4SLinus Torvalds 	}
2531da177e4SLinus Torvalds 
2541da177e4SLinus Torvalds 	/*
2551da177e4SLinus Torvalds 	 * Add it to the front, so that later devices can "override"
2561da177e4SLinus Torvalds 	 * earlier defaults
2571da177e4SLinus Torvalds 	 */
2581da177e4SLinus Torvalds 	list_add(&misc->list, &misc_list);
2591da177e4SLinus Torvalds  out:
2600e82d5b6SMatthias Kaehlcke 	mutex_unlock(&misc_mtx);
2611da177e4SLinus Torvalds 	return err;
2621da177e4SLinus Torvalds }
263e89bec3aSNaveen Kumar Parna EXPORT_SYMBOL(misc_register);
2641da177e4SLinus Torvalds 
2651da177e4SLinus Torvalds /**
266b844eba2SRafael J. Wysocki  *	misc_deregister - unregister a miscellaneous device
2671da177e4SLinus Torvalds  *	@misc: device to unregister
2681da177e4SLinus Torvalds  *
2691da177e4SLinus Torvalds  *	Unregister a miscellaneous device that was previously
270f368ed60SGreg Kroah-Hartman  *	successfully registered with misc_register().
2711da177e4SLinus Torvalds  */
2721da177e4SLinus Torvalds 
misc_deregister(struct miscdevice * misc)273f368ed60SGreg Kroah-Hartman void misc_deregister(struct miscdevice *misc)
2741da177e4SLinus Torvalds {
275b329becfSAkinobu Mita 	if (WARN_ON(list_empty(&misc->list)))
276f368ed60SGreg Kroah-Hartman 		return;
2771da177e4SLinus Torvalds 
2780e82d5b6SMatthias Kaehlcke 	mutex_lock(&misc_mtx);
2791da177e4SLinus Torvalds 	list_del(&misc->list);
280*eafd52e6SIvan Orlov 	device_destroy(&misc_class, MKDEV(MISC_MAJOR, misc->minor));
281ab760791SD Scott Phillips 	misc_minor_free(misc->minor);
2820e82d5b6SMatthias Kaehlcke 	mutex_unlock(&misc_mtx);
2831da177e4SLinus Torvalds }
284b844eba2SRafael J. Wysocki EXPORT_SYMBOL(misc_deregister);
2851da177e4SLinus Torvalds 
misc_init(void)2861da177e4SLinus Torvalds static int __init misc_init(void)
2871da177e4SLinus Torvalds {
2881b502217SDenis V. Lunev 	int err;
2891037b278SSudip Mukherjee 	struct proc_dir_entry *ret;
2901da177e4SLinus Torvalds 
291fddda2b7SChristoph Hellwig 	ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);
292*eafd52e6SIvan Orlov 	err = class_register(&misc_class);
293*eafd52e6SIvan Orlov 	if (err)
2941b502217SDenis V. Lunev 		goto fail_remove;
295573fc113SChristoph Hellwig 
2961b502217SDenis V. Lunev 	err = -EIO;
2971b502217SDenis V. Lunev 	if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
2981b502217SDenis V. Lunev 		goto fail_printk;
2991da177e4SLinus Torvalds 	return 0;
3001b502217SDenis V. Lunev 
3011b502217SDenis V. Lunev fail_printk:
3028ab44b40SVarsha Rao 	pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
303*eafd52e6SIvan Orlov 	class_unregister(&misc_class);
3041b502217SDenis V. Lunev fail_remove:
3051037b278SSudip Mukherjee 	if (ret)
3061b502217SDenis V. Lunev 		remove_proc_entry("misc", NULL);
3071b502217SDenis V. Lunev 	return err;
3081da177e4SLinus Torvalds }
3091da177e4SLinus Torvalds subsys_initcall(misc_init);
310