summaryrefslogtreecommitdiffstats
path: root/kernel/drivers/staging/comedi/comedi_fops.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drivers/staging/comedi/comedi_fops.c')
-rw-r--r--kernel/drivers/staging/comedi/comedi_fops.c245
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);