summaryrefslogtreecommitdiff
path: root/drivers/comedi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/comedi')
-rw-r--r--drivers/comedi/comedi_buf.c274
-rw-r--r--drivers/comedi/comedi_fops.c189
-rw-r--r--drivers/comedi/comedi_internal.h12
-rw-r--r--drivers/comedi/drivers.c134
-rw-r--r--drivers/comedi/drivers/8255.c20
-rw-r--r--drivers/comedi/drivers/c6xdigio.c46
-rw-r--r--drivers/comedi/drivers/comedi_bond.c4
-rw-r--r--drivers/comedi/drivers/multiq3.c9
-rw-r--r--drivers/comedi/drivers/ni_670x.c2
-rw-r--r--drivers/comedi/drivers/pcl818.c5
-rw-r--r--drivers/comedi/kcomedilib/kcomedilib_main.c120
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, &copy, file);
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ rc = -ENODEV;
+ } else {
+ rc = do_cmd_ioctl(dev, &cmd, &copy, 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, &copy, file);
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ rc = -ENODEV;
+ } else {
+ rc = do_cmdtest_ioctl(dev, &cmd, &copy, 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,