summaryrefslogtreecommitdiff
path: root/drivers/char/misc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/misc.c')
-rw-r--r--drivers/char/misc.c142
1 files changed, 85 insertions, 57 deletions
diff --git a/drivers/char/misc.c b/drivers/char/misc.c
index ca5141ed5ef3..726516fb0a3b 100644
--- a/drivers/char/misc.c
+++ b/drivers/char/misc.c
@@ -58,10 +58,28 @@ static LIST_HEAD(misc_list);
static DEFINE_MUTEX(misc_mtx);
/*
- * Assigned numbers, used for dynamic minors
+ * Assigned numbers.
*/
-#define DYNAMIC_MINORS 128 /* like dynamic majors */
-static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);
+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)
@@ -100,34 +118,38 @@ static const struct seq_operations misc_seq_ops = {
static int misc_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
- struct miscdevice *c;
+ struct miscdevice *c = NULL, *iter;
int err = -ENODEV;
const struct file_operations *new_fops = NULL;
mutex_lock(&misc_mtx);
- list_for_each_entry(c, &misc_list, list) {
- if (c->minor == minor) {
- new_fops = fops_get(c->fops);
- break;
- }
+ 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) {
+ /* 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(c, &misc_list, list) {
- if (c->minor == minor) {
- new_fops = fops_get(c->fops);
- break;
- }
+ 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;
}
+ if (!new_fops)
+ goto fail;
+
/*
* Place the miscdevice in the file's
* private_data so it can be used by the
@@ -144,7 +166,21 @@ fail:
return err;
}
-static struct class *misc_class;
+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,
@@ -176,21 +212,27 @@ int misc_register(struct miscdevice *misc)
int err = 0;
bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
+ if (misc->minor > MISC_DYNAMIC_MINOR) {
+ pr_err("Invalid fixed minor %d for miscdevice '%s'\n",
+ misc->minor, misc->name);
+ return -EINVAL;
+ }
+
INIT_LIST_HEAD(&misc->list);
mutex_lock(&misc_mtx);
if (is_dynamic) {
- int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
+ int i = misc_minor_alloc(misc->minor);
- if (i >= DYNAMIC_MINORS) {
+ if (i < 0) {
err = -EBUSY;
goto out;
}
- misc->minor = DYNAMIC_MINORS - i - 1;
- set_bit(i, misc_minors);
+ misc->minor = i;
} else {
struct miscdevice *c;
+ int i;
list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {
@@ -198,19 +240,22 @@ int misc_register(struct miscdevice *misc)
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,
+ 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) {
- int i = DYNAMIC_MINORS - misc->minor - 1;
-
- if (i < DYNAMIC_MINORS && i >= 0)
- clear_bit(i, misc_minors);
misc->minor = MISC_DYNAMIC_MINOR;
}
err = PTR_ERR(misc->this_device);
@@ -238,53 +283,36 @@ EXPORT_SYMBOL(misc_register);
void misc_deregister(struct miscdevice *misc)
{
- int i = DYNAMIC_MINORS - misc->minor - 1;
-
- if (WARN_ON(list_empty(&misc->list)))
- return;
-
mutex_lock(&misc_mtx);
- list_del(&misc->list);
- device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
- if (i < DYNAMIC_MINORS && i >= 0)
- clear_bit(i, misc_minors);
+ 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 char *misc_devnode(struct device *dev, umode_t *mode)
-{
- 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 int __init misc_init(void)
{
int err;
- struct proc_dir_entry *ret;
+ struct proc_dir_entry *misc_proc_file;
- ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);
- misc_class = class_create(THIS_MODULE, "misc");
- err = PTR_ERR(misc_class);
- if (IS_ERR(misc_class))
+ misc_proc_file = proc_create_seq("misc", 0, NULL, &misc_seq_ops);
+ err = class_register(&misc_class);
+ if (err)
goto fail_remove;
- err = -EIO;
- if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
+ err = __register_chrdev(MISC_MAJOR, 0, MINORMASK + 1, "misc", &misc_fops);
+ if (err < 0)
goto fail_printk;
- misc_class->devnode = misc_devnode;
return 0;
fail_printk:
pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
- class_destroy(misc_class);
+ class_unregister(&misc_class);
fail_remove:
- if (ret)
+ if (misc_proc_file)
remove_proc_entry("misc", NULL);
return err;
}