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