1289 lines · 36.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0+/* *  module/drivers.c *  functions for manipulating drivers * *  COMEDI - Linux Control and Measurement Device Interface *  Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> *  Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net> */ #include <linux/device.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/kernel.h>#include <linux/ioport.h>#include <linux/slab.h>#include <linux/dma-direction.h>#include <linux/interrupt.h>#include <linux/firmware.h>#include <linux/comedi/comedidev.h>#include "comedi_internal.h" struct comedi_driver *comedi_drivers;/* protects access to comedi_drivers */DEFINE_MUTEX(comedi_drivers_list_lock); /** * comedi_set_hw_dev() - Set hardware device associated with COMEDI device * @dev: COMEDI device. * @hw_dev: Hardware device. * * For automatically configured COMEDI devices (resulting from a call to * comedi_auto_config() or one of its wrappers from the low-level COMEDI * driver), comedi_set_hw_dev() is called automatically by the COMEDI core * to associate the COMEDI device with the hardware device.  It can also be * called directly by "legacy" low-level COMEDI drivers that rely on the * %COMEDI_DEVCONFIG ioctl to configure the hardware as long as the hardware * has a &struct device. * * If @dev->hw_dev is NULL, it gets a reference to @hw_dev and sets * @dev->hw_dev, otherwise, it does nothing.  Calling it multiple times * with the same hardware device is not considered an error.  If it gets * a reference to the hardware device, it will be automatically 'put' when * the device is detached from COMEDI. * * Returns 0 if @dev->hw_dev was NULL or the same as @hw_dev, otherwise * returns -EEXIST. */int comedi_set_hw_dev(struct comedi_device *dev, struct device *hw_dev){	if (hw_dev == dev->hw_dev)		return 0;	if (dev->hw_dev)		return -EEXIST;	dev->hw_dev = get_device(hw_dev);	return 0;}EXPORT_SYMBOL_GPL(comedi_set_hw_dev); static void comedi_clear_hw_dev(struct comedi_device *dev){	put_device(dev->hw_dev);	dev->hw_dev = NULL;} /** * comedi_alloc_devpriv() - Allocate memory for the device private data * @dev: COMEDI device. * @size: Size of the memory to allocate. * * The allocated memory is zero-filled.  @dev->private points to it on * return.  The memory will be automatically freed when the COMEDI device is * "detached". * * Returns a pointer to the allocated memory, or NULL on failure. */void *comedi_alloc_devpriv(struct comedi_device *dev, size_t size){	dev->private = kzalloc(size, GFP_KERNEL);	return dev->private;}EXPORT_SYMBOL_GPL(comedi_alloc_devpriv); /** * comedi_alloc_subdevices() - Allocate subdevices for COMEDI device * @dev: COMEDI device. * @num_subdevices: Number of subdevices to allocate. * * Allocates and initializes an array of &struct comedi_subdevice for the * COMEDI device.  If successful, sets @dev->subdevices to point to the * first one and @dev->n_subdevices to the number. * * Returns 0 on success, -EINVAL if @num_subdevices is < 1, or -ENOMEM if * failed to allocate the memory. */int comedi_alloc_subdevices(struct comedi_device *dev, int num_subdevices){	struct comedi_subdevice *s;	int i; 	if (num_subdevices < 1)		return -EINVAL; 	s = kzalloc_objs(*s, num_subdevices);	if (!s)		return -ENOMEM;	dev->subdevices = s;	dev->n_subdevices = num_subdevices; 	for (i = 0; i < num_subdevices; ++i) {		s = &dev->subdevices[i];		s->device = dev;		s->index = i;		s->async_dma_dir = DMA_NONE;		spin_lock_init(&s->spin_lock);		s->minor = -1;	}	return 0;}EXPORT_SYMBOL_GPL(comedi_alloc_subdevices); /** * comedi_alloc_subdev_readback() - Allocate memory for the subdevice readback * @s: COMEDI subdevice. * * This is called by low-level COMEDI drivers to allocate an array to record * the last values written to a subdevice's analog output channels (at least * by the %INSN_WRITE instruction), to allow them to be read back by an * %INSN_READ instruction.  It also provides a default handler for the * %INSN_READ instruction unless one has already been set. * * On success, @s->readback points to the first element of the array, which * is zero-filled.  The low-level driver is responsible for updating its * contents.  @s->insn_read will be set to comedi_readback_insn_read() * unless it is already non-NULL. * * Returns 0 on success, -EINVAL if the subdevice has no channels, or * -ENOMEM on allocation failure. */int comedi_alloc_subdev_readback(struct comedi_subdevice *s){	if (!s->n_chan)		return -EINVAL; 	s->readback = kcalloc(s->n_chan, sizeof(*s->readback), GFP_KERNEL);	if (!s->readback)		return -ENOMEM; 	if (!s->insn_read)		s->insn_read = comedi_readback_insn_read; 	return 0;}EXPORT_SYMBOL_GPL(comedi_alloc_subdev_readback); static void comedi_device_detach_cleanup(struct comedi_device *dev){	int i;	struct comedi_subdevice *s; 	lockdep_assert_held_write(&dev->attach_lock);	lockdep_assert_held(&dev->mutex);	if (dev->subdevices) {		for (i = 0; i < dev->n_subdevices; i++) {			s = &dev->subdevices[i];			if (comedi_can_auto_free_spriv(s))				kfree(s->private);			comedi_free_subdevice_minor(s);			if (s->async) {				comedi_buf_alloc(dev, s, 0);				kfree(s->async);			}			kfree(s->readback);		}		kfree(dev->subdevices);		dev->subdevices = NULL;		dev->n_subdevices = 0;	}	kfree(dev->private);	if (!IS_ERR(dev->pacer))		kfree(dev->pacer);	dev->private = NULL;	dev->pacer = NULL;	dev->driver = NULL;	dev->board_name = NULL;	dev->board_ptr = NULL;	dev->mmio = NULL;	dev->iobase = 0;	dev->iolen = 0;	dev->ioenabled = false;	dev->irq = 0;	dev->read_subdev = NULL;	dev->write_subdev = NULL;	dev->open = NULL;	dev->close = NULL;	comedi_clear_hw_dev(dev);} void comedi_device_detach_locked(struct comedi_device *dev){	lockdep_assert_held_write(&dev->attach_lock);	lockdep_assert_held(&dev->mutex);	comedi_device_cancel_all(dev);	dev->attached = false;	dev->detach_count++;	if (dev->driver)		dev->driver->detach(dev);	comedi_device_detach_cleanup(dev);} void comedi_device_detach(struct comedi_device *dev){	lockdep_assert_held(&dev->mutex);	down_write(&dev->attach_lock);	comedi_device_detach_locked(dev);	up_write(&dev->attach_lock);} static int poll_invalid(struct comedi_device *dev, struct comedi_subdevice *s){	return -EINVAL;} static int insn_device_inval(struct comedi_device *dev,			     struct comedi_insn *insn, unsigned int *data){	return -EINVAL;} static unsigned int get_zero_valid_routes(struct comedi_device *dev,					  unsigned int n_pairs,					  unsigned int *pair_data){	return 0;} int insn_inval(struct comedi_device *dev, struct comedi_subdevice *s,	       struct comedi_insn *insn, unsigned int *data){	return -EINVAL;} /** * comedi_readback_insn_read() - A generic (*insn_read) for subdevice readback. * @dev: COMEDI device. * @s: COMEDI subdevice. * @insn: COMEDI instruction. * @data: Pointer to return the readback data. * * Handles the %INSN_READ instruction for subdevices that use the readback * array allocated by comedi_alloc_subdev_readback().  It may be used * directly as the subdevice's handler (@s->insn_read) or called via a * wrapper. * * @insn->n is normally 1, which will read a single value.  If higher, the * same element of the readback array will be read multiple times. * * Returns @insn->n on success, or -EINVAL if @s->readback is NULL. */int comedi_readback_insn_read(struct comedi_device *dev,			      struct comedi_subdevice *s,			      struct comedi_insn *insn,			      unsigned int *data){	unsigned int chan = CR_CHAN(insn->chanspec);	int i; 	if (!s->readback)		return -EINVAL; 	for (i = 0; i < insn->n; i++)		data[i] = s->readback[chan]; 	return insn->n;}EXPORT_SYMBOL_GPL(comedi_readback_insn_read); /** * comedi_timeout() - Busy-wait for a driver condition to occur * @dev: COMEDI device. * @s: COMEDI subdevice. * @insn: COMEDI instruction. * @cb: Callback to check for the condition. * @context: Private context from the driver. * * Busy-waits for up to a second (%COMEDI_TIMEOUT_MS) for the condition or * some error (other than -EBUSY) to occur.  The parameters @dev, @s, @insn, * and @context are passed to the callback function, which returns -EBUSY to * continue waiting or some other value to stop waiting (generally 0 if the * condition occurred, or some error value). * * Returns -ETIMEDOUT if timed out, otherwise the return value from the * callback function. */int comedi_timeout(struct comedi_device *dev,		   struct comedi_subdevice *s,		   struct comedi_insn *insn,		   int (*cb)(struct comedi_device *dev,			     struct comedi_subdevice *s,			     struct comedi_insn *insn,			     unsigned long context),		   unsigned long context){	unsigned long timeout = jiffies + msecs_to_jiffies(COMEDI_TIMEOUT_MS);	int ret; 	while (time_before(jiffies, timeout)) {		ret = cb(dev, s, insn, context);		if (ret != -EBUSY)			return ret;	/* success (0) or non EBUSY errno */		cpu_relax();	}	return -ETIMEDOUT;}EXPORT_SYMBOL_GPL(comedi_timeout); /** * comedi_dio_insn_config() - Boilerplate (*insn_config) for DIO subdevices * @dev: COMEDI device. * @s: COMEDI subdevice. * @insn: COMEDI instruction. * @data: Instruction parameters and return data. * @mask: io_bits mask for grouped channels, or 0 for single channel. * * If @mask is 0, it is replaced with a single-bit mask corresponding to the * channel number specified by @insn->chanspec.  Otherwise, @mask * corresponds to a group of channels (which should include the specified * channel) that are always configured together as inputs or outputs. * * Partially handles the %INSN_CONFIG_DIO_INPUT, %INSN_CONFIG_DIO_OUTPUTS, * and %INSN_CONFIG_DIO_QUERY instructions.  The first two update * @s->io_bits to record the directions of the masked channels.  The last * one sets @data[1] to the current direction of the group of channels * (%COMEDI_INPUT) or %COMEDI_OUTPUT) as recorded in @s->io_bits. * * The caller is responsible for updating the DIO direction in the hardware * registers if this function returns 0. * * Returns 0 for a %INSN_CONFIG_DIO_INPUT or %INSN_CONFIG_DIO_OUTPUT * instruction, @insn->n (> 0) for a %INSN_CONFIG_DIO_QUERY instruction, or * -EINVAL for some other instruction. */int comedi_dio_insn_config(struct comedi_device *dev,			   struct comedi_subdevice *s,			   struct comedi_insn *insn,			   unsigned int *data,			   unsigned int mask){	unsigned int chan = CR_CHAN(insn->chanspec); 	if (!mask && chan < 32)		mask = 1U << chan; 	switch (data[0]) {	case INSN_CONFIG_DIO_INPUT:		s->io_bits &= ~mask;		break; 	case INSN_CONFIG_DIO_OUTPUT:		s->io_bits |= mask;		break; 	case INSN_CONFIG_DIO_QUERY:		data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT;		return insn->n; 	default:		return -EINVAL;	} 	return 0;}EXPORT_SYMBOL_GPL(comedi_dio_insn_config); /** * comedi_dio_update_state() - Update the internal state of DIO subdevices * @s: COMEDI subdevice. * @data: The channel mask and bits to update. * * Updates @s->state which holds the internal state of the outputs for DIO * or DO subdevices (up to 32 channels).  @data[0] contains a bit-mask of * the channels to be updated.  @data[1] contains a bit-mask of those * channels to be set to '1'.  The caller is responsible for updating the * outputs in hardware according to @s->state.  As a minimum, the channels * in the returned bit-mask need to be updated. * * Returns @mask with non-existent channels removed. */unsigned int comedi_dio_update_state(struct comedi_subdevice *s,				     unsigned int *data){	unsigned int chanmask = (s->n_chan < 32) ? ((1U << s->n_chan) - 1)						 : 0xffffffff;	unsigned int mask = data[0] & chanmask;	unsigned int bits = data[1]; 	if (mask) {		s->state &= ~mask;		s->state |= (bits & mask);	} 	return mask;}EXPORT_SYMBOL_GPL(comedi_dio_update_state); /** * comedi_bytes_per_scan_cmd() - Get length of asynchronous command "scan" in * bytes * @s: COMEDI subdevice. * @cmd: COMEDI command. * * Determines the overall scan length according to the subdevice type and the * number of channels in the scan for the specified command. * * For digital input, output or input/output subdevices, samples for * multiple channels are assumed to be packed into one or more unsigned * short or unsigned int values according to the subdevice's %SDF_LSAMPL * flag.  For other types of subdevice, samples are assumed to occupy a * whole unsigned short or unsigned int according to the %SDF_LSAMPL flag. * * Returns the overall scan length in bytes. */unsigned int comedi_bytes_per_scan_cmd(struct comedi_subdevice *s,				       struct comedi_cmd *cmd){	unsigned int num_samples;	unsigned int bits_per_sample; 	switch (s->type) {	case COMEDI_SUBD_DI:	case COMEDI_SUBD_DO:	case COMEDI_SUBD_DIO:		bits_per_sample = 8 * comedi_bytes_per_sample(s);		num_samples = DIV_ROUND_UP(cmd->scan_end_arg, bits_per_sample);		break;	default:		num_samples = cmd->scan_end_arg;		break;	}	return comedi_samples_to_bytes(s, num_samples);}EXPORT_SYMBOL_GPL(comedi_bytes_per_scan_cmd); static unsigned int _comedi_bytes_per_scan(struct comedi_subdevice *s){	struct comedi_cmd *cmd = &s->async->cmd; 	return comedi_bytes_per_scan_cmd(s, cmd);} /** * comedi_bytes_per_scan() - Get length of asynchronous command "scan" in bytes * @s: COMEDI subdevice. * * Determines the overall scan length according to the subdevice type and the * number of channels in the scan for the current command. * * For digital input, output or input/output subdevices, samples for * multiple channels are assumed to be packed into one or more unsigned * short or unsigned int values according to the subdevice's %SDF_LSAMPL * flag.  For other types of subdevice, samples are assumed to occupy a * whole unsigned short or unsigned int according to the %SDF_LSAMPL flag. * * Returns the overall scan length in bytes. */unsigned int comedi_bytes_per_scan(struct comedi_subdevice *s){	unsigned int num_bytes; 	if (comedi_get_is_subdevice_running(s)) {		num_bytes = _comedi_bytes_per_scan(s);		comedi_put_is_subdevice_running(s);	} else {		/* Use nomimal, single sample scan length. */		num_bytes = comedi_samples_to_bytes(s, 1);	}	return num_bytes;}EXPORT_SYMBOL_GPL(comedi_bytes_per_scan); static unsigned int __comedi_nscans_left(struct comedi_subdevice *s,					 unsigned int nscans){	struct comedi_async *async = s->async;	struct comedi_cmd *cmd = &async->cmd; 	if (cmd->stop_src == TRIG_COUNT) {		unsigned int scans_left = 0; 		if (async->scans_done < cmd->stop_arg)			scans_left = cmd->stop_arg - async->scans_done; 		if (nscans > scans_left)			nscans = scans_left;	}	return nscans;} static unsigned int _comedi_nscans_left(struct comedi_subdevice *s,					unsigned int nscans)

Lines 1–500 of 1289.