Commit | Line | Data |
---|---|---|
b79a7a20 DS |
1 | /* |
2 | kcomedilib/kcomedilib.c | |
3 | a comedlib interface for kernel modules | |
4 | ||
5 | COMEDI - Linux Control and Measurement Device Interface | |
6 | Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> | |
7 | ||
8 | This program is free software; you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | This program is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with this program; if not, write to the Free Software | |
20 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
21 | ||
22 | */ | |
23 | ||
24 | #define __NO_VERSION__ | |
25 | #include <linux/module.h> | |
26 | ||
27 | #include <linux/errno.h> | |
28 | #include <linux/kernel.h> | |
29 | #include <linux/sched.h> | |
30 | #include <linux/fcntl.h> | |
31 | #include <linux/delay.h> | |
32 | #include <linux/ioport.h> | |
33 | #include <linux/mm.h> | |
b79a7a20 DS |
34 | #include <asm/io.h> |
35 | ||
36 | #include "../comedi.h" | |
37 | #include "../comedilib.h" | |
38 | #include "../comedidev.h" | |
39 | ||
40 | MODULE_AUTHOR("David Schleef <ds@schleef.org>"); | |
41 | MODULE_DESCRIPTION("Comedi kernel library"); | |
42 | MODULE_LICENSE("GPL"); | |
43 | ||
0b3fb27f | 44 | void *comedi_open(const char *filename) |
b79a7a20 DS |
45 | { |
46 | struct comedi_device_file_info *dev_file_info; | |
71b5f4f1 | 47 | struct comedi_device *dev; |
b79a7a20 DS |
48 | unsigned int minor; |
49 | ||
50 | if (strncmp(filename, "/dev/comedi", 11) != 0) | |
51 | return NULL; | |
52 | ||
53 | minor = simple_strtoul(filename + 11, NULL, 0); | |
54 | ||
55 | if (minor >= COMEDI_NUM_BOARD_MINORS) | |
56 | return NULL; | |
57 | ||
58 | dev_file_info = comedi_get_device_file_info(minor); | |
6a98d36e | 59 | if (dev_file_info == NULL) |
b79a7a20 DS |
60 | return NULL; |
61 | dev = dev_file_info->device; | |
62 | ||
6a98d36e | 63 | if (dev == NULL || !dev->attached) |
b79a7a20 DS |
64 | return NULL; |
65 | ||
66 | if (!try_module_get(dev->driver->module)) | |
67 | return NULL; | |
68 | ||
0a85b6f0 | 69 | return (void *)dev; |
b79a7a20 DS |
70 | } |
71 | ||
0b3fb27f | 72 | void *comedi_open_old(unsigned int minor) |
b79a7a20 DS |
73 | { |
74 | struct comedi_device_file_info *dev_file_info; | |
71b5f4f1 | 75 | struct comedi_device *dev; |
b79a7a20 DS |
76 | |
77 | if (minor >= COMEDI_NUM_MINORS) | |
78 | return NULL; | |
79 | ||
80 | dev_file_info = comedi_get_device_file_info(minor); | |
6a98d36e | 81 | if (dev_file_info == NULL) |
b79a7a20 DS |
82 | return NULL; |
83 | dev = dev_file_info->device; | |
84 | ||
6a98d36e | 85 | if (dev == NULL || !dev->attached) |
b79a7a20 DS |
86 | return NULL; |
87 | ||
0a85b6f0 | 88 | return (void *)dev; |
b79a7a20 DS |
89 | } |
90 | ||
0b3fb27f | 91 | int comedi_close(void *d) |
b79a7a20 | 92 | { |
0a85b6f0 | 93 | struct comedi_device *dev = (struct comedi_device *)d; |
b79a7a20 DS |
94 | |
95 | module_put(dev->driver->module); | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
100 | int comedi_loglevel(int newlevel) | |
101 | { | |
102 | return 0; | |
103 | } | |
104 | ||
105 | void comedi_perror(const char *message) | |
106 | { | |
5f74ea14 | 107 | printk("%s: unknown error\n", message); |
b79a7a20 DS |
108 | } |
109 | ||
110 | char *comedi_strerror(int err) | |
111 | { | |
112 | return "unknown error"; | |
113 | } | |
114 | ||
0b3fb27f | 115 | int comedi_fileno(void *d) |
b79a7a20 | 116 | { |
0a85b6f0 | 117 | struct comedi_device *dev = (struct comedi_device *)d; |
b79a7a20 DS |
118 | |
119 | /* return something random */ | |
120 | return dev->minor; | |
121 | } | |
122 | ||
ea6d0d4c | 123 | int comedi_command(void *d, struct comedi_cmd *cmd) |
b79a7a20 | 124 | { |
0a85b6f0 | 125 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 126 | struct comedi_subdevice *s; |
d163679c | 127 | struct comedi_async *async; |
b79a7a20 DS |
128 | unsigned runflags; |
129 | ||
130 | if (cmd->subdev >= dev->n_subdevices) | |
131 | return -ENODEV; | |
132 | ||
133 | s = dev->subdevices + cmd->subdev; | |
134 | if (s->type == COMEDI_SUBD_UNUSED) | |
135 | return -EIO; | |
136 | ||
137 | async = s->async; | |
138 | if (async == NULL) | |
139 | return -ENODEV; | |
140 | ||
141 | if (s->busy) | |
142 | return -EBUSY; | |
143 | s->busy = d; | |
144 | ||
145 | if (async->cb_mask & COMEDI_CB_EOS) | |
146 | cmd->flags |= TRIG_WAKE_EOS; | |
147 | ||
148 | async->cmd = *cmd; | |
149 | ||
150 | runflags = SRF_RUNNING; | |
151 | ||
b79a7a20 DS |
152 | comedi_set_subdevice_runflags(s, ~0, runflags); |
153 | ||
154 | comedi_reset_async_buf(async); | |
155 | ||
156 | return s->do_cmd(dev, s); | |
157 | } | |
158 | ||
ea6d0d4c | 159 | int comedi_command_test(void *d, struct comedi_cmd *cmd) |
b79a7a20 | 160 | { |
0a85b6f0 | 161 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 162 | struct comedi_subdevice *s; |
b79a7a20 DS |
163 | |
164 | if (cmd->subdev >= dev->n_subdevices) | |
165 | return -ENODEV; | |
166 | ||
167 | s = dev->subdevices + cmd->subdev; | |
168 | if (s->type == COMEDI_SUBD_UNUSED) | |
169 | return -EIO; | |
170 | ||
171 | if (s->async == NULL) | |
172 | return -ENODEV; | |
173 | ||
174 | return s->do_cmdtest(dev, s, cmd); | |
175 | } | |
176 | ||
177 | /* | |
178 | * COMEDI_INSN | |
179 | * perform an instruction | |
180 | */ | |
90035c08 | 181 | int comedi_do_insn(void *d, struct comedi_insn *insn) |
b79a7a20 | 182 | { |
0a85b6f0 | 183 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 184 | struct comedi_subdevice *s; |
b79a7a20 DS |
185 | int ret = 0; |
186 | ||
187 | if (insn->insn & INSN_MASK_SPECIAL) { | |
188 | switch (insn->insn) { | |
189 | case INSN_GTOD: | |
190 | { | |
191 | struct timeval tv; | |
192 | ||
193 | do_gettimeofday(&tv); | |
194 | insn->data[0] = tv.tv_sec; | |
195 | insn->data[1] = tv.tv_usec; | |
196 | ret = 2; | |
197 | ||
198 | break; | |
199 | } | |
200 | case INSN_WAIT: | |
201 | /* XXX isn't the value supposed to be nanosecs? */ | |
202 | if (insn->n != 1 || insn->data[0] >= 100) { | |
203 | ret = -EINVAL; | |
204 | break; | |
205 | } | |
5f74ea14 | 206 | udelay(insn->data[0]); |
b79a7a20 DS |
207 | ret = 1; |
208 | break; | |
209 | case INSN_INTTRIG: | |
210 | if (insn->n != 1) { | |
211 | ret = -EINVAL; | |
212 | break; | |
213 | } | |
214 | if (insn->subdev >= dev->n_subdevices) { | |
5f74ea14 | 215 | printk("%d not usable subdevice\n", |
0a85b6f0 | 216 | insn->subdev); |
b79a7a20 DS |
217 | ret = -EINVAL; |
218 | break; | |
219 | } | |
220 | s = dev->subdevices + insn->subdev; | |
221 | if (!s->async) { | |
5f74ea14 | 222 | printk("no async\n"); |
b79a7a20 DS |
223 | ret = -EINVAL; |
224 | break; | |
225 | } | |
226 | if (!s->async->inttrig) { | |
5f74ea14 | 227 | printk("no inttrig\n"); |
b79a7a20 DS |
228 | ret = -EAGAIN; |
229 | break; | |
230 | } | |
231 | ret = s->async->inttrig(dev, s, insn->data[0]); | |
232 | if (ret >= 0) | |
233 | ret = 1; | |
234 | break; | |
235 | default: | |
236 | ret = -EINVAL; | |
237 | } | |
238 | } else { | |
239 | /* a subdevice instruction */ | |
240 | if (insn->subdev >= dev->n_subdevices) { | |
241 | ret = -EINVAL; | |
242 | goto error; | |
243 | } | |
244 | s = dev->subdevices + insn->subdev; | |
245 | ||
246 | if (s->type == COMEDI_SUBD_UNUSED) { | |
5f74ea14 | 247 | printk("%d not useable subdevice\n", insn->subdev); |
b79a7a20 DS |
248 | ret = -EIO; |
249 | goto error; | |
250 | } | |
251 | ||
252 | /* XXX check lock */ | |
253 | ||
197c82bf BP |
254 | ret = check_chanlist(s, 1, &insn->chanspec); |
255 | if (ret < 0) { | |
5f74ea14 | 256 | printk("bad chanspec\n"); |
b79a7a20 DS |
257 | ret = -EINVAL; |
258 | goto error; | |
259 | } | |
260 | ||
261 | if (s->busy) { | |
262 | ret = -EBUSY; | |
263 | goto error; | |
264 | } | |
265 | s->busy = d; | |
266 | ||
267 | switch (insn->insn) { | |
268 | case INSN_READ: | |
269 | ret = s->insn_read(dev, s, insn, insn->data); | |
270 | break; | |
271 | case INSN_WRITE: | |
272 | ret = s->insn_write(dev, s, insn, insn->data); | |
273 | break; | |
274 | case INSN_BITS: | |
275 | ret = s->insn_bits(dev, s, insn, insn->data); | |
276 | break; | |
277 | case INSN_CONFIG: | |
278 | /* XXX should check instruction length */ | |
279 | ret = s->insn_config(dev, s, insn, insn->data); | |
280 | break; | |
281 | default: | |
282 | ret = -EINVAL; | |
283 | break; | |
284 | } | |
285 | ||
286 | s->busy = NULL; | |
287 | } | |
288 | if (ret < 0) | |
289 | goto error; | |
290 | #if 0 | |
291 | /* XXX do we want this? -- abbotti #if'ed it out for now. */ | |
292 | if (ret != insn->n) { | |
5f74ea14 | 293 | printk("BUG: result of insn != insn.n\n"); |
b79a7a20 DS |
294 | ret = -EINVAL; |
295 | goto error; | |
296 | } | |
297 | #endif | |
0a85b6f0 | 298 | error: |
b79a7a20 DS |
299 | |
300 | return ret; | |
301 | } | |
302 | ||
303 | /* | |
304 | COMEDI_LOCK | |
305 | lock subdevice | |
306 | ||
307 | arg: | |
308 | subdevice number | |
309 | ||
310 | reads: | |
311 | none | |
312 | ||
313 | writes: | |
314 | none | |
315 | ||
316 | necessary locking: | |
317 | - ioctl/rt lock (this type) | |
318 | - lock while subdevice busy | |
319 | - lock while subdevice being programmed | |
320 | ||
321 | */ | |
0b3fb27f | 322 | int comedi_lock(void *d, unsigned int subdevice) |
b79a7a20 | 323 | { |
0a85b6f0 | 324 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 325 | struct comedi_subdevice *s; |
b79a7a20 DS |
326 | unsigned long flags; |
327 | int ret = 0; | |
328 | ||
82675f35 | 329 | if (subdevice >= dev->n_subdevices) |
b79a7a20 | 330 | return -EINVAL; |
82675f35 | 331 | |
b79a7a20 DS |
332 | s = dev->subdevices + subdevice; |
333 | ||
5f74ea14 | 334 | spin_lock_irqsave(&s->spin_lock, flags); |
b79a7a20 DS |
335 | |
336 | if (s->busy) { | |
337 | ret = -EBUSY; | |
338 | } else { | |
339 | if (s->lock) { | |
340 | ret = -EBUSY; | |
341 | } else { | |
342 | s->lock = d; | |
343 | } | |
344 | } | |
345 | ||
5f74ea14 | 346 | spin_unlock_irqrestore(&s->spin_lock, flags); |
b79a7a20 DS |
347 | |
348 | return ret; | |
349 | } | |
350 | ||
351 | /* | |
352 | COMEDI_UNLOCK | |
353 | unlock subdevice | |
354 | ||
355 | arg: | |
356 | subdevice number | |
357 | ||
358 | reads: | |
359 | none | |
360 | ||
361 | writes: | |
362 | none | |
363 | ||
364 | */ | |
0b3fb27f | 365 | int comedi_unlock(void *d, unsigned int subdevice) |
b79a7a20 | 366 | { |
0a85b6f0 | 367 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 368 | struct comedi_subdevice *s; |
b79a7a20 | 369 | unsigned long flags; |
d163679c | 370 | struct comedi_async *async; |
b79a7a20 DS |
371 | int ret; |
372 | ||
82675f35 | 373 | if (subdevice >= dev->n_subdevices) |
b79a7a20 | 374 | return -EINVAL; |
82675f35 | 375 | |
b79a7a20 DS |
376 | s = dev->subdevices + subdevice; |
377 | ||
378 | async = s->async; | |
379 | ||
5f74ea14 | 380 | spin_lock_irqsave(&s->spin_lock, flags); |
b79a7a20 DS |
381 | |
382 | if (s->busy) { | |
383 | ret = -EBUSY; | |
384 | } else if (s->lock && s->lock != (void *)d) { | |
385 | ret = -EACCES; | |
386 | } else { | |
387 | s->lock = NULL; | |
388 | ||
389 | if (async) { | |
390 | async->cb_mask = 0; | |
391 | async->cb_func = NULL; | |
392 | async->cb_arg = NULL; | |
393 | } | |
394 | ||
395 | ret = 0; | |
396 | } | |
397 | ||
5f74ea14 | 398 | spin_unlock_irqrestore(&s->spin_lock, flags); |
b79a7a20 DS |
399 | |
400 | return ret; | |
401 | } | |
402 | ||
403 | /* | |
404 | COMEDI_CANCEL | |
405 | cancel acquisition ioctl | |
406 | ||
407 | arg: | |
408 | subdevice number | |
409 | ||
410 | reads: | |
411 | nothing | |
412 | ||
413 | writes: | |
414 | nothing | |
415 | ||
416 | */ | |
0b3fb27f | 417 | int comedi_cancel(void *d, unsigned int subdevice) |
b79a7a20 | 418 | { |
0a85b6f0 | 419 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 420 | struct comedi_subdevice *s; |
b79a7a20 DS |
421 | int ret = 0; |
422 | ||
82675f35 | 423 | if (subdevice >= dev->n_subdevices) |
b79a7a20 | 424 | return -EINVAL; |
82675f35 | 425 | |
b79a7a20 DS |
426 | s = dev->subdevices + subdevice; |
427 | ||
428 | if (s->lock && s->lock != d) | |
429 | return -EACCES; | |
430 | ||
431 | #if 0 | |
432 | if (!s->busy) | |
433 | return 0; | |
434 | ||
435 | if (s->busy != d) | |
436 | return -EBUSY; | |
437 | #endif | |
438 | ||
439 | if (!s->cancel || !s->async) | |
440 | return -EINVAL; | |
441 | ||
197c82bf BP |
442 | ret = s->cancel(dev, s); |
443 | ||
444 | if (ret) | |
b79a7a20 DS |
445 | return ret; |
446 | ||
b79a7a20 DS |
447 | comedi_set_subdevice_runflags(s, SRF_RUNNING | SRF_RT, 0); |
448 | s->async->inttrig = NULL; | |
449 | s->busy = NULL; | |
450 | ||
451 | return 0; | |
452 | } | |
453 | ||
454 | /* | |
455 | registration of callback functions | |
456 | */ | |
0b3fb27f | 457 | int comedi_register_callback(void *d, unsigned int subdevice, |
0a85b6f0 MT |
458 | unsigned int mask, int (*cb) (unsigned int, |
459 | void *), void *arg) | |
b79a7a20 | 460 | { |
0a85b6f0 | 461 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 462 | struct comedi_subdevice *s; |
d163679c | 463 | struct comedi_async *async; |
b79a7a20 | 464 | |
82675f35 | 465 | if (subdevice >= dev->n_subdevices) |
b79a7a20 | 466 | return -EINVAL; |
82675f35 | 467 | |
b79a7a20 DS |
468 | s = dev->subdevices + subdevice; |
469 | ||
470 | async = s->async; | |
471 | if (s->type == COMEDI_SUBD_UNUSED || !async) | |
472 | return -EIO; | |
473 | ||
474 | /* are we locked? (ioctl lock) */ | |
475 | if (s->lock && s->lock != d) | |
476 | return -EACCES; | |
477 | ||
478 | /* are we busy? */ | |
479 | if (s->busy) | |
480 | return -EBUSY; | |
481 | ||
482 | if (!mask) { | |
483 | async->cb_mask = 0; | |
484 | async->cb_func = NULL; | |
485 | async->cb_arg = NULL; | |
486 | } else { | |
487 | async->cb_mask = mask; | |
488 | async->cb_func = cb; | |
489 | async->cb_arg = arg; | |
490 | } | |
491 | ||
492 | return 0; | |
493 | } | |
494 | ||
0b3fb27f | 495 | int comedi_poll(void *d, unsigned int subdevice) |
b79a7a20 | 496 | { |
0a85b6f0 | 497 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 498 | struct comedi_subdevice *s = dev->subdevices; |
d163679c | 499 | struct comedi_async *async; |
b79a7a20 | 500 | |
82675f35 | 501 | if (subdevice >= dev->n_subdevices) |
b79a7a20 | 502 | return -EINVAL; |
82675f35 | 503 | |
b79a7a20 DS |
504 | s = dev->subdevices + subdevice; |
505 | ||
506 | async = s->async; | |
507 | if (s->type == COMEDI_SUBD_UNUSED || !async) | |
508 | return -EIO; | |
509 | ||
510 | /* are we locked? (ioctl lock) */ | |
511 | if (s->lock && s->lock != d) | |
512 | return -EACCES; | |
513 | ||
514 | /* are we running? XXX wrong? */ | |
515 | if (!s->busy) | |
516 | return -EIO; | |
517 | ||
518 | return s->poll(dev, s); | |
519 | } | |
520 | ||
521 | /* WARNING: not portable */ | |
0b3fb27f | 522 | int comedi_map(void *d, unsigned int subdevice, void *ptr) |
b79a7a20 | 523 | { |
0a85b6f0 | 524 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 525 | struct comedi_subdevice *s; |
b79a7a20 | 526 | |
82675f35 | 527 | if (subdevice >= dev->n_subdevices) |
b79a7a20 | 528 | return -EINVAL; |
82675f35 | 529 | |
b79a7a20 DS |
530 | s = dev->subdevices + subdevice; |
531 | ||
532 | if (!s->async) | |
533 | return -EINVAL; | |
534 | ||
82675f35 | 535 | if (ptr) |
b79a7a20 | 536 | *((void **)ptr) = s->async->prealloc_buf; |
b79a7a20 DS |
537 | |
538 | /* XXX no reference counting */ | |
539 | ||
540 | return 0; | |
541 | } | |
542 | ||
543 | /* WARNING: not portable */ | |
0b3fb27f | 544 | int comedi_unmap(void *d, unsigned int subdevice) |
b79a7a20 | 545 | { |
0a85b6f0 | 546 | struct comedi_device *dev = (struct comedi_device *)d; |
34c43922 | 547 | struct comedi_subdevice *s; |
b79a7a20 | 548 | |
82675f35 | 549 | if (subdevice >= dev->n_subdevices) |
b79a7a20 | 550 | return -EINVAL; |
82675f35 | 551 | |
b79a7a20 DS |
552 | s = dev->subdevices + subdevice; |
553 | ||
554 | if (!s->async) | |
555 | return -EINVAL; | |
556 | ||
557 | /* XXX no reference counting */ | |
558 | ||
559 | return 0; | |
560 | } |