File Index
Files and directories
Sign in
FILE

kcomedilib_main.c

File preview with syntax highlighting.

linux-6.19.14/drivers/comedi/kcomedilib/kcomedilib_main.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * kcomedilib/kcomedilib.c
  4 * a comedlib interface for kernel modules
  5 *
  6 * COMEDI - Linux Control and Measurement Device Interface
  7 * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
  8 */
  9
 10#include <linux/module.h>
 11
 12#include <linux/errno.h>
 13#include <linux/kernel.h>
 14#include <linux/sched.h>
 15#include <linux/fcntl.h>
 16#include <linux/mm.h>
 17#include <linux/io.h>
 18#include <linux/bitmap.h>
 19
 20#include <linux/comedi.h>
 21#include <linux/comedi/comedidev.h>
 22#include <linux/comedi/comedilib.h>
 23
 24MODULE_AUTHOR("David Schleef <ds@schleef.org>");
 25MODULE_DESCRIPTION("Comedi kernel library");
 26MODULE_LICENSE("GPL");
 27
 28static DEFINE_MUTEX(kcomedilib_to_from_lock);
 29
 30/*
 31 * Row index is the "to" node, column index is the "from" node, element value
 32 * is the number of links from the "from" node to the "to" node.
 33 */
 34static unsigned char
 35	kcomedilib_to_from[COMEDI_NUM_BOARD_MINORS][COMEDI_NUM_BOARD_MINORS];
 36
 37static bool kcomedilib_set_link_from_to(unsigned int from, unsigned int to)
 38{
 39	DECLARE_BITMAP(destinations[2], COMEDI_NUM_BOARD_MINORS);
 40	unsigned int cur = 0;
 41	bool okay = true;
 42
 43	/*
 44	 * Allow "from" node to be out of range (no loop checking),
 45	 * but require "to" node to be in range.
 46	 */
 47	if (to >= COMEDI_NUM_BOARD_MINORS)
 48		return false;
 49	if (from >= COMEDI_NUM_BOARD_MINORS)
 50		return true;
 51
 52	/*
 53	 * Check that kcomedilib_to_from[to][from] can be made non-zero
 54	 * without creating a loop.
 55	 *
 56	 * Termination of the loop-testing code relies on the assumption that
 57	 * kcomedilib_to_from[][] does not contain any loops.
 58	 *
 59	 * Start with a set destinations set containing "from" as the only
 60	 * element and work backwards looking for loops.
 61	 */
 62	bitmap_zero(destinations[cur], COMEDI_NUM_BOARD_MINORS);
 63	set_bit(from, destinations[cur]);
 64	mutex_lock(&kcomedilib_to_from_lock);
 65	do {
 66		unsigned int next = 1 - cur;
 67		unsigned int t = 0;
 68
 69		if (test_bit(to, destinations[cur])) {
 70			/* Loop detected. */
 71			okay = false;
 72			break;
 73		}
 74		/* Create next set of destinations. */
 75		bitmap_zero(destinations[next], COMEDI_NUM_BOARD_MINORS);
 76		while ((t = find_next_bit(destinations[cur],
 77					  COMEDI_NUM_BOARD_MINORS,
 78					  t)) < COMEDI_NUM_BOARD_MINORS) {
 79			unsigned int f;
 80
 81			for (f = 0; f < COMEDI_NUM_BOARD_MINORS; f++) {
 82				if (kcomedilib_to_from[t][f])
 83					set_bit(f, destinations[next]);
 84			}
 85			t++;
 86		}
 87		cur = next;
 88	} while (!bitmap_empty(destinations[cur], COMEDI_NUM_BOARD_MINORS));
 89	if (okay) {
 90		/* Allow a maximum of 255 links from "from" to "to". */
 91		if (kcomedilib_to_from[to][from] < 255)
 92			kcomedilib_to_from[to][from]++;
 93		else
 94			okay = false;
 95	}
 96	mutex_unlock(&kcomedilib_to_from_lock);
 97	return okay;
 98}
 99
100static void kcomedilib_clear_link_from_to(unsigned int from, unsigned int to)
101{
102	if (to < COMEDI_NUM_BOARD_MINORS && from < COMEDI_NUM_BOARD_MINORS) {
103		mutex_lock(&kcomedilib_to_from_lock);
104		if (kcomedilib_to_from[to][from])
105			kcomedilib_to_from[to][from]--;
106		mutex_unlock(&kcomedilib_to_from_lock);
107	}
108}
109
110/**
111 * comedi_open_from() - Open a COMEDI device from the kernel with loop checks
112 * @filename: Fake pathname of the form "/dev/comediN".
113 * @from: Device number it is being opened from (if in range).
114 *
115 * Converts @filename to a COMEDI device number and "opens" it if it exists
116 * and is attached to a low-level COMEDI driver.
117 *
118 * If @from is in range, refuse to open the device if doing so would form a
119 * loop of devices opening each other.  There is also a limit of 255 on the
120 * number of concurrent opens from one device to another.
121 *
122 * Return: A pointer to the COMEDI device on success.
123 * Return %NULL on failure.
124 */
125struct comedi_device *comedi_open_from(const char *filename, int from)
126{
127	struct comedi_device *dev, *retval = NULL;
128	unsigned int minor;
129
130	if (strncmp(filename, "/dev/comedi", 11) != 0)
131		return NULL;
132
133	if (kstrtouint(filename + 11, 0, &minor))
134		return NULL;
135
136	if (minor >= COMEDI_NUM_BOARD_MINORS)
137		return NULL;
138
139	dev = comedi_dev_get_from_minor(minor);
140	if (!dev)
141		return NULL;
142
143	down_read(&dev->attach_lock);
144	if (dev->attached && kcomedilib_set_link_from_to(from, minor))
145		retval = dev;
146	else
147		retval = NULL;
148	up_read(&dev->attach_lock);
149
150	if (!retval)
151		comedi_dev_put(dev);
152
153	return retval;
154}
155EXPORT_SYMBOL_GPL(comedi_open_from);
156
157/**
158 * comedi_close_from() - Close a COMEDI device from the kernel with loop checks
159 * @dev: COMEDI device.
160 * @from: Device number it was opened from (if in range).
161 *
162 * Closes a COMEDI device previously opened by comedi_open_from().
163 *
164 * If @from is in range, it should be match the one used by comedi_open_from().
165 *
166 * Returns: 0
167 */
168int comedi_close_from(struct comedi_device *dev, int from)
169{
170	kcomedilib_clear_link_from_to(from, dev->minor);
171	comedi_dev_put(dev);
172	return 0;
173}
174EXPORT_SYMBOL_GPL(comedi_close_from);
175
176static int comedi_do_insn(struct comedi_device *dev,
177			  struct comedi_insn *insn,
178			  unsigned int *data)
179{
180	struct comedi_subdevice *s;
181	int ret;
182
183	mutex_lock(&dev->mutex);
184
185	if (!dev->attached) {
186		ret = -EINVAL;
187		goto error;
188	}
189
190	/* a subdevice instruction */
191	if (insn->subdev >= dev->n_subdevices) {
192		ret = -EINVAL;
193		goto error;
194	}
195	s = &dev->subdevices[insn->subdev];
196
197	if (s->type == COMEDI_SUBD_UNUSED) {
198		dev_err(dev->class_dev,
199			"%d not usable subdevice\n", insn->subdev);
200		ret = -EIO;
201		goto error;
202	}
203
204	/* XXX check lock */
205
206	ret = comedi_check_chanlist(s, 1, &insn->chanspec);
207	if (ret < 0) {
208		dev_err(dev->class_dev, "bad chanspec\n");
209		ret = -EINVAL;
210		goto error;
211	}
212
213	if (s->busy) {
214		ret = -EBUSY;
215		goto error;
216	}
217	s->busy = dev;
218
219	switch (insn->insn) {
220	case INSN_BITS:
221		ret = s->insn_bits(dev, s, insn, data);
222		break;
223	case INSN_CONFIG:
224		/* XXX should check instruction length */
225		ret = s->insn_config(dev, s, insn, data);
226		break;
227	default:
228		ret = -EINVAL;
229		break;
230	}
231
232	s->busy = NULL;
233error:
234
235	mutex_unlock(&dev->mutex);
236	return ret;
237}
238
239int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev,
240			  unsigned int chan, unsigned int *io)
241{
242	struct comedi_insn insn;
243	unsigned int data[2];
244	int ret;
245
246	memset(&insn, 0, sizeof(insn));
247	insn.insn = INSN_CONFIG;
248	insn.n = 2;
249	insn.subdev = subdev;
250	insn.chanspec = CR_PACK(chan, 0, 0);
251	data[0] = INSN_CONFIG_DIO_QUERY;
252	data[1] = 0;
253	ret = comedi_do_insn(dev, &insn, data);
254	if (ret >= 0)
255		*io = data[1];
256	return ret;
257}
258EXPORT_SYMBOL_GPL(comedi_dio_get_config);
259
260int comedi_dio_config(struct comedi_device *dev, unsigned int subdev,
261		      unsigned int chan, unsigned int io)
262{
263	struct comedi_insn insn;
264
265	memset(&insn, 0, sizeof(insn));
266	insn.insn = INSN_CONFIG;
267	insn.n = 1;
268	insn.subdev = subdev;
269	insn.chanspec = CR_PACK(chan, 0, 0);
270
271	return comedi_do_insn(dev, &insn, &io);
272}
273EXPORT_SYMBOL_GPL(comedi_dio_config);
274
275int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev,
276			 unsigned int mask, unsigned int *bits,
277			 unsigned int base_channel)
278{
279	struct comedi_insn insn;
280	unsigned int data[2];
281	unsigned int n_chan;
282	unsigned int shift;
283	int ret;
284
285	base_channel = CR_CHAN(base_channel);
286	n_chan = comedi_get_n_channels(dev, subdev);
287	if (base_channel >= n_chan)
288		return -EINVAL;
289
290	memset(&insn, 0, sizeof(insn));
291	insn.insn = INSN_BITS;
292	insn.chanspec = base_channel;
293	insn.n = 2;
294	insn.subdev = subdev;
295
296	data[0] = mask;
297	data[1] = *bits;
298
299	/*
300	 * Most drivers ignore the base channel in insn->chanspec.
301	 * Fix this here if the subdevice has <= 32 channels.
302	 */
303	if (n_chan <= 32) {
304		shift = base_channel;
305		if (shift) {
306			insn.chanspec = 0;
307			data[0] <<= shift;
308			data[1] <<= shift;
309		}
310	} else {
311		shift = 0;
312	}
313
314	ret = comedi_do_insn(dev, &insn, data);
315	*bits = data[1] >> shift;
316	return ret;
317}
318EXPORT_SYMBOL_GPL(comedi_dio_bitfield2);
319
320int comedi_find_subdevice_by_type(struct comedi_device *dev, int type,
321				  unsigned int subd)
322{
323	struct comedi_subdevice *s;
324	int ret = -ENODEV;
325
326	down_read(&dev->attach_lock);
327	if (dev->attached)
328		for (; subd < dev->n_subdevices; subd++) {
329			s = &dev->subdevices[subd];
330			if (s->type == type) {
331				ret = subd;
332				break;
333			}
334		}
335	up_read(&dev->attach_lock);
336	return ret;
337}
338EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type);
339
340int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice)
341{
342	int n;
343
344	down_read(&dev->attach_lock);
345	if (!dev->attached || subdevice >= dev->n_subdevices)
346		n = 0;
347	else
348		n = dev->subdevices[subdevice].n_chan;
349	up_read(&dev->attach_lock);
350
351	return n;
352}
353EXPORT_SYMBOL_GPL(comedi_get_n_channels);
354
355static int __init kcomedilib_module_init(void)
356{
357	return 0;
358}
359
360static void __exit kcomedilib_module_exit(void)
361{
362}
363
364module_init(kcomedilib_module_init);
365module_exit(kcomedilib_module_exit);
~/ Read-only file preview.