diff options
Diffstat (limited to 'drivers/comedi')
| -rw-r--r-- | drivers/comedi/comedi_buf.c | 274 | ||||
| -rw-r--r-- | drivers/comedi/comedi_fops.c | 189 | ||||
| -rw-r--r-- | drivers/comedi/comedi_internal.h | 12 | ||||
| -rw-r--r-- | drivers/comedi/drivers.c | 134 | ||||
| -rw-r--r-- | drivers/comedi/drivers/8255.c | 20 | ||||
| -rw-r--r-- | drivers/comedi/drivers/c6xdigio.c | 46 | ||||
| -rw-r--r-- | drivers/comedi/drivers/comedi_bond.c | 4 | ||||
| -rw-r--r-- | drivers/comedi/drivers/multiq3.c | 9 | ||||
| -rw-r--r-- | drivers/comedi/drivers/ni_670x.c | 2 | ||||
| -rw-r--r-- | drivers/comedi/drivers/pcl818.c | 5 | ||||
| -rw-r--r-- | drivers/comedi/kcomedilib/kcomedilib_main.c | 120 |
11 files changed, 607 insertions, 208 deletions
diff --git a/drivers/comedi/comedi_buf.c b/drivers/comedi/comedi_buf.c index c7c262a2d8ca..785977b40a93 100644 --- a/drivers/comedi/comedi_buf.c +++ b/drivers/comedi/comedi_buf.c @@ -273,19 +273,8 @@ unsigned int comedi_buf_write_n_available(struct comedi_subdevice *s) return free_end - async->buf_write_count; } -/** - * comedi_buf_write_alloc() - Reserve buffer space for writing - * @s: COMEDI subdevice. - * @nbytes: Maximum space to reserve in bytes. - * - * Reserve up to @nbytes bytes of space to be written in the COMEDI acquisition - * data buffer associated with the subdevice. The amount reserved is limited - * by the space available. - * - * Return: The amount of space reserved in bytes. - */ -unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s, - unsigned int nbytes) +unsigned int _comedi_buf_write_alloc(struct comedi_subdevice *s, + unsigned int nbytes) { struct comedi_async *async = s->async; unsigned int unalloc = comedi_buf_write_n_unalloc(s); @@ -303,6 +292,29 @@ unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s, return nbytes; } + +/** + * comedi_buf_write_alloc() - Reserve buffer space for writing + * @s: COMEDI subdevice. + * @nbytes: Maximum space to reserve in bytes. + * + * Reserve up to @nbytes bytes of space to be written in the COMEDI acquisition + * data buffer associated with the subdevice. The amount reserved is limited + * by the space available. + * + * Return: The amount of space reserved in bytes. + */ +unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s, + unsigned int nbytes) +{ + if (comedi_get_is_subdevice_running(s)) { + nbytes = _comedi_buf_write_alloc(s, nbytes); + comedi_put_is_subdevice_running(s); + } else { + nbytes = 0; + } + return nbytes; +} EXPORT_SYMBOL_GPL(comedi_buf_write_alloc); /* @@ -362,6 +374,24 @@ unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s) return async->buf_write_alloc_count - async->buf_write_count; } +unsigned int _comedi_buf_write_free(struct comedi_subdevice *s, + unsigned int nbytes) +{ + struct comedi_async *async = s->async; + unsigned int allocated = comedi_buf_write_n_allocated(s); + + if (nbytes > allocated) + nbytes = allocated; + + async->buf_write_count += nbytes; + async->buf_write_ptr += nbytes; + comedi_buf_munge(s, async->buf_write_count - async->munge_count); + if (async->buf_write_ptr >= async->prealloc_bufsz) + async->buf_write_ptr %= async->prealloc_bufsz; + + return nbytes; +} + /** * comedi_buf_write_free() - Free buffer space after it is written * @s: COMEDI subdevice. @@ -380,21 +410,34 @@ unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s) unsigned int comedi_buf_write_free(struct comedi_subdevice *s, unsigned int nbytes) { + if (comedi_get_is_subdevice_running(s)) { + nbytes = _comedi_buf_write_free(s, nbytes); + comedi_put_is_subdevice_running(s); + } else { + nbytes = 0; + } + return nbytes; +} +EXPORT_SYMBOL_GPL(comedi_buf_write_free); + +unsigned int _comedi_buf_read_n_available(struct comedi_subdevice *s) +{ struct comedi_async *async = s->async; - unsigned int allocated = comedi_buf_write_n_allocated(s); + unsigned int num_bytes; - if (nbytes > allocated) - nbytes = allocated; + if (!async) + return 0; - async->buf_write_count += nbytes; - async->buf_write_ptr += nbytes; - comedi_buf_munge(s, async->buf_write_count - async->munge_count); - if (async->buf_write_ptr >= async->prealloc_bufsz) - async->buf_write_ptr %= async->prealloc_bufsz; + num_bytes = async->munge_count - async->buf_read_count; - return nbytes; + /* + * ensure the async buffer 'counts' are read before we + * attempt to read data from the buffer + */ + smp_rmb(); + + return num_bytes; } -EXPORT_SYMBOL_GPL(comedi_buf_write_free); /** * comedi_buf_read_n_available() - Determine amount of readable buffer space @@ -409,23 +452,38 @@ EXPORT_SYMBOL_GPL(comedi_buf_write_free); */ unsigned int comedi_buf_read_n_available(struct comedi_subdevice *s) { - struct comedi_async *async = s->async; unsigned int num_bytes; - if (!async) - return 0; + if (comedi_get_is_subdevice_running(s)) { + num_bytes = _comedi_buf_read_n_available(s); + comedi_put_is_subdevice_running(s); + } else { + num_bytes = 0; + } + return num_bytes; +} +EXPORT_SYMBOL_GPL(comedi_buf_read_n_available); - num_bytes = async->munge_count - async->buf_read_count; +unsigned int _comedi_buf_read_alloc(struct comedi_subdevice *s, + unsigned int nbytes) +{ + struct comedi_async *async = s->async; + unsigned int available; + + available = async->munge_count - async->buf_read_alloc_count; + if (nbytes > available) + nbytes = available; + + async->buf_read_alloc_count += nbytes; /* * ensure the async buffer 'counts' are read before we - * attempt to read data from the buffer + * attempt to read data from the read-alloc'ed buffer space */ smp_rmb(); - return num_bytes; + return nbytes; } -EXPORT_SYMBOL_GPL(comedi_buf_read_n_available); /** * comedi_buf_read_alloc() - Reserve buffer space for reading @@ -445,21 +503,12 @@ EXPORT_SYMBOL_GPL(comedi_buf_read_n_available); unsigned int comedi_buf_read_alloc(struct comedi_subdevice *s, unsigned int nbytes) { - struct comedi_async *async = s->async; - unsigned int available; - - available = async->munge_count - async->buf_read_alloc_count; - if (nbytes > available) - nbytes = available; - - async->buf_read_alloc_count += nbytes; - - /* - * ensure the async buffer 'counts' are read before we - * attempt to read data from the read-alloc'ed buffer space - */ - smp_rmb(); - + if (comedi_get_is_subdevice_running(s)) { + nbytes = _comedi_buf_read_alloc(s, nbytes); + comedi_put_is_subdevice_running(s); + } else { + nbytes = 0; + } return nbytes; } EXPORT_SYMBOL_GPL(comedi_buf_read_alloc); @@ -469,21 +518,8 @@ static unsigned int comedi_buf_read_n_allocated(struct comedi_async *async) return async->buf_read_alloc_count - async->buf_read_count; } -/** - * comedi_buf_read_free() - Free buffer space after it has been read - * @s: COMEDI subdevice. - * @nbytes: Maximum space to free in bytes. - * - * Free up to @nbytes bytes of buffer space previously reserved for reading in - * the COMEDI acquisition data buffer associated with the subdevice. The - * amount of space freed is limited to the amount that was reserved. - * - * The freed space becomes available for allocation by the writer. - * - * Return: The amount of space freed in bytes. - */ -unsigned int comedi_buf_read_free(struct comedi_subdevice *s, - unsigned int nbytes) +unsigned int _comedi_buf_read_free(struct comedi_subdevice *s, + unsigned int nbytes) { struct comedi_async *async = s->async; unsigned int allocated; @@ -503,6 +539,31 @@ unsigned int comedi_buf_read_free(struct comedi_subdevice *s, async->buf_read_ptr %= async->prealloc_bufsz; return nbytes; } + +/** + * comedi_buf_read_free() - Free buffer space after it has been read + * @s: COMEDI subdevice. + * @nbytes: Maximum space to free in bytes. + * + * Free up to @nbytes bytes of buffer space previously reserved for reading in + * the COMEDI acquisition data buffer associated with the subdevice. The + * amount of space freed is limited to the amount that was reserved. + * + * The freed space becomes available for allocation by the writer. + * + * Return: The amount of space freed in bytes. + */ +unsigned int comedi_buf_read_free(struct comedi_subdevice *s, + unsigned int nbytes) +{ + if (comedi_get_is_subdevice_running(s)) { + nbytes = _comedi_buf_read_free(s, nbytes); + comedi_put_is_subdevice_running(s); + } else { + nbytes = 0; + } + return nbytes; +} EXPORT_SYMBOL_GPL(comedi_buf_read_free); static void comedi_buf_memcpy_to(struct comedi_subdevice *s, @@ -558,6 +619,38 @@ static void comedi_buf_memcpy_from(struct comedi_subdevice *s, } } +static unsigned int _comedi_buf_write_samples(struct comedi_subdevice *s, + const void *data, + unsigned int nsamples) +{ + unsigned int max_samples; + unsigned int nbytes; + + /* + * Make sure there is enough room in the buffer for all the samples. + * If not, clamp the nsamples to the number that will fit, flag the + * buffer overrun and add the samples that fit. + */ + max_samples = comedi_bytes_to_samples(s, comedi_buf_write_n_unalloc(s)); + if (nsamples > max_samples) { + dev_warn(s->device->class_dev, "buffer overrun\n"); + s->async->events |= COMEDI_CB_OVERFLOW; + nsamples = max_samples; + } + + if (nsamples == 0) + return 0; + + nbytes = comedi_samples_to_bytes(s, nsamples); + nbytes = _comedi_buf_write_alloc(s, nbytes); + comedi_buf_memcpy_to(s, data, nbytes); + _comedi_buf_write_free(s, nbytes); + _comedi_inc_scan_progress(s, nbytes); + s->async->events |= COMEDI_CB_BLOCK; + + return nbytes; +} + /** * comedi_buf_write_samples() - Write sample data to COMEDI buffer * @s: COMEDI subdevice. @@ -578,34 +671,42 @@ static void comedi_buf_memcpy_from(struct comedi_subdevice *s, unsigned int comedi_buf_write_samples(struct comedi_subdevice *s, const void *data, unsigned int nsamples) { + unsigned int nbytes; + + if (comedi_get_is_subdevice_running(s)) { + nbytes = _comedi_buf_write_samples(s, data, nsamples); + comedi_put_is_subdevice_running(s); + } else { + nbytes = 0; + } + return nbytes; +} +EXPORT_SYMBOL_GPL(comedi_buf_write_samples); + +static unsigned int _comedi_buf_read_samples(struct comedi_subdevice *s, + void *data, unsigned int nsamples) +{ unsigned int max_samples; unsigned int nbytes; - /* - * Make sure there is enough room in the buffer for all the samples. - * If not, clamp the nsamples to the number that will fit, flag the - * buffer overrun and add the samples that fit. - */ - max_samples = comedi_bytes_to_samples(s, comedi_buf_write_n_unalloc(s)); - if (nsamples > max_samples) { - dev_warn(s->device->class_dev, "buffer overrun\n"); - s->async->events |= COMEDI_CB_OVERFLOW; + /* clamp nsamples to the number of full samples available */ + max_samples = comedi_bytes_to_samples(s, + _comedi_buf_read_n_available(s)); + if (nsamples > max_samples) nsamples = max_samples; - } if (nsamples == 0) return 0; - nbytes = comedi_buf_write_alloc(s, + nbytes = _comedi_buf_read_alloc(s, comedi_samples_to_bytes(s, nsamples)); - comedi_buf_memcpy_to(s, data, nbytes); - comedi_buf_write_free(s, nbytes); - comedi_inc_scan_progress(s, nbytes); + comedi_buf_memcpy_from(s, data, nbytes); + _comedi_buf_read_free(s, nbytes); + _comedi_inc_scan_progress(s, nbytes); s->async->events |= COMEDI_CB_BLOCK; return nbytes; } -EXPORT_SYMBOL_GPL(comedi_buf_write_samples); /** * comedi_buf_read_samples() - Read sample data from COMEDI buffer @@ -624,25 +725,14 @@ EXPORT_SYMBOL_GPL(comedi_buf_write_samples); unsigned int comedi_buf_read_samples(struct comedi_subdevice *s, void *data, unsigned int nsamples) { - unsigned int max_samples; unsigned int nbytes; - /* clamp nsamples to the number of full samples available */ - max_samples = comedi_bytes_to_samples(s, - comedi_buf_read_n_available(s)); - if (nsamples > max_samples) - nsamples = max_samples; - - if (nsamples == 0) - return 0; - - nbytes = comedi_buf_read_alloc(s, - comedi_samples_to_bytes(s, nsamples)); - comedi_buf_memcpy_from(s, data, nbytes); - comedi_buf_read_free(s, nbytes); - comedi_inc_scan_progress(s, nbytes); - s->async->events |= COMEDI_CB_BLOCK; - + if (comedi_get_is_subdevice_running(s)) { + nbytes = _comedi_buf_read_samples(s, data, nsamples); + comedi_put_is_subdevice_running(s); + } else { + nbytes = 0; + } return nbytes; } EXPORT_SYMBOL_GPL(comedi_buf_read_samples); diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c index 7e2f2b1a1c36..657c98cd723e 100644 --- a/drivers/comedi/comedi_fops.c +++ b/drivers/comedi/comedi_fops.c @@ -38,6 +38,7 @@ * 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_BUSY: command was started and subdevice still busy * COMEDI_SRF_FREE_SPRIV: free s->private on detach * * COMEDI_SRF_BUSY_MASK: runflags that indicate the subdevice is "busy" @@ -45,9 +46,11 @@ #define COMEDI_SRF_RT BIT(1) #define COMEDI_SRF_ERROR BIT(2) #define COMEDI_SRF_RUNNING BIT(27) +#define COMEDI_SRF_BUSY BIT(28) #define COMEDI_SRF_FREE_SPRIV BIT(31) -#define COMEDI_SRF_BUSY_MASK (COMEDI_SRF_ERROR | COMEDI_SRF_RUNNING) +#define COMEDI_SRF_BUSY_MASK \ + (COMEDI_SRF_ERROR | COMEDI_SRF_RUNNING | COMEDI_SRF_BUSY) /** * struct comedi_file - Per-file private data for COMEDI device @@ -665,6 +668,11 @@ static bool comedi_is_runflags_in_error(unsigned int runflags) return runflags & COMEDI_SRF_ERROR; } +static bool comedi_is_runflags_busy(unsigned int runflags) +{ + return runflags & COMEDI_SRF_BUSY; +} + /** * comedi_is_subdevice_running() - Check if async command running on subdevice * @s: COMEDI subdevice. @@ -687,6 +695,46 @@ static bool __comedi_is_subdevice_running(struct comedi_subdevice *s) return comedi_is_runflags_running(runflags); } +/** + * comedi_get_is_subdevice_running() - Get if async command running on subdevice + * @s: COMEDI subdevice. + * + * If an asynchronous COMEDI command is running on the subdevice, increment + * a reference counter. If the function return value indicates that a + * command is running, then the details of the command will not be destroyed + * before a matching call to comedi_put_is_subdevice_running(). + * + * Return: %true if an asynchronous COMEDI command is active on the + * subdevice, else %false. + */ +bool comedi_get_is_subdevice_running(struct comedi_subdevice *s) +{ + unsigned long flags; + bool running; + + spin_lock_irqsave(&s->spin_lock, flags); + running = __comedi_is_subdevice_running(s); + if (running) + refcount_inc(&s->async->run_active); + spin_unlock_irqrestore(&s->spin_lock, flags); + return running; +} +EXPORT_SYMBOL_GPL(comedi_get_is_subdevice_running); + +/** + * comedi_put_is_subdevice_running() - Put if async command running on subdevice + * @s: COMEDI subdevice. + * + * Decrements the reference counter that was incremented when + * comedi_get_is_subdevice_running() returned %true. + */ +void comedi_put_is_subdevice_running(struct comedi_subdevice *s) +{ + if (refcount_dec_and_test(&s->async->run_active)) + complete_all(&s->async->run_complete); +} +EXPORT_SYMBOL_GPL(comedi_put_is_subdevice_running); + bool comedi_can_auto_free_spriv(struct comedi_subdevice *s) { unsigned int runflags = __comedi_get_subdevice_runflags(s); @@ -736,20 +784,28 @@ static void do_become_nonbusy(struct comedi_device *dev, struct comedi_subdevice *s) { struct comedi_async *async = s->async; + unsigned int runflags; + unsigned long flags; lockdep_assert_held(&dev->mutex); - comedi_update_subdevice_runflags(s, COMEDI_SRF_RUNNING, 0); - if (async) { + spin_lock_irqsave(&s->spin_lock, flags); + runflags = __comedi_get_subdevice_runflags(s); + __comedi_clear_subdevice_runflags(s, COMEDI_SRF_RUNNING | + COMEDI_SRF_BUSY); + spin_unlock_irqrestore(&s->spin_lock, flags); + if (comedi_is_runflags_busy(runflags)) { + /* + * "Run active" counter was set to 1 when setting up the + * command. Decrement it and wait for it to become 0. + */ + comedi_put_is_subdevice_running(s); + wait_for_completion(&async->run_complete); comedi_buf_reset(s); async->inttrig = NULL; kfree(async->cmd.chanlist); async->cmd.chanlist = NULL; s->busy = NULL; wake_up_interruptible_all(&async->wait_head); - } else { - dev_err(dev->class_dev, - "BUG: (?) %s called with async=NULL\n", __func__); - s->busy = NULL; } } @@ -1150,15 +1206,15 @@ static int do_bufinfo_ioctl(struct comedi_device *dev, if (!(async->cmd.flags & CMDF_WRITE)) { /* command was set up in "read" direction */ if (bi.bytes_read) { - comedi_buf_read_alloc(s, bi.bytes_read); - bi.bytes_read = comedi_buf_read_free(s, bi.bytes_read); + _comedi_buf_read_alloc(s, bi.bytes_read); + bi.bytes_read = _comedi_buf_read_free(s, bi.bytes_read); } /* * If nothing left to read, and command has stopped, and * {"read" position not updated or command stopped normally}, * then become non-busy. */ - if (comedi_buf_read_n_available(s) == 0 && + if (_comedi_buf_read_n_available(s) == 0 && !comedi_is_runflags_running(runflags) && (bi.bytes_read == 0 || !comedi_is_runflags_in_error(runflags))) { @@ -1175,9 +1231,9 @@ static int do_bufinfo_ioctl(struct comedi_device *dev, if (comedi_is_runflags_in_error(runflags)) retval = -EPIPE; } else if (bi.bytes_written) { - comedi_buf_write_alloc(s, bi.bytes_written); + _comedi_buf_write_alloc(s, bi.bytes_written); bi.bytes_written = - comedi_buf_write_free(s, bi.bytes_written); + _comedi_buf_write_free(s, bi.bytes_written); } bi.bytes_read = 0; } @@ -1860,8 +1916,14 @@ static int do_cmd_ioctl(struct comedi_device *dev, if (async->cmd.flags & CMDF_WAKE_EOS) async->cb_mask |= COMEDI_CB_EOS; + /* + * Set the "run active" counter with an initial count of 1 that will + * complete the "safe to reset" event when it is decremented to 0. + */ + refcount_set(&s->async->run_active, 1); + reinit_completion(&s->async->run_complete); comedi_update_subdevice_runflags(s, COMEDI_SRF_BUSY_MASK, - COMEDI_SRF_RUNNING); + COMEDI_SRF_RUNNING | COMEDI_SRF_BUSY); /* * Set s->busy _after_ setting COMEDI_SRF_RUNNING flag to avoid @@ -2284,15 +2346,10 @@ static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, rc = check_insnlist_len(dev, insnlist.n_insns); if (rc) break; - insns = kcalloc(insnlist.n_insns, sizeof(*insns), GFP_KERNEL); - if (!insns) { - rc = -ENOMEM; - break; - } - if (copy_from_user(insns, insnlist.insns, - sizeof(*insns) * insnlist.n_insns)) { - rc = -EFAULT; - kfree(insns); + insns = memdup_array_user(insnlist.insns, insnlist.n_insns, + sizeof(*insns)); + if (IS_ERR(insns)) { + rc = PTR_ERR(insns); break; } rc = do_insnlist_ioctl(dev, insns, insnlist.n_insns, file); @@ -2512,7 +2569,7 @@ static __poll_t comedi_poll(struct file *file, poll_table *wait) 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_read_n_available(s) > 0) + _comedi_buf_read_n_available(s) > 0) mask |= EPOLLIN | EPOLLRDNORM; } @@ -2645,7 +2702,7 @@ static ssize_t comedi_write(struct file *file, const char __user *buf, break; /* Allocate all free buffer space. */ - comedi_buf_write_alloc(s, async->prealloc_bufsz); + _comedi_buf_write_alloc(s, async->prealloc_bufsz); m = comedi_buf_write_n_allocated(s); n = min_t(size_t, m, nbytes); @@ -2673,7 +2730,7 @@ static ssize_t comedi_write(struct file *file, const char __user *buf, n -= m; retval = -EFAULT; } - comedi_buf_write_free(s, n); + _comedi_buf_write_free(s, n); count += n; nbytes -= n; @@ -2759,7 +2816,7 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, while (count == 0 && !retval) { set_current_state(TASK_INTERRUPTIBLE); - m = comedi_buf_read_n_available(s); + m = _comedi_buf_read_n_available(s); n = min_t(size_t, m, nbytes); if (n == 0) { @@ -2799,8 +2856,8 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, retval = -EFAULT; } - comedi_buf_read_alloc(s, n); - comedi_buf_read_free(s, n); + _comedi_buf_read_alloc(s, n); + _comedi_buf_read_free(s, n); count += n; nbytes -= n; @@ -2834,7 +2891,7 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, 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) + _comedi_buf_read_n_available(s) == 0) do_become_nonbusy(dev, s); mutex_unlock(&dev->mutex); } @@ -3023,7 +3080,12 @@ static int compat_chaninfo(struct file *file, unsigned long arg) chaninfo.rangelist = compat_ptr(chaninfo32.rangelist); mutex_lock(&dev->mutex); - err = do_chaninfo_ioctl(dev, &chaninfo); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + err = -ENODEV; + } else { + err = do_chaninfo_ioctl(dev, &chaninfo); + } mutex_unlock(&dev->mutex); return err; } @@ -3044,7 +3106,12 @@ static int compat_rangeinfo(struct file *file, unsigned long arg) rangeinfo.range_ptr = compat_ptr(rangeinfo32.range_ptr); mutex_lock(&dev->mutex); - err = do_rangeinfo_ioctl(dev, &rangeinfo); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + err = -ENODEV; + } else { + err = do_rangeinfo_ioctl(dev, &rangeinfo); + } mutex_unlock(&dev->mutex); return err; } @@ -3120,7 +3187,12 @@ static int compat_cmd(struct file *file, unsigned long arg) return rc; mutex_lock(&dev->mutex); - rc = do_cmd_ioctl(dev, &cmd, ©, file); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + rc = -ENODEV; + } else { + rc = do_cmd_ioctl(dev, &cmd, ©, file); + } mutex_unlock(&dev->mutex); if (copy) { /* Special case: copy cmd back to user. */ @@ -3145,7 +3217,12 @@ static int compat_cmdtest(struct file *file, unsigned long arg) return rc; mutex_lock(&dev->mutex); - rc = do_cmdtest_ioctl(dev, &cmd, ©, file); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + rc = -ENODEV; + } else { + rc = do_cmdtest_ioctl(dev, &cmd, ©, file); + } mutex_unlock(&dev->mutex); if (copy) { err = put_compat_cmd(compat_ptr(arg), &cmd); @@ -3205,7 +3282,12 @@ static int compat_insnlist(struct file *file, unsigned long arg) } mutex_lock(&dev->mutex); - rc = do_insnlist_ioctl(dev, insns, insnlist32.n_insns, file); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + rc = -ENODEV; + } else { + rc = do_insnlist_ioctl(dev, insns, insnlist32.n_insns, file); + } mutex_unlock(&dev->mutex); kfree(insns); return rc; @@ -3224,7 +3306,12 @@ static int compat_insn(struct file *file, unsigned long arg) return rc; mutex_lock(&dev->mutex); - rc = do_insn_ioctl(dev, &insn, file); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + rc = -ENODEV; + } else { + rc = do_insn_ioctl(dev, &insn, file); + } mutex_unlock(&dev->mutex); return rc; } @@ -3299,18 +3386,7 @@ static const struct file_operations comedi_fops = { .llseek = noop_llseek, }; -/** - * 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 - * interrupt handler. These may change the run state of the asynchronous - * command, wake a task, and/or send a %SIGIO signal. - */ -void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s) +void _comedi_event(struct comedi_device *dev, struct comedi_subdevice *s) { struct comedi_async *async = s->async; unsigned int events; @@ -3346,6 +3422,25 @@ void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s) if (si_code) kill_fasync(&dev->async_queue, SIGIO, si_code); } + +/** + * 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 + * interrupt handler. These may change the run state of the asynchronous + * command, wake a task, and/or send a %SIGIO signal. + */ +void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s) +{ + if (comedi_get_is_subdevice_running(s)) { + comedi_event(dev, s); + comedi_put_is_subdevice_running(s); + } +} EXPORT_SYMBOL_GPL(comedi_event); /* Note: the ->mutex is pre-locked on successful return */ diff --git a/drivers/comedi/comedi_internal.h b/drivers/comedi/comedi_internal.h index cf10ba016ebc..41a3b09f8f05 100644 --- a/drivers/comedi/comedi_internal.h +++ b/drivers/comedi/comedi_internal.h @@ -36,6 +36,18 @@ struct comedi_buf_map * comedi_buf_map_from_subdev_get(struct comedi_subdevice *s); unsigned int comedi_buf_write_n_available(struct comedi_subdevice *s); unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s); +unsigned int _comedi_buf_write_alloc(struct comedi_subdevice *s, + unsigned int nbytes); +unsigned int _comedi_buf_write_free(struct comedi_subdevice *s, + unsigned int nbytes); +unsigned int _comedi_buf_read_n_available(struct comedi_subdevice *s); +unsigned int _comedi_buf_read_alloc(struct comedi_subdevice *s, + unsigned int nbytes); +unsigned int _comedi_buf_read_free(struct comedi_subdevice *s, + unsigned int nbytes); +void _comedi_inc_scan_progress(struct comedi_subdevice *s, + unsigned int num_bytes); +void _comedi_event(struct comedi_device *dev, struct comedi_subdevice *s); void comedi_device_cancel_all(struct comedi_device *dev); bool comedi_can_auto_free_spriv(struct comedi_subdevice *s); diff --git a/drivers/comedi/drivers.c b/drivers/comedi/drivers.c index c9ebaadc5e82..69cd2a253c66 100644 --- a/drivers/comedi/drivers.c +++ b/drivers/comedi/drivers.c @@ -441,6 +441,13 @@ unsigned int comedi_bytes_per_scan_cmd(struct comedi_subdevice *s, } EXPORT_SYMBOL_GPL(comedi_bytes_per_scan_cmd); +static unsigned int _comedi_bytes_per_scan(struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + return comedi_bytes_per_scan_cmd(s, cmd); +} + /** * comedi_bytes_per_scan() - Get length of asynchronous command "scan" in bytes * @s: COMEDI subdevice. @@ -458,9 +465,16 @@ EXPORT_SYMBOL_GPL(comedi_bytes_per_scan_cmd); */ unsigned int comedi_bytes_per_scan(struct comedi_subdevice *s) { - struct comedi_cmd *cmd = &s->async->cmd; + unsigned int num_bytes; - return comedi_bytes_per_scan_cmd(s, cmd); + if (comedi_get_is_subdevice_running(s)) { + num_bytes = _comedi_bytes_per_scan(s); + comedi_put_is_subdevice_running(s); + } else { + /* Use nomimal, single sample scan length. */ + num_bytes = comedi_samples_to_bytes(s, 1); + } + return num_bytes; } EXPORT_SYMBOL_GPL(comedi_bytes_per_scan); @@ -482,6 +496,17 @@ static unsigned int __comedi_nscans_left(struct comedi_subdevice *s, return nscans; } +static unsigned int _comedi_nscans_left(struct comedi_subdevice *s, + unsigned int nscans) +{ + if (nscans == 0) { + unsigned int nbytes = _comedi_buf_read_n_available(s); + + nscans = nbytes / _comedi_bytes_per_scan(s); + } + return __comedi_nscans_left(s, nscans); +} + /** * comedi_nscans_left() - Return the number of scans left in the command * @s: COMEDI subdevice. @@ -499,25 +524,18 @@ static unsigned int __comedi_nscans_left(struct comedi_subdevice *s, unsigned int comedi_nscans_left(struct comedi_subdevice *s, unsigned int nscans) { - if (nscans == 0) { - unsigned int nbytes = comedi_buf_read_n_available(s); - - nscans = nbytes / comedi_bytes_per_scan(s); + if (comedi_get_is_subdevice_running(s)) { + nscans = _comedi_nscans_left(s, nscans); + comedi_put_is_subdevice_running(s); + } else { + nscans = 0; } - return __comedi_nscans_left(s, nscans); + return nscans; } EXPORT_SYMBOL_GPL(comedi_nscans_left); -/** - * comedi_nsamples_left() - Return the number of samples left in the command - * @s: COMEDI subdevice. - * @nsamples: The expected number of samples. - * - * Returns the number of samples remaining to complete the command, or the - * specified expected number of samples (@nsamples), whichever is fewer. - */ -unsigned int comedi_nsamples_left(struct comedi_subdevice *s, - unsigned int nsamples) +static unsigned int _comedi_nsamples_left(struct comedi_subdevice *s, + unsigned int nsamples) { struct comedi_async *async = s->async; struct comedi_cmd *cmd = &async->cmd; @@ -538,24 +556,34 @@ unsigned int comedi_nsamples_left(struct comedi_subdevice *s, return samples_left; return nsamples; } -EXPORT_SYMBOL_GPL(comedi_nsamples_left); /** - * comedi_inc_scan_progress() - Update scan progress in asynchronous command + * comedi_nsamples_left() - Return the number of samples left in the command * @s: COMEDI subdevice. - * @num_bytes: Amount of data in bytes to increment scan progress. + * @nsamples: The expected number of samples. * - * Increments the scan progress by the number of bytes specified by @num_bytes. - * If the scan progress reaches or exceeds the scan length in bytes, reduce - * it modulo the scan length in bytes and set the "end of scan" asynchronous - * event flag (%COMEDI_CB_EOS) to be processed later. + * Returns the number of samples remaining to complete the command, or the + * specified expected number of samples (@nsamples), whichever is fewer. */ -void comedi_inc_scan_progress(struct comedi_subdevice *s, - unsigned int num_bytes) +unsigned int comedi_nsamples_left(struct comedi_subdevice *s, + unsigned int nsamples) +{ + if (comedi_get_is_subdevice_running(s)) { + nsamples = _comedi_nsamples_left(s, nsamples); + comedi_put_is_subdevice_running(s); + } else { + nsamples = 0; + } + return nsamples; +} +EXPORT_SYMBOL_GPL(comedi_nsamples_left); + +void _comedi_inc_scan_progress(struct comedi_subdevice *s, + unsigned int num_bytes) { struct comedi_async *async = s->async; struct comedi_cmd *cmd = &async->cmd; - unsigned int scan_length = comedi_bytes_per_scan(s); + unsigned int scan_length = _comedi_bytes_per_scan(s); /* track the 'cur_chan' for non-SDF_PACKED subdevices */ if (!(s->subdev_flags & SDF_PACKED)) { @@ -576,8 +604,43 @@ void comedi_inc_scan_progress(struct comedi_subdevice *s, async->events |= COMEDI_CB_EOS; } } + +/** + * comedi_inc_scan_progress() - Update scan progress in asynchronous command + * @s: COMEDI subdevice. + * @num_bytes: Amount of data in bytes to increment scan progress. + * + * Increments the scan progress by the number of bytes specified by @num_bytes. + * If the scan progress reaches or exceeds the scan length in bytes, reduce + * it modulo the scan length in bytes and set the "end of scan" asynchronous + * event flag (%COMEDI_CB_EOS) to be processed later. + */ +void comedi_inc_scan_progress(struct comedi_subdevice *s, + unsigned int num_bytes) +{ + if (comedi_get_is_subdevice_running(s)) { + _comedi_inc_scan_progress(s, num_bytes); + comedi_put_is_subdevice_running(s); + } +} EXPORT_SYMBOL_GPL(comedi_inc_scan_progress); +static unsigned int _comedi_handle_events(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int events = s->async->events; + + if (events == 0) + return events; + + if ((events & COMEDI_CB_CANCEL_MASK) && s->cancel) + s->cancel(dev, s); + + _comedi_event(dev, s); + + return events; +} + /** * comedi_handle_events() - Handle events and possibly stop acquisition * @dev: COMEDI device. @@ -597,16 +660,14 @@ EXPORT_SYMBOL_GPL(comedi_inc_scan_progress); unsigned int comedi_handle_events(struct comedi_device *dev, struct comedi_subdevice *s) { - unsigned int events = s->async->events; - - if (events == 0) - return events; - - if ((events & COMEDI_CB_CANCEL_MASK) && s->cancel) - s->cancel(dev, s); - - comedi_event(dev, s); + unsigned int events; + if (comedi_get_is_subdevice_running(s)) { + events = _comedi_handle_events(dev, s); + comedi_put_is_subdevice_running(s); + } else { + events = 0; + } return events; } EXPORT_SYMBOL_GPL(comedi_handle_events); @@ -677,6 +738,7 @@ static int __comedi_device_postconfig_async(struct comedi_device *dev, return -ENOMEM; init_waitqueue_head(&async->wait_head); + init_completion(&async->run_complete); s->async = async; async->max_bufsize = comedi_default_buf_maxsize_kb * 1024; diff --git a/drivers/comedi/drivers/8255.c b/drivers/comedi/drivers/8255.c index f45f7bd1c61a..5f70938b4477 100644 --- a/drivers/comedi/drivers/8255.c +++ b/drivers/comedi/drivers/8255.c @@ -77,19 +77,17 @@ static int dev_8255_attach(struct comedi_device *dev, * base address of the chip. */ ret = __comedi_request_region(dev, iobase, I8255_SIZE); + if (ret) + return ret; + ret = subdev_8255_io_init(dev, s, iobase); if (ret) { + /* + * Release the I/O port region here, as the + * "detach" handler cannot find it. + */ + release_region(iobase, I8255_SIZE); s->type = COMEDI_SUBD_UNUSED; - } else { - ret = subdev_8255_io_init(dev, s, iobase); - if (ret) { - /* - * Release the I/O port region here, as the - * "detach" handler cannot find it. - */ - release_region(iobase, I8255_SIZE); - s->type = COMEDI_SUBD_UNUSED; - return ret; - } + return ret; } } diff --git a/drivers/comedi/drivers/c6xdigio.c b/drivers/comedi/drivers/c6xdigio.c index 14b90d1c64dc..8a38d97d463b 100644 --- a/drivers/comedi/drivers/c6xdigio.c +++ b/drivers/comedi/drivers/c6xdigio.c @@ -249,9 +249,6 @@ static int c6xdigio_attach(struct comedi_device *dev, if (ret) return ret; - /* Make sure that PnP ports get activated */ - pnp_register_driver(&c6xdigio_pnp_driver); - s = &dev->subdevices[0]; /* pwm output subdevice */ s->type = COMEDI_SUBD_PWM; @@ -278,19 +275,46 @@ static int c6xdigio_attach(struct comedi_device *dev, return 0; } -static void c6xdigio_detach(struct comedi_device *dev) -{ - comedi_legacy_detach(dev); - pnp_unregister_driver(&c6xdigio_pnp_driver); -} - static struct comedi_driver c6xdigio_driver = { .driver_name = "c6xdigio", .module = THIS_MODULE, .attach = c6xdigio_attach, - .detach = c6xdigio_detach, + .detach = comedi_legacy_detach, }; -module_comedi_driver(c6xdigio_driver); + +static bool c6xdigio_pnp_registered = false; + +static int __init c6xdigio_module_init(void) +{ + int ret; + + ret = comedi_driver_register(&c6xdigio_driver); + if (ret) + return ret; + + if (IS_ENABLED(CONFIG_PNP)) { + /* Try to activate the PnP ports */ + ret = pnp_register_driver(&c6xdigio_pnp_driver); + if (ret) { + pr_warn("failed to register pnp driver - err %d\n", + ret); + ret = 0; /* ignore the error. */ + } else { + c6xdigio_pnp_registered = true; + } + } + + return 0; +} +module_init(c6xdigio_module_init); + +static void __exit c6xdigio_module_exit(void) +{ + if (c6xdigio_pnp_registered) + pnp_unregister_driver(&c6xdigio_pnp_driver); + comedi_driver_unregister(&c6xdigio_driver); +} +module_exit(c6xdigio_module_exit); MODULE_AUTHOR("Comedi https://www.comedi.org"); MODULE_DESCRIPTION("Comedi driver for the C6x_DIGIO DSP daughter card"); diff --git a/drivers/comedi/drivers/comedi_bond.c b/drivers/comedi/drivers/comedi_bond.c index 78c39fa84177..30650fa36fff 100644 --- a/drivers/comedi/drivers/comedi_bond.c +++ b/drivers/comedi/drivers/comedi_bond.c @@ -205,7 +205,7 @@ static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it) snprintf(file, sizeof(file), "/dev/comedi%d", minor); file[sizeof(file) - 1] = 0; - d = comedi_open(file); + d = comedi_open_from(file, dev->minor); if (!d) { dev_err(dev->class_dev, @@ -326,7 +326,7 @@ static void bonding_detach(struct comedi_device *dev) if (!bdev) continue; if (!test_and_set_bit(bdev->minor, devs_closed)) - comedi_close(bdev->dev); + comedi_close_from(bdev->dev, dev->minor); kfree(bdev); } kfree(devpriv->devs); diff --git a/drivers/comedi/drivers/multiq3.c b/drivers/comedi/drivers/multiq3.c index 07ff5383da99..ac369e9a262d 100644 --- a/drivers/comedi/drivers/multiq3.c +++ b/drivers/comedi/drivers/multiq3.c @@ -67,6 +67,11 @@ #define MULTIQ3_TRSFRCNTR_OL 0x10 /* xfer CNTR to OL (x and y) */ #define MULTIQ3_EFLAG_RESET 0x06 /* reset E bit of flag reg */ +/* + * Limit on the number of optional encoder channels + */ +#define MULTIQ3_MAX_ENC_CHANS 8 + static void multiq3_set_ctrl(struct comedi_device *dev, unsigned int bits) { /* @@ -312,6 +317,10 @@ static int multiq3_attach(struct comedi_device *dev, s->insn_read = multiq3_encoder_insn_read; s->insn_config = multiq3_encoder_insn_config; + /* sanity check for number of encoder channels */ + if (s->n_chan > MULTIQ3_MAX_ENC_CHANS) + s->n_chan = MULTIQ3_MAX_ENC_CHANS; + for (i = 0; i < s->n_chan; i++) multiq3_encoder_reset(dev, i); diff --git a/drivers/comedi/drivers/ni_670x.c b/drivers/comedi/drivers/ni_670x.c index c875d251c230..563a9c790f12 100644 --- a/drivers/comedi/drivers/ni_670x.c +++ b/drivers/comedi/drivers/ni_670x.c @@ -199,7 +199,7 @@ static int ni_670x_auto_attach(struct comedi_device *dev, const struct comedi_lrange **range_table_list; range_table_list = kmalloc_array(32, - sizeof(struct comedi_lrange *), + sizeof(*range_table_list), GFP_KERNEL); if (!range_table_list) return -ENOMEM; diff --git a/drivers/comedi/drivers/pcl818.c b/drivers/comedi/drivers/pcl818.c index 4127adcfb229..06fe06396f23 100644 --- a/drivers/comedi/drivers/pcl818.c +++ b/drivers/comedi/drivers/pcl818.c @@ -1111,10 +1111,9 @@ static void pcl818_detach(struct comedi_device *dev) { struct pcl818_private *devpriv = dev->private; - if (devpriv) { - pcl818_ai_cancel(dev, dev->read_subdev); + if (devpriv) pcl818_reset(dev); - } + pcl818_free_dma(dev); comedi_legacy_detach(dev); } diff --git a/drivers/comedi/kcomedilib/kcomedilib_main.c b/drivers/comedi/kcomedilib/kcomedilib_main.c index 43fbe1a63b14..baa9eaaf97d4 100644 --- a/drivers/comedi/kcomedilib/kcomedilib_main.c +++ b/drivers/comedi/kcomedilib/kcomedilib_main.c @@ -15,6 +15,7 @@ #include <linux/fcntl.h> #include <linux/mm.h> #include <linux/io.h> +#include <linux/bitmap.h> #include <linux/comedi.h> #include <linux/comedi/comedidev.h> @@ -24,7 +25,104 @@ MODULE_AUTHOR("David Schleef <ds@schleef.org>"); MODULE_DESCRIPTION("Comedi kernel library"); MODULE_LICENSE("GPL"); -struct comedi_device *comedi_open(const char *filename) +static DEFINE_MUTEX(kcomedilib_to_from_lock); + +/* + * Row index is the "to" node, column index is the "from" node, element value + * is the number of links from the "from" node to the "to" node. + */ +static unsigned char + kcomedilib_to_from[COMEDI_NUM_BOARD_MINORS][COMEDI_NUM_BOARD_MINORS]; + +static bool kcomedilib_set_link_from_to(unsigned int from, unsigned int to) +{ + DECLARE_BITMAP(destinations[2], COMEDI_NUM_BOARD_MINORS); + unsigned int cur = 0; + bool okay = true; + + /* + * Allow "from" node to be out of range (no loop checking), + * but require "to" node to be in range. + */ + if (to >= COMEDI_NUM_BOARD_MINORS) + return false; + if (from >= COMEDI_NUM_BOARD_MINORS) + return true; + + /* + * Check that kcomedilib_to_from[to][from] can be made non-zero + * without creating a loop. + * + * Termination of the loop-testing code relies on the assumption that + * kcomedilib_to_from[][] does not contain any loops. + * + * Start with a set destinations set containing "from" as the only + * element and work backwards looking for loops. + */ + bitmap_zero(destinations[cur], COMEDI_NUM_BOARD_MINORS); + set_bit(from, destinations[cur]); + mutex_lock(&kcomedilib_to_from_lock); + do { + unsigned int next = 1 - cur; + unsigned int t = 0; + + if (test_bit(to, destinations[cur])) { + /* Loop detected. */ + okay = false; + break; + } + /* Create next set of destinations. */ + bitmap_zero(destinations[next], COMEDI_NUM_BOARD_MINORS); + while ((t = find_next_bit(destinations[cur], + COMEDI_NUM_BOARD_MINORS, + t)) < COMEDI_NUM_BOARD_MINORS) { + unsigned int f; + + for (f = 0; f < COMEDI_NUM_BOARD_MINORS; f++) { + if (kcomedilib_to_from[t][f]) + set_bit(f, destinations[next]); + } + t++; + } + cur = next; + } while (!bitmap_empty(destinations[cur], COMEDI_NUM_BOARD_MINORS)); + if (okay) { + /* Allow a maximum of 255 links from "from" to "to". */ + if (kcomedilib_to_from[to][from] < 255) + kcomedilib_to_from[to][from]++; + else + okay = false; + } + mutex_unlock(&kcomedilib_to_from_lock); + return okay; +} + +static void kcomedilib_clear_link_from_to(unsigned int from, unsigned int to) +{ + if (to < COMEDI_NUM_BOARD_MINORS && from < COMEDI_NUM_BOARD_MINORS) { + mutex_lock(&kcomedilib_to_from_lock); + if (kcomedilib_to_from[to][from]) + kcomedilib_to_from[to][from]--; + mutex_unlock(&kcomedilib_to_from_lock); + } +} + +/** + * comedi_open_from() - Open a COMEDI device from the kernel with loop checks + * @filename: Fake pathname of the form "/dev/comediN". + * @from: Device number it is being opened from (if in range). + * + * Converts @filename to a COMEDI device number and "opens" it if it exists + * and is attached to a low-level COMEDI driver. + * + * If @from is in range, refuse to open the device if doing so would form a + * loop of devices opening each other. There is also a limit of 255 on the + * number of concurrent opens from one device to another. + * + * Return: A pointer to the COMEDI device on success. + * Return %NULL on failure. + */ +struct comedi_device *comedi_open_from(const char *filename, int from) { struct comedi_device *dev, *retval = NULL; unsigned int minor; @@ -43,7 +141,7 @@ struct comedi_device *comedi_open(const char *filename) return NULL; down_read(&dev->attach_lock); - if (dev->attached) + if (dev->attached && kcomedilib_set_link_from_to(from, minor)) retval = dev; else retval = NULL; @@ -54,14 +152,26 @@ struct comedi_device *comedi_open(const char *filename) return retval; } -EXPORT_SYMBOL_GPL(comedi_open); +EXPORT_SYMBOL_GPL(comedi_open_from); -int comedi_close(struct comedi_device *dev) +/** + * comedi_close_from() - Close a COMEDI device from the kernel with loop checks + * @dev: COMEDI device. + * @from: Device number it was opened from (if in range). + * + * Closes a COMEDI device previously opened by comedi_open_from(). + * + * If @from is in range, it should be match the one used by comedi_open_from(). + * + * Returns: 0 + */ +int comedi_close_from(struct comedi_device *dev, int from) { + kcomedilib_clear_link_from_to(from, dev->minor); comedi_dev_put(dev); return 0; } -EXPORT_SYMBOL_GPL(comedi_close); +EXPORT_SYMBOL_GPL(comedi_close_from); static int comedi_do_insn(struct comedi_device *dev, struct comedi_insn *insn, |
