Path: blob/master/drivers/comedi/kcomedilib/kcomedilib_main.c
52467 views
// SPDX-License-Identifier: GPL-2.0+1/*2* kcomedilib/kcomedilib.c3* a comedlib interface for kernel modules4*5* COMEDI - Linux Control and Measurement Device Interface6* Copyright (C) 1997-2000 David A. Schleef <[email protected]>7*/89#include <linux/module.h>1011#include <linux/errno.h>12#include <linux/kernel.h>13#include <linux/sched.h>14#include <linux/fcntl.h>15#include <linux/mm.h>16#include <linux/io.h>17#include <linux/bitmap.h>1819#include <linux/comedi.h>20#include <linux/comedi/comedidev.h>21#include <linux/comedi/comedilib.h>2223MODULE_AUTHOR("David Schleef <[email protected]>");24MODULE_DESCRIPTION("Comedi kernel library");25MODULE_LICENSE("GPL");2627static DEFINE_MUTEX(kcomedilib_to_from_lock);2829/*30* Row index is the "to" node, column index is the "from" node, element value31* is the number of links from the "from" node to the "to" node.32*/33static unsigned char34kcomedilib_to_from[COMEDI_NUM_BOARD_MINORS][COMEDI_NUM_BOARD_MINORS];3536static bool kcomedilib_set_link_from_to(unsigned int from, unsigned int to)37{38DECLARE_BITMAP(destinations[2], COMEDI_NUM_BOARD_MINORS);39unsigned int cur = 0;40bool okay = true;4142/*43* Allow "from" node to be out of range (no loop checking),44* but require "to" node to be in range.45*/46if (to >= COMEDI_NUM_BOARD_MINORS)47return false;48if (from >= COMEDI_NUM_BOARD_MINORS)49return true;5051/*52* Check that kcomedilib_to_from[to][from] can be made non-zero53* without creating a loop.54*55* Termination of the loop-testing code relies on the assumption that56* kcomedilib_to_from[][] does not contain any loops.57*58* Start with a set destinations set containing "from" as the only59* element and work backwards looking for loops.60*/61bitmap_zero(destinations[cur], COMEDI_NUM_BOARD_MINORS);62set_bit(from, destinations[cur]);63mutex_lock(&kcomedilib_to_from_lock);64do {65unsigned int next = 1 - cur;66unsigned int t = 0;6768if (test_bit(to, destinations[cur])) {69/* Loop detected. */70okay = false;71break;72}73/* Create next set of destinations. */74bitmap_zero(destinations[next], COMEDI_NUM_BOARD_MINORS);75while ((t = find_next_bit(destinations[cur],76COMEDI_NUM_BOARD_MINORS,77t)) < COMEDI_NUM_BOARD_MINORS) {78unsigned int f;7980for (f = 0; f < COMEDI_NUM_BOARD_MINORS; f++) {81if (kcomedilib_to_from[t][f])82set_bit(f, destinations[next]);83}84t++;85}86cur = next;87} while (!bitmap_empty(destinations[cur], COMEDI_NUM_BOARD_MINORS));88if (okay) {89/* Allow a maximum of 255 links from "from" to "to". */90if (kcomedilib_to_from[to][from] < 255)91kcomedilib_to_from[to][from]++;92else93okay = false;94}95mutex_unlock(&kcomedilib_to_from_lock);96return okay;97}9899static void kcomedilib_clear_link_from_to(unsigned int from, unsigned int to)100{101if (to < COMEDI_NUM_BOARD_MINORS && from < COMEDI_NUM_BOARD_MINORS) {102mutex_lock(&kcomedilib_to_from_lock);103if (kcomedilib_to_from[to][from])104kcomedilib_to_from[to][from]--;105mutex_unlock(&kcomedilib_to_from_lock);106}107}108109/**110* comedi_open_from() - Open a COMEDI device from the kernel with loop checks111* @filename: Fake pathname of the form "/dev/comediN".112* @from: Device number it is being opened from (if in range).113*114* Converts @filename to a COMEDI device number and "opens" it if it exists115* and is attached to a low-level COMEDI driver.116*117* If @from is in range, refuse to open the device if doing so would form a118* loop of devices opening each other. There is also a limit of 255 on the119* number of concurrent opens from one device to another.120*121* Return: A pointer to the COMEDI device on success.122* Return %NULL on failure.123*/124struct comedi_device *comedi_open_from(const char *filename, int from)125{126struct comedi_device *dev, *retval = NULL;127unsigned int minor;128129if (strncmp(filename, "/dev/comedi", 11) != 0)130return NULL;131132if (kstrtouint(filename + 11, 0, &minor))133return NULL;134135if (minor >= COMEDI_NUM_BOARD_MINORS)136return NULL;137138dev = comedi_dev_get_from_minor(minor);139if (!dev)140return NULL;141142down_read(&dev->attach_lock);143if (dev->attached && kcomedilib_set_link_from_to(from, minor))144retval = dev;145else146retval = NULL;147up_read(&dev->attach_lock);148149if (!retval)150comedi_dev_put(dev);151152return retval;153}154EXPORT_SYMBOL_GPL(comedi_open_from);155156/**157* comedi_close_from() - Close a COMEDI device from the kernel with loop checks158* @dev: COMEDI device.159* @from: Device number it was opened from (if in range).160*161* Closes a COMEDI device previously opened by comedi_open_from().162*163* If @from is in range, it should be match the one used by comedi_open_from().164*165* Returns: 0166*/167int comedi_close_from(struct comedi_device *dev, int from)168{169kcomedilib_clear_link_from_to(from, dev->minor);170comedi_dev_put(dev);171return 0;172}173EXPORT_SYMBOL_GPL(comedi_close_from);174175static int comedi_do_insn(struct comedi_device *dev,176struct comedi_insn *insn,177unsigned int *data)178{179struct comedi_subdevice *s;180int ret;181182mutex_lock(&dev->mutex);183184if (!dev->attached) {185ret = -EINVAL;186goto error;187}188189/* a subdevice instruction */190if (insn->subdev >= dev->n_subdevices) {191ret = -EINVAL;192goto error;193}194s = &dev->subdevices[insn->subdev];195196if (s->type == COMEDI_SUBD_UNUSED) {197dev_err(dev->class_dev,198"%d not usable subdevice\n", insn->subdev);199ret = -EIO;200goto error;201}202203/* XXX check lock */204205ret = comedi_check_chanlist(s, 1, &insn->chanspec);206if (ret < 0) {207dev_err(dev->class_dev, "bad chanspec\n");208ret = -EINVAL;209goto error;210}211212if (s->busy) {213ret = -EBUSY;214goto error;215}216s->busy = dev;217218switch (insn->insn) {219case INSN_BITS:220ret = s->insn_bits(dev, s, insn, data);221break;222case INSN_CONFIG:223/* XXX should check instruction length */224ret = s->insn_config(dev, s, insn, data);225break;226default:227ret = -EINVAL;228break;229}230231s->busy = NULL;232error:233234mutex_unlock(&dev->mutex);235return ret;236}237238int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev,239unsigned int chan, unsigned int *io)240{241struct comedi_insn insn;242unsigned int data[2];243int ret;244245memset(&insn, 0, sizeof(insn));246insn.insn = INSN_CONFIG;247insn.n = 2;248insn.subdev = subdev;249insn.chanspec = CR_PACK(chan, 0, 0);250data[0] = INSN_CONFIG_DIO_QUERY;251data[1] = 0;252ret = comedi_do_insn(dev, &insn, data);253if (ret >= 0)254*io = data[1];255return ret;256}257EXPORT_SYMBOL_GPL(comedi_dio_get_config);258259int comedi_dio_config(struct comedi_device *dev, unsigned int subdev,260unsigned int chan, unsigned int io)261{262struct comedi_insn insn;263264memset(&insn, 0, sizeof(insn));265insn.insn = INSN_CONFIG;266insn.n = 1;267insn.subdev = subdev;268insn.chanspec = CR_PACK(chan, 0, 0);269270return comedi_do_insn(dev, &insn, &io);271}272EXPORT_SYMBOL_GPL(comedi_dio_config);273274int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev,275unsigned int mask, unsigned int *bits,276unsigned int base_channel)277{278struct comedi_insn insn;279unsigned int data[2];280unsigned int n_chan;281unsigned int shift;282int ret;283284base_channel = CR_CHAN(base_channel);285n_chan = comedi_get_n_channels(dev, subdev);286if (base_channel >= n_chan)287return -EINVAL;288289memset(&insn, 0, sizeof(insn));290insn.insn = INSN_BITS;291insn.chanspec = base_channel;292insn.n = 2;293insn.subdev = subdev;294295data[0] = mask;296data[1] = *bits;297298/*299* Most drivers ignore the base channel in insn->chanspec.300* Fix this here if the subdevice has <= 32 channels.301*/302if (n_chan <= 32) {303shift = base_channel;304if (shift) {305insn.chanspec = 0;306data[0] <<= shift;307data[1] <<= shift;308}309} else {310shift = 0;311}312313ret = comedi_do_insn(dev, &insn, data);314*bits = data[1] >> shift;315return ret;316}317EXPORT_SYMBOL_GPL(comedi_dio_bitfield2);318319int comedi_find_subdevice_by_type(struct comedi_device *dev, int type,320unsigned int subd)321{322struct comedi_subdevice *s;323int ret = -ENODEV;324325down_read(&dev->attach_lock);326if (dev->attached)327for (; subd < dev->n_subdevices; subd++) {328s = &dev->subdevices[subd];329if (s->type == type) {330ret = subd;331break;332}333}334up_read(&dev->attach_lock);335return ret;336}337EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type);338339int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice)340{341int n;342343down_read(&dev->attach_lock);344if (!dev->attached || subdevice >= dev->n_subdevices)345n = 0;346else347n = dev->subdevices[subdevice].n_chan;348up_read(&dev->attach_lock);349350return n;351}352EXPORT_SYMBOL_GPL(comedi_get_n_channels);353354static int __init kcomedilib_module_init(void)355{356return 0;357}358359static void __exit kcomedilib_module_exit(void)360{361}362363module_init(kcomedilib_module_init);364module_exit(kcomedilib_module_exit);365366367