310 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * linux/drivers/char/misc.c4 *5 * Generic misc open routine by Johan Myreen6 *7 * Based on code from Linus8 *9 * Teemu Rantanen's Microsoft Busmouse support and Derrick Cole's10 * changes incorporated into 0.97pl411 * by Peter Cervasio (pete%q106fm.uucp@wupost.wustl.edu) (08SEP92)12 * See busmouse.c for particulars.13 *14 * Made things a lot mode modular - easy to compile in just one or two15 * of the misc drivers, as they are now completely independent. Linus.16 *17 * Support for loadable modules. 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk>18 *19 * Fixed a failing symbol register to free the device registration20 * Alan Cox <alan@lxorguk.ukuu.org.uk> 21-Jan-9621 *22 * Dynamic minors and /proc/mice by Alessandro Rubini. 26-Mar-9623 *24 * Renamed to misc and miscdevice to be more accurate. Alan Cox 26-Mar-9625 *26 * Handling of mouse minor numbers for kerneld:27 * Idea by Jacques Gelinas <jack@solucorp.qc.ca>,28 * adapted by Bjorn Ekwall <bj0rn@blox.se>29 * corrected by Alan Cox <alan@lxorguk.ukuu.org.uk>30 *31 * Changes for kmod (from kerneld):32 * Cyrus Durgin <cider@speakeasy.org>33 *34 * Added devfs support. Richard Gooch <rgooch@atnf.csiro.au> 10-Jan-199835 */36 37#include <linux/module.h>38 39#include <linux/fs.h>40#include <linux/errno.h>41#include <linux/miscdevice.h>42#include <linux/kernel.h>43#include <linux/major.h>44#include <linux/mutex.h>45#include <linux/proc_fs.h>46#include <linux/seq_file.h>47#include <linux/stat.h>48#include <linux/init.h>49#include <linux/device.h>50#include <linux/tty.h>51#include <linux/kmod.h>52#include <linux/gfp.h>53 54/*55 * Head entry for the doubly linked miscdevice list56 */57static LIST_HEAD(misc_list);58static DEFINE_MUTEX(misc_mtx);59 60/*61 * Assigned numbers, used for dynamic minors62 */63#define DYNAMIC_MINORS 128 /* like dynamic majors */64static DEFINE_IDA(misc_minors_ida);65 66static int misc_minor_alloc(void)67{68 int ret;69 70 ret = ida_alloc_max(&misc_minors_ida, DYNAMIC_MINORS - 1, GFP_KERNEL);71 if (ret >= 0) {72 ret = DYNAMIC_MINORS - ret - 1;73 } else {74 ret = ida_alloc_range(&misc_minors_ida, MISC_DYNAMIC_MINOR + 1,75 MINORMASK, GFP_KERNEL);76 }77 return ret;78}79 80static void misc_minor_free(int minor)81{82 if (minor < DYNAMIC_MINORS)83 ida_free(&misc_minors_ida, DYNAMIC_MINORS - minor - 1);84 else if (minor > MISC_DYNAMIC_MINOR)85 ida_free(&misc_minors_ida, minor);86}87 88#ifdef CONFIG_PROC_FS89static void *misc_seq_start(struct seq_file *seq, loff_t *pos)90{91 mutex_lock(&misc_mtx);92 return seq_list_start(&misc_list, *pos);93}94 95static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)96{97 return seq_list_next(v, &misc_list, pos);98}99 100static HWJS_SUSPENDS void misc_seq_stop(struct seq_file *seq, void *v)101{102 mutex_unlock(&misc_mtx);103}104 105static int misc_seq_show(struct seq_file *seq, void *v)106{107 const struct miscdevice *p = list_entry(v, struct miscdevice, list);108 109 seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");110 return 0;111}112 113 114static const struct seq_operations misc_seq_ops = {115 .start = misc_seq_start,116 .next = misc_seq_next,117 .stop = misc_seq_stop,118 .show = misc_seq_show,119};120#endif121 122static int misc_open(struct inode *inode, struct file *file)123{124 int minor = iminor(inode);125 struct miscdevice *c = NULL, *iter;126 int err = -ENODEV;127 const struct file_operations *new_fops = NULL;128 129 mutex_lock(&misc_mtx);130 131 list_for_each_entry(iter, &misc_list, list) {132 if (iter->minor != minor)133 continue;134 c = iter;135 new_fops = fops_get(iter->fops);136 break;137 }138 139 if (!new_fops) {140 mutex_unlock(&misc_mtx);141 request_module("char-major-%d-%d", MISC_MAJOR, minor);142 mutex_lock(&misc_mtx);143 144 list_for_each_entry(iter, &misc_list, list) {145 if (iter->minor != minor)146 continue;147 c = iter;148 new_fops = fops_get(iter->fops);149 break;150 }151 if (!new_fops)152 goto fail;153 }154 155 /*156 * Place the miscdevice in the file's157 * private_data so it can be used by the158 * file operations, including f_op->open below159 */160 file->private_data = c;161 162 err = 0;163 replace_fops(file, new_fops);164 if (file->f_op->open)165 err = file->f_op->open(inode, file);166fail:167 mutex_unlock(&misc_mtx);168 return err;169}170 171static char *misc_devnode(const struct device *dev, umode_t *mode)172{173 const struct miscdevice *c = dev_get_drvdata(dev);174 175 if (mode && c->mode)176 *mode = c->mode;177 if (c->nodename)178 return kstrdup(c->nodename, GFP_KERNEL);179 return NULL;180}181 182static const struct class misc_class = {183 .name = "misc",184 .devnode = misc_devnode,185};186 187static const struct file_operations misc_fops = {188 .owner = THIS_MODULE,189 .open = misc_open,190 .llseek = noop_llseek,191};192 193/**194 * misc_register - register a miscellaneous device195 * @misc: device structure196 *197 * Register a miscellaneous device with the kernel. If the minor198 * number is set to %MISC_DYNAMIC_MINOR a minor number is assigned199 * and placed in the minor field of the structure. For other cases200 * the minor number requested is used.201 *202 * The structure passed is linked into the kernel and may not be203 * destroyed until it has been unregistered. By default, an open()204 * syscall to the device sets file->private_data to point to the205 * structure. Drivers don't need open in fops for this.206 *207 * A zero is returned on success and a negative errno code for208 * failure.209 */210 211int misc_register(struct miscdevice *misc)212{213 dev_t dev;214 int err = 0;215 bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);216 217 INIT_LIST_HEAD(&misc->list);218 219 mutex_lock(&misc_mtx);220 221 if (is_dynamic) {222 int i = misc_minor_alloc();223 224 if (i < 0) {225 err = -EBUSY;226 goto out;227 }228 misc->minor = i;229 } else {230 struct miscdevice *c;231 232 list_for_each_entry(c, &misc_list, list) {233 if (c->minor == misc->minor) {234 err = -EBUSY;235 goto out;236 }237 }238 }239 240 dev = MKDEV(MISC_MAJOR, misc->minor);241 242 misc->this_device =243 device_create_with_groups(&misc_class, misc->parent, dev,244 misc, misc->groups, "%s", misc->name);245 if (IS_ERR(misc->this_device)) {246 if (is_dynamic) {247 misc_minor_free(misc->minor);248 misc->minor = MISC_DYNAMIC_MINOR;249 }250 err = PTR_ERR(misc->this_device);251 goto out;252 }253 254 /*255 * Add it to the front, so that later devices can "override"256 * earlier defaults257 */258 list_add(&misc->list, &misc_list);259 out:260 mutex_unlock(&misc_mtx);261 return err;262}263EXPORT_SYMBOL(misc_register);264 265/**266 * misc_deregister - unregister a miscellaneous device267 * @misc: device to unregister268 *269 * Unregister a miscellaneous device that was previously270 * successfully registered with misc_register().271 */272 273void misc_deregister(struct miscdevice *misc)274{275 if (WARN_ON(list_empty(&misc->list)))276 return;277 278 mutex_lock(&misc_mtx);279 list_del(&misc->list);280 device_destroy(&misc_class, MKDEV(MISC_MAJOR, misc->minor));281 misc_minor_free(misc->minor);282 mutex_unlock(&misc_mtx);283}284EXPORT_SYMBOL(misc_deregister);285 286static int __init misc_init(void)287{288 int err;289 struct proc_dir_entry *ret;290 291 ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);292 err = class_register(&misc_class);293 if (err)294 goto fail_remove;295 296 err = -EIO;297 if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))298 goto fail_printk;299 return 0;300 301fail_printk:302 pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);303 class_unregister(&misc_class);304fail_remove:305 if (ret)306 remove_proc_entry("misc", NULL);307 return err;308}309subsys_initcall(misc_init);310