staging: comedi: remove comedi_clear_subdevice_minor()
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / comedi_fops.c
index 6bcbb52510ef9e2b82ffd8c6cb1b329e40e78ae2..64721f7f089be06ded2f352d940a802b41fe9c1f 100644 (file)
 
 #include "comedi_internal.h"
 
+#define COMEDI_NUM_MINORS 0x100
+#define COMEDI_NUM_SUBDEVICE_MINORS    \
+       (COMEDI_NUM_MINORS - COMEDI_NUM_BOARD_MINORS)
+
 #ifdef CONFIG_COMEDI_DEBUG
 int comedi_debug;
 EXPORT_SYMBOL(comedi_debug);
@@ -81,20 +85,96 @@ struct comedi_file_info {
        struct comedi_device *device;
        struct comedi_subdevice *read_subdevice;
        struct comedi_subdevice *write_subdevice;
-       struct device *hardware_device;
 };
 
-static DEFINE_SPINLOCK(comedi_file_info_table_lock);
-static struct comedi_file_info *comedi_file_info_table[COMEDI_NUM_MINORS];
+static DEFINE_MUTEX(comedi_board_minor_table_lock);
+static struct comedi_file_info
+*comedi_board_minor_table[COMEDI_NUM_BOARD_MINORS];
+
+static DEFINE_MUTEX(comedi_subdevice_minor_table_lock);
+/* Note: indexed by minor - COMEDI_NUM_BOARD_MINORS. */
+static struct comedi_file_info
+*comedi_subdevice_minor_table[COMEDI_NUM_SUBDEVICE_MINORS];
+
+static struct class *comedi_class;
+static struct cdev comedi_cdev;
+
+static void comedi_device_init(struct comedi_device *dev)
+{
+       spin_lock_init(&dev->spinlock);
+       mutex_init(&dev->mutex);
+       dev->minor = -1;
+}
+
+static void comedi_device_cleanup(struct comedi_device *dev)
+{
+       struct module *driver_module = NULL;
+
+       if (dev == NULL)
+               return;
+       mutex_lock(&dev->mutex);
+       if (dev->attached)
+               driver_module = dev->driver->module;
+       comedi_device_detach(dev);
+       while (dev->use_count > 0) {
+               if (driver_module)
+                       module_put(driver_module);
+               module_put(THIS_MODULE);
+               dev->use_count--;
+       }
+       mutex_unlock(&dev->mutex);
+       mutex_destroy(&dev->mutex);
+}
+
+static struct comedi_file_info *comedi_clear_board_minor(unsigned minor)
+{
+       struct comedi_file_info *info;
+
+       mutex_lock(&comedi_board_minor_table_lock);
+       info = comedi_board_minor_table[minor];
+       comedi_board_minor_table[minor] = NULL;
+       mutex_unlock(&comedi_board_minor_table_lock);
+       return info;
+}
+
+static void comedi_free_board_file_info(struct comedi_file_info *info)
+{
+       if (info) {
+               struct comedi_device *dev = info->device;
+               if (dev) {
+                       if (dev->class_dev) {
+                               device_destroy(comedi_class,
+                                              MKDEV(COMEDI_MAJOR, dev->minor));
+                       }
+                       comedi_device_cleanup(dev);
+                       kfree(dev);
+               }
+               kfree(info);
+       }
+}
 
-static struct comedi_file_info *comedi_file_info_from_minor(unsigned minor)
+static struct comedi_file_info
+*comedi_file_info_from_board_minor(unsigned minor)
 {
        struct comedi_file_info *info;
 
-       BUG_ON(minor >= COMEDI_NUM_MINORS);
-       spin_lock(&comedi_file_info_table_lock);
-       info = comedi_file_info_table[minor];
-       spin_unlock(&comedi_file_info_table_lock);
+       BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS);
+       mutex_lock(&comedi_board_minor_table_lock);
+       info = comedi_board_minor_table[minor];
+       mutex_unlock(&comedi_board_minor_table_lock);
+       return info;
+}
+
+static struct comedi_file_info
+*comedi_file_info_from_subdevice_minor(unsigned minor)
+{
+       struct comedi_file_info *info;
+       unsigned int i = minor - COMEDI_NUM_BOARD_MINORS;
+
+       BUG_ON(i >= COMEDI_NUM_SUBDEVICE_MINORS);
+       mutex_lock(&comedi_subdevice_minor_table_lock);
+       info = comedi_subdevice_minor_table[i];
+       mutex_unlock(&comedi_subdevice_minor_table_lock);
        return info;
 }
 
@@ -104,30 +184,59 @@ comedi_dev_from_file_info(struct comedi_file_info *info)
        return info ? info->device : NULL;
 }
 
+static struct comedi_device *comedi_dev_from_board_minor(unsigned minor)
+{
+       struct comedi_file_info *info;
+
+       info = comedi_file_info_from_board_minor(minor);
+       return comedi_dev_from_file_info(info);
+}
+
+static struct comedi_device *comedi_dev_from_subdevice_minor(unsigned minor)
+{
+       struct comedi_file_info *info;
+
+       info = comedi_file_info_from_subdevice_minor(minor);
+       return comedi_dev_from_file_info(info);
+}
+
 struct comedi_device *comedi_dev_from_minor(unsigned minor)
 {
-       return comedi_dev_from_file_info(comedi_file_info_from_minor(minor));
+       if (minor < COMEDI_NUM_BOARD_MINORS)
+               return comedi_dev_from_board_minor(minor);
+       else
+               return comedi_dev_from_subdevice_minor(minor);
 }
 EXPORT_SYMBOL_GPL(comedi_dev_from_minor);
 
 static struct comedi_subdevice *
-comedi_read_subdevice(const struct comedi_file_info *info)
+comedi_read_subdevice(const struct comedi_device *dev, unsigned int minor)
 {
-       if (info->read_subdevice)
-               return info->read_subdevice;
-       if (info->device)
-               return info->device->read_subdev;
-       return NULL;
+       struct comedi_file_info *info;
+
+       if (minor >= COMEDI_NUM_BOARD_MINORS) {
+               info = comedi_file_info_from_subdevice_minor(minor);
+               if (!info || info->device != dev)
+                       return NULL;
+               if (info->read_subdevice)
+                       return info->read_subdevice;
+       }
+       return dev->read_subdev;
 }
 
 static struct comedi_subdevice *
-comedi_write_subdevice(const struct comedi_file_info *info)
+comedi_write_subdevice(const struct comedi_device *dev, unsigned int minor)
 {
-       if (info->write_subdevice)
-               return info->write_subdevice;
-       if (info->device)
-               return info->device->write_subdev;
-       return NULL;
+       struct comedi_file_info *info;
+
+       if (minor >= COMEDI_NUM_BOARD_MINORS) {
+               info = comedi_file_info_from_subdevice_minor(minor);
+               if (!info || info->device != dev)
+                       return NULL;
+               if (info->write_subdevice)
+                       return info->write_subdevice;
+       }
+       return dev->write_subdev;
 }
 
 static int resize_async_buffer(struct comedi_device *dev,
@@ -172,27 +281,34 @@ static int resize_async_buffer(struct comedi_device *dev,
 
 /* sysfs attribute files */
 
-static ssize_t show_max_read_buffer_kb(struct device *dev,
+static ssize_t show_max_read_buffer_kb(struct device *csdev,
                                       struct device_attribute *attr, char *buf)
 {
-       struct comedi_file_info *info = dev_get_drvdata(dev);
-       struct comedi_subdevice *s = comedi_read_subdevice(info);
+       unsigned int minor = MINOR(csdev->devt);
+       struct comedi_device *dev;
+       struct comedi_subdevice *s;
        unsigned int size = 0;
 
-       mutex_lock(&info->device->mutex);
+       dev = comedi_dev_from_minor(minor);
+       if (!dev)
+               return -ENODEV;
+
+       mutex_lock(&dev->mutex);
+       s = comedi_read_subdevice(dev, minor);
        if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
                size = s->async->max_bufsize / 1024;
-       mutex_unlock(&info->device->mutex);
+       mutex_unlock(&dev->mutex);
 
        return snprintf(buf, PAGE_SIZE, "%i\n", size);
 }
 
-static ssize_t store_max_read_buffer_kb(struct device *dev,
+static ssize_t store_max_read_buffer_kb(struct device *csdev,
                                        struct device_attribute *attr,
                                        const char *buf, size_t count)
 {
-       struct comedi_file_info *info = dev_get_drvdata(dev);
-       struct comedi_subdevice *s = comedi_read_subdevice(info);
+       unsigned int minor = MINOR(csdev->devt);
+       struct comedi_device *dev;
+       struct comedi_subdevice *s;
        unsigned int size;
        int err;
 
@@ -203,37 +319,49 @@ static ssize_t store_max_read_buffer_kb(struct device *dev,
                return -EINVAL;
        size *= 1024;
 
-       mutex_lock(&info->device->mutex);
+       dev = comedi_dev_from_minor(minor);
+       if (!dev)
+               return -ENODEV;
+
+       mutex_lock(&dev->mutex);
+       s = comedi_read_subdevice(dev, minor);
        if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
                s->async->max_bufsize = size;
        else
                err = -EINVAL;
-       mutex_unlock(&info->device->mutex);
+       mutex_unlock(&dev->mutex);
 
        return err ? err : count;
 }
 
-static ssize_t show_read_buffer_kb(struct device *dev,
+static ssize_t show_read_buffer_kb(struct device *csdev,
                                   struct device_attribute *attr, char *buf)
 {
-       struct comedi_file_info *info = dev_get_drvdata(dev);
-       struct comedi_subdevice *s = comedi_read_subdevice(info);
+       unsigned int minor = MINOR(csdev->devt);
+       struct comedi_device *dev;
+       struct comedi_subdevice *s;
        unsigned int size = 0;
 
-       mutex_lock(&info->device->mutex);
+       dev = comedi_dev_from_minor(minor);
+       if (!dev)
+               return -ENODEV;
+
+       mutex_lock(&dev->mutex);
+       s = comedi_read_subdevice(dev, minor);
        if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
                size = s->async->prealloc_bufsz / 1024;
-       mutex_unlock(&info->device->mutex);
+       mutex_unlock(&dev->mutex);
 
        return snprintf(buf, PAGE_SIZE, "%i\n", size);
 }
 
-static ssize_t store_read_buffer_kb(struct device *dev,
+static ssize_t store_read_buffer_kb(struct device *csdev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
 {
-       struct comedi_file_info *info = dev_get_drvdata(dev);
-       struct comedi_subdevice *s = comedi_read_subdevice(info);
+       unsigned int minor = MINOR(csdev->devt);
+       struct comedi_device *dev;
+       struct comedi_subdevice *s;
        unsigned int size;
        int err;
 
@@ -244,38 +372,50 @@ static ssize_t store_read_buffer_kb(struct device *dev,
                return -EINVAL;
        size *= 1024;
 
-       mutex_lock(&info->device->mutex);
+       dev = comedi_dev_from_minor(minor);
+       if (!dev)
+               return -ENODEV;
+
+       mutex_lock(&dev->mutex);
+       s = comedi_read_subdevice(dev, minor);
        if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
-               err = resize_async_buffer(info->device, s, s->async, size);
+               err = resize_async_buffer(dev, s, s->async, size);
        else
                err = -EINVAL;
-       mutex_unlock(&info->device->mutex);
+       mutex_unlock(&dev->mutex);
 
        return err ? err : count;
 }
 
-static ssize_t show_max_write_buffer_kb(struct device *dev,
+static ssize_t show_max_write_buffer_kb(struct device *csdev,
                                        struct device_attribute *attr,
                                        char *buf)
 {
-       struct comedi_file_info *info = dev_get_drvdata(dev);
-       struct comedi_subdevice *s = comedi_write_subdevice(info);
+       unsigned int minor = MINOR(csdev->devt);
+       struct comedi_device *dev;
+       struct comedi_subdevice *s;
        unsigned int size = 0;
 
-       mutex_lock(&info->device->mutex);
+       dev = comedi_dev_from_minor(minor);
+       if (!dev)
+               return -ENODEV;
+
+       mutex_lock(&dev->mutex);
+       s = comedi_write_subdevice(dev, minor);
        if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
                size = s->async->max_bufsize / 1024;
-       mutex_unlock(&info->device->mutex);
+       mutex_unlock(&dev->mutex);
 
        return snprintf(buf, PAGE_SIZE, "%i\n", size);
 }
 
-static ssize_t store_max_write_buffer_kb(struct device *dev,
+static ssize_t store_max_write_buffer_kb(struct device *csdev,
                                         struct device_attribute *attr,
                                         const char *buf, size_t count)
 {
-       struct comedi_file_info *info = dev_get_drvdata(dev);
-       struct comedi_subdevice *s = comedi_write_subdevice(info);
+       unsigned int minor = MINOR(csdev->devt);
+       struct comedi_device *dev;
+       struct comedi_subdevice *s;
        unsigned int size;
        int err;
 
@@ -286,37 +426,49 @@ static ssize_t store_max_write_buffer_kb(struct device *dev,
                return -EINVAL;
        size *= 1024;
 
-       mutex_lock(&info->device->mutex);
+       dev = comedi_dev_from_minor(minor);
+       if (!dev)
+               return -ENODEV;
+
+       mutex_lock(&dev->mutex);
+       s = comedi_write_subdevice(dev, minor);
        if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
                s->async->max_bufsize = size;
        else
                err = -EINVAL;
-       mutex_unlock(&info->device->mutex);
+       mutex_unlock(&dev->mutex);
 
        return err ? err : count;
 }
 
-static ssize_t show_write_buffer_kb(struct device *dev,
+static ssize_t show_write_buffer_kb(struct device *csdev,
                                    struct device_attribute *attr, char *buf)
 {
-       struct comedi_file_info *info = dev_get_drvdata(dev);
-       struct comedi_subdevice *s = comedi_write_subdevice(info);
+       unsigned int minor = MINOR(csdev->devt);
+       struct comedi_device *dev;
+       struct comedi_subdevice *s;
        unsigned int size = 0;
 
-       mutex_lock(&info->device->mutex);
+       dev = comedi_dev_from_minor(minor);
+       if (!dev)
+               return -ENODEV;
+
+       mutex_lock(&dev->mutex);
+       s = comedi_write_subdevice(dev, minor);
        if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
                size = s->async->prealloc_bufsz / 1024;
-       mutex_unlock(&info->device->mutex);
+       mutex_unlock(&dev->mutex);
 
        return snprintf(buf, PAGE_SIZE, "%i\n", size);
 }
 
-static ssize_t store_write_buffer_kb(struct device *dev,
+static ssize_t store_write_buffer_kb(struct device *csdev,
                                     struct device_attribute *attr,
                                     const char *buf, size_t count)
 {
-       struct comedi_file_info *info = dev_get_drvdata(dev);
-       struct comedi_subdevice *s = comedi_write_subdevice(info);
+       unsigned int minor = MINOR(csdev->devt);
+       struct comedi_device *dev;
+       struct comedi_subdevice *s;
        unsigned int size;
        int err;
 
@@ -327,12 +479,17 @@ static ssize_t store_write_buffer_kb(struct device *dev,
                return -EINVAL;
        size *= 1024;
 
-       mutex_lock(&info->device->mutex);
+       dev = comedi_dev_from_minor(minor);
+       if (!dev)
+               return -ENODEV;
+
+       mutex_lock(&dev->mutex);
+       s = comedi_write_subdevice(dev, minor);
        if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
-               err = resize_async_buffer(info->device, s, s->async, size);
+               err = resize_async_buffer(dev, s, s->async, size);
        else
                err = -EINVAL;
-       mutex_unlock(&info->device->mutex);
+       mutex_unlock(&dev->mutex);
 
        return err ? err : count;
 }
@@ -463,7 +620,6 @@ static int do_devconfig_ioctl(struct comedi_device *dev,
                              struct comedi_devconfig __user *arg)
 {
        struct comedi_devconfig it;
-       int ret;
 
        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;
@@ -490,15 +646,12 @@ static int do_devconfig_ioctl(struct comedi_device *dev,
                return -EINVAL;
        }
 
-       ret = comedi_device_attach(dev, &it);
-       if (ret == 0) {
-               if (!try_module_get(dev->driver->module)) {
-                       comedi_device_detach(dev);
-                       ret = -ENOSYS;
-               }
-       }
+       if (dev->minor >= comedi_num_legacy_minors)
+               /* don't re-use dynamically allocated comedi devices */
+               return -EBUSY;
 
-       return ret;
+       /* This increments the driver module count on success. */
+       return comedi_device_attach(dev, &it);
 }
 
 /*
@@ -581,7 +734,6 @@ static int do_devinfo_ioctl(struct comedi_device *dev,
                            struct file *file)
 {
        const unsigned minor = iminor(file_inode(file));
-       struct comedi_file_info *info = comedi_file_info_from_minor(minor);
        struct comedi_subdevice *s;
        struct comedi_devinfo devinfo;
 
@@ -593,13 +745,13 @@ static int do_devinfo_ioctl(struct comedi_device *dev,
        strlcpy(devinfo.driver_name, dev->driver->driver_name, COMEDI_NAMELEN);
        strlcpy(devinfo.board_name, dev->board_name, COMEDI_NAMELEN);
 
-       s = comedi_read_subdevice(info);
+       s = comedi_read_subdevice(dev, minor);
        if (s)
                devinfo.read_subdevice = s->index;
        else
                devinfo.read_subdevice = -1;
 
-       s = comedi_write_subdevice(info);
+       s = comedi_write_subdevice(dev, minor);
        if (s)
                devinfo.write_subdevice = s->index;
        else
@@ -1616,8 +1768,7 @@ static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd,
                                  unsigned long arg)
 {
        const unsigned minor = iminor(file_inode(file));
-       struct comedi_file_info *info = comedi_file_info_from_minor(minor);
-       struct comedi_device *dev = comedi_dev_from_file_info(info);
+       struct comedi_device *dev = comedi_dev_from_minor(minor);
        int rc;
 
        if (!dev)
@@ -1635,9 +1786,20 @@ static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd,
                }
                rc = do_devconfig_ioctl(dev,
                                        (struct comedi_devconfig __user *)arg);
-               if (rc == 0)
-                       /* Evade comedi_auto_unconfig(). */
-                       info->hardware_device = NULL;
+               if (rc == 0) {
+                       if (arg == 0 &&
+                           dev->minor >= comedi_num_legacy_minors) {
+                               /* Successfully unconfigured a dynamically
+                                * allocated device.  Try and remove it. */
+                               struct comedi_file_info *info;
+                               info = comedi_clear_board_minor(dev->minor);
+                               if (info) {
+                                       mutex_unlock(&dev->mutex);
+                                       comedi_free_board_file_info(info);
+                                       return rc;
+                               }
+                       }
+               }
                goto done;
        }
 
@@ -1744,8 +1906,7 @@ static struct vm_operations_struct comedi_vm_ops = {
 static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
 {
        const unsigned minor = iminor(file_inode(file));
-       struct comedi_file_info *info = comedi_file_info_from_minor(minor);
-       struct comedi_device *dev = comedi_dev_from_file_info(info);
+       struct comedi_device *dev = comedi_dev_from_minor(minor);
        struct comedi_subdevice *s;
        struct comedi_async *async;
        unsigned long start = vma->vm_start;
@@ -1766,9 +1927,9 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
        }
 
        if (vma->vm_flags & VM_WRITE)
-               s = comedi_write_subdevice(info);
+               s = comedi_write_subdevice(dev, minor);
        else
-               s = comedi_read_subdevice(info);
+               s = comedi_read_subdevice(dev, minor);
        if (!s) {
                retval = -EINVAL;
                goto done;
@@ -1824,8 +1985,7 @@ static unsigned int comedi_poll(struct file *file, poll_table *wait)
 {
        unsigned int mask = 0;
        const unsigned minor = iminor(file_inode(file));
-       struct comedi_file_info *info = comedi_file_info_from_minor(minor);
-       struct comedi_device *dev = comedi_dev_from_file_info(info);
+       struct comedi_device *dev = comedi_dev_from_minor(minor);
        struct comedi_subdevice *s;
 
        if (!dev)
@@ -1838,7 +1998,7 @@ static unsigned int comedi_poll(struct file *file, poll_table *wait)
                goto done;
        }
 
-       s = comedi_read_subdevice(info);
+       s = comedi_read_subdevice(dev, minor);
        if (s && s->async) {
                poll_wait(file, &s->async->wait_head, wait);
                if (!s->busy || !comedi_is_subdevice_running(s) ||
@@ -1846,7 +2006,7 @@ static unsigned int comedi_poll(struct file *file, poll_table *wait)
                        mask |= POLLIN | POLLRDNORM;
        }
 
-       s = comedi_write_subdevice(info);
+       s = comedi_write_subdevice(dev, minor);
        if (s && s->async) {
                unsigned int bps = bytes_per_sample(s->async->subdevice);
 
@@ -1870,8 +2030,7 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
        int n, m, count = 0, retval = 0;
        DECLARE_WAITQUEUE(wait, current);
        const unsigned minor = iminor(file_inode(file));
-       struct comedi_file_info *info = comedi_file_info_from_minor(minor);
-       struct comedi_device *dev = comedi_dev_from_file_info(info);
+       struct comedi_device *dev = comedi_dev_from_minor(minor);
 
        if (!dev)
                return -ENODEV;
@@ -1881,7 +2040,7 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
                return -ENODEV;
        }
 
-       s = comedi_write_subdevice(info);
+       s = comedi_write_subdevice(dev, minor);
        if (!s || !s->async)
                return -EIO;
 
@@ -1965,8 +2124,7 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
        int n, m, count = 0, retval = 0;
        DECLARE_WAITQUEUE(wait, current);
        const unsigned minor = iminor(file_inode(file));
-       struct comedi_file_info *info = comedi_file_info_from_minor(minor);
-       struct comedi_device *dev = comedi_dev_from_file_info(info);
+       struct comedi_device *dev = comedi_dev_from_minor(minor);
 
        if (!dev)
                return -ENODEV;
@@ -1976,7 +2134,7 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
                return -ENODEV;
        }
 
-       s = comedi_read_subdevice(info);
+       s = comedi_read_subdevice(dev, minor);
        if (!s || !s->async)
                return -EIO;
 
@@ -2195,9 +2353,6 @@ static const struct file_operations comedi_fops = {
        .llseek = noop_llseek,
 };
 
-static struct class *comedi_class;
-static struct cdev comedi_cdev;
-
 void comedi_error(const struct comedi_device *dev, const char *s)
 {
        dev_err(dev->class_dev, "%s: %s\n", dev->driver->driver_name, s);
@@ -2247,104 +2402,76 @@ void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s)
 }
 EXPORT_SYMBOL(comedi_event);
 
-static void comedi_device_init(struct comedi_device *dev)
-{
-       memset(dev, 0, sizeof(*dev));
-       spin_lock_init(&dev->spinlock);
-       mutex_init(&dev->mutex);
-       dev->minor = -1;
-}
-
-static void comedi_device_cleanup(struct comedi_device *dev)
-{
-       if (dev == NULL)
-               return;
-       mutex_lock(&dev->mutex);
-       comedi_device_detach(dev);
-       mutex_unlock(&dev->mutex);
-       mutex_destroy(&dev->mutex);
-}
-
-int comedi_alloc_board_minor(struct device *hardware_device)
+/* Note: the ->mutex is pre-locked on successful return */
+struct comedi_device *comedi_alloc_board_minor(struct device *hardware_device)
 {
        struct comedi_file_info *info;
+       struct comedi_device *dev;
        struct device *csdev;
        unsigned i;
 
        info = kzalloc(sizeof(*info), GFP_KERNEL);
        if (info == NULL)
-               return -ENOMEM;
-       info->device = kzalloc(sizeof(struct comedi_device), GFP_KERNEL);
-       if (info->device == NULL) {
+               return ERR_PTR(-ENOMEM);
+       dev = kzalloc(sizeof(struct comedi_device), GFP_KERNEL);
+       if (dev == NULL) {
                kfree(info);
-               return -ENOMEM;
+               return ERR_PTR(-ENOMEM);
        }
-       info->hardware_device = hardware_device;
-       comedi_device_init(info->device);
-       spin_lock(&comedi_file_info_table_lock);
-       for (i = 0; i < COMEDI_NUM_BOARD_MINORS; ++i) {
-               if (comedi_file_info_table[i] == NULL) {
-                       comedi_file_info_table[i] = info;
+       info->device = dev;
+       comedi_device_init(dev);
+       comedi_set_hw_dev(dev, hardware_device);
+       mutex_lock(&dev->mutex);
+       mutex_lock(&comedi_board_minor_table_lock);
+       for (i = hardware_device ? comedi_num_legacy_minors : 0;
+            i < COMEDI_NUM_BOARD_MINORS; ++i) {
+               if (comedi_board_minor_table[i] == NULL) {
+                       comedi_board_minor_table[i] = info;
                        break;
                }
        }
-       spin_unlock(&comedi_file_info_table_lock);
+       mutex_unlock(&comedi_board_minor_table_lock);
        if (i == COMEDI_NUM_BOARD_MINORS) {
-               comedi_device_cleanup(info->device);
-               kfree(info->device);
+               mutex_unlock(&dev->mutex);
+               comedi_device_cleanup(dev);
+               kfree(dev);
                kfree(info);
                pr_err("comedi: error: ran out of minor numbers for board device files.\n");
-               return -EBUSY;
+               return ERR_PTR(-EBUSY);
        }
-       info->device->minor = i;
+       dev->minor = i;
        csdev = device_create(comedi_class, hardware_device,
                              MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i", i);
        if (!IS_ERR(csdev))
-               info->device->class_dev = csdev;
-       dev_set_drvdata(csdev, info);
+               dev->class_dev = csdev;
 
-       return i;
+       /* Note: dev->mutex needs to be unlocked by the caller. */
+       return dev;
 }
 
-void comedi_free_board_minor(unsigned minor)
+static void comedi_free_board_minor(unsigned minor)
 {
-       struct comedi_file_info *info;
-
        BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS);
-       spin_lock(&comedi_file_info_table_lock);
-       info = comedi_file_info_table[minor];
-       comedi_file_info_table[minor] = NULL;
-       spin_unlock(&comedi_file_info_table_lock);
-
-       if (info) {
-               struct comedi_device *dev = info->device;
-               if (dev) {
-                       if (dev->class_dev) {
-                               device_destroy(comedi_class,
-                                              MKDEV(COMEDI_MAJOR, dev->minor));
-                       }
-                       comedi_device_cleanup(dev);
-                       kfree(dev);
-               }
-               kfree(info);
-       }
+       comedi_free_board_file_info(comedi_clear_board_minor(minor));
 }
 
-int comedi_find_board_minor(struct device *hardware_device)
+void comedi_release_hardware_device(struct device *hardware_device)
 {
        int minor;
        struct comedi_file_info *info;
 
-       for (minor = 0; minor < COMEDI_NUM_BOARD_MINORS; minor++) {
-               spin_lock(&comedi_file_info_table_lock);
-               info = comedi_file_info_table[minor];
-               if (info && info->hardware_device == hardware_device) {
-                       spin_unlock(&comedi_file_info_table_lock);
-                       return minor;
+       for (minor = comedi_num_legacy_minors; minor < COMEDI_NUM_BOARD_MINORS;
+            minor++) {
+               mutex_lock(&comedi_board_minor_table_lock);
+               info = comedi_board_minor_table[minor];
+               if (info && info->device->hw_dev == hardware_device) {
+                       comedi_board_minor_table[minor] = NULL;
+                       mutex_unlock(&comedi_board_minor_table_lock);
+                       comedi_free_board_file_info(info);
+                       break;
                }
-               spin_unlock(&comedi_file_info_table_lock);
+               mutex_unlock(&comedi_board_minor_table_lock);
        }
-       return -ENODEV;
 }
 
 int comedi_alloc_subdevice_minor(struct comedi_subdevice *s)
@@ -2362,26 +2489,26 @@ int comedi_alloc_subdevice_minor(struct comedi_subdevice *s)
                info->read_subdevice = s;
        if (s->subdev_flags & SDF_CMD_WRITE)
                info->write_subdevice = s;
-       spin_lock(&comedi_file_info_table_lock);
-       for (i = COMEDI_FIRST_SUBDEVICE_MINOR; i < COMEDI_NUM_MINORS; ++i) {
-               if (comedi_file_info_table[i] == NULL) {
-                       comedi_file_info_table[i] = info;
+       mutex_lock(&comedi_subdevice_minor_table_lock);
+       for (i = 0; i < COMEDI_NUM_SUBDEVICE_MINORS; ++i) {
+               if (comedi_subdevice_minor_table[i] == NULL) {
+                       comedi_subdevice_minor_table[i] = info;
                        break;
                }
        }
-       spin_unlock(&comedi_file_info_table_lock);
-       if (i == COMEDI_NUM_MINORS) {
+       mutex_unlock(&comedi_subdevice_minor_table_lock);
+       if (i == COMEDI_NUM_SUBDEVICE_MINORS) {
                kfree(info);
                pr_err("comedi: error: ran out of minor numbers for subdevice files.\n");
                return -EBUSY;
        }
+       i += COMEDI_NUM_BOARD_MINORS;
        s->minor = i;
        csdev = device_create(comedi_class, dev->class_dev,
                              MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i_subd%i",
                              dev->minor, s->index);
        if (!IS_ERR(csdev))
                s->class_dev = csdev;
-       dev_set_drvdata(csdev, info);
 
        return 0;
 }
@@ -2389,6 +2516,7 @@ int comedi_alloc_subdevice_minor(struct comedi_subdevice *s)
 void comedi_free_subdevice_minor(struct comedi_subdevice *s)
 {
        struct comedi_file_info *info;
+       unsigned int i;
 
        if (s == NULL)
                return;
@@ -2396,13 +2524,13 @@ void comedi_free_subdevice_minor(struct comedi_subdevice *s)
                return;
 
        BUG_ON(s->minor >= COMEDI_NUM_MINORS);
-       BUG_ON(s->minor < COMEDI_FIRST_SUBDEVICE_MINOR);
-
-       spin_lock(&comedi_file_info_table_lock);
-       info = comedi_file_info_table[s->minor];
-       comedi_file_info_table[s->minor] = NULL;
-       spin_unlock(&comedi_file_info_table_lock);
+       BUG_ON(s->minor < COMEDI_NUM_BOARD_MINORS);
 
+       i = s->minor - COMEDI_NUM_BOARD_MINORS;
+       mutex_lock(&comedi_subdevice_minor_table_lock);
+       info = comedi_subdevice_minor_table[i];
+       comedi_subdevice_minor_table[i] = NULL;
+       mutex_unlock(&comedi_subdevice_minor_table_lock);
        if (s->class_dev) {
                device_destroy(comedi_class, MKDEV(COMEDI_MAJOR, s->minor));
                s->class_dev = NULL;
@@ -2432,9 +2560,6 @@ static int __init comedi_init(void)
                return -EINVAL;
        }
 
-       memset(comedi_file_info_table, 0,
-              sizeof(struct comedi_file_info *) * COMEDI_NUM_MINORS);
-
        retval = register_chrdev_region(MKDEV(COMEDI_MAJOR, 0),
                                        COMEDI_NUM_MINORS, "comedi");
        if (retval)
@@ -2463,14 +2588,17 @@ static int __init comedi_init(void)
 
        /* create devices files for legacy/manual use */
        for (i = 0; i < comedi_num_legacy_minors; i++) {
-               int minor;
-               minor = comedi_alloc_board_minor(NULL);
-               if (minor < 0) {
+               struct comedi_device *dev;
+               dev = comedi_alloc_board_minor(NULL);
+               if (IS_ERR(dev)) {
                        comedi_cleanup_board_minors();
                        cdev_del(&comedi_cdev);
                        unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0),
                                                 COMEDI_NUM_MINORS);
-                       return minor;
+                       return PTR_ERR(dev);
+               } else {
+                       /* comedi_alloc_board_minor() locked the mutex */
+                       mutex_unlock(&dev->mutex);
                }
        }
 
@@ -2483,8 +2611,10 @@ static void __exit comedi_cleanup(void)
        int i;
 
        comedi_cleanup_board_minors();
-       for (i = 0; i < COMEDI_NUM_MINORS; ++i)
-               BUG_ON(comedi_file_info_table[i]);
+       for (i = 0; i < COMEDI_NUM_BOARD_MINORS; ++i)
+               BUG_ON(comedi_board_minor_table[i]);
+       for (i = 0; i < COMEDI_NUM_SUBDEVICE_MINORS; ++i)
+               BUG_ON(comedi_subdevice_minor_table[i]);
 
        class_destroy(comedi_class);
        cdev_del(&comedi_cdev);