[ Upstream commit31b636d2c4] When this was first reported [1], the possibility of having sufficient number of dynamic misc devices was theoretical, in the case of dlm driver. In practice, its userspace never created more than one device. What we know from commitab760791c0("char: misc: Increase the maximum number of dynamic misc devices to 1048448"), is that the miscdevice interface has been used for allocating more than the single-shot devices it was designed for. And it is not only coresight_tmc, but many other drivers are able to create multiple devices. On systems like the ones described in the above commit, it is certain that the dynamic allocation will allocate certain reserved minor numbers, leading to failures when a later driver tries to claim its reserved number. Instead of excluding the historically statically allocated range from dynamic allocation, restrict the latter to minors above 255. That also removes the need for DYNAMIC_MINORS and the convolution in allocating minor numbers, simplifying the code. Since commitab760791c0("char: misc: Increase the maximum number of dynamic misc devices to 1048448") has been applied, such range is already possible. And given such devices already need to be dynamically created, there should be no systems where this might become a problem. [1] https://lore.kernel.org/all/1257813017-28598-3-git-send-email-cascardo@holoscopio.com/ Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@igalia.com> Link: https://lore.kernel.org/r/20250423-misc-dynrange-v4-1-133b5ae4ca18@igalia.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
317 lines
7.2 KiB
C
317 lines
7.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* 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>
|
|
#include <linux/mutex.h>
|
|
#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>
|
|
#include <linux/gfp.h>
|
|
|
|
/*
|
|
* Head entry for the doubly linked miscdevice list
|
|
*/
|
|
static LIST_HEAD(misc_list);
|
|
static DEFINE_MUTEX(misc_mtx);
|
|
|
|
/*
|
|
* Assigned numbers.
|
|
*/
|
|
static DEFINE_IDA(misc_minors_ida);
|
|
|
|
static int misc_minor_alloc(int minor)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (minor == MISC_DYNAMIC_MINOR) {
|
|
/* allocate free id */
|
|
ret = ida_alloc_range(&misc_minors_ida, MISC_DYNAMIC_MINOR + 1,
|
|
MINORMASK, GFP_KERNEL);
|
|
} else {
|
|
ret = ida_alloc_range(&misc_minors_ida, minor, minor, GFP_KERNEL);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void misc_minor_free(int minor)
|
|
{
|
|
ida_free(&misc_minors_ida, minor);
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
mutex_lock(&misc_mtx);
|
|
return seq_list_start(&misc_list, *pos);
|
|
}
|
|
|
|
static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
return seq_list_next(v, &misc_list, pos);
|
|
}
|
|
|
|
static void misc_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
mutex_unlock(&misc_mtx);
|
|
}
|
|
|
|
static int misc_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
const struct miscdevice *p = list_entry(v, struct miscdevice, list);
|
|
|
|
seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct seq_operations misc_seq_ops = {
|
|
.start = misc_seq_start,
|
|
.next = misc_seq_next,
|
|
.stop = misc_seq_stop,
|
|
.show = misc_seq_show,
|
|
};
|
|
#endif
|
|
|
|
static int misc_open(struct inode *inode, struct file *file)
|
|
{
|
|
int minor = iminor(inode);
|
|
struct miscdevice *c = NULL, *iter;
|
|
int err = -ENODEV;
|
|
const struct file_operations *new_fops = NULL;
|
|
|
|
mutex_lock(&misc_mtx);
|
|
|
|
list_for_each_entry(iter, &misc_list, list) {
|
|
if (iter->minor != minor)
|
|
continue;
|
|
c = iter;
|
|
new_fops = fops_get(iter->fops);
|
|
break;
|
|
}
|
|
|
|
/* Only request module for fixed minor code */
|
|
if (!new_fops && minor < MISC_DYNAMIC_MINOR) {
|
|
mutex_unlock(&misc_mtx);
|
|
request_module("char-major-%d-%d", MISC_MAJOR, minor);
|
|
mutex_lock(&misc_mtx);
|
|
|
|
list_for_each_entry(iter, &misc_list, list) {
|
|
if (iter->minor != minor)
|
|
continue;
|
|
c = iter;
|
|
new_fops = fops_get(iter->fops);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!new_fops)
|
|
goto fail;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
err = 0;
|
|
replace_fops(file, new_fops);
|
|
if (file->f_op->open)
|
|
err = file->f_op->open(inode, file);
|
|
fail:
|
|
mutex_unlock(&misc_mtx);
|
|
return err;
|
|
}
|
|
|
|
static char *misc_devnode(const struct device *dev, umode_t *mode)
|
|
{
|
|
const struct miscdevice *c = dev_get_drvdata(dev);
|
|
|
|
if (mode && c->mode)
|
|
*mode = c->mode;
|
|
if (c->nodename)
|
|
return kstrdup(c->nodename, GFP_KERNEL);
|
|
return NULL;
|
|
}
|
|
|
|
static const struct class misc_class = {
|
|
.name = "misc",
|
|
.devnode = misc_devnode,
|
|
};
|
|
|
|
static const struct file_operations misc_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = misc_open,
|
|
.llseek = noop_llseek,
|
|
};
|
|
|
|
/**
|
|
* misc_register - register a miscellaneous device
|
|
* @misc: device structure
|
|
*
|
|
* 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
|
|
* 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.
|
|
*
|
|
* A zero is returned on success and a negative errno code for
|
|
* failure.
|
|
*/
|
|
|
|
int misc_register(struct miscdevice *misc)
|
|
{
|
|
dev_t dev;
|
|
int err = 0;
|
|
bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
|
|
|
|
INIT_LIST_HEAD(&misc->list);
|
|
|
|
mutex_lock(&misc_mtx);
|
|
|
|
if (is_dynamic) {
|
|
int i = misc_minor_alloc(misc->minor);
|
|
|
|
if (i < 0) {
|
|
err = -EBUSY;
|
|
goto out;
|
|
}
|
|
misc->minor = i;
|
|
} else {
|
|
struct miscdevice *c;
|
|
int i;
|
|
|
|
list_for_each_entry(c, &misc_list, list) {
|
|
if (c->minor == misc->minor) {
|
|
err = -EBUSY;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
i = misc_minor_alloc(misc->minor);
|
|
if (i < 0) {
|
|
err = -EBUSY;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
dev = MKDEV(MISC_MAJOR, misc->minor);
|
|
|
|
misc->this_device =
|
|
device_create_with_groups(&misc_class, misc->parent, dev,
|
|
misc, misc->groups, "%s", misc->name);
|
|
if (IS_ERR(misc->this_device)) {
|
|
misc_minor_free(misc->minor);
|
|
if (is_dynamic) {
|
|
misc->minor = MISC_DYNAMIC_MINOR;
|
|
}
|
|
err = PTR_ERR(misc->this_device);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Add it to the front, so that later devices can "override"
|
|
* earlier defaults
|
|
*/
|
|
list_add(&misc->list, &misc_list);
|
|
out:
|
|
mutex_unlock(&misc_mtx);
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL(misc_register);
|
|
|
|
/**
|
|
* misc_deregister - unregister a miscellaneous device
|
|
* @misc: device to unregister
|
|
*
|
|
* Unregister a miscellaneous device that was previously
|
|
* successfully registered with misc_register().
|
|
*/
|
|
|
|
void misc_deregister(struct miscdevice *misc)
|
|
{
|
|
if (WARN_ON(list_empty(&misc->list)))
|
|
return;
|
|
|
|
mutex_lock(&misc_mtx);
|
|
list_del_init(&misc->list);
|
|
device_destroy(&misc_class, MKDEV(MISC_MAJOR, misc->minor));
|
|
misc_minor_free(misc->minor);
|
|
if (misc->minor > MISC_DYNAMIC_MINOR)
|
|
misc->minor = MISC_DYNAMIC_MINOR;
|
|
mutex_unlock(&misc_mtx);
|
|
}
|
|
EXPORT_SYMBOL(misc_deregister);
|
|
|
|
static int __init misc_init(void)
|
|
{
|
|
int err;
|
|
struct proc_dir_entry *ret;
|
|
|
|
ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);
|
|
err = class_register(&misc_class);
|
|
if (err)
|
|
goto fail_remove;
|
|
|
|
err = __register_chrdev(MISC_MAJOR, 0, MINORMASK + 1, "misc", &misc_fops);
|
|
if (err < 0)
|
|
goto fail_printk;
|
|
return 0;
|
|
|
|
fail_printk:
|
|
pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
|
|
class_unregister(&misc_class);
|
|
fail_remove:
|
|
if (ret)
|
|
remove_proc_entry("misc", NULL);
|
|
return err;
|
|
}
|
|
subsys_initcall(misc_init);
|