comedi_test: fix race when cancelling command
[comedi.git] / comedi / drivers / comedi_test.c
1 /*
2     comedi/drivers/comedi_test.c
3
4     Generates fake waveform signals that can be read through
5     the command interface.  It does _not_ read from any board;
6     it just generates deterministic waveforms.
7     Useful for various testing purposes.
8
9     Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>
10     Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
11
12     COMEDI - Linux Control and Measurement Device Interface
13     Copyright (C) 2000 David A. Schleef <ds@schleef.org>
14
15     This program is free software; you can redistribute it and/or modify
16     it under the terms of the GNU General Public License as published by
17     the Free Software Foundation; either version 2 of the License, or
18     (at your option) any later version.
19
20     This program is distributed in the hope that it will be useful,
21     but WITHOUT ANY WARRANTY; without even the implied warranty of
22     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     GNU General Public License for more details.
24
25     You should have received a copy of the GNU General Public License
26     along with this program; if not, write to the Free Software
27     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28
29 ************************************************************************/
30 /*
31 Driver: comedi_test
32 Description: generates fake waveforms
33 Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
34   <fmhess@users.sourceforge.net>, ds
35 Devices:
36 Status: works
37 Updated: Sat, 16 Mar 2002 17:34:48 -0800
38
39 This driver is mainly for testing purposes, but can also be used to
40 generate sample waveforms on systems that don't have data acquisition
41 hardware.
42
43 Configuration options:
44   [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
45   [1] - Period in microseconds for fake waveforms (default 0.1 sec)
46
47 Generates a sawtooth wave on channel 0, square wave on channel 1, additional
48 waveforms could be added to other channels (currently they return flatline
49 zero volts).
50
51 */
52
53 #include <linux/comedidev.h>
54
55 #include <asm/div64.h>
56
57 #include "comedi_fc.h"
58
59 /* Board descriptions */
60 typedef struct waveform_board_struct {
61         const char *name;
62         int ai_chans;
63         int ai_bits;
64         int have_dio;
65 } waveform_board;
66
67 #define N_CHANS 8
68
69 static const waveform_board waveform_boards[] = {
70         {
71               name:     "comedi_test",
72               ai_chans:N_CHANS,
73               ai_bits:  16,
74               have_dio:0,
75                 },
76 };
77
78 #define thisboard ((const waveform_board *)dev->board_ptr)
79
80 /* Data unique to this driver */
81 typedef struct {
82         struct timer_list timer;
83         struct timeval last;    // time at which last timer interrupt occured
84         unsigned int uvolt_amplitude;   // waveform amplitude in microvolts
85         unsigned long usec_period;      // waveform period in microseconds
86         volatile unsigned long usec_current;    // current time (modulo waveform period)
87         volatile unsigned long usec_remainder;  // usec since last scan;
88         volatile unsigned long ai_count;        // number of conversions remaining
89         unsigned int scan_period;       // scan period in usec
90         unsigned int convert_period;    // conversion period in usec
91         volatile unsigned timer_running:1;
92         volatile lsampl_t ao_loopbacks[N_CHANS];
93 } waveform_private;
94 #define devpriv ((waveform_private *)dev->private)
95
96 static int waveform_attach(comedi_device * dev, comedi_devconfig * it);
97 static int waveform_detach(comedi_device * dev);
98 static comedi_driver driver_waveform = {
99       driver_name:"comedi_test",
100       module:THIS_MODULE,
101       attach:waveform_attach,
102       detach:waveform_detach,
103       board_name:&waveform_boards[0].name,
104       offset:sizeof(waveform_board),
105       num_names:sizeof(waveform_boards) / sizeof(waveform_board),
106 };
107
108 COMEDI_INITCLEANUP(driver_waveform);
109
110 static int waveform_ai_cmdtest(comedi_device * dev, comedi_subdevice * s,
111         comedi_cmd * cmd);
112 static int waveform_ai_cmd(comedi_device * dev, comedi_subdevice * s);
113 static int waveform_ai_cancel(comedi_device * dev, comedi_subdevice * s);
114 static int waveform_ai_insn_read(comedi_device * dev, comedi_subdevice * s,
115         comedi_insn * insn, lsampl_t * data);
116 static int waveform_ao_insn_write(comedi_device * dev, comedi_subdevice * s,
117         comedi_insn * insn, lsampl_t * data);
118 static sampl_t fake_sawtooth(comedi_device * dev, unsigned int range,
119         unsigned long current_time);
120 static sampl_t fake_squarewave(comedi_device * dev, unsigned int range,
121         unsigned long current_time);
122 static sampl_t fake_flatline(comedi_device * dev, unsigned int range,
123         unsigned long current_time);
124 static sampl_t fake_waveform(comedi_device * dev, unsigned int channel,
125         unsigned int range, unsigned long current_time);
126
127 static const int nano_per_micro = 1000; // 1000 nanosec in a microsec
128
129 // fake analog input ranges
130 static const comedi_lrange waveform_ai_ranges = {
131         2,
132         {
133                         BIP_RANGE(10),
134                         BIP_RANGE(5),
135                 }
136 };
137
138 /*
139    This is the background routine used to generate arbitrary data.
140    It should run in the background; therefore it is scheduled by
141    a timer mechanism.
142 */
143 static void waveform_ai_interrupt(unsigned long arg)
144 {
145         comedi_device *dev = (comedi_device *) arg;
146         comedi_async *async = dev->read_subdev->async;
147         comedi_cmd *cmd = &async->cmd;
148         unsigned int i, j;
149         // all times in microsec
150         unsigned long elapsed_time;
151         unsigned int num_scans;
152         struct timeval now;
153
154         do_gettimeofday(&now);
155
156         elapsed_time =
157                 1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec -
158                 devpriv->last.tv_usec;
159         devpriv->last = now;
160         num_scans =
161                 (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
162         devpriv->usec_remainder =
163                 (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
164         async->events = 0;
165
166         for (i = 0; i < num_scans; i++) {
167                 for (j = 0; j < cmd->chanlist_len; j++) {
168                         cfc_write_to_buffer(dev->read_subdev,
169                                 fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
170                                         CR_RANGE(cmd->chanlist[j]),
171                                         devpriv->usec_current +
172                                         i * devpriv->scan_period +
173                                         j * devpriv->convert_period));
174                 }
175                 devpriv->ai_count++;
176                 if (cmd->stop_src == TRIG_COUNT
177                         && devpriv->ai_count >= cmd->stop_arg) {
178                         async->events |= COMEDI_CB_EOA;
179                         break;
180                 }
181         }
182
183         devpriv->usec_current += elapsed_time;
184         devpriv->usec_current %= devpriv->usec_period;
185
186         if ((async->events & COMEDI_CB_EOA) == 0 && devpriv->timer_running)
187                 mod_timer(&devpriv->timer, jiffies + 1);
188         else
189                 del_timer(&devpriv->timer);
190
191         comedi_event(dev, dev->read_subdev);
192 }
193
194 static int waveform_attach(comedi_device * dev, comedi_devconfig * it)
195 {
196         comedi_subdevice *s;
197         int amplitude = it->options[0];
198         int period = it->options[1];
199
200         printk("comedi%d: comedi_test: ", dev->minor);
201
202         dev->board_name = thisboard->name;
203
204         if (alloc_private(dev, sizeof(waveform_private)) < 0)
205                 return -ENOMEM;
206
207         // set default amplitude and period
208         if (amplitude <= 0)
209                 amplitude = 1000000;    // 1 volt
210         if (period <= 0)
211                 period = 100000;        // 0.1 sec
212
213         devpriv->uvolt_amplitude = amplitude;
214         devpriv->usec_period = period;
215
216         printk("%i microvolt, %li microsecond waveform ",
217                 devpriv->uvolt_amplitude, devpriv->usec_period);
218         dev->n_subdevices = 2;
219         if (alloc_subdevices(dev, dev->n_subdevices) < 0)
220                 return -ENOMEM;
221
222         s = dev->subdevices + 0;
223         dev->read_subdev = s;
224         /* analog input subdevice */
225         s->type = COMEDI_SUBD_AI;
226         s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
227         s->n_chan = thisboard->ai_chans;
228         s->maxdata = (1 << thisboard->ai_bits) - 1;
229         s->range_table = &waveform_ai_ranges;
230         s->len_chanlist = s->n_chan * 2;
231         s->insn_read = waveform_ai_insn_read;
232         s->do_cmd = waveform_ai_cmd;
233         s->do_cmdtest = waveform_ai_cmdtest;
234         s->cancel = waveform_ai_cancel;
235
236         s = dev->subdevices + 1;
237         dev->write_subdev = s;
238         /* analog output subdevice (loopback) */
239         s->type = COMEDI_SUBD_AO;
240         s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
241         s->n_chan = thisboard->ai_chans;
242         s->maxdata = (1 << thisboard->ai_bits) - 1;
243         s->range_table = &waveform_ai_ranges;
244         s->len_chanlist = s->n_chan * 2;
245         s->insn_write = waveform_ao_insn_write;
246         s->do_cmd = 0;
247         s->do_cmdtest = 0;
248         s->cancel = 0;
249         {
250                 /* Our default loopback value is just a 0V flatline */
251                 int i;
252                 for (i = 0; i < s->n_chan; i++)
253                         devpriv->ao_loopbacks[i] = s->maxdata / 2;
254         }
255
256         init_timer(&(devpriv->timer));
257         devpriv->timer.function = waveform_ai_interrupt;
258         devpriv->timer.data = (unsigned long)dev;
259
260         printk("attached\n");
261
262         return 1;
263 }
264
265 static int waveform_detach(comedi_device * dev)
266 {
267         printk("comedi%d: comedi_test: remove\n", dev->minor);
268
269         if (dev->private) {
270                 waveform_ai_cancel(dev, dev->read_subdev);
271         }
272
273         return 0;
274 }
275
276 static int waveform_ai_cmdtest(comedi_device * dev, comedi_subdevice * s,
277         comedi_cmd * cmd)
278 {
279         int err = 0;
280         int tmp;
281
282         /* step 1: make sure trigger sources are trivially valid */
283
284         tmp = cmd->start_src;
285         cmd->start_src &= TRIG_NOW;
286         if (!cmd->start_src || tmp != cmd->start_src)
287                 err++;
288
289         tmp = cmd->scan_begin_src;
290         cmd->scan_begin_src &= TRIG_TIMER;
291         if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
292                 err++;
293
294         tmp = cmd->convert_src;
295         cmd->convert_src &= TRIG_NOW | TRIG_TIMER;
296         if (!cmd->convert_src || tmp != cmd->convert_src)
297                 err++;
298
299         tmp = cmd->scan_end_src;
300         cmd->scan_end_src &= TRIG_COUNT;
301         if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
302                 err++;
303
304         tmp = cmd->stop_src;
305         cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
306         if (!cmd->stop_src || tmp != cmd->stop_src)
307                 err++;
308
309         if (err)
310                 return 1;
311
312         /* step 2: make sure trigger sources are unique and mutually compatible */
313
314         if (cmd->convert_src != TRIG_NOW && cmd->convert_src != TRIG_TIMER)
315                 err++;
316         if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
317                 err++;
318
319         if (err)
320                 return 2;
321
322         /* step 3: make sure arguments are trivially compatible */
323
324         if (cmd->start_arg != 0) {
325                 cmd->start_arg = 0;
326                 err++;
327         }
328         if (cmd->convert_src == TRIG_NOW) {
329                 if (cmd->convert_arg != 0) {
330                         cmd->convert_arg = 0;
331                         err++;
332                 }
333         }
334         if (cmd->scan_begin_src == TRIG_TIMER) {
335                 if (cmd->scan_begin_arg < nano_per_micro) {
336                         cmd->scan_begin_arg = nano_per_micro;
337                         err++;
338                 }
339                 if (cmd->convert_src == TRIG_TIMER &&
340                         cmd->scan_begin_arg <
341                         cmd->convert_arg * cmd->chanlist_len) {
342                         cmd->scan_begin_arg =
343                                 cmd->convert_arg * cmd->chanlist_len;
344                         err++;
345                 }
346         }
347         // XXX these checks are generic and should go in core if not there already
348         if (!cmd->chanlist_len) {
349                 cmd->chanlist_len = 1;
350                 err++;
351         }
352         if (cmd->scan_end_arg != cmd->chanlist_len) {
353                 cmd->scan_end_arg = cmd->chanlist_len;
354                 err++;
355         }
356
357         if (cmd->stop_src == TRIG_COUNT) {
358                 if (!cmd->stop_arg) {
359                         cmd->stop_arg = 1;
360                         err++;
361                 }
362         } else {                /* TRIG_NONE */
363                 if (cmd->stop_arg != 0) {
364                         cmd->stop_arg = 0;
365                         err++;
366                 }
367         }
368
369         if (err)
370                 return 3;
371
372         /* step 4: fix up any arguments */
373
374         if (cmd->scan_begin_src == TRIG_TIMER) {
375                 tmp = cmd->scan_begin_arg;
376                 // round to nearest microsec
377                 cmd->scan_begin_arg =
378                         nano_per_micro * ((tmp +
379                                 (nano_per_micro / 2)) / nano_per_micro);
380                 if (tmp != cmd->scan_begin_arg)
381                         err++;
382         }
383         if (cmd->convert_src == TRIG_TIMER) {
384                 tmp = cmd->convert_arg;
385                 // round to nearest microsec
386                 cmd->convert_arg =
387                         nano_per_micro * ((tmp +
388                                 (nano_per_micro / 2)) / nano_per_micro);
389                 if (tmp != cmd->convert_arg)
390                         err++;
391         }
392
393         if (err)
394                 return 4;
395
396         return 0;
397 }
398
399 static int waveform_ai_cmd(comedi_device * dev, comedi_subdevice * s)
400 {
401         comedi_cmd *cmd = &s->async->cmd;
402
403         if (cmd->flags & TRIG_RT) {
404                 comedi_error(dev,
405                         "commands at RT priority not supported in this driver");
406                 return -1;
407         }
408
409         devpriv->timer_running = 1;
410         devpriv->ai_count = 0;
411         devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
412
413         if (cmd->convert_src == TRIG_NOW)
414                 devpriv->convert_period = 0;
415         else if (cmd->convert_src == TRIG_TIMER)
416                 devpriv->convert_period = cmd->convert_arg / nano_per_micro;
417         else {
418                 comedi_error(dev, "bug setting conversion period");
419                 return -1;
420         }
421
422         do_gettimeofday(&devpriv->last);
423         devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period;
424         devpriv->usec_remainder = 0;
425
426         devpriv->timer.expires = jiffies + 1;
427         add_timer(&devpriv->timer);
428         return 0;
429 }
430
431 static int waveform_ai_cancel(comedi_device * dev, comedi_subdevice * s)
432 {
433         devpriv->timer_running = 0;
434         del_timer_sync(&devpriv->timer);
435         return 0;
436 }
437
438 static sampl_t fake_sawtooth(comedi_device * dev, unsigned int range_index,
439         unsigned long current_time)
440 {
441         comedi_subdevice *s = dev->read_subdev;
442         unsigned int offset = s->maxdata / 2;
443         u64 value;
444         const comedi_krange *krange = &s->range_table->range[range_index];
445         u64 binary_amplitude;
446
447         binary_amplitude = s->maxdata;
448         binary_amplitude *= devpriv->uvolt_amplitude;
449         do_div(binary_amplitude, krange->max - krange->min);
450
451         current_time %= devpriv->usec_period;
452         value = current_time;
453         value *= binary_amplitude * 2;
454         do_div(value, devpriv->usec_period);
455         value -= binary_amplitude;      // get rid of sawtooth's dc offset
456
457         return offset + value;
458 }
459 static sampl_t fake_squarewave(comedi_device * dev, unsigned int range_index,
460         unsigned long current_time)
461 {
462         comedi_subdevice *s = dev->read_subdev;
463         unsigned int offset = s->maxdata / 2;
464         u64 value;
465         const comedi_krange *krange = &s->range_table->range[range_index];
466         current_time %= devpriv->usec_period;
467
468         value = s->maxdata;
469         value *= devpriv->uvolt_amplitude;
470         do_div(value, krange->max - krange->min);
471
472         if (current_time < devpriv->usec_period / 2)
473                 value *= -1;
474
475         return offset + value;
476 }
477
478 static sampl_t fake_flatline(comedi_device * dev, unsigned int range_index,
479         unsigned long current_time)
480 {
481         return dev->read_subdev->maxdata / 2;
482 }
483
484 // generates a different waveform depending on what channel is read
485 static sampl_t fake_waveform(comedi_device * dev, unsigned int channel,
486         unsigned int range, unsigned long current_time)
487 {
488         enum {
489                 SAWTOOTH_CHAN,
490                 SQUARE_CHAN,
491         };
492         switch (channel) {
493         case SAWTOOTH_CHAN:
494                 return fake_sawtooth(dev, range, current_time);
495                 break;
496         case SQUARE_CHAN:
497                 return fake_squarewave(dev, range, current_time);
498                 break;
499         default:
500                 break;
501         }
502
503         return fake_flatline(dev, range, current_time);
504 }
505
506 static int waveform_ai_insn_read(comedi_device * dev, comedi_subdevice * s,
507         comedi_insn * insn, lsampl_t * data)
508 {
509         int i, chan = CR_CHAN(insn->chanspec);
510
511         for (i = 0; i < insn->n; i++)
512                 data[i] = devpriv->ao_loopbacks[chan];
513
514         return insn->n;
515 }
516
517 static int waveform_ao_insn_write(comedi_device * dev, comedi_subdevice * s,
518         comedi_insn * insn, lsampl_t * data)
519 {
520         int i, chan = CR_CHAN(insn->chanspec);
521
522         for (i = 0; i < insn->n; i++)
523                 devpriv->ao_loopbacks[chan] = data[i];
524
525         return insn->n;
526 }