// 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 = kcalloc(num_subdevices, sizeof(*s), GFP_KERNEL);
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(&dev->attach_lock);
lockdep_assert_held(&dev->mutex);
if (dev->subdevices