misc.c 7.07 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0
Linus Torvalds's avatar
Linus Torvalds committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
/*
 * linux/drivers/char/misc.c
 *
 * Generic misc open routine by Johan Myreen
 *
 * Based on code from Linus
 *
 * Teemu Rantanen's Microsoft Busmouse support and Derrick Cole's
 *   changes incorporated into 0.97pl4
 *   by Peter Cervasio (pete%q106fm.uucp@wupost.wustl.edu) (08SEP92)
 *   See busmouse.c for particulars.
 *
 * Made things a lot mode modular - easy to compile in just one or two
 * of the misc drivers, as they are now completely independent. Linus.
 *
 * Support for loadable modules. 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk>
 *
 * Fixed a failing symbol register to free the device registration
 *		Alan Cox <alan@lxorguk.ukuu.org.uk> 21-Jan-96
 *
 * Dynamic minors and /proc/mice by Alessandro Rubini. 26-Mar-96
 *
 * Renamed to misc and miscdevice to be more accurate. Alan Cox 26-Mar-96
 *
 * Handling of mouse minor numbers for kerneld:
 *  Idea by Jacques Gelinas <jack@solucorp.qc.ca>,
 *  adapted by Bjorn Ekwall <bj0rn@blox.se>
 *  corrected by Alan Cox <alan@lxorguk.ukuu.org.uk>
 *
 * Changes for kmod (from kerneld):
 *	Cyrus Durgin <cider@speakeasy.org>
 *
 * Added devfs support. Richard Gooch <rgooch@atnf.csiro.au>  10-Jan-1998
 */

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
44
#include <linux/mutex.h>
Linus Torvalds's avatar
Linus Torvalds committed
45 46 47 48 49 50 51
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
52
#include <linux/gfp.h>
Linus Torvalds's avatar
Linus Torvalds committed
53 54 55 56 57

/*
 * Head entry for the doubly linked miscdevice list
 */
static LIST_HEAD(misc_list);
58
static DEFINE_MUTEX(misc_mtx);
Linus Torvalds's avatar
Linus Torvalds committed
59 60 61 62 63

/*
 * Assigned numbers, used for dynamic minors
 */
#define DYNAMIC_MINORS 64 /* like dynamic majors */
64
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);
Linus Torvalds's avatar
Linus Torvalds committed
65 66 67 68

#ifdef CONFIG_PROC_FS
static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
{
69
	mutex_lock(&misc_mtx);
70
	return seq_list_start(&misc_list, *pos);
Linus Torvalds's avatar
Linus Torvalds committed
71 72 73 74
}

static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
75
	return seq_list_next(v, &misc_list, pos);
Linus Torvalds's avatar
Linus Torvalds committed
76 77 78 79
}

static void misc_seq_stop(struct seq_file *seq, void *v)
{
80
	mutex_unlock(&misc_mtx);
Linus Torvalds's avatar
Linus Torvalds committed
81 82 83 84
}

static int misc_seq_show(struct seq_file *seq, void *v)
{
85
	const struct miscdevice *p = list_entry(v, struct miscdevice, list);
Linus Torvalds's avatar
Linus Torvalds committed
86 87 88 89 90 91

	seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
	return 0;
}


92
static const struct seq_operations misc_seq_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
93 94 95 96 97 98 99 100 101 102 103
	.start = misc_seq_start,
	.next  = misc_seq_next,
	.stop  = misc_seq_stop,
	.show  = misc_seq_show,
};

static int misc_seq_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &misc_seq_ops);
}

104
static const struct file_operations misc_proc_fops = {
Linus Torvalds's avatar
Linus Torvalds committed
105 106 107 108 109 110 111 112
	.owner	 = THIS_MODULE,
	.open    = misc_seq_open,
	.read    = seq_read,
	.llseek  = seq_lseek,
	.release = seq_release,
};
#endif

113
static int misc_open(struct inode *inode, struct file *file)
Linus Torvalds's avatar
Linus Torvalds committed
114 115 116 117
{
	int minor = iminor(inode);
	struct miscdevice *c;
	int err = -ENODEV;
118
	const struct file_operations *new_fops = NULL;
119

120
	mutex_lock(&misc_mtx);
121

Linus Torvalds's avatar
Linus Torvalds committed
122 123
	list_for_each_entry(c, &misc_list, list) {
		if (c->minor == minor) {
124
			new_fops = fops_get(c->fops);
Linus Torvalds's avatar
Linus Torvalds committed
125 126 127
			break;
		}
	}
128

Linus Torvalds's avatar
Linus Torvalds committed
129
	if (!new_fops) {
130
		mutex_unlock(&misc_mtx);
Linus Torvalds's avatar
Linus Torvalds committed
131
		request_module("char-major-%d-%d", MISC_MAJOR, minor);
132
		mutex_lock(&misc_mtx);
Linus Torvalds's avatar
Linus Torvalds committed
133 134 135 136 137 138 139 140 141 142 143

		list_for_each_entry(c, &misc_list, list) {
			if (c->minor == minor) {
				new_fops = fops_get(c->fops);
				break;
			}
		}
		if (!new_fops)
			goto fail;
	}

144 145 146 147 148 149 150
	/*
	 * Place the miscdevice in the file's
	 * private_data so it can be used by the
	 * file operations, including f_op->open below
	 */
	file->private_data = c;

Linus Torvalds's avatar
Linus Torvalds committed
151
	err = 0;
152
	replace_fops(file, new_fops);
153
	if (file->f_op->open)
154
		err = file->f_op->open(inode, file);
Linus Torvalds's avatar
Linus Torvalds committed
155
fail:
156
	mutex_unlock(&misc_mtx);
Linus Torvalds's avatar
Linus Torvalds committed
157 158 159
	return err;
}

160
static struct class *misc_class;
Linus Torvalds's avatar
Linus Torvalds committed
161

162
static const struct file_operations misc_fops = {
Linus Torvalds's avatar
Linus Torvalds committed
163 164
	.owner		= THIS_MODULE,
	.open		= misc_open,
165
	.llseek		= noop_llseek,
Linus Torvalds's avatar
Linus Torvalds committed
166 167 168 169 170
};

/**
 *	misc_register	-	register a miscellaneous device
 *	@misc: device structure
171
 *
Linus Torvalds's avatar
Linus Torvalds committed
172 173 174 175 176 177
 *	Register a miscellaneous device with the kernel. If the minor
 *	number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
 *	and placed in the minor field of the structure. For other cases
 *	the minor number requested is used.
 *
 *	The structure passed is linked into the kernel and may not be
178 179 180
 *	destroyed until it has been unregistered. By default, an open()
 *	syscall to the device sets file->private_data to point to the
 *	structure. Drivers don't need open in fops for this.
Linus Torvalds's avatar
Linus Torvalds committed
181 182 183 184
 *
 *	A zero is returned on success and a negative errno code for
 *	failure.
 */
185

186
int misc_register(struct miscdevice *misc)
Linus Torvalds's avatar
Linus Torvalds committed
187 188
{
	dev_t dev;
189
	int err = 0;
190
	bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
Linus Torvalds's avatar
Linus Torvalds committed
191

192 193
	INIT_LIST_HEAD(&misc->list);

194
	mutex_lock(&misc_mtx);
Linus Torvalds's avatar
Linus Torvalds committed
195

196
	if (is_dynamic) {
197
		int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
198

199
		if (i >= DYNAMIC_MINORS) {
200 201
			err = -EBUSY;
			goto out;
Linus Torvalds's avatar
Linus Torvalds committed
202
		}
203 204
		misc->minor = DYNAMIC_MINORS - i - 1;
		set_bit(i, misc_minors);
205 206 207 208 209
	} else {
		struct miscdevice *c;

		list_for_each_entry(c, &misc_list, list) {
			if (c->minor == misc->minor) {
210 211
				err = -EBUSY;
				goto out;
212 213
			}
		}
Linus Torvalds's avatar
Linus Torvalds committed
214 215 216 217
	}

	dev = MKDEV(MISC_MAJOR, misc->minor);

Takashi Iwai's avatar
Takashi Iwai committed
218 219 220
	misc->this_device =
		device_create_with_groups(misc_class, misc->parent, dev,
					  misc, misc->groups, "%s", misc->name);
221
	if (IS_ERR(misc->this_device)) {
222 223 224 225 226 227 228
		if (is_dynamic) {
			int i = DYNAMIC_MINORS - misc->minor - 1;

			if (i < DYNAMIC_MINORS && i >= 0)
				clear_bit(i, misc_minors);
			misc->minor = MISC_DYNAMIC_MINOR;
		}
229
		err = PTR_ERR(misc->this_device);
Linus Torvalds's avatar
Linus Torvalds committed
230 231 232 233 234 235 236 237 238
		goto out;
	}

	/*
	 * Add it to the front, so that later devices can "override"
	 * earlier defaults
	 */
	list_add(&misc->list, &misc_list);
 out:
239
	mutex_unlock(&misc_mtx);
Linus Torvalds's avatar
Linus Torvalds committed
240 241 242 243
	return err;
}

/**
244
 *	misc_deregister - unregister a miscellaneous device
Linus Torvalds's avatar
Linus Torvalds committed
245 246 247
 *	@misc: device to unregister
 *
 *	Unregister a miscellaneous device that was previously
248
 *	successfully registered with misc_register().
Linus Torvalds's avatar
Linus Torvalds committed
249 250
 */

251
void misc_deregister(struct miscdevice *misc)
Linus Torvalds's avatar
Linus Torvalds committed
252
{
253
	int i = DYNAMIC_MINORS - misc->minor - 1;
Linus Torvalds's avatar
Linus Torvalds committed
254

255
	if (WARN_ON(list_empty(&misc->list)))
256
		return;
Linus Torvalds's avatar
Linus Torvalds committed
257

258
	mutex_lock(&misc_mtx);
Linus Torvalds's avatar
Linus Torvalds committed
259
	list_del(&misc->list);
260
	device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
261
	if (i < DYNAMIC_MINORS && i >= 0)
262
		clear_bit(i, misc_minors);
263
	mutex_unlock(&misc_mtx);
Linus Torvalds's avatar
Linus Torvalds committed
264 265 266
}

EXPORT_SYMBOL(misc_register);
267
EXPORT_SYMBOL(misc_deregister);
Linus Torvalds's avatar
Linus Torvalds committed
268

269
static char *misc_devnode(struct device *dev, umode_t *mode)
270 271 272
{
	struct miscdevice *c = dev_get_drvdata(dev);

273 274 275 276
	if (mode && c->mode)
		*mode = c->mode;
	if (c->nodename)
		return kstrdup(c->nodename, GFP_KERNEL);
277 278 279
	return NULL;
}

Linus Torvalds's avatar
Linus Torvalds committed
280 281
static int __init misc_init(void)
{
282
	int err;
Sudip Mukherjee's avatar
Sudip Mukherjee committed
283
	struct proc_dir_entry *ret;
Linus Torvalds's avatar
Linus Torvalds committed
284

Sudip Mukherjee's avatar
Sudip Mukherjee committed
285
	ret = proc_create("misc", 0, NULL, &misc_proc_fops);
286
	misc_class = class_create(THIS_MODULE, "misc");
287
	err = PTR_ERR(misc_class);
Linus Torvalds's avatar
Linus Torvalds committed
288
	if (IS_ERR(misc_class))
289
		goto fail_remove;
290

291
	err = -EIO;
292
	if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
293
		goto fail_printk;
294
	misc_class->devnode = misc_devnode;
Linus Torvalds's avatar
Linus Torvalds committed
295
	return 0;
296 297

fail_printk:
298
	pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
299 300
	class_destroy(misc_class);
fail_remove:
Sudip Mukherjee's avatar
Sudip Mukherjee committed
301 302
	if (ret)
		remove_proc_entry("misc", NULL);
303
	return err;
Linus Torvalds's avatar
Linus Torvalds committed
304 305
}
subsys_initcall(misc_init);