diff options
Diffstat (limited to 'kernel/drivers/staging/comedi/comedi_fops.c')
-rw-r--r-- | kernel/drivers/staging/comedi/comedi_fops.c | 245 |
1 files changed, 132 insertions, 113 deletions
diff --git a/kernel/drivers/staging/comedi/comedi_fops.c b/kernel/drivers/staging/comedi/comedi_fops.c index e78ddbe5a..7b4af519e 100644 --- a/kernel/drivers/staging/comedi/comedi_fops.c +++ b/kernel/drivers/staging/comedi/comedi_fops.c @@ -43,13 +43,30 @@ #include "comedi_internal.h" +/* + * comedi_subdevice "runflags" + * COMEDI_SRF_RT: DEPRECATED: command is running real-time + * COMEDI_SRF_ERROR: indicates an COMEDI_CB_ERROR event has occurred + * since the last command was started + * COMEDI_SRF_RUNNING: command is running + * COMEDI_SRF_FREE_SPRIV: free s->private on detach + * + * COMEDI_SRF_BUSY_MASK: runflags that indicate the subdevice is "busy" + */ +#define COMEDI_SRF_RT BIT(1) +#define COMEDI_SRF_ERROR BIT(2) +#define COMEDI_SRF_RUNNING BIT(27) +#define COMEDI_SRF_FREE_SPRIV BIT(31) + +#define COMEDI_SRF_BUSY_MASK (COMEDI_SRF_ERROR | COMEDI_SRF_RUNNING) + /** - * struct comedi_file - per-file private data for comedi device - * @dev: comedi_device struct - * @read_subdev: current "read" subdevice - * @write_subdev: current "write" subdevice - * @last_detach_count: last known detach count - * @last_attached: last known attached/detached state + * struct comedi_file - Per-file private data for COMEDI device + * @dev: COMEDI device. + * @read_subdev: Current "read" subdevice. + * @write_subdev: Current "write" subdevice. + * @last_detach_count: Last known detach count. + * @last_attached: Last known attached/detached state. */ struct comedi_file { struct comedi_device *dev; @@ -114,15 +131,15 @@ static void comedi_dev_kref_release(struct kref *kref) } /** - * comedi_dev_put - release a use of a comedi device structure - * @dev: comedi_device struct + * comedi_dev_put() - Release a use of a COMEDI device + * @dev: COMEDI device. * - * Must be called when a user of a comedi device is finished with it. - * When the last user of the comedi device calls this function, the - * comedi device is destroyed. + * Must be called when a user of a COMEDI device is finished with it. + * When the last user of the COMEDI device calls this function, the + * COMEDI device is destroyed. * - * Return 1 if the comedi device is destroyed by this call or dev is - * NULL, otherwise return 0. Callers must not assume the comedi + * Return: 1 if the COMEDI device is destroyed by this call or @dev is + * NULL, otherwise return 0. Callers must not assume the COMEDI * device is still valid if this function returns 0. */ int comedi_dev_put(struct comedi_device *dev) @@ -198,7 +215,6 @@ static struct comedi_subdevice 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]; if (s && s->device != dev) @@ -211,7 +227,6 @@ 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); @@ -224,7 +239,6 @@ static struct comedi_device *comedi_dev_get_from_subdevice_minor(unsigned minor) 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); @@ -233,15 +247,15 @@ static struct comedi_device *comedi_dev_get_from_subdevice_minor(unsigned minor) } /** - * comedi_dev_get_from_minor - get comedi device by minor device number - * @minor: minor device number + * comedi_dev_get_from_minor() - Get COMEDI device by minor device number + * @minor: Minor device number. * - * Finds the comedi device associated by the minor device number, if any, - * and increments its reference count. The comedi device is prevented from + * Finds the COMEDI device associated with the minor device number, if any, + * and increments its reference count. The COMEDI device is prevented from * being freed until a matching call is made to comedi_dev_put(). * - * Return a pointer to the comedi device if it exists, with its usage - * reference incremented. Return NULL if no comedi device exists with the + * Return: A pointer to the COMEDI device if it exists, with its usage + * reference incremented. Return NULL if no COMEDI device exists with the * specified minor device number. */ struct comedi_device *comedi_dev_get_from_minor(unsigned minor) @@ -651,11 +665,11 @@ static bool comedi_is_runflags_in_error(unsigned runflags) } /** - * comedi_is_subdevice_running - check if async command running on subdevice - * @s: comedi_subdevice struct + * comedi_is_subdevice_running() - Check if async command running on subdevice + * @s: COMEDI subdevice. * - * Return true if an asynchronous comedi command is active on the comedi - * subdevice, else return false. + * Return: %true if an asynchronous COMEDI command is active on the + * subdevice, else %false. */ bool comedi_is_subdevice_running(struct comedi_subdevice *s) { @@ -679,19 +693,44 @@ static bool comedi_is_subdevice_idle(struct comedi_subdevice *s) return !(runflags & COMEDI_SRF_BUSY_MASK); } +bool comedi_can_auto_free_spriv(struct comedi_subdevice *s) +{ + unsigned runflags = __comedi_get_subdevice_runflags(s); + + return runflags & COMEDI_SRF_FREE_SPRIV; +} + +/** + * comedi_set_spriv_auto_free() - Mark subdevice private data as freeable + * @s: COMEDI subdevice. + * + * Mark the subdevice as having a pointer to private data that can be + * automatically freed when the COMEDI device is detached from the low-level + * driver. + */ +void comedi_set_spriv_auto_free(struct comedi_subdevice *s) +{ + __comedi_set_subdevice_runflags(s, COMEDI_SRF_FREE_SPRIV); +} +EXPORT_SYMBOL_GPL(comedi_set_spriv_auto_free); + /** - * comedi_alloc_spriv() - Allocate memory for the subdevice private data. - * @s: comedi_subdevice struct - * @size: size of the memory to allocate + * comedi_alloc_spriv - Allocate memory for the subdevice private data + * @s: COMEDI subdevice. + * @size: Size of the memory to allocate. + * + * Allocate memory for the subdevice private data and point @s->private + * to it. The memory will be freed automatically when the COMEDI device + * is detached from the low-level driver. * - * This also sets the subdevice runflags to allow the core to automatically - * free the private data during the detach. + * Return: A pointer to the allocated memory @s->private on success. + * Return NULL on failure. */ void *comedi_alloc_spriv(struct comedi_subdevice *s, size_t size) { s->private = kzalloc(size, GFP_KERNEL); if (s->private) - s->runflags |= COMEDI_SRF_FREE_SPRIV; + comedi_set_spriv_auto_free(s); return s->private; } EXPORT_SYMBOL_GPL(comedi_alloc_spriv); @@ -1048,11 +1087,6 @@ static int do_chaninfo_ioctl(struct comedi_device *dev, if (put_user(x, it.rangelist + i)) return -EFAULT; } -#if 0 - if (copy_to_user(it.rangelist, s->range_type_list, - s->n_chan * sizeof(unsigned int))) - return -EFAULT; -#endif } return 0; @@ -1112,7 +1146,7 @@ static int do_bufinfo_ioctl(struct comedi_device *dev, comedi_buf_read_free(s, bi.bytes_read); if (comedi_is_subdevice_idle(s) && - comedi_buf_n_bytes_ready(s) == 0) { + comedi_buf_read_n_available(s) == 0) { do_become_nonbusy(dev, s); } } @@ -1307,7 +1341,7 @@ static int parse_insn(struct comedi_device *dev, struct comedi_insn *insn, goto out; } /* This looks arbitrary. It is. */ - s->busy = &parse_insn; + s->busy = parse_insn; switch (insn->insn) { case INSN_READ: ret = s->insn_read(dev, s, insn, data); @@ -1725,7 +1759,7 @@ cleanup: /* * COMEDI_CMDTEST ioctl - * asynchronous aquisition command testing + * asynchronous acquisition command testing * * arg: * pointer to comedi_cmd structure @@ -2127,7 +2161,7 @@ static void comedi_vm_close(struct vm_area_struct *area) comedi_buf_map_put(bm); } -static struct vm_operations_struct comedi_vm_ops = { +static const struct vm_operations_struct comedi_vm_ops = { .open = comedi_vm_open, .close = comedi_vm_close, }; @@ -2228,9 +2262,9 @@ static unsigned int comedi_poll(struct file *file, poll_table *wait) unsigned int mask = 0; struct comedi_file *cfp = file->private_data; struct comedi_device *dev = cfp->dev; - struct comedi_subdevice *s; + struct comedi_subdevice *s, *s_read; - mutex_lock(&dev->mutex); + down_read(&dev->attach_lock); if (!dev->attached) { dev_dbg(dev->class_dev, "no driver attached\n"); @@ -2238,9 +2272,10 @@ static unsigned int comedi_poll(struct file *file, poll_table *wait) } s = comedi_file_read_subdevice(file); + s_read = s; if (s && s->async) { poll_wait(file, &s->async->wait_head, wait); - if (!s->busy || !comedi_is_subdevice_running(s) || + if (s->busy != file || !comedi_is_subdevice_running(s) || (s->async->cmd.flags & CMDF_WRITE) || comedi_buf_read_n_available(s) > 0) mask |= POLLIN | POLLRDNORM; @@ -2250,16 +2285,16 @@ static unsigned int comedi_poll(struct file *file, poll_table *wait) if (s && s->async) { unsigned int bps = comedi_bytes_per_sample(s); - poll_wait(file, &s->async->wait_head, wait); - comedi_buf_write_alloc(s, s->async->prealloc_bufsz); - if (!s->busy || !comedi_is_subdevice_running(s) || + if (s != s_read) + poll_wait(file, &s->async->wait_head, wait); + if (s->busy != file || !comedi_is_subdevice_running(s) || !(s->async->cmd.flags & CMDF_WRITE) || - comedi_buf_write_n_allocated(s) >= bps) + comedi_buf_write_n_available(s) >= bps) mask |= POLLOUT | POLLWRNORM; } done: - mutex_unlock(&dev->mutex); + up_read(&dev->attach_lock); return mask; } @@ -2415,7 +2450,9 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, { struct comedi_subdevice *s; struct comedi_async *async; - int n, m, count = 0, retval = 0; + unsigned int n, m; + ssize_t count = 0; + int retval = 0; DECLARE_WAITQUEUE(wait, current); struct comedi_file *cfp = file->private_data; struct comedi_device *dev = cfp->dev; @@ -2441,28 +2478,19 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, } async = s->async; - if (!s->busy || !nbytes) - goto out; - if (s->busy != file) { - retval = -EACCES; - goto out; - } - if (async->cmd.flags & CMDF_WRITE) { + if (s->busy != file || (async->cmd.flags & CMDF_WRITE)) { retval = -EINVAL; goto out; } add_wait_queue(&async->wait_head, &wait); - while (nbytes > 0 && !retval) { - set_current_state(TASK_INTERRUPTIBLE); + while (count == 0 && !retval) { + unsigned int rp, n1, n2; - n = nbytes; + set_current_state(TASK_INTERRUPTIBLE); m = comedi_buf_read_n_available(s); - if (async->buf_read_ptr + m > async->prealloc_bufsz) - m = async->prealloc_bufsz - async->buf_read_ptr; - if (m < n) - n = m; + n = min_t(size_t, m, nbytes); if (n == 0) { unsigned runflags = comedi_get_subdevice_runflags(s); @@ -2470,11 +2498,12 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, if (!comedi_is_runflags_running(runflags)) { if (comedi_is_runflags_in_error(runflags)) retval = -EPIPE; - else - retval = 0; - become_nonbusy = true; + if (retval || nbytes) + become_nonbusy = true; break; } + if (nbytes == 0) + break; if (file->f_flags & O_NONBLOCK) { retval = -EAGAIN; break; @@ -2484,22 +2513,21 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, retval = -ERESTARTSYS; break; } - if (!s->busy) { - retval = 0; - break; - } - if (s->busy != file) { - retval = -EACCES; - break; - } - if (async->cmd.flags & CMDF_WRITE) { + if (s->busy != file || + (async->cmd.flags & CMDF_WRITE)) { retval = -EINVAL; break; } continue; } - m = copy_to_user(buf, async->prealloc_buf + - async->buf_read_ptr, n); + rp = async->buf_read_ptr; + n1 = min(n, async->prealloc_bufsz - rp); + n2 = n - n1; + m = copy_to_user(buf, async->prealloc_buf + rp, n1); + if (m) + m += n2; + else if (n2) + m = copy_to_user(buf + n1, async->prealloc_buf, n2); if (m) { n -= m; retval = -EFAULT; @@ -2512,11 +2540,10 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, nbytes -= n; buf += n; - break; /* makes device work like a pipe */ } remove_wait_queue(&async->wait_head, &wait); set_current_state(TASK_RUNNING); - if (become_nonbusy || comedi_is_subdevice_idle(s)) { + if (become_nonbusy && count == 0) { struct comedi_subdevice *new_s; /* @@ -2532,13 +2559,17 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, * sufficient (unless there have been 2**32 detaches in the * meantime!), but check the subdevice pointer as well just in * case. + * + * Also check the subdevice is still in a suitable state to + * become non-busy in case it changed behind our back. */ new_s = comedi_file_read_subdevice(file); if (dev->attached && old_detach_count == dev->detach_count && - s == new_s && new_s->async == async) { - if (become_nonbusy || comedi_buf_n_bytes_ready(s) == 0) - do_become_nonbusy(dev, s); - } + s == new_s && new_s->async == async && s->busy == file && + !(async->cmd.flags & CMDF_WRITE) && + !comedi_is_subdevice_running(s) && + comedi_buf_read_n_available(s) == 0) + do_become_nonbusy(dev, s); mutex_unlock(&dev->mutex); } out: @@ -2567,14 +2598,14 @@ static int comedi_open(struct inode *inode, struct file *file) cfp->dev = dev; mutex_lock(&dev->mutex); - if (!dev->attached && !capable(CAP_NET_ADMIN)) { - dev_dbg(dev->class_dev, "not attached and not CAP_NET_ADMIN\n"); + if (!dev->attached && !capable(CAP_SYS_ADMIN)) { + dev_dbg(dev->class_dev, "not attached and not CAP_SYS_ADMIN\n"); rc = -ENODEV; goto out; } if (dev->attached && dev->use_count == 0) { if (!try_module_get(dev->driver->module)) { - rc = -ENOSYS; + rc = -ENXIO; goto out; } if (dev->open) { @@ -2657,15 +2688,15 @@ static const struct file_operations comedi_fops = { }; /** - * comedi_event - handle events for asynchronous comedi command - * @dev: comedi_device struct - * @s: comedi_subdevice struct associated with dev - * Context: interrupt (usually), s->spin_lock spin-lock not held + * comedi_event() - Handle events for asynchronous COMEDI command + * @dev: COMEDI device. + * @s: COMEDI subdevice. + * Context: in_interrupt() (usually), @s->spin_lock spin-lock not held. * - * If an asynchronous comedi command is active on the subdevice, process - * any COMEDI_CB_... event flags that have been set, usually by an + * If an asynchronous COMEDI command is active on the subdevice, process + * any %COMEDI_CB_... event flags that have been set, usually by an * interrupt handler. These may change the run state of the asynchronous - * command, wake a task, and/or send a SIGIO signal. + * command, wake a task, and/or send a %SIGIO signal. */ void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s) { @@ -2745,12 +2776,6 @@ struct comedi_device *comedi_alloc_board_minor(struct device *hardware_device) return dev; } -static void comedi_free_board_minor(unsigned minor) -{ - BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS); - comedi_free_board_dev(comedi_clear_board_minor(minor)); -} - void comedi_release_hardware_device(struct device *hardware_device) { int minor; @@ -2806,12 +2831,10 @@ void comedi_free_subdevice_minor(struct comedi_subdevice *s) if (!s) return; - if (s->minor < 0) + if (s->minor < COMEDI_NUM_BOARD_MINORS || + s->minor >= COMEDI_NUM_MINORS) return; - BUG_ON(s->minor >= COMEDI_NUM_MINORS); - BUG_ON(s->minor < COMEDI_NUM_BOARD_MINORS); - i = s->minor - COMEDI_NUM_BOARD_MINORS; mutex_lock(&comedi_subdevice_minor_table_lock); if (s == comedi_subdevice_minor_table[i]) @@ -2825,10 +2848,13 @@ void comedi_free_subdevice_minor(struct comedi_subdevice *s) static void comedi_cleanup_board_minors(void) { + struct comedi_device *dev; unsigned i; - for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) - comedi_free_board_minor(i); + for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) { + dev = comedi_clear_board_minor(i); + comedi_free_board_dev(dev); + } } static int __init comedi_init(void) @@ -2900,14 +2926,7 @@ module_init(comedi_init); static void __exit comedi_cleanup(void) { - int i; - comedi_cleanup_board_minors(); - 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); unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS); |