at master 7.5 kB view raw
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * linux/drivers/char/misc.c 4 * 5 * Generic misc open routine by Johan Myreen 6 * 7 * Based on code from Linus 8 * 9 * Teemu Rantanen's Microsoft Busmouse support and Derrick Cole's 10 * changes incorporated into 0.97pl4 11 * 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 two 15 * 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 registration 20 * Alan Cox <alan@lxorguk.ukuu.org.uk> 21-Jan-96 21 * 22 * Dynamic minors and /proc/mice by Alessandro Rubini. 26-Mar-96 23 * 24 * Renamed to misc and miscdevice to be more accurate. Alan Cox 26-Mar-96 25 * 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-1998 35 */ 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 list 56 */ 57static LIST_HEAD(misc_list); 58static DEFINE_MUTEX(misc_mtx); 59 60/* 61 * Assigned numbers. 62 */ 63static DEFINE_IDA(misc_minors_ida); 64 65static int misc_minor_alloc(int minor) 66{ 67 int ret = 0; 68 69 if (minor == MISC_DYNAMIC_MINOR) { 70 /* allocate free id */ 71 ret = ida_alloc_range(&misc_minors_ida, MISC_DYNAMIC_MINOR + 1, 72 MINORMASK, GFP_KERNEL); 73 } else { 74 ret = ida_alloc_range(&misc_minors_ida, minor, minor, GFP_KERNEL); 75 } 76 return ret; 77} 78 79static void misc_minor_free(int minor) 80{ 81 ida_free(&misc_minors_ida, minor); 82} 83 84#ifdef CONFIG_PROC_FS 85static void *misc_seq_start(struct seq_file *seq, loff_t *pos) 86{ 87 mutex_lock(&misc_mtx); 88 return seq_list_start(&misc_list, *pos); 89} 90 91static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos) 92{ 93 return seq_list_next(v, &misc_list, pos); 94} 95 96static void misc_seq_stop(struct seq_file *seq, void *v) 97{ 98 mutex_unlock(&misc_mtx); 99} 100 101static int misc_seq_show(struct seq_file *seq, void *v) 102{ 103 const struct miscdevice *p = list_entry(v, struct miscdevice, list); 104 105 seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : ""); 106 return 0; 107} 108 109 110static const struct seq_operations misc_seq_ops = { 111 .start = misc_seq_start, 112 .next = misc_seq_next, 113 .stop = misc_seq_stop, 114 .show = misc_seq_show, 115}; 116#endif 117 118static int misc_open(struct inode *inode, struct file *file) 119{ 120 int minor = iminor(inode); 121 struct miscdevice *c = NULL, *iter; 122 int err = -ENODEV; 123 const struct file_operations *new_fops = NULL; 124 125 mutex_lock(&misc_mtx); 126 127 list_for_each_entry(iter, &misc_list, list) { 128 if (iter->minor != minor) 129 continue; 130 c = iter; 131 new_fops = fops_get(iter->fops); 132 break; 133 } 134 135 /* Only request module for fixed minor code */ 136 if (!new_fops && minor < MISC_DYNAMIC_MINOR) { 137 mutex_unlock(&misc_mtx); 138 request_module("char-major-%d-%d", MISC_MAJOR, minor); 139 mutex_lock(&misc_mtx); 140 141 list_for_each_entry(iter, &misc_list, list) { 142 if (iter->minor != minor) 143 continue; 144 c = iter; 145 new_fops = fops_get(iter->fops); 146 break; 147 } 148 } 149 150 if (!new_fops) 151 goto fail; 152 153 /* 154 * Place the miscdevice in the file's 155 * private_data so it can be used by the 156 * file operations, including f_op->open below 157 */ 158 file->private_data = c; 159 160 err = 0; 161 replace_fops(file, new_fops); 162 if (file->f_op->open) 163 err = file->f_op->open(inode, file); 164fail: 165 mutex_unlock(&misc_mtx); 166 return err; 167} 168 169static char *misc_devnode(const struct device *dev, umode_t *mode) 170{ 171 const struct miscdevice *c = dev_get_drvdata(dev); 172 173 if (mode && c->mode) 174 *mode = c->mode; 175 if (c->nodename) 176 return kstrdup(c->nodename, GFP_KERNEL); 177 return NULL; 178} 179 180static const struct class misc_class = { 181 .name = "misc", 182 .devnode = misc_devnode, 183}; 184 185static const struct file_operations misc_fops = { 186 .owner = THIS_MODULE, 187 .open = misc_open, 188 .llseek = noop_llseek, 189}; 190 191/** 192 * misc_register - register a miscellaneous device 193 * @misc: device structure 194 * 195 * Register a miscellaneous device with the kernel. If the minor 196 * number is set to %MISC_DYNAMIC_MINOR a minor number is assigned 197 * and placed in the minor field of the structure. For other cases 198 * the minor number requested is used. 199 * 200 * The structure passed is linked into the kernel and may not be 201 * destroyed until it has been unregistered. By default, an open() 202 * syscall to the device sets file->private_data to point to the 203 * structure. Drivers don't need open in fops for this. 204 * 205 * A zero is returned on success and a negative errno code for 206 * failure. 207 */ 208 209int misc_register(struct miscdevice *misc) 210{ 211 dev_t dev; 212 int err = 0; 213 bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR); 214 215 if (misc->minor > MISC_DYNAMIC_MINOR) { 216 pr_err("Invalid fixed minor %d for miscdevice '%s'\n", 217 misc->minor, misc->name); 218 return -EINVAL; 219 } 220 221 INIT_LIST_HEAD(&misc->list); 222 223 mutex_lock(&misc_mtx); 224 225 if (is_dynamic) { 226 int i = misc_minor_alloc(misc->minor); 227 228 if (i < 0) { 229 err = -EBUSY; 230 goto out; 231 } 232 misc->minor = i; 233 } else { 234 struct miscdevice *c; 235 int i; 236 237 list_for_each_entry(c, &misc_list, list) { 238 if (c->minor == misc->minor) { 239 err = -EBUSY; 240 goto out; 241 } 242 } 243 244 i = misc_minor_alloc(misc->minor); 245 if (i < 0) { 246 err = -EBUSY; 247 goto out; 248 } 249 } 250 251 dev = MKDEV(MISC_MAJOR, misc->minor); 252 253 misc->this_device = 254 device_create_with_groups(&misc_class, misc->parent, dev, 255 misc, misc->groups, "%s", misc->name); 256 if (IS_ERR(misc->this_device)) { 257 misc_minor_free(misc->minor); 258 if (is_dynamic) { 259 misc->minor = MISC_DYNAMIC_MINOR; 260 } 261 err = PTR_ERR(misc->this_device); 262 goto out; 263 } 264 265 /* 266 * Add it to the front, so that later devices can "override" 267 * earlier defaults 268 */ 269 list_add(&misc->list, &misc_list); 270 out: 271 mutex_unlock(&misc_mtx); 272 return err; 273} 274EXPORT_SYMBOL(misc_register); 275 276/** 277 * misc_deregister - unregister a miscellaneous device 278 * @misc: device to unregister 279 * 280 * Unregister a miscellaneous device that was previously 281 * successfully registered with misc_register(). 282 */ 283 284void misc_deregister(struct miscdevice *misc) 285{ 286 mutex_lock(&misc_mtx); 287 list_del_init(&misc->list); 288 device_destroy(&misc_class, MKDEV(MISC_MAJOR, misc->minor)); 289 misc_minor_free(misc->minor); 290 if (misc->minor > MISC_DYNAMIC_MINOR) 291 misc->minor = MISC_DYNAMIC_MINOR; 292 mutex_unlock(&misc_mtx); 293} 294EXPORT_SYMBOL(misc_deregister); 295 296static int __init misc_init(void) 297{ 298 int err; 299 struct proc_dir_entry *misc_proc_file; 300 301 misc_proc_file = proc_create_seq("misc", 0, NULL, &misc_seq_ops); 302 err = class_register(&misc_class); 303 if (err) 304 goto fail_remove; 305 306 err = __register_chrdev(MISC_MAJOR, 0, MINORMASK + 1, "misc", &misc_fops); 307 if (err < 0) 308 goto fail_printk; 309 return 0; 310 311fail_printk: 312 pr_err("unable to get major %d for misc devices\n", MISC_MAJOR); 313 class_unregister(&misc_class); 314fail_remove: 315 if (misc_proc_file) 316 remove_proc_entry("misc", NULL); 317 return err; 318} 319subsys_initcall(misc_init);