Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/comedi/kcomedilib/kcomedilib_main.c
52467 views
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 <[email protected]>
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 <[email protected]>");
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
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
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
*/
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
*/
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
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
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
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
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
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
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
355
static int __init kcomedilib_module_init(void)
356
{
357
return 0;
358
}
359
360
static void __exit kcomedilib_module_exit(void)
361
{
362
}
363
364
module_init(kcomedilib_module_init);
365
module_exit(kcomedilib_module_exit);
366
367