// SPDX-License-Identifier: GPL-2.0+ /* * kcomedilib/kcomedilib.c * a comedlib interface for kernel modules * * COMEDI - Linux Control and Measurement Device Interface * Copyright (C) 1997-2000 David A. Schleef */ #include #include #include #include #include #include #include #include #include #include #include MODULE_AUTHOR("David Schleef "); MODULE_DESCRIPTION("Comedi kernel library"); MODULE_LICENSE("GPL"); static DEFINE_MUTEX(kcomedilib_to_from_lock); /* * Row index is the "to" node, column index is the "from" node, element value * is the number of links from the "from" node to the "to" node. */ static unsigned char kcomedilib_to_from[COMEDI_NUM_BOARD_MINORS][COMEDI_NUM_BOARD_MINORS]; static bool kcomedilib_set_link_from_to(unsigned int from, unsigned int to) { DECLARE_BITMAP(destinations[2], COMEDI_NUM_BOARD_MINORS); unsigned int cur = 0; bool okay = true; /* * Allow "from" node to be out of range (no loop checking), * but require "to" node to be in range. */ if (to >= COMEDI_NUM_BOARD_MINORS) return false; if (from >= COMEDI_NUM_BOARD_MINORS) return true; /* * Check that kcomedilib_to_from[to][from] can be made non-zero * without creating a loop. * * Termination of the loop-testing code relies on the assumption that * kcomedilib_to_from[][] does not contain any loops. * * Start with a set destinations set containing "from" as the only * element and work backwards looking for loops. */ bitmap_zero(destinations[cur], COMEDI_NUM_BOARD_MINORS); set_bit(from, destinations[cur]); mutex_lock(&kcomedilib_to_from_lock); do { unsigned int next = 1 - cur; unsigned int t = 0; if (test_bit(to, destinations[cur])) { /* Loop detected. */ okay = false; break; } /* Create next set of destinations. */ bitmap_zero(destinations[next], COMEDI_NUM_BOARD_MINORS); while ((t = find_next_bit(destinations[cur], COMEDI_NUM_BOARD_MINORS, t)) < COMEDI_NUM_BOARD_MINORS) { unsigned int f; for (f = 0; f < COMEDI_NUM_BOARD_MINORS; f++) { if (kcomedilib_to_from[t][f]) set_bit(f, destinations[next]); } t++; } cur = next; } while (!bitmap_empty(destinations[cur], COMEDI_NUM_BOARD_MINORS)); if (okay) { /* Allow a maximum of 255 links from "from" to "to". */ if (kcomedilib_to_from[to][from] < 255) kcomedilib_to_from[to][from]++; else okay = false; } mutex_unlock(&kcomedilib_to_from_lock); return okay; } static void kcomedilib_clear_link_from_to(unsigned int from, unsigned int to) { if (to < COMEDI_NUM_BOARD_MINORS && from < COMEDI_NUM_BOARD_MINORS) { mutex_lock(&kcomedilib_to_from_lock); if (kcomedilib_to_from[to][from]) kcomedilib_to_from[to][from]--; mutex_unlock(&kcomedilib_to_from_lock); } } /** * comedi_open_from() - Open a COMEDI device from the kernel with loop checks * @filename: Fake pathname of the form "/dev/comediN". * @from: Device number it is being opened from (if in range). * * Converts @filename to a COMEDI device number and "opens" it if it exists * and is attached to a low-level COMEDI driver. * * If @from is in range, refuse to open the device if doing so would form a * loop of devices opening each other. There is also a limit of 255 on the * number of concurrent opens from one device to another. * * Return: A pointer to the COMEDI device on success. * Return %NULL on failure. */ struct comedi_device *comedi_open_from(const char *filename, int from) { struct comedi_device *dev, *retval = NULL; unsigned int minor; if (strncmp(filename, "/dev/comedi", 11) != 0) return NULL; if (kstrtouint(filename + 11, 0, &minor)) return NULL; if (minor >= COMEDI_NUM_BOARD_MINORS) return NULL; dev = comedi_dev_get_from_minor(minor); if (!dev) return NULL; down_read(&dev->attach_lock); if (dev->attached && kcomedilib_set_link_from_to(from, minor)) retval = dev; else retval = NULL; up_read(&dev->attach_lock); if (!retval) comedi_dev_put(dev); return retval; } EXPORT_SYMBOL_GPL(comedi_open_from); /** * comedi_close_from() - Close a COMEDI device from the kernel with loop checks * @dev: COMEDI device. * @from: Device number it was opened from (if in range). * * Closes a COMEDI device previously opened by comedi_open_from(). * * If @from is in range, it should be match the one used by comedi_open_from(). * * Returns: 0 */ int comedi_close_from(struct comedi_device *dev, int from) { kcomedilib_clear_link_from_to(from, dev->minor); comedi_dev_put(dev); return 0; } EXPORT_SYMBOL_GPL(comedi_close_from); static int comedi_do_insn(struct comedi_device *dev, struct comedi_insn *insn, unsigned int *data) { struct comedi_subdevice *s; int ret; mutex_lock(&dev->mutex); if (!dev->attached) { ret = -EINVAL; goto error; } /* a subdevice instruction */ if (insn->subdev >= dev->n_subdevices) { ret = -EINVAL; goto error; } s = &dev->subdevices[insn->subdev]; if (s->type == COMEDI_SUBD_UNUSED) { dev_err(dev->class_dev, "%d not usable subdevice\n", insn->subdev); ret = -EIO; goto error; } /* XXX check lock */ ret = comedi_check_chanlist(s, 1, &insn->chanspec); if (ret < 0) { dev_err(dev->class_dev, "bad chanspec\n"); ret = -EINVAL; goto error; } if (s->busy) { ret = -EBUSY; goto error; } s->busy = dev; switch (insn->insn) { case INSN_BITS: ret = s->insn_bits(dev, s, insn, data); break; case INSN_CONFIG: /* XXX should check instruction length */ ret = s->insn_config(dev, s, insn, data); break; default: ret = -EINVAL; break; } s->busy = NULL; error: mutex_unlock(&dev->mutex); return ret; } int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev, unsigned int chan, unsigned int *io) { struct comedi_insn insn; unsigned int data[2]; int ret; memset(&insn, 0, sizeof(insn)); insn.insn = INSN_CONFIG; insn.n = 2; insn.subdev = subdev; insn.chanspec = CR_PACK(chan, 0, 0); data[0] = INSN_CONFIG_DIO_QUERY; data[1] = 0; ret = comedi_do_insn(dev, &insn, data); if (ret >= 0) *io = data[1]; return ret; } EXPORT_SYMBOL_GPL(comedi_dio_get_config); int comedi_dio_config(struct comedi_device *dev, unsigned int subdev, unsigned int chan, unsigned int io) { struct comedi_insn insn; memset(&insn, 0, sizeof(insn)); insn.insn = INSN_CONFIG; insn.n = 1; insn.subdev = subdev; insn.chanspec = CR_PACK(chan, 0, 0); return comedi_do_insn(dev, &insn, &io); } EXPORT_SYMBOL_GPL(comedi_dio_config); int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev, unsigned int mask, unsigned int *bits, unsigned int base_channel) { struct comedi_insn insn; unsigned int data[2]; unsigned int n_chan; unsigned int shift; int ret; base_channel = CR_CHAN(base_channel); n_chan = comedi_get_n_channels(dev, subdev); if (base_channel >= n_chan) return -EINVAL; memset(&insn, 0, sizeof(insn)); insn.insn = INSN_BITS; insn.chanspec = base_channel; insn.n = 2; insn.subdev = subdev; data[0] = mask; data[1] = *bits; /* * Most drivers ignore the base channel in insn->chanspec. * Fix this here if the subdevice has <= 32 channels. */ if (n_chan <= 32) { shift = base_channel; if (shift) { insn.chanspec = 0; data[0] <<= shift; data[1] <<= shift; } } else { shift = 0; } ret = comedi_do_insn(dev, &insn, data); *bits = data[1] >> shift; return ret; } EXPORT_SYMBOL_GPL(comedi_dio_bitfield2); int comedi_find_subdevice_by_type(struct comedi_device *dev, int type, unsigned int subd) { struct comedi_subdevice *s; int ret = -ENODEV; down_read(&dev->attach_lock); if (dev->attached) for (; subd < dev->n_subdevices; subd++) { s = &dev->subdevices[subd]; if (s->type == type) { ret = subd; break; } } up_read(&dev->attach_lock); return ret; } EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type); int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice) { int n; down_read(&dev->attach_lock); if (!dev->attached || subdevice >= dev->n_subdevices) n = 0; else n = dev->subdevices[subdevice].n_chan; up_read(&dev->attach_lock); return n; } EXPORT_SYMBOL_GPL(comedi_get_n_channels); static int __init kcomedilib_module_init(void) { return 0; } static void __exit kcomedilib_module_exit(void) { } module_init(kcomedilib_module_init); module_exit(kcomedilib_module_exit);