aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/comedi/comedi_fops.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/comedi/comedi_fops.c')
-rw-r--r--drivers/staging/comedi/comedi_fops.c279
1 files changed, 237 insertions, 42 deletions
diff --git a/drivers/staging/comedi/comedi_fops.c b/drivers/staging/comedi/comedi_fops.c
index 9c32f0276009..f143cb64d69e 100644
--- a/drivers/staging/comedi/comedi_fops.c
+++ b/drivers/staging/comedi/comedi_fops.c
@@ -43,6 +43,22 @@
#include "comedi_internal.h"
+/**
+ * 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 {
+ struct comedi_device *dev;
+ struct comedi_subdevice *read_subdev;
+ struct comedi_subdevice *write_subdev;
+ unsigned int last_detach_count;
+ bool last_attached:1;
+};
+
#define COMEDI_NUM_MINORS 0x100
#define COMEDI_NUM_SUBDEVICE_MINORS \
(COMEDI_NUM_MINORS - COMEDI_NUM_BOARD_MINORS)
@@ -239,6 +255,54 @@ comedi_write_subdevice(const struct comedi_device *dev, unsigned int minor)
return dev->write_subdev;
}
+static void comedi_file_reset(struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+ struct comedi_subdevice *s, *read_s, *write_s;
+ unsigned int minor = iminor(file_inode(file));
+
+ read_s = dev->read_subdev;
+ write_s = dev->write_subdev;
+ if (minor >= COMEDI_NUM_BOARD_MINORS) {
+ s = comedi_subdevice_from_minor(dev, minor);
+ if (s == NULL || s->subdev_flags & SDF_CMD_READ)
+ read_s = s;
+ if (s == NULL || s->subdev_flags & SDF_CMD_WRITE)
+ write_s = s;
+ }
+ cfp->last_attached = dev->attached;
+ cfp->last_detach_count = dev->detach_count;
+ ACCESS_ONCE(cfp->read_subdev) = read_s;
+ ACCESS_ONCE(cfp->write_subdev) = write_s;
+}
+
+static void comedi_file_check(struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
+
+ if (cfp->last_attached != dev->attached ||
+ cfp->last_detach_count != dev->detach_count)
+ comedi_file_reset(file);
+}
+
+static struct comedi_subdevice *comedi_file_read_subdevice(struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+
+ comedi_file_check(file);
+ return ACCESS_ONCE(cfp->read_subdev);
+}
+
+static struct comedi_subdevice *comedi_file_write_subdevice(struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+
+ comedi_file_check(file);
+ return ACCESS_ONCE(cfp->write_subdev);
+}
+
static int resize_async_buffer(struct comedi_device *dev,
struct comedi_subdevice *s, unsigned new_size)
{
@@ -776,7 +840,6 @@ static int do_devinfo_ioctl(struct comedi_device *dev,
struct comedi_devinfo __user *arg,
struct file *file)
{
- const unsigned minor = iminor(file_inode(file));
struct comedi_subdevice *s;
struct comedi_devinfo devinfo;
@@ -788,13 +851,13 @@ static int do_devinfo_ioctl(struct comedi_device *dev,
strlcpy(devinfo.driver_name, dev->driver->driver_name, COMEDI_NAMELEN);
strlcpy(devinfo.board_name, dev->board_name, COMEDI_NAMELEN);
- s = comedi_read_subdevice(dev, minor);
+ s = comedi_file_read_subdevice(file);
if (s)
devinfo.read_subdevice = s->index;
else
devinfo.read_subdevice = -1;
- s = comedi_write_subdevice(dev, minor);
+ s = comedi_file_write_subdevice(file);
if (s)
devinfo.write_subdevice = s->index;
else
@@ -991,7 +1054,7 @@ static int do_bufinfo_ioctl(struct comedi_device *dev,
if (s->busy != file)
return -EACCES;
- if (bi.bytes_read && (s->subdev_flags & SDF_CMD_READ)) {
+ if (bi.bytes_read && !(async->cmd.flags & CMDF_WRITE)) {
bi.bytes_read = comedi_buf_read_alloc(s, bi.bytes_read);
comedi_buf_read_free(s, bi.bytes_read);
@@ -1001,7 +1064,7 @@ static int do_bufinfo_ioctl(struct comedi_device *dev,
}
}
- if (bi.bytes_written && (s->subdev_flags & SDF_CMD_WRITE)) {
+ if (bi.bytes_written && (async->cmd.flags & CMDF_WRITE)) {
bi.bytes_written =
comedi_buf_write_alloc(s, bi.bytes_written);
comedi_buf_write_free(s, bi.bytes_written);
@@ -1451,6 +1514,21 @@ static int __comedi_get_user_cmd(struct comedi_device *dev,
return -EINVAL;
}
+ /*
+ * Set the CMDF_WRITE flag to the correct state if the subdevice
+ * supports only "read" commands or only "write" commands.
+ */
+ switch (s->subdev_flags & (SDF_CMD_READ | SDF_CMD_WRITE)) {
+ case SDF_CMD_READ:
+ cmd->flags &= ~CMDF_WRITE;
+ break;
+ case SDF_CMD_WRITE:
+ cmd->flags |= CMDF_WRITE;
+ break;
+ default:
+ break;
+ }
+
return 0;
}
@@ -1552,9 +1630,7 @@ static int do_cmd_ioctl(struct comedi_device *dev,
comedi_buf_reset(s);
- async->cb_mask =
- COMEDI_CB_EOA | COMEDI_CB_BLOCK | COMEDI_CB_ERROR |
- COMEDI_CB_OVERFLOW;
+ async->cb_mask = COMEDI_CB_BLOCK | COMEDI_CB_CANCEL_MASK;
if (async->cmd.flags & CMDF_WAKE_EOS)
async->cb_mask |= COMEDI_CB_EOS;
@@ -1720,7 +1796,6 @@ static int do_cancel_ioctl(struct comedi_device *dev, unsigned long arg,
void *file)
{
struct comedi_subdevice *s;
- int ret;
if (arg >= dev->n_subdevices)
return -EINVAL;
@@ -1734,9 +1809,7 @@ static int do_cancel_ioctl(struct comedi_device *dev, unsigned long arg,
if (s->busy != file)
return -EBUSY;
- ret = do_cancel(dev, s);
-
- return ret;
+ return do_cancel(dev, s);
}
/*
@@ -1774,11 +1847,96 @@ static int do_poll_ioctl(struct comedi_device *dev, unsigned long arg,
return -EINVAL;
}
+/*
+ * COMEDI_SETRSUBD ioctl
+ * sets the current "read" subdevice on a per-file basis
+ *
+ * arg:
+ * subdevice number
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * nothing
+ */
+static int do_setrsubd_ioctl(struct comedi_device *dev, unsigned long arg,
+ struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_subdevice *s_old, *s_new;
+
+ if (arg >= dev->n_subdevices)
+ return -EINVAL;
+
+ s_new = &dev->subdevices[arg];
+ s_old = comedi_file_read_subdevice(file);
+ if (s_old == s_new)
+ return 0; /* no change */
+
+ if (!(s_new->subdev_flags & SDF_CMD_READ))
+ return -EINVAL;
+
+ /*
+ * Check the file isn't still busy handling a "read" command on the
+ * old subdevice (if any).
+ */
+ if (s_old && s_old->busy == file && s_old->async &&
+ !(s_old->async->cmd.flags & CMDF_WRITE))
+ return -EBUSY;
+
+ ACCESS_ONCE(cfp->read_subdev) = s_new;
+ return 0;
+}
+
+/*
+ * COMEDI_SETWSUBD ioctl
+ * sets the current "write" subdevice on a per-file basis
+ *
+ * arg:
+ * subdevice number
+ *
+ * reads:
+ * nothing
+ *
+ * writes:
+ * nothing
+ */
+static int do_setwsubd_ioctl(struct comedi_device *dev, unsigned long arg,
+ struct file *file)
+{
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_subdevice *s_old, *s_new;
+
+ if (arg >= dev->n_subdevices)
+ return -EINVAL;
+
+ s_new = &dev->subdevices[arg];
+ s_old = comedi_file_write_subdevice(file);
+ if (s_old == s_new)
+ return 0; /* no change */
+
+ if (!(s_new->subdev_flags & SDF_CMD_WRITE))
+ return -EINVAL;
+
+ /*
+ * Check the file isn't still busy handling a "write" command on the
+ * old subdevice (if any).
+ */
+ if (s_old && s_old->busy == file && s_old->async &&
+ (s_old->async->cmd.flags & CMDF_WRITE))
+ return -EBUSY;
+
+ ACCESS_ONCE(cfp->write_subdev) = s_new;
+ return 0;
+}
+
static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
- const unsigned minor = iminor(file_inode(file));
- struct comedi_device *dev = file->private_data;
+ unsigned minor = iminor(file_inode(file));
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
int rc;
mutex_lock(&dev->mutex);
@@ -1867,6 +2025,12 @@ static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd,
case COMEDI_POLL:
rc = do_poll_ioctl(dev, arg, file);
break;
+ case COMEDI_SETRSUBD:
+ rc = do_setrsubd_ioctl(dev, arg, file);
+ break;
+ case COMEDI_SETWSUBD:
+ rc = do_setwsubd_ioctl(dev, arg, file);
+ break;
default:
rc = -ENOTTY;
break;
@@ -1900,8 +2064,8 @@ static struct vm_operations_struct comedi_vm_ops = {
static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
{
- const unsigned minor = iminor(file_inode(file));
- struct comedi_device *dev = file->private_data;
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
struct comedi_subdevice *s;
struct comedi_async *async;
struct comedi_buf_map *bm = NULL;
@@ -1927,9 +2091,9 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
}
if (vma->vm_flags & VM_WRITE)
- s = comedi_write_subdevice(dev, minor);
+ s = comedi_file_write_subdevice(file);
else
- s = comedi_read_subdevice(dev, minor);
+ s = comedi_file_read_subdevice(file);
if (!s) {
retval = -EINVAL;
goto done;
@@ -1992,8 +2156,8 @@ done:
static unsigned int comedi_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
- const unsigned minor = iminor(file_inode(file));
- struct comedi_device *dev = file->private_data;
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
struct comedi_subdevice *s;
mutex_lock(&dev->mutex);
@@ -2003,21 +2167,23 @@ static unsigned int comedi_poll(struct file *file, poll_table *wait)
goto done;
}
- s = comedi_read_subdevice(dev, minor);
+ s = comedi_file_read_subdevice(file);
if (s && s->async) {
poll_wait(file, &s->async->wait_head, wait);
if (!s->busy || !comedi_is_subdevice_running(s) ||
+ (s->async->cmd.flags & CMDF_WRITE) ||
comedi_buf_read_n_available(s) > 0)
mask |= POLLIN | POLLRDNORM;
}
- s = comedi_write_subdevice(dev, minor);
+ s = comedi_file_write_subdevice(file);
if (s && s->async) {
- unsigned int bps = bytes_per_sample(s);
+ 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) ||
+ !(s->async->cmd.flags & CMDF_WRITE) ||
comedi_buf_write_n_allocated(s) >= bps)
mask |= POLLOUT | POLLWRNORM;
}
@@ -2034,8 +2200,8 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
struct comedi_async *async;
int n, m, count = 0, retval = 0;
DECLARE_WAITQUEUE(wait, current);
- const unsigned minor = iminor(file_inode(file));
- struct comedi_device *dev = file->private_data;
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
bool on_wait_queue = false;
bool attach_locked;
unsigned int old_detach_count;
@@ -2051,7 +2217,7 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
goto out;
}
- s = comedi_write_subdevice(dev, minor);
+ s = comedi_file_write_subdevice(file);
if (!s || !s->async) {
retval = -EIO;
goto out;
@@ -2065,6 +2231,10 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
retval = -EACCES;
goto out;
}
+ if (!(async->cmd.flags & CMDF_WRITE)) {
+ retval = -EINVAL;
+ goto out;
+ }
add_wait_queue(&async->wait_head, &wait);
on_wait_queue = true;
@@ -2099,7 +2269,7 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
* meantime!), but check the subdevice pointer
* as well just in case.
*/
- new_s = comedi_write_subdevice(dev, minor);
+ new_s = comedi_file_write_subdevice(file);
if (dev->attached &&
old_detach_count == dev->detach_count &&
s == new_s && new_s->async == async)
@@ -2136,6 +2306,10 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
retval = -EACCES;
break;
}
+ if (!(async->cmd.flags & CMDF_WRITE)) {
+ retval = -EINVAL;
+ break;
+ }
continue;
}
@@ -2170,8 +2344,8 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
struct comedi_async *async;
int n, m, count = 0, retval = 0;
DECLARE_WAITQUEUE(wait, current);
- const unsigned minor = iminor(file_inode(file));
- struct comedi_device *dev = file->private_data;
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
unsigned int old_detach_count;
bool become_nonbusy = false;
bool attach_locked;
@@ -2187,7 +2361,7 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
goto out;
}
- s = comedi_read_subdevice(dev, minor);
+ s = comedi_file_read_subdevice(file);
if (!s || !s->async) {
retval = -EIO;
goto out;
@@ -2200,6 +2374,10 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
retval = -EACCES;
goto out;
}
+ if (async->cmd.flags & CMDF_WRITE) {
+ retval = -EINVAL;
+ goto out;
+ }
add_wait_queue(&async->wait_head, &wait);
while (nbytes > 0 && !retval) {
@@ -2239,6 +2417,10 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
retval = -EACCES;
break;
}
+ if (async->cmd.flags & CMDF_WRITE) {
+ retval = -EINVAL;
+ break;
+ }
continue;
}
m = copy_to_user(buf, async->prealloc_buf +
@@ -2276,7 +2458,7 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
* meantime!), but check the subdevice pointer as well just in
* case.
*/
- new_s = comedi_read_subdevice(dev, minor);
+ 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)
@@ -2294,6 +2476,7 @@ out:
static int comedi_open(struct inode *inode, struct file *file)
{
const unsigned minor = iminor(inode);
+ struct comedi_file *cfp;
struct comedi_device *dev = comedi_dev_get_from_minor(minor);
int rc;
@@ -2302,6 +2485,12 @@ static int comedi_open(struct inode *inode, struct file *file)
return -ENODEV;
}
+ cfp = kzalloc(sizeof(*cfp), GFP_KERNEL);
+ if (!cfp)
+ return -ENOMEM;
+
+ 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");
@@ -2323,26 +2512,31 @@ static int comedi_open(struct inode *inode, struct file *file)
}
dev->use_count++;
- file->private_data = dev;
+ file->private_data = cfp;
+ comedi_file_reset(file);
rc = 0;
out:
mutex_unlock(&dev->mutex);
- if (rc)
+ if (rc) {
comedi_dev_put(dev);
+ kfree(cfp);
+ }
return rc;
}
static int comedi_fasync(int fd, struct file *file, int on)
{
- struct comedi_device *dev = file->private_data;
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
return fasync_helper(fd, file, on, &dev->async_queue);
}
static int comedi_close(struct inode *inode, struct file *file)
{
- struct comedi_device *dev = file->private_data;
+ struct comedi_file *cfp = file->private_data;
+ struct comedi_device *dev = cfp->dev;
struct comedi_subdevice *s = NULL;
int i;
@@ -2368,6 +2562,7 @@ static int comedi_close(struct inode *inode, struct file *file)
mutex_unlock(&dev->mutex);
comedi_dev_put(dev);
+ kfree(cfp);
return 0;
}
@@ -2395,14 +2590,14 @@ void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s)
if (!comedi_is_subdevice_running(s))
return;
- if (s->
- async->events & (COMEDI_CB_EOA | COMEDI_CB_ERROR |
- COMEDI_CB_OVERFLOW)) {
+ if (s->async->events & COMEDI_CB_CANCEL_MASK)
runflags_mask |= SRF_RUNNING;
- }
- /* remember if an error event has occurred, so an error
- * can be returned the next time the user does a read() */
- if (s->async->events & (COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) {
+
+ /*
+ * Remember if an error event has occurred, so an error
+ * can be returned the next time the user does a read().
+ */
+ if (s->async->events & COMEDI_CB_ERROR_MASK) {
runflags_mask |= SRF_ERROR;
runflags |= SRF_ERROR;
}