diff options
Diffstat (limited to 'drivers/staging/comedi/comedi_fops.c')
-rw-r--r-- | drivers/staging/comedi/comedi_fops.c | 3436 |
1 files changed, 0 insertions, 3436 deletions
diff --git a/drivers/staging/comedi/comedi_fops.c b/drivers/staging/comedi/comedi_fops.c deleted file mode 100644 index df77b6bf5c64..000000000000 --- a/drivers/staging/comedi/comedi_fops.c +++ /dev/null @@ -1,3436 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * comedi/comedi_fops.c - * comedi kernel module - * - * COMEDI - Linux Control and Measurement Device Interface - * Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org> - * compat ioctls: - * Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk> - * Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/> - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/module.h> -#include <linux/errno.h> -#include <linux/kernel.h> -#include <linux/sched/signal.h> -#include <linux/fcntl.h> -#include <linux/delay.h> -#include <linux/mm.h> -#include <linux/slab.h> -#include <linux/poll.h> -#include <linux/device.h> -#include <linux/fs.h> -#include "comedidev.h" -#include <linux/cdev.h> - -#include <linux/io.h> -#include <linux/uaccess.h> -#include <linux/compat.h> - -#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. - * @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; - unsigned int last_attached:1; -}; - -#define COMEDI_NUM_MINORS 0x100 -#define COMEDI_NUM_SUBDEVICE_MINORS \ - (COMEDI_NUM_MINORS - COMEDI_NUM_BOARD_MINORS) - -static unsigned short comedi_num_legacy_minors; -module_param(comedi_num_legacy_minors, ushort, 0444); -MODULE_PARM_DESC(comedi_num_legacy_minors, - "number of comedi minor devices to reserve for non-auto-configured devices (default 0)" - ); - -unsigned int comedi_default_buf_size_kb = CONFIG_COMEDI_DEFAULT_BUF_SIZE_KB; -module_param(comedi_default_buf_size_kb, uint, 0644); -MODULE_PARM_DESC(comedi_default_buf_size_kb, - "default asynchronous buffer size in KiB (default " - __MODULE_STRING(CONFIG_COMEDI_DEFAULT_BUF_SIZE_KB) ")"); - -unsigned int comedi_default_buf_maxsize_kb = - CONFIG_COMEDI_DEFAULT_BUF_MAXSIZE_KB; -module_param(comedi_default_buf_maxsize_kb, uint, 0644); -MODULE_PARM_DESC(comedi_default_buf_maxsize_kb, - "default maximum size of asynchronous buffer in KiB (default " - __MODULE_STRING(CONFIG_COMEDI_DEFAULT_BUF_MAXSIZE_KB) ")"); - -static DEFINE_MUTEX(comedi_board_minor_table_lock); -static struct comedi_device -*comedi_board_minor_table[COMEDI_NUM_BOARD_MINORS]; - -static DEFINE_MUTEX(comedi_subdevice_minor_table_lock); -/* Note: indexed by minor - COMEDI_NUM_BOARD_MINORS. */ -static struct comedi_subdevice -*comedi_subdevice_minor_table[COMEDI_NUM_SUBDEVICE_MINORS]; - -static struct class *comedi_class; -static struct cdev comedi_cdev; - -static void comedi_device_init(struct comedi_device *dev) -{ - kref_init(&dev->refcount); - spin_lock_init(&dev->spinlock); - mutex_init(&dev->mutex); - init_rwsem(&dev->attach_lock); - dev->minor = -1; -} - -static void comedi_dev_kref_release(struct kref *kref) -{ - struct comedi_device *dev = - container_of(kref, struct comedi_device, refcount); - - mutex_destroy(&dev->mutex); - put_device(dev->class_dev); - kfree(dev); -} - -/** - * 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. - * - * 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) -{ - if (dev) - return kref_put(&dev->refcount, comedi_dev_kref_release); - return 1; -} -EXPORT_SYMBOL_GPL(comedi_dev_put); - -static struct comedi_device *comedi_dev_get(struct comedi_device *dev) -{ - if (dev) - kref_get(&dev->refcount); - return dev; -} - -static void comedi_device_cleanup(struct comedi_device *dev) -{ - struct module *driver_module = NULL; - - if (!dev) - return; - mutex_lock(&dev->mutex); - if (dev->attached) - driver_module = dev->driver->module; - comedi_device_detach(dev); - if (driver_module && dev->use_count) - module_put(driver_module); - mutex_unlock(&dev->mutex); -} - -static bool comedi_clear_board_dev(struct comedi_device *dev) -{ - unsigned int i = dev->minor; - bool cleared = false; - - lockdep_assert_held(&dev->mutex); - mutex_lock(&comedi_board_minor_table_lock); - if (dev == comedi_board_minor_table[i]) { - comedi_board_minor_table[i] = NULL; - cleared = true; - } - mutex_unlock(&comedi_board_minor_table_lock); - return cleared; -} - -static struct comedi_device *comedi_clear_board_minor(unsigned int minor) -{ - struct comedi_device *dev; - - mutex_lock(&comedi_board_minor_table_lock); - dev = comedi_board_minor_table[minor]; - comedi_board_minor_table[minor] = NULL; - mutex_unlock(&comedi_board_minor_table_lock); - return dev; -} - -static void comedi_free_board_dev(struct comedi_device *dev) -{ - if (dev) { - comedi_device_cleanup(dev); - if (dev->class_dev) { - device_destroy(comedi_class, - MKDEV(COMEDI_MAJOR, dev->minor)); - } - comedi_dev_put(dev); - } -} - -static struct comedi_subdevice * -comedi_subdevice_from_minor(const struct comedi_device *dev, unsigned int minor) -{ - struct comedi_subdevice *s; - unsigned int i = minor - COMEDI_NUM_BOARD_MINORS; - - mutex_lock(&comedi_subdevice_minor_table_lock); - s = comedi_subdevice_minor_table[i]; - if (s && s->device != dev) - s = NULL; - mutex_unlock(&comedi_subdevice_minor_table_lock); - return s; -} - -static struct comedi_device *comedi_dev_get_from_board_minor(unsigned int minor) -{ - struct comedi_device *dev; - - mutex_lock(&comedi_board_minor_table_lock); - dev = comedi_dev_get(comedi_board_minor_table[minor]); - mutex_unlock(&comedi_board_minor_table_lock); - return dev; -} - -static struct comedi_device * -comedi_dev_get_from_subdevice_minor(unsigned int minor) -{ - struct comedi_device *dev; - struct comedi_subdevice *s; - unsigned int i = minor - COMEDI_NUM_BOARD_MINORS; - - mutex_lock(&comedi_subdevice_minor_table_lock); - s = comedi_subdevice_minor_table[i]; - dev = comedi_dev_get(s ? s->device : NULL); - mutex_unlock(&comedi_subdevice_minor_table_lock); - return dev; -} - -/** - * comedi_dev_get_from_minor() - Get COMEDI device by minor device number - * @minor: Minor device number. - * - * 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 - * specified minor device number. - */ -struct comedi_device *comedi_dev_get_from_minor(unsigned int minor) -{ - if (minor < COMEDI_NUM_BOARD_MINORS) - return comedi_dev_get_from_board_minor(minor); - - return comedi_dev_get_from_subdevice_minor(minor); -} -EXPORT_SYMBOL_GPL(comedi_dev_get_from_minor); - -static struct comedi_subdevice * -comedi_read_subdevice(const struct comedi_device *dev, unsigned int minor) -{ - struct comedi_subdevice *s; - - lockdep_assert_held(&dev->mutex); - if (minor >= COMEDI_NUM_BOARD_MINORS) { - s = comedi_subdevice_from_minor(dev, minor); - if (!s || (s->subdev_flags & SDF_CMD_READ)) - return s; - } - return dev->read_subdev; -} - -static struct comedi_subdevice * -comedi_write_subdevice(const struct comedi_device *dev, unsigned int minor) -{ - struct comedi_subdevice *s; - - lockdep_assert_held(&dev->mutex); - if (minor >= COMEDI_NUM_BOARD_MINORS) { - s = comedi_subdevice_from_minor(dev, minor); - if (!s || (s->subdev_flags & SDF_CMD_WRITE)) - return s; - } - 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 || s->subdev_flags & SDF_CMD_READ) - read_s = s; - if (!s || s->subdev_flags & SDF_CMD_WRITE) - write_s = s; - } - cfp->last_attached = dev->attached; - cfp->last_detach_count = dev->detach_count; - WRITE_ONCE(cfp->read_subdev, read_s); - WRITE_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 READ_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 READ_ONCE(cfp->write_subdev); -} - -static int resize_async_buffer(struct comedi_device *dev, - struct comedi_subdevice *s, - unsigned int new_size) -{ - struct comedi_async *async = s->async; - int retval; - - lockdep_assert_held(&dev->mutex); - - if (new_size > async->max_bufsize) - return -EPERM; - - if (s->busy) { - dev_dbg(dev->class_dev, - "subdevice is busy, cannot resize buffer\n"); - return -EBUSY; - } - if (comedi_buf_is_mmapped(s)) { - dev_dbg(dev->class_dev, - "subdevice is mmapped, cannot resize buffer\n"); - return -EBUSY; - } - - /* make sure buffer is an integral number of pages (we round up) */ - new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK; - - retval = comedi_buf_alloc(dev, s, new_size); - if (retval < 0) - return retval; - - if (s->buf_change) { - retval = s->buf_change(dev, s); - if (retval < 0) - return retval; - } - - dev_dbg(dev->class_dev, "subd %d buffer resized to %i bytes\n", - s->index, async->prealloc_bufsz); - return 0; -} - -/* sysfs attribute files */ - -static ssize_t max_read_buffer_kb_show(struct device *csdev, - struct device_attribute *attr, char *buf) -{ - unsigned int minor = MINOR(csdev->devt); - struct comedi_device *dev; - struct comedi_subdevice *s; - unsigned int size = 0; - - dev = comedi_dev_get_from_minor(minor); - if (!dev) - return -ENODEV; - - mutex_lock(&dev->mutex); - s = comedi_read_subdevice(dev, minor); - if (s && (s->subdev_flags & SDF_CMD_READ) && s->async) - size = s->async->max_bufsize / 1024; - mutex_unlock(&dev->mutex); - - comedi_dev_put(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", size); -} - -static ssize_t max_read_buffer_kb_store(struct device *csdev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned int minor = MINOR(csdev->devt); - struct comedi_device *dev; - struct comedi_subdevice *s; - unsigned int size; - int err; - - err = kstrtouint(buf, 10, &size); - if (err) - return err; - if (size > (UINT_MAX / 1024)) - return -EINVAL; - size *= 1024; - - dev = comedi_dev_get_from_minor(minor); - if (!dev) - return -ENODEV; - - mutex_lock(&dev->mutex); - s = comedi_read_subdevice(dev, minor); - if (s && (s->subdev_flags & SDF_CMD_READ) && s->async) - s->async->max_bufsize = size; - else - err = -EINVAL; - mutex_unlock(&dev->mutex); - - comedi_dev_put(dev); - return err ? err : count; -} -static DEVICE_ATTR_RW(max_read_buffer_kb); - -static ssize_t read_buffer_kb_show(struct device *csdev, - struct device_attribute *attr, char *buf) -{ - unsigned int minor = MINOR(csdev->devt); - struct comedi_device *dev; - struct comedi_subdevice *s; - unsigned int size = 0; - - dev = comedi_dev_get_from_minor(minor); - if (!dev) - return -ENODEV; - - mutex_lock(&dev->mutex); - s = comedi_read_subdevice(dev, minor); - if (s && (s->subdev_flags & SDF_CMD_READ) && s->async) - size = s->async->prealloc_bufsz / 1024; - mutex_unlock(&dev->mutex); - - comedi_dev_put(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", size); -} - -static ssize_t read_buffer_kb_store(struct device *csdev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned int minor = MINOR(csdev->devt); - struct comedi_device *dev; - struct comedi_subdevice *s; - unsigned int size; - int err; - - err = kstrtouint(buf, 10, &size); - if (err) - return err; - if (size > (UINT_MAX / 1024)) - return -EINVAL; - size *= 1024; - - dev = comedi_dev_get_from_minor(minor); - if (!dev) - return -ENODEV; - - mutex_lock(&dev->mutex); - s = comedi_read_subdevice(dev, minor); - if (s && (s->subdev_flags & SDF_CMD_READ) && s->async) - err = resize_async_buffer(dev, s, size); - else - err = -EINVAL; - mutex_unlock(&dev->mutex); - - comedi_dev_put(dev); - return err ? err : count; -} -static DEVICE_ATTR_RW(read_buffer_kb); - -static ssize_t max_write_buffer_kb_show(struct device *csdev, - struct device_attribute *attr, - char *buf) -{ - unsigned int minor = MINOR(csdev->devt); - struct comedi_device *dev; - struct comedi_subdevice *s; - unsigned int size = 0; - - dev = comedi_dev_get_from_minor(minor); - if (!dev) - return -ENODEV; - - mutex_lock(&dev->mutex); - s = comedi_write_subdevice(dev, minor); - if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async) - size = s->async->max_bufsize / 1024; - mutex_unlock(&dev->mutex); - - comedi_dev_put(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", size); -} - -static ssize_t max_write_buffer_kb_store(struct device *csdev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned int minor = MINOR(csdev->devt); - struct comedi_device *dev; - struct comedi_subdevice *s; - unsigned int size; - int err; - - err = kstrtouint(buf, 10, &size); - if (err) - return err; - if (size > (UINT_MAX / 1024)) - return -EINVAL; - size *= 1024; - - dev = comedi_dev_get_from_minor(minor); - if (!dev) - return -ENODEV; - - mutex_lock(&dev->mutex); - s = comedi_write_subdevice(dev, minor); - if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async) - s->async->max_bufsize = size; - else - err = -EINVAL; - mutex_unlock(&dev->mutex); - - comedi_dev_put(dev); - return err ? err : count; -} -static DEVICE_ATTR_RW(max_write_buffer_kb); - -static ssize_t write_buffer_kb_show(struct device *csdev, - struct device_attribute *attr, char *buf) -{ - unsigned int minor = MINOR(csdev->devt); - struct comedi_device *dev; - struct comedi_subdevice *s; - unsigned int size = 0; - - dev = comedi_dev_get_from_minor(minor); - if (!dev) - return -ENODEV; - - mutex_lock(&dev->mutex); - s = comedi_write_subdevice(dev, minor); - if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async) - size = s->async->prealloc_bufsz / 1024; - mutex_unlock(&dev->mutex); - - comedi_dev_put(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", size); -} - -static ssize_t write_buffer_kb_store(struct device *csdev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned int minor = MINOR(csdev->devt); - struct comedi_device *dev; - struct comedi_subdevice *s; - unsigned int size; - int err; - - err = kstrtouint(buf, 10, &size); - if (err) - return err; - if (size > (UINT_MAX / 1024)) - return -EINVAL; - size *= 1024; - - dev = comedi_dev_get_from_minor(minor); - if (!dev) - return -ENODEV; - - mutex_lock(&dev->mutex); - s = comedi_write_subdevice(dev, minor); - if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async) - err = resize_async_buffer(dev, s, size); - else - err = -EINVAL; - mutex_unlock(&dev->mutex); - - comedi_dev_put(dev); - return err ? err : count; -} -static DEVICE_ATTR_RW(write_buffer_kb); - -static struct attribute *comedi_dev_attrs[] = { - &dev_attr_max_read_buffer_kb.attr, - &dev_attr_read_buffer_kb.attr, - &dev_attr_max_write_buffer_kb.attr, - &dev_attr_write_buffer_kb.attr, - NULL, -}; -ATTRIBUTE_GROUPS(comedi_dev); - -static void __comedi_clear_subdevice_runflags(struct comedi_subdevice *s, - unsigned int bits) -{ - s->runflags &= ~bits; -} - -static void __comedi_set_subdevice_runflags(struct comedi_subdevice *s, - unsigned int bits) -{ - s->runflags |= bits; -} - -static void comedi_update_subdevice_runflags(struct comedi_subdevice *s, - unsigned int mask, - unsigned int bits) -{ - unsigned long flags; - - spin_lock_irqsave(&s->spin_lock, flags); - __comedi_clear_subdevice_runflags(s, mask); - __comedi_set_subdevice_runflags(s, bits & mask); - spin_unlock_irqrestore(&s->spin_lock, flags); -} - -static unsigned int __comedi_get_subdevice_runflags(struct comedi_subdevice *s) -{ - return s->runflags; -} - -static unsigned int comedi_get_subdevice_runflags(struct comedi_subdevice *s) -{ - unsigned long flags; - unsigned int runflags; - - spin_lock_irqsave(&s->spin_lock, flags); - runflags = __comedi_get_subdevice_runflags(s); - spin_unlock_irqrestore(&s->spin_lock, flags); - return runflags; -} - -static bool comedi_is_runflags_running(unsigned int runflags) -{ - return runflags & COMEDI_SRF_RUNNING; -} - -static bool comedi_is_runflags_in_error(unsigned int runflags) -{ - return runflags & COMEDI_SRF_ERROR; -} - -/** - * 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 - * subdevice, else %false. - */ -bool comedi_is_subdevice_running(struct comedi_subdevice *s) -{ - unsigned int runflags = comedi_get_subdevice_runflags(s); - - return comedi_is_runflags_running(runflags); -} -EXPORT_SYMBOL_GPL(comedi_is_subdevice_running); - -static bool __comedi_is_subdevice_running(struct comedi_subdevice *s) -{ - unsigned int runflags = __comedi_get_subdevice_runflags(s); - - return comedi_is_runflags_running(runflags); -} - -bool comedi_can_auto_free_spriv(struct comedi_subdevice *s) -{ - unsigned int 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. - * @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. - * - * 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) - comedi_set_spriv_auto_free(s); - return s->private; -} -EXPORT_SYMBOL_GPL(comedi_alloc_spriv); - -/* - * This function restores a subdevice to an idle state. - */ -static void do_become_nonbusy(struct comedi_device *dev, - struct comedi_subdevice *s) -{ - struct comedi_async *async = s->async; - - lockdep_assert_held(&dev->mutex); - comedi_update_subdevice_runflags(s, COMEDI_SRF_RUNNING, 0); - if (async) { - 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; - } -} - -static int do_cancel(struct comedi_device *dev, struct comedi_subdevice *s) -{ - int ret = 0; - - lockdep_assert_held(&dev->mutex); - if (comedi_is_subdevice_running(s) && s->cancel) - ret = s->cancel(dev, s); - - do_become_nonbusy(dev, s); - - return ret; -} - -void comedi_device_cancel_all(struct comedi_device *dev) -{ - struct comedi_subdevice *s; - int i; - - lockdep_assert_held(&dev->mutex); - if (!dev->attached) - return; - - for (i = 0; i < dev->n_subdevices; i++) { - s = &dev->subdevices[i]; - if (s->async) - do_cancel(dev, s); - } -} - -static int is_device_busy(struct comedi_device *dev) -{ - struct comedi_subdevice *s; - int i; - - lockdep_assert_held(&dev->mutex); - if (!dev->attached) - return 0; - - for (i = 0; i < dev->n_subdevices; i++) { - s = &dev->subdevices[i]; - if (s->busy) - return 1; - if (s->async && comedi_buf_is_mmapped(s)) - return 1; - } - - return 0; -} - -/* - * COMEDI_DEVCONFIG ioctl - * attaches (and configures) or detaches a legacy device - * - * arg: - * pointer to comedi_devconfig structure (NULL if detaching) - * - * reads: - * comedi_devconfig structure (if attaching) - * - * writes: - * nothing - */ -static int do_devconfig_ioctl(struct comedi_device *dev, - struct comedi_devconfig __user *arg) -{ - struct comedi_devconfig it; - - lockdep_assert_held(&dev->mutex); - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - if (!arg) { - if (is_device_busy(dev)) - return -EBUSY; - if (dev->attached) { - struct module *driver_module = dev->driver->module; - - comedi_device_detach(dev); - module_put(driver_module); - } - return 0; - } - - if (copy_from_user(&it, arg, sizeof(it))) - return -EFAULT; - - it.board_name[COMEDI_NAMELEN - 1] = 0; - - if (it.options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { - dev_warn(dev->class_dev, - "comedi_config --init_data is deprecated\n"); - return -EINVAL; - } - - if (dev->minor >= comedi_num_legacy_minors) - /* don't re-use dynamically allocated comedi devices */ - return -EBUSY; - - /* This increments the driver module count on success. */ - return comedi_device_attach(dev, &it); -} - -/* - * COMEDI_BUFCONFIG ioctl - * buffer configuration - * - * arg: - * pointer to comedi_bufconfig structure - * - * reads: - * comedi_bufconfig structure - * - * writes: - * modified comedi_bufconfig structure - */ -static int do_bufconfig_ioctl(struct comedi_device *dev, - struct comedi_bufconfig __user *arg) -{ - struct comedi_bufconfig bc; - struct comedi_async *async; - struct comedi_subdevice *s; - int retval = 0; - - lockdep_assert_held(&dev->mutex); - if (copy_from_user(&bc, arg, sizeof(bc))) - return -EFAULT; - - if (bc.subdevice >= dev->n_subdevices) - return -EINVAL; - - s = &dev->subdevices[bc.subdevice]; - async = s->async; - - if (!async) { - dev_dbg(dev->class_dev, - "subdevice does not have async capability\n"); - bc.size = 0; - bc.maximum_size = 0; - goto copyback; - } - - if (bc.maximum_size) { - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - async->max_bufsize = bc.maximum_size; - } - - if (bc.size) { - retval = resize_async_buffer(dev, s, bc.size); - if (retval < 0) - return retval; - } - - bc.size = async->prealloc_bufsz; - bc.maximum_size = async->max_bufsize; - -copyback: - if (copy_to_user(arg, &bc, sizeof(bc))) - return -EFAULT; - - return 0; -} - -/* - * COMEDI_DEVINFO ioctl - * device info - * - * arg: - * pointer to comedi_devinfo structure - * - * reads: - * nothing - * - * writes: - * comedi_devinfo structure - */ -static int do_devinfo_ioctl(struct comedi_device *dev, - struct comedi_devinfo __user *arg, - struct file *file) -{ - struct comedi_subdevice *s; - struct comedi_devinfo devinfo; - - lockdep_assert_held(&dev->mutex); - memset(&devinfo, 0, sizeof(devinfo)); - - /* fill devinfo structure */ - devinfo.version_code = COMEDI_VERSION_CODE; - devinfo.n_subdevs = dev->n_subdevices; - strscpy(devinfo.driver_name, dev->driver->driver_name, COMEDI_NAMELEN); - strscpy(devinfo.board_name, dev->board_name, COMEDI_NAMELEN); - - s = comedi_file_read_subdevice(file); - if (s) - devinfo.read_subdevice = s->index; - else - devinfo.read_subdevice = -1; - - s = comedi_file_write_subdevice(file); - if (s) - devinfo.write_subdevice = s->index; - else - devinfo.write_subdevice = -1; - - if (copy_to_user(arg, &devinfo, sizeof(devinfo))) - return -EFAULT; - - return 0; -} - -/* - * COMEDI_SUBDINFO ioctl - * subdevices info - * - * arg: - * pointer to array of comedi_subdinfo structures - * - * reads: - * nothing - * - * writes: - * array of comedi_subdinfo structures - */ -static int do_subdinfo_ioctl(struct comedi_device *dev, - struct comedi_subdinfo __user *arg, void *file) -{ - int ret, i; - struct comedi_subdinfo *tmp, *us; - struct comedi_subdevice *s; - - lockdep_assert_held(&dev->mutex); - tmp = kcalloc(dev->n_subdevices, sizeof(*tmp), GFP_KERNEL); - if (!tmp) - return -ENOMEM; - - /* fill subdinfo structs */ - for (i = 0; i < dev->n_subdevices; i++) { - s = &dev->subdevices[i]; - us = tmp + i; - - us->type = s->type; - us->n_chan = s->n_chan; - us->subd_flags = s->subdev_flags; - if (comedi_is_subdevice_running(s)) - us->subd_flags |= SDF_RUNNING; -#define TIMER_nanosec 5 /* backwards compatibility */ - us->timer_type = TIMER_nanosec; - us->len_chanlist = s->len_chanlist; - us->maxdata = s->maxdata; - if (s->range_table) { - us->range_type = - (i << 24) | (0 << 16) | (s->range_table->length); - } else { - us->range_type = 0; /* XXX */ - } - - if (s->busy) - us->subd_flags |= SDF_BUSY; - if (s->busy == file) - us->subd_flags |= SDF_BUSY_OWNER; - if (s->lock) - us->subd_flags |= SDF_LOCKED; - if (s->lock == file) - us->subd_flags |= SDF_LOCK_OWNER; - if (!s->maxdata && s->maxdata_list) - us->subd_flags |= SDF_MAXDATA; - if (s->range_table_list) - us->subd_flags |= SDF_RANGETYPE; - if (s->do_cmd) - us->subd_flags |= SDF_CMD; - - if (s->insn_bits != &insn_inval) - us->insn_bits_support = COMEDI_SUPPORTED; - else - us->insn_bits_support = COMEDI_UNSUPPORTED; - } - - ret = copy_to_user(arg, tmp, dev->n_subdevices * sizeof(*tmp)); - - kfree(tmp); - - return ret ? -EFAULT : 0; -} - -/* - * COMEDI_CHANINFO ioctl - * subdevice channel info - * - * arg: - * pointer to comedi_chaninfo structure - * - * reads: - * comedi_chaninfo structure - * - * writes: - * array of maxdata values to chaninfo->maxdata_list if requested - * array of range table lengths to chaninfo->range_table_list if requested - */ -static int do_chaninfo_ioctl(struct comedi_device *dev, - struct comedi_chaninfo *it) -{ - struct comedi_subdevice *s; - - lockdep_assert_held(&dev->mutex); - - if (it->subdev >= dev->n_subdevices) - return -EINVAL; - s = &dev->subdevices[it->subdev]; - - if (it->maxdata_list) { - if (s->maxdata || !s->maxdata_list) - return -EINVAL; - if (copy_to_user(it->maxdata_list, s->maxdata_list, - s->n_chan * sizeof(unsigned int))) - return -EFAULT; - } - - if (it->flaglist) - return -EINVAL; /* flaglist not supported */ - - if (it->rangelist) { - int i; - - if (!s->range_table_list) - return -EINVAL; - for (i = 0; i < s->n_chan; i++) { - int x; - - x = (dev->minor << 28) | (it->subdev << 24) | (i << 16) | - (s->range_table_list[i]->length); - if (put_user(x, it->rangelist + i)) - return -EFAULT; - } - } - - return 0; -} - -/* - * COMEDI_BUFINFO ioctl - * buffer information - * - * arg: - * pointer to comedi_bufinfo structure - * - * reads: - * comedi_bufinfo structure - * - * writes: - * modified comedi_bufinfo structure - */ -static int do_bufinfo_ioctl(struct comedi_device *dev, - struct comedi_bufinfo __user *arg, void *file) -{ - struct comedi_bufinfo bi; - struct comedi_subdevice *s; - struct comedi_async *async; - unsigned int runflags; - int retval = 0; - bool become_nonbusy = false; - - lockdep_assert_held(&dev->mutex); - if (copy_from_user(&bi, arg, sizeof(bi))) - return -EFAULT; - - if (bi.subdevice >= dev->n_subdevices) - return -EINVAL; - - s = &dev->subdevices[bi.subdevice]; - - async = s->async; - - if (!async || s->busy != file) - return -EINVAL; - - runflags = comedi_get_subdevice_runflags(s); - 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); - } - /* - * 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 && - !comedi_is_runflags_running(runflags) && - (bi.bytes_read == 0 || - !comedi_is_runflags_in_error(runflags))) { - become_nonbusy = true; - if (comedi_is_runflags_in_error(runflags)) - retval = -EPIPE; - } - bi.bytes_written = 0; - } else { - /* command was set up in "write" direction */ - if (!comedi_is_runflags_running(runflags)) { - bi.bytes_written = 0; - become_nonbusy = true; - if (comedi_is_runflags_in_error(runflags)) - retval = -EPIPE; - } else if (bi.bytes_written) { - comedi_buf_write_alloc(s, bi.bytes_written); - bi.bytes_written = - comedi_buf_write_free(s, bi.bytes_written); - } - bi.bytes_read = 0; - } - - bi.buf_write_count = async->buf_write_count; - bi.buf_write_ptr = async->buf_write_ptr; - bi.buf_read_count = async->buf_read_count; - bi.buf_read_ptr = async->buf_read_ptr; - - if (become_nonbusy) - do_become_nonbusy(dev, s); - - if (retval) - return retval; - - if (copy_to_user(arg, &bi, sizeof(bi))) - return -EFAULT; - - return 0; -} - -static int check_insn_config_length(struct comedi_insn *insn, - unsigned int *data) -{ - if (insn->n < 1) - return -EINVAL; - - switch (data[0]) { - case INSN_CONFIG_DIO_OUTPUT: - case INSN_CONFIG_DIO_INPUT: - case INSN_CONFIG_DISARM: - case INSN_CONFIG_RESET: - if (insn->n == 1) - return 0; - break; - case INSN_CONFIG_ARM: - case INSN_CONFIG_DIO_QUERY: - case INSN_CONFIG_BLOCK_SIZE: - case INSN_CONFIG_FILTER: - case INSN_CONFIG_SERIAL_CLOCK: - case INSN_CONFIG_BIDIRECTIONAL_DATA: - case INSN_CONFIG_ALT_SOURCE: - case INSN_CONFIG_SET_COUNTER_MODE: - case INSN_CONFIG_8254_READ_STATUS: - case INSN_CONFIG_SET_ROUTING: - case INSN_CONFIG_GET_ROUTING: - case INSN_CONFIG_GET_PWM_STATUS: - case INSN_CONFIG_PWM_SET_PERIOD: - case INSN_CONFIG_PWM_GET_PERIOD: - if (insn->n == 2) - return 0; - break; - case INSN_CONFIG_SET_GATE_SRC: - case INSN_CONFIG_GET_GATE_SRC: - case INSN_CONFIG_SET_CLOCK_SRC: - case INSN_CONFIG_GET_CLOCK_SRC: - case INSN_CONFIG_SET_OTHER_SRC: - case INSN_CONFIG_GET_COUNTER_STATUS: - case INSN_CONFIG_PWM_SET_H_BRIDGE: - case INSN_CONFIG_PWM_GET_H_BRIDGE: - case INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE: - if (insn->n == 3) - return 0; - break; - case INSN_CONFIG_PWM_OUTPUT: - case INSN_CONFIG_ANALOG_TRIG: - case INSN_CONFIG_TIMER_1: - if (insn->n == 5) - return 0; - break; - case INSN_CONFIG_DIGITAL_TRIG: - if (insn->n == 6) - return 0; - break; - case INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS: - if (insn->n >= 4) - return 0; - break; - /* - * by default we allow the insn since we don't have checks for - * all possible cases yet - */ - default: - pr_warn("No check for data length of config insn id %i is implemented\n", - data[0]); - pr_warn("Add a check to %s in %s\n", __func__, __FILE__); - pr_warn("Assuming n=%i is correct\n", insn->n); - return 0; - } - return -EINVAL; -} - -static int check_insn_device_config_length(struct comedi_insn *insn, - unsigned int *data) -{ - if (insn->n < 1) - return -EINVAL; - - switch (data[0]) { - case INSN_DEVICE_CONFIG_TEST_ROUTE: - case INSN_DEVICE_CONFIG_CONNECT_ROUTE: - case INSN_DEVICE_CONFIG_DISCONNECT_ROUTE: - if (insn->n == 3) - return 0; - break; - case INSN_DEVICE_CONFIG_GET_ROUTES: - /* - * Big enough for config_id and the length of the userland - * memory buffer. Additional length should be in factors of 2 - * to communicate any returned route pairs (source,destination). - */ - if (insn->n >= 2) - return 0; - break; - } - return -EINVAL; -} - -/** - * get_valid_routes() - Calls low-level driver get_valid_routes function to - * either return a count of valid routes to user, or copy - * of list of all valid device routes to buffer in - * userspace. - * @dev: comedi device pointer - * @data: data from user insn call. The length of the data must be >= 2. - * data[0] must contain the INSN_DEVICE_CONFIG config_id. - * data[1](input) contains the number of _pairs_ for which memory is - * allotted from the user. If the user specifies '0', then only - * the number of pairs available is returned. - * data[1](output) returns either the number of pairs available (if none - * where requested) or the number of _pairs_ that are copied back - * to the user. - * data[2::2] returns each (source, destination) pair. - * - * Return: -EINVAL if low-level driver does not allocate and return routes as - * expected. Returns 0 otherwise. - */ -static int get_valid_routes(struct comedi_device *dev, unsigned int *data) -{ - lockdep_assert_held(&dev->mutex); - data[1] = dev->get_valid_routes(dev, data[1], data + 2); - return 0; -} - -static int parse_insn(struct comedi_device *dev, struct comedi_insn *insn, - unsigned int *data, void *file) -{ - struct comedi_subdevice *s; - int ret = 0; - int i; - - lockdep_assert_held(&dev->mutex); - if (insn->insn & INSN_MASK_SPECIAL) { - /* a non-subdevice instruction */ - - switch (insn->insn) { - case INSN_GTOD: - { - struct timespec64 tv; - - if (insn->n != 2) { - ret = -EINVAL; - break; - } - - ktime_get_real_ts64(&tv); - /* unsigned data safe until 2106 */ - data[0] = (unsigned int)tv.tv_sec; - data[1] = tv.tv_nsec / NSEC_PER_USEC; - ret = 2; - - break; - } - case INSN_WAIT: - if (insn->n != 1 || data[0] >= 100000) { - ret = -EINVAL; - break; - } - udelay(data[0] / 1000); - ret = 1; - break; - case INSN_INTTRIG: - if (insn->n != 1) { - ret = -EINVAL; - break; - } - if (insn->subdev >= dev->n_subdevices) { - dev_dbg(dev->class_dev, - "%d not usable subdevice\n", - insn->subdev); - ret = -EINVAL; - break; - } - s = &dev->subdevices[insn->subdev]; - if (!s->async) { - dev_dbg(dev->class_dev, "no async\n"); - ret = -EINVAL; - break; - } - if (!s->async->inttrig) { - dev_dbg(dev->class_dev, "no inttrig\n"); - ret = -EAGAIN; - break; - } - ret = s->async->inttrig(dev, s, data[0]); - if (ret >= 0) - ret = 1; - break; - case INSN_DEVICE_CONFIG: - ret = check_insn_device_config_length(insn, data); - if (ret) - break; - - if (data[0] == INSN_DEVICE_CONFIG_GET_ROUTES) { - /* - * data[1] should be the number of _pairs_ that - * the memory can hold. - */ - data[1] = (insn->n - 2) / 2; - ret = get_valid_routes(dev, data); - break; - } - - /* other global device config instructions. */ - ret = dev->insn_device_config(dev, insn, data); - break; - default: - dev_dbg(dev->class_dev, "invalid insn\n"); - ret = -EINVAL; - break; - } - } else { - /* a subdevice instruction */ - unsigned int maxdata; - - if (insn->subdev >= dev->n_subdevices) { - dev_dbg(dev->class_dev, "subdevice %d out of range\n", - insn->subdev); - ret = -EINVAL; - goto out; - } - s = &dev->subdevices[insn->subdev]; - - if (s->type == COMEDI_SUBD_UNUSED) { - dev_dbg(dev->class_dev, "%d not usable subdevice\n", - insn->subdev); - ret = -EIO; - goto out; - } - - /* are we locked? (ioctl lock) */ - if (s->lock && s->lock != file) { - dev_dbg(dev->class_dev, "device locked\n"); - ret = -EACCES; - goto out; - } - - ret = comedi_check_chanlist(s, 1, &insn->chanspec); - if (ret < 0) { - ret = -EINVAL; - dev_dbg(dev->class_dev, "bad chanspec\n"); - goto out; - } - - if (s->busy) { - ret = -EBUSY; - goto out; - } - /* This looks arbitrary. It is. */ - s->busy = parse_insn; - switch (insn->insn) { - case INSN_READ: - ret = s->insn_read(dev, s, insn, data); - if (ret == -ETIMEDOUT) { - dev_dbg(dev->class_dev, - "subdevice %d read instruction timed out\n", - s->index); - } - break; - case INSN_WRITE: - maxdata = s->maxdata_list - ? s->maxdata_list[CR_CHAN(insn->chanspec)] - : s->maxdata; - for (i = 0; i < insn->n; ++i) { - if (data[i] > maxdata) { - ret = -EINVAL; - dev_dbg(dev->class_dev, - "bad data value(s)\n"); - break; - } - } - if (ret == 0) { - ret = s->insn_write(dev, s, insn, data); - if (ret == -ETIMEDOUT) { - dev_dbg(dev->class_dev, - "subdevice %d write instruction timed out\n", - s->index); - } - } - break; - case INSN_BITS: - if (insn->n != 2) { - ret = -EINVAL; - } else { - /* - * Most drivers ignore the base channel in - * insn->chanspec. Fix this here if - * the subdevice has <= 32 channels. - */ - unsigned int orig_mask = data[0]; - unsigned int shift = 0; - - if (s->n_chan <= 32) { - shift = CR_CHAN(insn->chanspec); - if (shift > 0) { - insn->chanspec = 0; - data[0] <<= shift; - data[1] <<= shift; - } - } - ret = s->insn_bits(dev, s, insn, data); - data[0] = orig_mask; - if (shift > 0) - data[1] >>= shift; - } - break; - case INSN_CONFIG: - ret = check_insn_config_length(insn, data); - if (ret) - break; - ret = s->insn_config(dev, s, insn, data); - break; - default: - ret = -EINVAL; - break; - } - - s->busy = NULL; - } - -out: - return ret; -} - -/* - * COMEDI_INSNLIST ioctl - * synchronous instruction list - * - * arg: - * pointer to comedi_insnlist structure - * - * reads: - * comedi_insnlist structure - * array of comedi_insn structures from insnlist->insns pointer - * data (for writes) from insns[].data pointers - * - * writes: - * data (for reads) to insns[].data pointers - */ -/* arbitrary limits */ -#define MIN_SAMPLES 16 -#define MAX_SAMPLES 65536 -static int do_insnlist_ioctl(struct comedi_device *dev, - struct comedi_insn *insns, - unsigned int n_insns, - void *file) -{ - unsigned int *data = NULL; - unsigned int max_n_data_required = MIN_SAMPLES; - int i = 0; - int ret = 0; - - lockdep_assert_held(&dev->mutex); - - /* Determine maximum memory needed for all instructions. */ - for (i = 0; i < n_insns; ++i) { - if (insns[i].n > MAX_SAMPLES) { - dev_dbg(dev->class_dev, - "number of samples too large\n"); - ret = -EINVAL; - goto error; - } - max_n_data_required = max(max_n_data_required, insns[i].n); - } - - /* Allocate scratch space for all instruction data. */ - data = kmalloc_array(max_n_data_required, sizeof(unsigned int), - GFP_KERNEL); - if (!data) { - ret = -ENOMEM; - goto error; - } - - for (i = 0; i < n_insns; ++i) { - if (insns[i].insn & INSN_MASK_WRITE) { - if (copy_from_user(data, insns[i].data, - insns[i].n * sizeof(unsigned int))) { - dev_dbg(dev->class_dev, - "copy_from_user failed\n"); - ret = -EFAULT; - goto error; - } - } - ret = parse_insn(dev, insns + i, data, file); - if (ret < 0) - goto error; - if (insns[i].insn & INSN_MASK_READ) { - if (copy_to_user(insns[i].data, data, - insns[i].n * sizeof(unsigned int))) { - dev_dbg(dev->class_dev, - "copy_to_user failed\n"); - ret = -EFAULT; - goto error; - } - } - if (need_resched()) - schedule(); - } - -error: - kfree(data); - - if (ret < 0) - return ret; - return i; -} - -/* - * COMEDI_INSN ioctl - * synchronous instruction - * - * arg: - * pointer to comedi_insn structure - * - * reads: - * comedi_insn structure - * data (for writes) from insn->data pointer - * - * writes: - * data (for reads) to insn->data pointer - */ -static int do_insn_ioctl(struct comedi_device *dev, - struct comedi_insn *insn, void *file) -{ - unsigned int *data = NULL; - unsigned int n_data = MIN_SAMPLES; - int ret = 0; - - lockdep_assert_held(&dev->mutex); - - n_data = max(n_data, insn->n); - - /* This is where the behavior of insn and insnlist deviate. */ - if (insn->n > MAX_SAMPLES) { - insn->n = MAX_SAMPLES; - n_data = MAX_SAMPLES; - } - - data = kmalloc_array(n_data, sizeof(unsigned int), GFP_KERNEL); - if (!data) { - ret = -ENOMEM; - goto error; - } - - if (insn->insn & INSN_MASK_WRITE) { - if (copy_from_user(data, - insn->data, - insn->n * sizeof(unsigned int))) { - ret = -EFAULT; - goto error; - } - } - ret = parse_insn(dev, insn, data, file); - if (ret < 0) - goto error; - if (insn->insn & INSN_MASK_READ) { - if (copy_to_user(insn->data, - data, - insn->n * sizeof(unsigned int))) { - ret = -EFAULT; - goto error; - } - } - ret = insn->n; - -error: - kfree(data); - - return ret; -} - -static int __comedi_get_user_cmd(struct comedi_device *dev, - struct comedi_cmd *cmd) -{ - struct comedi_subdevice *s; - - lockdep_assert_held(&dev->mutex); - if (cmd->subdev >= dev->n_subdevices) { - dev_dbg(dev->class_dev, "%d no such subdevice\n", cmd->subdev); - return -ENODEV; - } - - s = &dev->subdevices[cmd->subdev]; - - if (s->type == COMEDI_SUBD_UNUSED) { - dev_dbg(dev->class_dev, "%d not valid subdevice\n", - cmd->subdev); - return -EIO; - } - - if (!s->do_cmd || !s->do_cmdtest || !s->async) { - dev_dbg(dev->class_dev, - "subdevice %d does not support commands\n", - cmd->subdev); - return -EIO; - } - - /* make sure channel/gain list isn't too long */ - if (cmd->chanlist_len > s->len_chanlist) { - dev_dbg(dev->class_dev, "channel/gain list too long %d > %d\n", - cmd->chanlist_len, s->len_chanlist); - 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; -} - -static int __comedi_get_user_chanlist(struct comedi_device *dev, - struct comedi_subdevice *s, - unsigned int __user *user_chanlist, - struct comedi_cmd *cmd) -{ - unsigned int *chanlist; - int ret; - - lockdep_assert_held(&dev->mutex); - cmd->chanlist = NULL; - chanlist = memdup_user(user_chanlist, - cmd->chanlist_len * sizeof(unsigned int)); - if (IS_ERR(chanlist)) - return PTR_ERR(chanlist); - - /* make sure each element in channel/gain list is valid */ - ret = comedi_check_chanlist(s, cmd->chanlist_len, chanlist); - if (ret < 0) { - kfree(chanlist); - return ret; - } - - cmd->chanlist = chanlist; - - return 0; -} - -/* - * COMEDI_CMD ioctl - * asynchronous acquisition command set-up - * - * arg: - * pointer to comedi_cmd structure - * - * reads: - * comedi_cmd structure - * channel/range list from cmd->chanlist pointer - * - * writes: - * possibly modified comedi_cmd structure (when -EAGAIN returned) - */ -static int do_cmd_ioctl(struct comedi_device *dev, - struct comedi_cmd *cmd, bool *copy, void *file) -{ - struct comedi_subdevice *s; - struct comedi_async *async; - unsigned int __user *user_chanlist; - int ret; - - lockdep_assert_held(&dev->mutex); - - /* do some simple cmd validation */ - ret = __comedi_get_user_cmd(dev, cmd); - if (ret) - return ret; - - /* save user's chanlist pointer so it can be restored later */ - user_chanlist = (unsigned int __user *)cmd->chanlist; - - s = &dev->subdevices[cmd->subdev]; - async = s->async; - - /* are we locked? (ioctl lock) */ - if (s->lock && s->lock != file) { - dev_dbg(dev->class_dev, "subdevice locked\n"); - return -EACCES; - } - - /* are we busy? */ - if (s->busy) { - dev_dbg(dev->class_dev, "subdevice busy\n"); - return -EBUSY; - } - - /* make sure channel/gain list isn't too short */ - if (cmd->chanlist_len < 1) { - dev_dbg(dev->class_dev, "channel/gain list too short %u < 1\n", - cmd->chanlist_len); - return -EINVAL; - } - - async->cmd = *cmd; - async->cmd.data = NULL; - - /* load channel/gain list */ - ret = __comedi_get_user_chanlist(dev, s, user_chanlist, &async->cmd); - if (ret) - goto cleanup; - - ret = s->do_cmdtest(dev, s, &async->cmd); - - if (async->cmd.flags & CMDF_BOGUS || ret) { - dev_dbg(dev->class_dev, "test returned %d\n", ret); - *cmd = async->cmd; - /* restore chanlist pointer before copying back */ - cmd->chanlist = (unsigned int __force *)user_chanlist; - cmd->data = NULL; - *copy = true; - ret = -EAGAIN; - goto cleanup; - } - - if (!async->prealloc_bufsz) { - ret = -ENOMEM; - dev_dbg(dev->class_dev, "no buffer (?)\n"); - goto cleanup; - } - - comedi_buf_reset(s); - - async->cb_mask = COMEDI_CB_BLOCK | COMEDI_CB_CANCEL_MASK; - if (async->cmd.flags & CMDF_WAKE_EOS) - async->cb_mask |= COMEDI_CB_EOS; - - comedi_update_subdevice_runflags(s, COMEDI_SRF_BUSY_MASK, - COMEDI_SRF_RUNNING); - - /* - * Set s->busy _after_ setting COMEDI_SRF_RUNNING flag to avoid - * race with comedi_read() or comedi_write(). - */ - s->busy = file; - ret = s->do_cmd(dev, s); - if (ret == 0) - return 0; - -cleanup: - do_become_nonbusy(dev, s); - - return ret; -} - -/* - * COMEDI_CMDTEST ioctl - * asynchronous acquisition command testing - * - * arg: - * pointer to comedi_cmd structure - * - * reads: - * comedi_cmd structure - * channel/range list from cmd->chanlist pointer - * - * writes: - * possibly modified comedi_cmd structure - */ -static int do_cmdtest_ioctl(struct comedi_device *dev, - struct comedi_cmd *cmd, bool *copy, void *file) -{ - struct comedi_subdevice *s; - unsigned int __user *user_chanlist; - int ret; - - lockdep_assert_held(&dev->mutex); - - /* do some simple cmd validation */ - ret = __comedi_get_user_cmd(dev, cmd); - if (ret) - return ret; - - /* save user's chanlist pointer so it can be restored later */ - user_chanlist = (unsigned int __user *)cmd->chanlist; - - s = &dev->subdevices[cmd->subdev]; - - /* user_chanlist can be NULL for COMEDI_CMDTEST ioctl */ - if (user_chanlist) { - /* load channel/gain list */ - ret = __comedi_get_user_chanlist(dev, s, user_chanlist, cmd); - if (ret) - return ret; - } - - ret = s->do_cmdtest(dev, s, cmd); - - kfree(cmd->chanlist); /* free kernel copy of user chanlist */ - - /* restore chanlist pointer before copying back */ - cmd->chanlist = (unsigned int __force *)user_chanlist; - *copy = true; - - return ret; -} - -/* - * COMEDI_LOCK ioctl - * lock subdevice - * - * arg: - * subdevice number - * - * reads: - * nothing - * - * writes: - * nothing - */ -static int do_lock_ioctl(struct comedi_device *dev, unsigned long arg, - void *file) -{ - int ret = 0; - unsigned long flags; - struct comedi_subdevice *s; - - lockdep_assert_held(&dev->mutex); - if (arg >= dev->n_subdevices) - return -EINVAL; - s = &dev->subdevices[arg]; - - spin_lock_irqsave(&s->spin_lock, flags); - if (s->busy || s->lock) - ret = -EBUSY; - else - s->lock = file; - spin_unlock_irqrestore(&s->spin_lock, flags); - - return ret; -} - -/* - * COMEDI_UNLOCK ioctl - * unlock subdevice - * - * arg: - * subdevice number - * - * reads: - * nothing - * - * writes: - * nothing - */ -static int do_unlock_ioctl(struct comedi_device *dev, unsigned long arg, - void *file) -{ - struct comedi_subdevice *s; - - lockdep_assert_held(&dev->mutex); - if (arg >= dev->n_subdevices) - return -EINVAL; - s = &dev->subdevices[arg]; - - if (s->busy) - return -EBUSY; - - if (s->lock && s->lock != file) - return -EACCES; - - if (s->lock == file) - s->lock = NULL; - - return 0; -} - -/* - * COMEDI_CANCEL ioctl - * cancel asynchronous acquisition - * - * arg: - * subdevice number - * - * reads: - * nothing - * - * writes: - * nothing - */ -static int do_cancel_ioctl(struct comedi_device *dev, unsigned long arg, - void *file) -{ - struct comedi_subdevice *s; - - lockdep_assert_held(&dev->mutex); - if (arg >= dev->n_subdevices) - return -EINVAL; - s = &dev->subdevices[arg]; - if (!s->async) - return -EINVAL; - - if (!s->busy) - return 0; - - if (s->busy != file) - return -EBUSY; - - return do_cancel(dev, s); -} - -/* - * COMEDI_POLL ioctl - * instructs driver to synchronize buffers - * - * arg: - * subdevice number - * - * reads: - * nothing - * - * writes: - * nothing - */ -static int do_poll_ioctl(struct comedi_device *dev, unsigned long arg, - void *file) -{ - struct comedi_subdevice *s; - - lockdep_assert_held(&dev->mutex); - if (arg >= dev->n_subdevices) - return -EINVAL; - s = &dev->subdevices[arg]; - - if (!s->busy) - return 0; - - if (s->busy != file) - return -EBUSY; - - if (s->poll) - return s->poll(dev, s); - - 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; - - lockdep_assert_held(&dev->mutex); - 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; - - WRITE_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; - - lockdep_assert_held(&dev->mutex); - 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; - - WRITE_ONCE(cfp->write_subdev, s_new); - return 0; -} - -static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - unsigned int minor = iminor(file_inode(file)); - struct comedi_file *cfp = file->private_data; - struct comedi_device *dev = cfp->dev; - int rc; - - mutex_lock(&dev->mutex); - - /* - * Device config is special, because it must work on - * an unconfigured device. - */ - if (cmd == COMEDI_DEVCONFIG) { - if (minor >= COMEDI_NUM_BOARD_MINORS) { - /* Device config not appropriate on non-board minors. */ - rc = -ENOTTY; - goto done; - } - rc = do_devconfig_ioctl(dev, - (struct comedi_devconfig __user *)arg); - if (rc == 0) { - if (arg == 0 && - dev->minor >= comedi_num_legacy_minors) { - /* - * Successfully unconfigured a dynamically - * allocated device. Try and remove it. - */ - if (comedi_clear_board_dev(dev)) { - mutex_unlock(&dev->mutex); - comedi_free_board_dev(dev); - return rc; - } - } - } - goto done; - } - - if (!dev->attached) { - dev_dbg(dev->class_dev, "no driver attached\n"); - rc = -ENODEV; - goto done; - } - - switch (cmd) { - case COMEDI_BUFCONFIG: - rc = do_bufconfig_ioctl(dev, - (struct comedi_bufconfig __user *)arg); - break; - case COMEDI_DEVINFO: - rc = do_devinfo_ioctl(dev, (struct comedi_devinfo __user *)arg, - file); - break; - case COMEDI_SUBDINFO: - rc = do_subdinfo_ioctl(dev, - (struct comedi_subdinfo __user *)arg, - file); - break; - case COMEDI_CHANINFO: { - struct comedi_chaninfo it; - - if (copy_from_user(&it, (void __user *)arg, sizeof(it))) - rc = -EFAULT; - else - rc = do_chaninfo_ioctl(dev, &it); - break; - } - case COMEDI_RANGEINFO: { - struct comedi_rangeinfo it; - - if (copy_from_user(&it, (void __user *)arg, sizeof(it))) - rc = -EFAULT; - else - rc = do_rangeinfo_ioctl(dev, &it); - break; - } - case COMEDI_BUFINFO: - rc = do_bufinfo_ioctl(dev, - (struct comedi_bufinfo __user *)arg, - file); - break; - case COMEDI_LOCK: - rc = do_lock_ioctl(dev, arg, file); - break; - case COMEDI_UNLOCK: - rc = do_unlock_ioctl(dev, arg, file); - break; - case COMEDI_CANCEL: - rc = do_cancel_ioctl(dev, arg, file); - break; - case COMEDI_CMD: { - struct comedi_cmd cmd; - bool copy = false; - - if (copy_from_user(&cmd, (void __user *)arg, sizeof(cmd))) { - rc = -EFAULT; - break; - } - rc = do_cmd_ioctl(dev, &cmd, ©, file); - if (copy && copy_to_user((void __user *)arg, &cmd, sizeof(cmd))) - rc = -EFAULT; - break; - } - case COMEDI_CMDTEST: { - struct comedi_cmd cmd; - bool copy = false; - - if (copy_from_user(&cmd, (void __user *)arg, sizeof(cmd))) { - rc = -EFAULT; - break; - } - rc = do_cmdtest_ioctl(dev, &cmd, ©, file); - if (copy && copy_to_user((void __user *)arg, &cmd, sizeof(cmd))) - rc = -EFAULT; - break; - } - case COMEDI_INSNLIST: { - struct comedi_insnlist insnlist; - struct comedi_insn *insns = NULL; - - if (copy_from_user(&insnlist, (void __user *)arg, - sizeof(insnlist))) { - rc = -EFAULT; - 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); - break; - } - rc = do_insnlist_ioctl(dev, insns, insnlist.n_insns, file); - kfree(insns); - break; - } - case COMEDI_INSN: { - struct comedi_insn insn; - - if (copy_from_user(&insn, (void __user *)arg, sizeof(insn))) - rc = -EFAULT; - else - rc = do_insn_ioctl(dev, &insn, file); - break; - } - 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; - } - -done: - mutex_unlock(&dev->mutex); - return rc; -} - -static void comedi_vm_open(struct vm_area_struct *area) -{ - struct comedi_buf_map *bm; - - bm = area->vm_private_data; - comedi_buf_map_get(bm); -} - -static void comedi_vm_close(struct vm_area_struct *area) -{ - struct comedi_buf_map *bm; - - bm = area->vm_private_data; - comedi_buf_map_put(bm); -} - -static int comedi_vm_access(struct vm_area_struct *vma, unsigned long addr, - void *buf, int len, int write) -{ - struct comedi_buf_map *bm = vma->vm_private_data; - unsigned long offset = - addr - vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT); - - if (len < 0) - return -EINVAL; - if (len > vma->vm_end - addr) - len = vma->vm_end - addr; - return comedi_buf_map_access(bm, offset, buf, len, write); -} - -static const struct vm_operations_struct comedi_vm_ops = { - .open = comedi_vm_open, - .close = comedi_vm_close, - .access = comedi_vm_access, -}; - -static int comedi_mmap(struct file *file, struct vm_area_struct *vma) -{ - 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; - struct comedi_buf_page *buf; - unsigned long start = vma->vm_start; - unsigned long size; - int n_pages; - int i; - int retval = 0; - - /* - * 'trylock' avoids circular dependency with current->mm->mmap_lock - * and down-reading &dev->attach_lock should normally succeed without - * contention unless the device is in the process of being attached - * or detached. - */ - if (!down_read_trylock(&dev->attach_lock)) - return -EAGAIN; - - if (!dev->attached) { - dev_dbg(dev->class_dev, "no driver attached\n"); - retval = -ENODEV; - goto done; - } - - if (vma->vm_flags & VM_WRITE) - s = comedi_file_write_subdevice(file); - else - s = comedi_file_read_subdevice(file); - if (!s) { - retval = -EINVAL; - goto done; - } - - async = s->async; - if (!async) { - retval = -EINVAL; - goto done; - } - - if (vma->vm_pgoff != 0) { - dev_dbg(dev->class_dev, "mmap() offset must be 0.\n"); - retval = -EINVAL; - goto done; - } - - size = vma->vm_end - vma->vm_start; - if (size > async->prealloc_bufsz) { - retval = -EFAULT; - goto done; - } - if (offset_in_page(size)) { - retval = -EFAULT; - goto done; - } - - n_pages = vma_pages(vma); - - /* get reference to current buf map (if any) */ - bm = comedi_buf_map_from_subdev_get(s); - if (!bm || n_pages > bm->n_pages) { - retval = -EINVAL; - goto done; - } - if (bm->dma_dir != DMA_NONE) { - /* - * DMA buffer was allocated as a single block. - * Address is in page_list[0]. - */ - buf = &bm->page_list[0]; - retval = dma_mmap_coherent(bm->dma_hw_dev, vma, buf->virt_addr, - buf->dma_addr, n_pages * PAGE_SIZE); - } else { - for (i = 0; i < n_pages; ++i) { - unsigned long pfn; - - buf = &bm->page_list[i]; - pfn = page_to_pfn(virt_to_page(buf->virt_addr)); - retval = remap_pfn_range(vma, start, pfn, PAGE_SIZE, - PAGE_SHARED); - if (retval) - break; - - start += PAGE_SIZE; - } - } - - if (retval == 0) { - vma->vm_ops = &comedi_vm_ops; - vma->vm_private_data = bm; - - vma->vm_ops->open(vma); - } - -done: - up_read(&dev->attach_lock); - comedi_buf_map_put(bm); /* put reference to buf map - okay if NULL */ - return retval; -} - -static __poll_t comedi_poll(struct file *file, poll_table *wait) -{ - __poll_t mask = 0; - struct comedi_file *cfp = file->private_data; - struct comedi_device *dev = cfp->dev; - struct comedi_subdevice *s, *s_read; - - down_read(&dev->attach_lock); - - if (!dev->attached) { - dev_dbg(dev->class_dev, "no driver attached\n"); - goto done; - } - - s = comedi_file_read_subdevice(file); - s_read = s; - if (s && s->async) { - 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) - mask |= EPOLLIN | EPOLLRDNORM; - } - - s = comedi_file_write_subdevice(file); - if (s && s->async) { - unsigned int bps = comedi_bytes_per_sample(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_available(s) >= bps) - mask |= EPOLLOUT | EPOLLWRNORM; - } - -done: - up_read(&dev->attach_lock); - return mask; -} - -static ssize_t comedi_write(struct file *file, const char __user *buf, - size_t nbytes, loff_t *offset) -{ - struct comedi_subdevice *s; - struct comedi_async *async; - 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; - bool become_nonbusy = false; - bool attach_locked; - unsigned int old_detach_count; - - /* Protect against device detachment during operation. */ - down_read(&dev->attach_lock); - attach_locked = true; - old_detach_count = dev->detach_count; - - if (!dev->attached) { - dev_dbg(dev->class_dev, "no driver attached\n"); - retval = -ENODEV; - goto out; - } - - s = comedi_file_write_subdevice(file); - if (!s || !s->async) { - retval = -EIO; - goto out; - } - - async = s->async; - if (s->busy != file || !(async->cmd.flags & CMDF_WRITE)) { - retval = -EINVAL; - goto out; - } - - add_wait_queue(&async->wait_head, &wait); - while (count == 0 && !retval) { - unsigned int runflags; - unsigned int wp, n1, n2; - - set_current_state(TASK_INTERRUPTIBLE); - - runflags = comedi_get_subdevice_runflags(s); - if (!comedi_is_runflags_running(runflags)) { - if (comedi_is_runflags_in_error(runflags)) - retval = -EPIPE; - if (retval || nbytes) - become_nonbusy = true; - break; - } - if (nbytes == 0) - break; - - /* Allocate all free buffer space. */ - comedi_buf_write_alloc(s, async->prealloc_bufsz); - m = comedi_buf_write_n_allocated(s); - n = min_t(size_t, m, nbytes); - - if (n == 0) { - if (file->f_flags & O_NONBLOCK) { - retval = -EAGAIN; - break; - } - schedule(); - if (signal_pending(current)) { - retval = -ERESTARTSYS; - break; - } - if (s->busy != file || - !(async->cmd.flags & CMDF_WRITE)) { - retval = -EINVAL; - break; - } - continue; - } - - set_current_state(TASK_RUNNING); - wp = async->buf_write_ptr; - n1 = min(n, async->prealloc_bufsz - wp); - n2 = n - n1; - m = copy_from_user(async->prealloc_buf + wp, buf, n1); - if (m) - m += n2; - else if (n2) - m = copy_from_user(async->prealloc_buf, buf + n1, n2); - if (m) { - n -= m; - retval = -EFAULT; - } - comedi_buf_write_free(s, n); - - count += n; - nbytes -= n; - - buf += n; - } - remove_wait_queue(&async->wait_head, &wait); - set_current_state(TASK_RUNNING); - if (become_nonbusy && count == 0) { - struct comedi_subdevice *new_s; - - /* - * To avoid deadlock, cannot acquire dev->mutex - * while dev->attach_lock is held. - */ - up_read(&dev->attach_lock); - attach_locked = false; - mutex_lock(&dev->mutex); - /* - * Check device hasn't become detached behind our back. - * Checking dev->detach_count is unchanged ought to be - * 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_write_subdevice(file); - if (dev->attached && old_detach_count == dev->detach_count && - s == new_s && new_s->async == async && s->busy == file && - (async->cmd.flags & CMDF_WRITE) && - !comedi_is_subdevice_running(s)) - do_become_nonbusy(dev, s); - mutex_unlock(&dev->mutex); - } -out: - if (attach_locked) - up_read(&dev->attach_lock); - - return count ? count : retval; -} - -static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, - loff_t *offset) -{ - struct comedi_subdevice *s; - struct comedi_async *async; - 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; - unsigned int old_detach_count; - bool become_nonbusy = false; - bool attach_locked; - - /* Protect against device detachment during operation. */ - down_read(&dev->attach_lock); - attach_locked = true; - old_detach_count = dev->detach_count; - - if (!dev->attached) { - dev_dbg(dev->class_dev, "no driver attached\n"); - retval = -ENODEV; - goto out; - } - - s = comedi_file_read_subdevice(file); - if (!s || !s->async) { - retval = -EIO; - goto out; - } - - async = s->async; - if (s->busy != file || (async->cmd.flags & CMDF_WRITE)) { - retval = -EINVAL; - goto out; - } - - add_wait_queue(&async->wait_head, &wait); - while (count == 0 && !retval) { - unsigned int rp, n1, n2; - - set_current_state(TASK_INTERRUPTIBLE); - - m = comedi_buf_read_n_available(s); - n = min_t(size_t, m, nbytes); - - if (n == 0) { - unsigned int runflags = - comedi_get_subdevice_runflags(s); - - if (!comedi_is_runflags_running(runflags)) { - if (comedi_is_runflags_in_error(runflags)) - retval = -EPIPE; - if (retval || nbytes) - become_nonbusy = true; - break; - } - if (nbytes == 0) - break; - if (file->f_flags & O_NONBLOCK) { - retval = -EAGAIN; - break; - } - schedule(); - if (signal_pending(current)) { - retval = -ERESTARTSYS; - break; - } - if (s->busy != file || - (async->cmd.flags & CMDF_WRITE)) { - retval = -EINVAL; - break; - } - continue; - } - - set_current_state(TASK_RUNNING); - 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; - } - - comedi_buf_read_alloc(s, n); - comedi_buf_read_free(s, n); - - count += n; - nbytes -= n; - - buf += n; - } - remove_wait_queue(&async->wait_head, &wait); - set_current_state(TASK_RUNNING); - if (become_nonbusy && count == 0) { - struct comedi_subdevice *new_s; - - /* - * To avoid deadlock, cannot acquire dev->mutex - * while dev->attach_lock is held. - */ - up_read(&dev->attach_lock); - attach_locked = false; - mutex_lock(&dev->mutex); - /* - * Check device hasn't become detached behind our back. - * Checking dev->detach_count is unchanged ought to be - * 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 && 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: - if (attach_locked) - up_read(&dev->attach_lock); - - return count ? count : retval; -} - -static int comedi_open(struct inode *inode, struct file *file) -{ - const unsigned int minor = iminor(inode); - struct comedi_file *cfp; - struct comedi_device *dev = comedi_dev_get_from_minor(minor); - int rc; - - if (!dev) { - pr_debug("invalid minor number\n"); - return -ENODEV; - } - - cfp = kzalloc(sizeof(*cfp), GFP_KERNEL); - if (!cfp) { - comedi_dev_put(dev); - return -ENOMEM; - } - - cfp->dev = dev; - - mutex_lock(&dev->mutex); - 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 = -ENXIO; - goto out; - } - if (dev->open) { - rc = dev->open(dev); - if (rc < 0) { - module_put(dev->driver->module); - goto out; - } - } - } - - dev->use_count++; - file->private_data = cfp; - comedi_file_reset(file); - rc = 0; - -out: - mutex_unlock(&dev->mutex); - if (rc) { - comedi_dev_put(dev); - kfree(cfp); - } - return rc; -} - -static int comedi_fasync(int fd, struct file *file, int on) -{ - 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_file *cfp = file->private_data; - struct comedi_device *dev = cfp->dev; - struct comedi_subdevice *s = NULL; - int i; - - mutex_lock(&dev->mutex); - - if (dev->subdevices) { - for (i = 0; i < dev->n_subdevices; i++) { - s = &dev->subdevices[i]; - - if (s->busy == file) - do_cancel(dev, s); - if (s->lock == file) - s->lock = NULL; - } - } - if (dev->attached && dev->use_count == 1) { - if (dev->close) - dev->close(dev); - module_put(dev->driver->module); - } - - dev->use_count--; - - mutex_unlock(&dev->mutex); - comedi_dev_put(dev); - kfree(cfp); - - return 0; -} - -#ifdef CONFIG_COMPAT - -#define COMEDI32_CHANINFO _IOR(CIO, 3, struct comedi32_chaninfo_struct) -#define COMEDI32_RANGEINFO _IOR(CIO, 8, struct comedi32_rangeinfo_struct) -/* - * N.B. COMEDI32_CMD and COMEDI_CMD ought to use _IOWR, not _IOR. - * It's too late to change it now, but it only affects the command number. - */ -#define COMEDI32_CMD _IOR(CIO, 9, struct comedi32_cmd_struct) -/* - * N.B. COMEDI32_CMDTEST and COMEDI_CMDTEST ought to use _IOWR, not _IOR. - * It's too late to change it now, but it only affects the command number. - */ -#define COMEDI32_CMDTEST _IOR(CIO, 10, struct comedi32_cmd_struct) -#define COMEDI32_INSNLIST _IOR(CIO, 11, struct comedi32_insnlist_struct) -#define COMEDI32_INSN _IOR(CIO, 12, struct comedi32_insn_struct) - -struct comedi32_chaninfo_struct { - unsigned int subdev; - compat_uptr_t maxdata_list; /* 32-bit 'unsigned int *' */ - compat_uptr_t flaglist; /* 32-bit 'unsigned int *' */ - compat_uptr_t rangelist; /* 32-bit 'unsigned int *' */ - unsigned int unused[4]; -}; - -struct comedi32_rangeinfo_struct { - unsigned int range_type; - compat_uptr_t range_ptr; /* 32-bit 'void *' */ -}; - -struct comedi32_cmd_struct { - unsigned int subdev; - unsigned int flags; - unsigned int start_src; - unsigned int start_arg; - unsigned int scan_begin_src; - unsigned int scan_begin_arg; - unsigned int convert_src; - unsigned int convert_arg; - unsigned int scan_end_src; - unsigned int scan_end_arg; - unsigned int stop_src; - unsigned int stop_arg; - compat_uptr_t chanlist; /* 32-bit 'unsigned int *' */ - unsigned int chanlist_len; - compat_uptr_t data; /* 32-bit 'short *' */ - unsigned int data_len; -}; - -struct comedi32_insn_struct { - unsigned int insn; - unsigned int n; - compat_uptr_t data; /* 32-bit 'unsigned int *' */ - unsigned int subdev; - unsigned int chanspec; - unsigned int unused[3]; -}; - -struct comedi32_insnlist_struct { - unsigned int n_insns; - compat_uptr_t insns; /* 32-bit 'struct comedi_insn *' */ -}; - -/* Handle 32-bit COMEDI_CHANINFO ioctl. */ -static int compat_chaninfo(struct file *file, unsigned long arg) -{ - struct comedi_file *cfp = file->private_data; - struct comedi_device *dev = cfp->dev; - struct comedi32_chaninfo_struct chaninfo32; - struct comedi_chaninfo chaninfo; - int err; - - if (copy_from_user(&chaninfo32, compat_ptr(arg), sizeof(chaninfo32))) - return -EFAULT; - - memset(&chaninfo, 0, sizeof(chaninfo)); - chaninfo.subdev = chaninfo32.subdev; - chaninfo.maxdata_list = compat_ptr(chaninfo32.maxdata_list); - chaninfo.flaglist = compat_ptr(chaninfo32.flaglist); - chaninfo.rangelist = compat_ptr(chaninfo32.rangelist); - - mutex_lock(&dev->mutex); - err = do_chaninfo_ioctl(dev, &chaninfo); - mutex_unlock(&dev->mutex); - return err; -} - -/* Handle 32-bit COMEDI_RANGEINFO ioctl. */ -static int compat_rangeinfo(struct file *file, unsigned long arg) -{ - struct comedi_file *cfp = file->private_data; - struct comedi_device *dev = cfp->dev; - struct comedi32_rangeinfo_struct rangeinfo32; - struct comedi_rangeinfo rangeinfo; - int err; - - if (copy_from_user(&rangeinfo32, compat_ptr(arg), sizeof(rangeinfo32))) - return -EFAULT; - memset(&rangeinfo, 0, sizeof(rangeinfo)); - rangeinfo.range_type = rangeinfo32.range_type; - rangeinfo.range_ptr = compat_ptr(rangeinfo32.range_ptr); - - mutex_lock(&dev->mutex); - err = do_rangeinfo_ioctl(dev, &rangeinfo); - mutex_unlock(&dev->mutex); - return err; -} - -/* Copy 32-bit cmd structure to native cmd structure. */ -static int get_compat_cmd(struct comedi_cmd *cmd, - struct comedi32_cmd_struct __user *cmd32) -{ - struct comedi32_cmd_struct v32; - - if (copy_from_user(&v32, cmd32, sizeof(v32))) - return -EFAULT; - - cmd->subdev = v32.subdev; - cmd->flags = v32.flags; - cmd->start_src = v32.start_src; - cmd->start_arg = v32.start_arg; - cmd->scan_begin_src = v32.scan_begin_src; - cmd->scan_begin_arg = v32.scan_begin_arg; - cmd->convert_src = v32.convert_src; - cmd->convert_arg = v32.convert_arg; - cmd->scan_end_src = v32.scan_end_src; - cmd->scan_end_arg = v32.scan_end_arg; - cmd->stop_src = v32.stop_src; - cmd->stop_arg = v32.stop_arg; - cmd->chanlist = (unsigned int __force *)compat_ptr(v32.chanlist); - cmd->chanlist_len = v32.chanlist_len; - cmd->data = compat_ptr(v32.data); - cmd->data_len = v32.data_len; - return 0; -} - -/* Copy native cmd structure to 32-bit cmd structure. */ -static int put_compat_cmd(struct comedi32_cmd_struct __user *cmd32, - struct comedi_cmd *cmd) -{ - struct comedi32_cmd_struct v32; - - memset(&v32, 0, sizeof(v32)); - v32.subdev = cmd->subdev; - v32.flags = cmd->flags; - v32.start_src = cmd->start_src; - v32.start_arg = cmd->start_arg; - v32.scan_begin_src = cmd->scan_begin_src; - v32.scan_begin_arg = cmd->scan_begin_arg; - v32.convert_src = cmd->convert_src; - v32.convert_arg = cmd->convert_arg; - v32.scan_end_src = cmd->scan_end_src; - v32.scan_end_arg = cmd->scan_end_arg; - v32.stop_src = cmd->stop_src; - v32.stop_arg = cmd->stop_arg; - /* Assume chanlist pointer is unchanged. */ - v32.chanlist = ptr_to_compat((unsigned int __user *)cmd->chanlist); - v32.chanlist_len = cmd->chanlist_len; - v32.data = ptr_to_compat(cmd->data); - v32.data_len = cmd->data_len; - if (copy_to_user(cmd32, &v32, sizeof(v32))) - return -EFAULT; - return 0; -} - -/* Handle 32-bit COMEDI_CMD ioctl. */ -static int compat_cmd(struct file *file, unsigned long arg) -{ - struct comedi_file *cfp = file->private_data; - struct comedi_device *dev = cfp->dev; - struct comedi_cmd cmd; - bool copy = false; - int rc, err; - - rc = get_compat_cmd(&cmd, compat_ptr(arg)); - if (rc) - return rc; - - mutex_lock(&dev->mutex); - rc = do_cmd_ioctl(dev, &cmd, ©, file); - mutex_unlock(&dev->mutex); - if (copy) { - /* Special case: copy cmd back to user. */ - err = put_compat_cmd(compat_ptr(arg), &cmd); - if (err) - rc = err; - } - return rc; -} - -/* Handle 32-bit COMEDI_CMDTEST ioctl. */ -static int compat_cmdtest(struct file *file, unsigned long arg) -{ - struct comedi_file *cfp = file->private_data; - struct comedi_device *dev = cfp->dev; - struct comedi_cmd cmd; - bool copy = false; - int rc, err; - - rc = get_compat_cmd(&cmd, compat_ptr(arg)); - if (rc) - return rc; - - mutex_lock(&dev->mutex); - rc = do_cmdtest_ioctl(dev, &cmd, ©, file); - mutex_unlock(&dev->mutex); - if (copy) { - err = put_compat_cmd(compat_ptr(arg), &cmd); - if (err) - rc = err; - } - return rc; -} - -/* Copy 32-bit insn structure to native insn structure. */ -static int get_compat_insn(struct comedi_insn *insn, - struct comedi32_insn_struct __user *insn32) -{ - struct comedi32_insn_struct v32; - - /* Copy insn structure. Ignore the unused members. */ - if (copy_from_user(&v32, insn32, sizeof(v32))) - return -EFAULT; - memset(insn, 0, sizeof(*insn)); - insn->insn = v32.insn; - insn->n = v32.n; - insn->data = compat_ptr(v32.data); - insn->subdev = v32.subdev; - insn->chanspec = v32.chanspec; - return 0; -} - -/* Handle 32-bit COMEDI_INSNLIST ioctl. */ -static int compat_insnlist(struct file *file, unsigned long arg) -{ - struct comedi_file *cfp = file->private_data; - struct comedi_device *dev = cfp->dev; - struct comedi32_insnlist_struct insnlist32; - struct comedi32_insn_struct __user *insn32; - struct comedi_insn *insns; - unsigned int n; - int rc; - - if (copy_from_user(&insnlist32, compat_ptr(arg), sizeof(insnlist32))) - return -EFAULT; - - insns = kcalloc(insnlist32.n_insns, sizeof(*insns), GFP_KERNEL); - if (!insns) - return -ENOMEM; - - /* Copy insn structures. */ - insn32 = compat_ptr(insnlist32.insns); - for (n = 0; n < insnlist32.n_insns; n++) { - rc = get_compat_insn(insns + n, insn32 + n); - if (rc) { - kfree(insns); - return rc; - } - } - - mutex_lock(&dev->mutex); - rc = do_insnlist_ioctl(dev, insns, insnlist32.n_insns, file); - mutex_unlock(&dev->mutex); - return rc; -} - -/* Handle 32-bit COMEDI_INSN ioctl. */ -static int compat_insn(struct file *file, unsigned long arg) -{ - struct comedi_file *cfp = file->private_data; - struct comedi_device *dev = cfp->dev; - struct comedi_insn insn; - int rc; - - rc = get_compat_insn(&insn, (void __user *)arg); - if (rc) - return rc; - - mutex_lock(&dev->mutex); - rc = do_insn_ioctl(dev, &insn, file); - mutex_unlock(&dev->mutex); - return rc; -} - -/* - * compat_ioctl file operation. - * - * Returns -ENOIOCTLCMD for unrecognised ioctl codes. - */ -static long comedi_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - int rc; - - switch (cmd) { - case COMEDI_DEVCONFIG: - case COMEDI_DEVINFO: - case COMEDI_SUBDINFO: - case COMEDI_BUFCONFIG: - case COMEDI_BUFINFO: - /* Just need to translate the pointer argument. */ - arg = (unsigned long)compat_ptr(arg); - rc = comedi_unlocked_ioctl(file, cmd, arg); - break; - case COMEDI_LOCK: - case COMEDI_UNLOCK: - case COMEDI_CANCEL: - case COMEDI_POLL: - case COMEDI_SETRSUBD: - case COMEDI_SETWSUBD: - /* No translation needed. */ - rc = comedi_unlocked_ioctl(file, cmd, arg); - break; - case COMEDI32_CHANINFO: - rc = compat_chaninfo(file, arg); - break; - case COMEDI32_RANGEINFO: - rc = compat_rangeinfo(file, arg); - break; - case COMEDI32_CMD: - rc = compat_cmd(file, arg); - break; - case COMEDI32_CMDTEST: - rc = compat_cmdtest(file, arg); - break; - case COMEDI32_INSNLIST: - rc = compat_insnlist(file, arg); - break; - case COMEDI32_INSN: - rc = compat_insn(file, arg); - break; - default: - rc = -ENOIOCTLCMD; - break; - } - return rc; -} -#else -#define comedi_compat_ioctl NULL -#endif - -static const struct file_operations comedi_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = comedi_unlocked_ioctl, - .compat_ioctl = comedi_compat_ioctl, - .open = comedi_open, - .release = comedi_close, - .read = comedi_read, - .write = comedi_write, - .mmap = comedi_mmap, - .poll = comedi_poll, - .fasync = comedi_fasync, - .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) -{ - struct comedi_async *async = s->async; - unsigned int events; - int si_code = 0; - unsigned long flags; - - spin_lock_irqsave(&s->spin_lock, flags); - - events = async->events; - async->events = 0; - if (!__comedi_is_subdevice_running(s)) { - spin_unlock_irqrestore(&s->spin_lock, flags); - return; - } - - if (events & COMEDI_CB_CANCEL_MASK) - __comedi_clear_subdevice_runflags(s, COMEDI_SRF_RUNNING); - - /* - * Remember if an error event has occurred, so an error can be - * returned the next time the user does a read() or write(). - */ - if (events & COMEDI_CB_ERROR_MASK) - __comedi_set_subdevice_runflags(s, COMEDI_SRF_ERROR); - - if (async->cb_mask & events) { - wake_up_interruptible(&async->wait_head); - si_code = async->cmd.flags & CMDF_WRITE ? POLL_OUT : POLL_IN; - } - - spin_unlock_irqrestore(&s->spin_lock, flags); - - if (si_code) - kill_fasync(&dev->async_queue, SIGIO, si_code); -} -EXPORT_SYMBOL_GPL(comedi_event); - -/* Note: the ->mutex is pre-locked on successful return */ -struct comedi_device *comedi_alloc_board_minor(struct device *hardware_device) -{ - struct comedi_device *dev; - struct device *csdev; - unsigned int i; - - dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) - return ERR_PTR(-ENOMEM); - comedi_device_init(dev); - comedi_set_hw_dev(dev, hardware_device); - mutex_lock(&dev->mutex); - mutex_lock(&comedi_board_minor_table_lock); - for (i = hardware_device ? comedi_num_legacy_minors : 0; - i < COMEDI_NUM_BOARD_MINORS; ++i) { - if (!comedi_board_minor_table[i]) { - comedi_board_minor_table[i] = dev; - break; - } - } - mutex_unlock(&comedi_board_minor_table_lock); - if (i == COMEDI_NUM_BOARD_MINORS) { - mutex_unlock(&dev->mutex); - comedi_device_cleanup(dev); - comedi_dev_put(dev); - dev_err(hardware_device, - "ran out of minor numbers for board device files\n"); - return ERR_PTR(-EBUSY); - } - dev->minor = i; - csdev = device_create(comedi_class, hardware_device, - MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i", i); - if (!IS_ERR(csdev)) - dev->class_dev = get_device(csdev); - - /* Note: dev->mutex needs to be unlocked by the caller. */ - return dev; -} - -void comedi_release_hardware_device(struct device *hardware_device) -{ - int minor; - struct comedi_device *dev; - - for (minor = comedi_num_legacy_minors; minor < COMEDI_NUM_BOARD_MINORS; - minor++) { - mutex_lock(&comedi_board_minor_table_lock); - dev = comedi_board_minor_table[minor]; - if (dev && dev->hw_dev == hardware_device) { - comedi_board_minor_table[minor] = NULL; - mutex_unlock(&comedi_board_minor_table_lock); - comedi_free_board_dev(dev); - break; - } - mutex_unlock(&comedi_board_minor_table_lock); - } -} - -int comedi_alloc_subdevice_minor(struct comedi_subdevice *s) -{ - struct comedi_device *dev = s->device; - struct device *csdev; - unsigned int i; - - mutex_lock(&comedi_subdevice_minor_table_lock); - for (i = 0; i < COMEDI_NUM_SUBDEVICE_MINORS; ++i) { - if (!comedi_subdevice_minor_table[i]) { - comedi_subdevice_minor_table[i] = s; - break; - } - } - mutex_unlock(&comedi_subdevice_minor_table_lock); - if (i == COMEDI_NUM_SUBDEVICE_MINORS) { - dev_err(dev->class_dev, - "ran out of minor numbers for subdevice files\n"); - return -EBUSY; - } - i += COMEDI_NUM_BOARD_MINORS; - s->minor = i; - csdev = device_create(comedi_class, dev->class_dev, - MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i_subd%i", - dev->minor, s->index); - if (!IS_ERR(csdev)) - s->class_dev = csdev; - - return 0; -} - -void comedi_free_subdevice_minor(struct comedi_subdevice *s) -{ - unsigned int i; - - if (!s) - return; - if (s->minor < COMEDI_NUM_BOARD_MINORS || - s->minor >= COMEDI_NUM_MINORS) - return; - - i = s->minor - COMEDI_NUM_BOARD_MINORS; - mutex_lock(&comedi_subdevice_minor_table_lock); - if (s == comedi_subdevice_minor_table[i]) - comedi_subdevice_minor_table[i] = NULL; - mutex_unlock(&comedi_subdevice_minor_table_lock); - if (s->class_dev) { - device_destroy(comedi_class, MKDEV(COMEDI_MAJOR, s->minor)); - s->class_dev = NULL; - } -} - -static void comedi_cleanup_board_minors(void) -{ - struct comedi_device *dev; - unsigned int 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) -{ - int i; - int retval; - - pr_info("version " COMEDI_RELEASE " - http://www.comedi.org\n"); - - if (comedi_num_legacy_minors > COMEDI_NUM_BOARD_MINORS) { - pr_err("invalid value for module parameter \"comedi_num_legacy_minors\". Valid values are 0 through %i.\n", - COMEDI_NUM_BOARD_MINORS); - return -EINVAL; - } - - retval = register_chrdev_region(MKDEV(COMEDI_MAJOR, 0), - COMEDI_NUM_MINORS, "comedi"); - if (retval) - return retval; - - cdev_init(&comedi_cdev, &comedi_fops); - comedi_cdev.owner = THIS_MODULE; - - retval = kobject_set_name(&comedi_cdev.kobj, "comedi"); - if (retval) - goto out_unregister_chrdev_region; - - retval = cdev_add(&comedi_cdev, MKDEV(COMEDI_MAJOR, 0), - COMEDI_NUM_MINORS); - if (retval) - goto out_unregister_chrdev_region; - - comedi_class = class_create(THIS_MODULE, "comedi"); - if (IS_ERR(comedi_class)) { - retval = PTR_ERR(comedi_class); - pr_err("failed to create class\n"); - goto out_cdev_del; - } - - comedi_class->dev_groups = comedi_dev_groups; - - /* create devices files for legacy/manual use */ - for (i = 0; i < comedi_num_legacy_minors; i++) { - struct comedi_device *dev; - - dev = comedi_alloc_board_minor(NULL); - if (IS_ERR(dev)) { - retval = PTR_ERR(dev); - goto out_cleanup_board_minors; - } - /* comedi_alloc_board_minor() locked the mutex */ - lockdep_assert_held(&dev->mutex); - mutex_unlock(&dev->mutex); - } - - /* XXX requires /proc interface */ - comedi_proc_init(); - - return 0; - -out_cleanup_board_minors: - comedi_cleanup_board_minors(); - class_destroy(comedi_class); -out_cdev_del: - cdev_del(&comedi_cdev); -out_unregister_chrdev_region: - unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS); - return retval; -} -module_init(comedi_init); - -static void __exit comedi_cleanup(void) -{ - comedi_cleanup_board_minors(); - class_destroy(comedi_class); - cdev_del(&comedi_cdev); - unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS); - - comedi_proc_cleanup(); -} -module_exit(comedi_cleanup); - -MODULE_AUTHOR("https://www.comedi.org"); -MODULE_DESCRIPTION("Comedi core module"); -MODULE_LICENSE("GPL"); |