xref: /linux/drivers/comedi/kcomedilib/kcomedilib_main.c (revision 83bd89291f5cc866f60d32c34e268896c7ba8a3d)
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 
24 MODULE_AUTHOR("David Schleef <ds@schleef.org>");
25 MODULE_DESCRIPTION("Comedi kernel library");
26 MODULE_LICENSE("GPL");
27 
28 static 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  */
34 static unsigned char
35 	kcomedilib_to_from[COMEDI_NUM_BOARD_MINORS][COMEDI_NUM_BOARD_MINORS];
36 
kcomedilib_set_link_from_to(unsigned int from,unsigned int to)37 static 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 
kcomedilib_clear_link_from_to(unsigned int from,unsigned int to)100 static 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  */
comedi_open_from(const char * filename,int from)125 struct 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 }
155 EXPORT_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  */
comedi_close_from(struct comedi_device * dev,int from)168 int 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 }
174 EXPORT_SYMBOL_GPL(comedi_close_from);
175 
comedi_do_insn(struct comedi_device * dev,struct comedi_insn * insn,unsigned int * data)176 static 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;
233 error:
234 
235 	mutex_unlock(&dev->mutex);
236 	return ret;
237 }
238 
comedi_dio_get_config(struct comedi_device * dev,unsigned int subdev,unsigned int chan,unsigned int * io)239 int 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 }
258 EXPORT_SYMBOL_GPL(comedi_dio_get_config);
259 
comedi_dio_config(struct comedi_device * dev,unsigned int subdev,unsigned int chan,unsigned int io)260 int 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 }
273 EXPORT_SYMBOL_GPL(comedi_dio_config);
274 
comedi_dio_bitfield2(struct comedi_device * dev,unsigned int subdev,unsigned int mask,unsigned int * bits,unsigned int base_channel)275 int 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 }
318 EXPORT_SYMBOL_GPL(comedi_dio_bitfield2);
319 
comedi_find_subdevice_by_type(struct comedi_device * dev,int type,unsigned int subd)320 int 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 }
338 EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type);
339 
comedi_get_n_channels(struct comedi_device * dev,unsigned int subdevice)340 int 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 }
353 EXPORT_SYMBOL_GPL(comedi_get_n_channels);
354 
kcomedilib_module_init(void)355 static int __init kcomedilib_module_init(void)
356 {
357 	return 0;
358 }
359 
kcomedilib_module_exit(void)360 static void __exit kcomedilib_module_exit(void)
361 {
362 }
363 
364 module_init(kcomedilib_module_init);
365 module_exit(kcomedilib_module_exit);
366