staging: comedi: use refcount in sysfs attribute handlers
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / comedi_fops.c
index eafa18efdeeaeb645aa186e1e9885ae060e1c326..37400e85c41709b8642f8c919d36e67e766cd78b 100644 (file)
@@ -89,12 +89,37 @@ static struct cdev comedi_cdev;
 
 static void comedi_device_init(struct comedi_device *dev)
 {
+       kref_init(&dev->refcount);
        spin_lock_init(&dev->spinlock);
        mutex_init(&dev->mutex);
        init_rwsem(&dev->attach_lock);
        dev->minor = -1;
 }
 
+static void comedi_dev_kref_release(struct kref *kref)
+{
+       struct comedi_device *dev =
+               container_of(kref, struct comedi_device, refcount);
+
+       mutex_destroy(&dev->mutex);
+       kfree(dev);
+}
+
+int comedi_dev_put(struct comedi_device *dev)
+{
+       if (dev)
+               return kref_put(&dev->refcount, comedi_dev_kref_release);
+       return 1;
+}
+EXPORT_SYMBOL_GPL(comedi_dev_put);
+
+static struct comedi_device *comedi_dev_get(struct comedi_device *dev)
+{
+       if (dev)
+               kref_get(&dev->refcount);
+       return dev;
+}
+
 static void comedi_device_cleanup(struct comedi_device *dev)
 {
        struct module *driver_module = NULL;
@@ -112,7 +137,6 @@ static void comedi_device_cleanup(struct comedi_device *dev)
                dev->use_count--;
        }
        mutex_unlock(&dev->mutex);
-       mutex_destroy(&dev->mutex);
 }
 
 static bool comedi_clear_board_dev(struct comedi_device *dev)
@@ -148,7 +172,7 @@ static void comedi_free_board_dev(struct comedi_device *dev)
                                       MKDEV(COMEDI_MAJOR, dev->minor));
                }
                comedi_device_cleanup(dev);
-               kfree(dev);
+               comedi_dev_put(dev);
        }
 }
 
@@ -193,6 +217,40 @@ struct comedi_device *comedi_dev_from_minor(unsigned minor)
 }
 EXPORT_SYMBOL_GPL(comedi_dev_from_minor);
 
+static struct comedi_device *comedi_dev_get_from_board_minor(unsigned minor)
+{
+       struct comedi_device *dev;
+
+       BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS);
+       mutex_lock(&comedi_board_minor_table_lock);
+       dev = comedi_dev_get(comedi_board_minor_table[minor]);
+       mutex_unlock(&comedi_board_minor_table_lock);
+       return dev;
+}
+
+static struct comedi_device *comedi_dev_get_from_subdevice_minor(unsigned minor)
+{
+       struct comedi_device *dev;
+       struct comedi_subdevice *s;
+       unsigned int i = minor - COMEDI_NUM_BOARD_MINORS;
+
+       BUG_ON(i >= COMEDI_NUM_SUBDEVICE_MINORS);
+       mutex_lock(&comedi_subdevice_minor_table_lock);
+       s = comedi_subdevice_minor_table[i];
+       dev = comedi_dev_get(s ? s->device : NULL);
+       mutex_unlock(&comedi_subdevice_minor_table_lock);
+       return dev;
+}
+
+struct comedi_device *comedi_dev_get_from_minor(unsigned minor)
+{
+       if (minor < COMEDI_NUM_BOARD_MINORS)
+               return comedi_dev_get_from_board_minor(minor);
+       else
+               return comedi_dev_get_from_subdevice_minor(minor);
+}
+EXPORT_SYMBOL_GPL(comedi_dev_get_from_minor);
+
 static struct comedi_subdevice *
 comedi_read_subdevice(const struct comedi_device *dev, unsigned int minor)
 {
@@ -270,7 +328,7 @@ static ssize_t max_read_buffer_kb_show(struct device *csdev,
        struct comedi_subdevice *s;
        unsigned int size = 0;
 
-       dev = comedi_dev_from_minor(minor);
+       dev = comedi_dev_get_from_minor(minor);
        if (!dev)
                return -ENODEV;
 
@@ -280,6 +338,7 @@ static ssize_t max_read_buffer_kb_show(struct device *csdev,
                size = s->async->max_bufsize / 1024;
        mutex_unlock(&dev->mutex);
 
+       comedi_dev_put(dev);
        return snprintf(buf, PAGE_SIZE, "%i\n", size);
 }
 
@@ -300,7 +359,7 @@ static ssize_t max_read_buffer_kb_store(struct device *csdev,
                return -EINVAL;
        size *= 1024;
 
-       dev = comedi_dev_from_minor(minor);
+       dev = comedi_dev_get_from_minor(minor);
        if (!dev)
                return -ENODEV;
 
@@ -312,6 +371,7 @@ static ssize_t max_read_buffer_kb_store(struct device *csdev,
                err = -EINVAL;
        mutex_unlock(&dev->mutex);
 
+       comedi_dev_put(dev);
        return err ? err : count;
 }
 static DEVICE_ATTR_RW(max_read_buffer_kb);
@@ -324,7 +384,7 @@ static ssize_t read_buffer_kb_show(struct device *csdev,
        struct comedi_subdevice *s;
        unsigned int size = 0;
 
-       dev = comedi_dev_from_minor(minor);
+       dev = comedi_dev_get_from_minor(minor);
        if (!dev)
                return -ENODEV;
 
@@ -334,6 +394,7 @@ static ssize_t read_buffer_kb_show(struct device *csdev,
                size = s->async->prealloc_bufsz / 1024;
        mutex_unlock(&dev->mutex);
 
+       comedi_dev_put(dev);
        return snprintf(buf, PAGE_SIZE, "%i\n", size);
 }
 
@@ -354,7 +415,7 @@ static ssize_t read_buffer_kb_store(struct device *csdev,
                return -EINVAL;
        size *= 1024;
 
-       dev = comedi_dev_from_minor(minor);
+       dev = comedi_dev_get_from_minor(minor);
        if (!dev)
                return -ENODEV;
 
@@ -366,6 +427,7 @@ static ssize_t read_buffer_kb_store(struct device *csdev,
                err = -EINVAL;
        mutex_unlock(&dev->mutex);
 
+       comedi_dev_put(dev);
        return err ? err : count;
 }
 static DEVICE_ATTR_RW(read_buffer_kb);
@@ -379,7 +441,7 @@ static ssize_t max_write_buffer_kb_show(struct device *csdev,
        struct comedi_subdevice *s;
        unsigned int size = 0;
 
-       dev = comedi_dev_from_minor(minor);
+       dev = comedi_dev_get_from_minor(minor);
        if (!dev)
                return -ENODEV;
 
@@ -389,6 +451,7 @@ static ssize_t max_write_buffer_kb_show(struct device *csdev,
                size = s->async->max_bufsize / 1024;
        mutex_unlock(&dev->mutex);
 
+       comedi_dev_put(dev);
        return snprintf(buf, PAGE_SIZE, "%i\n", size);
 }
 
@@ -409,7 +472,7 @@ static ssize_t max_write_buffer_kb_store(struct device *csdev,
                return -EINVAL;
        size *= 1024;
 
-       dev = comedi_dev_from_minor(minor);
+       dev = comedi_dev_get_from_minor(minor);
        if (!dev)
                return -ENODEV;
 
@@ -421,6 +484,7 @@ static ssize_t max_write_buffer_kb_store(struct device *csdev,
                err = -EINVAL;
        mutex_unlock(&dev->mutex);
 
+       comedi_dev_put(dev);
        return err ? err : count;
 }
 static DEVICE_ATTR_RW(max_write_buffer_kb);
@@ -433,7 +497,7 @@ static ssize_t write_buffer_kb_show(struct device *csdev,
        struct comedi_subdevice *s;
        unsigned int size = 0;
 
-       dev = comedi_dev_from_minor(minor);
+       dev = comedi_dev_get_from_minor(minor);
        if (!dev)
                return -ENODEV;
 
@@ -443,6 +507,7 @@ static ssize_t write_buffer_kb_show(struct device *csdev,
                size = s->async->prealloc_bufsz / 1024;
        mutex_unlock(&dev->mutex);
 
+       comedi_dev_put(dev);
        return snprintf(buf, PAGE_SIZE, "%i\n", size);
 }
 
@@ -463,7 +528,7 @@ static ssize_t write_buffer_kb_store(struct device *csdev,
                return -EINVAL;
        size *= 1024;
 
-       dev = comedi_dev_from_minor(minor);
+       dev = comedi_dev_get_from_minor(minor);
        if (!dev)
                return -ENODEV;
 
@@ -475,6 +540,7 @@ static ssize_t write_buffer_kb_store(struct device *csdev,
                err = -EINVAL;
        mutex_unlock(&dev->mutex);
 
+       comedi_dev_put(dev);
        return err ? err : count;
 }
 static DEVICE_ATTR_RW(write_buffer_kb);
@@ -2023,38 +2089,77 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
        DECLARE_WAITQUEUE(wait, current);
        const unsigned minor = iminor(file_inode(file));
        struct comedi_device *dev = comedi_dev_from_minor(minor);
+       bool on_wait_queue = false;
+       bool attach_locked;
+       unsigned int old_detach_count;
 
        if (!dev)
                return -ENODEV;
 
+       /* Protect against device detachment during operation. */
+       down_read(&dev->attach_lock);
+       attach_locked = true;
+       old_detach_count = dev->detach_count;
+
        if (!dev->attached) {
                DPRINTK("no driver configured on comedi%i\n", dev->minor);
-               return -ENODEV;
+               retval = -ENODEV;
+               goto out;
        }
 
        s = comedi_write_subdevice(dev, minor);
-       if (!s || !s->async)
-               return -EIO;
+       if (!s || !s->async) {
+               retval = -EIO;
+               goto out;
+       }
 
        async = s->async;
 
        if (!s->busy || !nbytes)
-               return 0;
-       if (s->busy != file)
-               return -EACCES;
+               goto out;
+       if (s->busy != file) {
+               retval = -EACCES;
+               goto out;
+       }
 
        add_wait_queue(&async->wait_head, &wait);
+       on_wait_queue = true;
        while (nbytes > 0 && !retval) {
                set_current_state(TASK_INTERRUPTIBLE);
 
                if (!comedi_is_subdevice_running(s)) {
                        if (count == 0) {
-                               mutex_lock(&dev->mutex);
+                               struct comedi_subdevice *new_s;
+
                                if (comedi_is_subdevice_in_error(s))
                                        retval = -EPIPE;
                                else
                                        retval = 0;
-                               do_become_nonbusy(dev, s);
+                               /*
+                                * To avoid deadlock, cannot acquire dev->mutex
+                                * while dev->attach_lock is held.  Need to
+                                * remove task from the async wait queue before
+                                * releasing dev->attach_lock, as it might not
+                                * be valid afterwards.
+                                */
+                               remove_wait_queue(&async->wait_head, &wait);
+                               on_wait_queue = false;
+                               up_read(&dev->attach_lock);
+                               attach_locked = false;
+                               mutex_lock(&dev->mutex);
+                               /*
+                                * Become non-busy unless things have changed
+                                * behind our back.  Checking dev->detach_count
+                                * is unchanged ought to be sufficient (unless
+                                * there have been 2**32 detaches in the
+                                * meantime!), but check the subdevice pointer
+                                * as well just in case.
+                                */
+                               new_s = comedi_write_subdevice(dev, minor);
+                               if (dev->attached &&
+                                   old_detach_count == dev->detach_count &&
+                                   s == new_s && new_s->async == async)
+                                       do_become_nonbusy(dev, s);
                                mutex_unlock(&dev->mutex);
                        }
                        break;
@@ -2104,8 +2209,12 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
                buf += n;
                break;          /* makes device work like a pipe */
        }
+out:
+       if (on_wait_queue)
+               remove_wait_queue(&async->wait_head, &wait);
        set_current_state(TASK_RUNNING);
-       remove_wait_queue(&async->wait_head, &wait);
+       if (attach_locked)
+               up_read(&dev->attach_lock);
 
        return count ? count : retval;
 }
@@ -2119,24 +2228,37 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
        DECLARE_WAITQUEUE(wait, current);
        const unsigned minor = iminor(file_inode(file));
        struct comedi_device *dev = comedi_dev_from_minor(minor);
+       unsigned int old_detach_count;
+       bool become_nonbusy = false;
+       bool attach_locked;
 
        if (!dev)
                return -ENODEV;
 
+       /* Protect against device detachment during operation. */
+       down_read(&dev->attach_lock);
+       attach_locked = true;
+       old_detach_count = dev->detach_count;
+
        if (!dev->attached) {
                DPRINTK("no driver configured on comedi%i\n", dev->minor);
-               return -ENODEV;
+               retval = -ENODEV;
+               goto out;
        }
 
        s = comedi_read_subdevice(dev, minor);
-       if (!s || !s->async)
-               return -EIO;
+       if (!s || !s->async) {
+               retval = -EIO;
+               goto out;
+       }
 
        async = s->async;
        if (!s->busy || !nbytes)
-               return 0;
-       if (s->busy != file)
-               return -EACCES;
+               goto out;
+       if (s->busy != file) {
+               retval = -EACCES;
+               goto out;
+       }
 
        add_wait_queue(&async->wait_head, &wait);
        while (nbytes > 0 && !retval) {
@@ -2154,13 +2276,11 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
 
                if (n == 0) {
                        if (!comedi_is_subdevice_running(s)) {
-                               mutex_lock(&dev->mutex);
-                               do_become_nonbusy(dev, s);
                                if (comedi_is_subdevice_in_error(s))
                                        retval = -EPIPE;
                                else
                                        retval = 0;
-                               mutex_unlock(&dev->mutex);
+                               become_nonbusy = true;
                                break;
                        }
                        if (file->f_flags & O_NONBLOCK) {
@@ -2198,14 +2318,37 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
                buf += n;
                break;          /* makes device work like a pipe */
        }
-       if (comedi_is_subdevice_idle(s)) {
+       remove_wait_queue(&async->wait_head, &wait);
+       set_current_state(TASK_RUNNING);
+       if (become_nonbusy || comedi_is_subdevice_idle(s)) {
+               struct comedi_subdevice *new_s;
+
+               /*
+                * To avoid deadlock, cannot acquire dev->mutex
+                * while dev->attach_lock is held.
+                */
+               up_read(&dev->attach_lock);
+               attach_locked = false;
                mutex_lock(&dev->mutex);
-               if (async->buf_read_count - async->buf_write_count == 0)
-                       do_become_nonbusy(dev, s);
+               /*
+                * Check device hasn't become detached behind our back.
+                * Checking dev->detach_count is unchanged ought to be
+                * sufficient (unless there have been 2**32 detaches in the
+                * meantime!), but check the subdevice pointer as well just in
+                * case.
+                */
+               new_s = comedi_read_subdevice(dev, minor);
+               if (dev->attached && old_detach_count == dev->detach_count &&
+                   s == new_s && new_s->async == async) {
+                       if (become_nonbusy ||
+                           async->buf_read_count - async->buf_write_count == 0)
+                               do_become_nonbusy(dev, s);
+               }
                mutex_unlock(&dev->mutex);
        }
-       set_current_state(TASK_RUNNING);
-       remove_wait_queue(&async->wait_head, &wait);
+out:
+       if (attach_locked)
+               up_read(&dev->attach_lock);
 
        return count ? count : retval;
 }
@@ -2213,7 +2356,8 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
 static int comedi_open(struct inode *inode, struct file *file)
 {
        const unsigned minor = iminor(inode);
-       struct comedi_device *dev = comedi_dev_from_minor(minor);
+       struct comedi_device *dev = comedi_dev_get_from_minor(minor);
+       int rc;
 
        if (!dev) {
                DPRINTK("invalid minor number\n");
@@ -2238,8 +2382,8 @@ static int comedi_open(struct inode *inode, struct file *file)
                goto ok;
        if (!capable(CAP_NET_ADMIN) && dev->in_request_module) {
                DPRINTK("in request module\n");
-               mutex_unlock(&dev->mutex);
-               return -ENODEV;
+               rc = -ENODEV;
+               goto out;
        }
        if (capable(CAP_NET_ADMIN) && dev->in_request_module)
                goto ok;
@@ -2256,8 +2400,8 @@ static int comedi_open(struct inode *inode, struct file *file)
 
        if (!dev->attached && !capable(CAP_NET_ADMIN)) {
                DPRINTK("not attached and not CAP_NET_ADMIN\n");
-               mutex_unlock(&dev->mutex);
-               return -ENODEV;
+               rc = -ENODEV;
+               goto out;
        }
 ok:
        __module_get(THIS_MODULE);
@@ -2265,26 +2409,28 @@ ok:
        if (dev->attached) {
                if (!try_module_get(dev->driver->module)) {
                        module_put(THIS_MODULE);
-                       mutex_unlock(&dev->mutex);
-                       return -ENOSYS;
+                       rc = -ENOSYS;
+                       goto out;
                }
        }
 
        if (dev->attached && dev->use_count == 0 && dev->open) {
-               int rc = dev->open(dev);
+               rc = dev->open(dev);
                if (rc < 0) {
                        module_put(dev->driver->module);
                        module_put(THIS_MODULE);
-                       mutex_unlock(&dev->mutex);
-                       return rc;
+                       goto out;
                }
        }
 
        dev->use_count++;
+       rc = 0;
 
+out:
        mutex_unlock(&dev->mutex);
-
-       return 0;
+       if (rc)
+               comedi_dev_put(dev);
+       return rc;
 }
 
 static int comedi_fasync(int fd, struct file *file, int on)
@@ -2330,6 +2476,7 @@ static int comedi_close(struct inode *inode, struct file *file)
        dev->use_count--;
 
        mutex_unlock(&dev->mutex);
+       comedi_dev_put(dev);
 
        return 0;
 }
@@ -2417,7 +2564,7 @@ struct comedi_device *comedi_alloc_board_minor(struct device *hardware_device)
        if (i == COMEDI_NUM_BOARD_MINORS) {
                mutex_unlock(&dev->mutex);
                comedi_device_cleanup(dev);
-               kfree(dev);
+               comedi_dev_put(dev);
                pr_err("comedi: error: ran out of minor numbers for board device files.\n");
                return ERR_PTR(-EBUSY);
        }