fixing end of scan events in some drivers
[comedi.git] / comedi / drivers / comedi_rt_timer.c
1 /*
2     module/comedi_rt_timer.c
3     virtual driver for using RTL timing sources
4
5     Authors: David A. Schleef, Frank M. Hess
6
7     COMEDI - Linux Control and Measurement Device Interface
8     Copyright (C) 1999,2001 David A. Schleef <ds@schleef.org>
9
10     This program is free software; you can redistribute it and/or modify
11     it under the terms of the GNU General Public License as published by
12     the Free Software Foundation; either version 2 of the License, or
13     (at your option) any later version.
14
15     This program is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18     GNU General Public License for more details.
19
20     You should have received a copy of the GNU General Public License
21     along with this program; if not, write to the Free Software
22     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23
24
25 **************************************************************************
26 */
27 /*
28 Driver: comedi_rt_timer.o
29 Description: Command emulator using real-time tasks
30 Author: ds, fmhess
31 Devices:
32 Status: works
33
34 This driver requires RTAI or RTLinux to work correctly.  It doesn't
35 actually drive hardware directly, but calls other drivers and uses
36 a real-time task to emulate commands for drivers and devices that
37 are incapable of native commands.  Thus, you can get accurately
38 timed I/O on any device.
39
40 Since the timing is all done in software, sampling jitter is much
41 higher than with a device that has an on-board timer, and maximum
42 sample rate is much lower.
43
44 Configuration options:
45   [0] - minor number of device you wish to emulate commands for
46   [1] - subdevice number you wish to emulate commands for
47 */
48 /*
49 TODO:
50         Support for digital io commands could be added, except I can't see why
51                 anyone would want to use them
52         What happens if device we are emulating for is de-configured?
53 */
54
55
56 #include <linux/kernel.h>
57 #include <linux/module.h>
58 #include <asm/io.h>
59 #include <linux/comedidev.h>
60 #include <linux/comedilib.h>
61 #ifdef CONFIG_COMEDI_RTL_V1
62 #include <rtl_sched.h>
63 #include <asm/rt_irq.h>
64 #endif
65 #ifdef CONFIG_COMEDI_RTL
66 #include <rtl.h>
67 #include <rtl_sched.h>
68 #include <rtl_compat.h>
69 #include <asm/div64.h>
70
71 #ifndef RTLINUX_VERSION_CODE
72 #define RTLINUX_VERSION_CODE 0
73 #endif
74 #ifndef RTLINUX_VERSION
75 #define RTLINUX_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
76 #endif
77
78 // begin hack to workaround broken HRT_TO_8254() function on rtlinux
79 #if RTLINUX_VERSION_CODE <= RTLINUX_VERSION(3,0,100)
80 // this function sole purpose is to divide a long long by 838
81 static inline RTIME nano2count(long long ns)
82 {
83         do_div(ns, 838);
84         return ns;
85 }
86 #ifdef rt_get_time()
87 #undef rt_get_time()
88 #endif
89 #define rt_get_time() nano2count(gethrtime())
90
91 #else
92
93 #define nano2count(x) HRT_TO_8254(x)
94 #endif
95 // end hack
96
97 // rtl-rtai compatibility
98 #define rt_task_wait_period() rt_task_wait()
99 #define rt_pend_linux_srq(irq) rtl_global_pend_irq(irq)
100 #define rt_free_srq(irq) rtl_free_soft_irq(irq)
101 #define rt_request_srq(x,y,z) rtl_get_soft_irq(y,"timer")
102 #define rt_task_init(a,b,c,d,e,f,g) rt_task_init(a,b,c,d,(e)+1)
103 #define rt_task_resume(x) rt_task_wakeup(x)
104 #define rt_set_oneshot_mode()
105 #define start_rt_timer(x)
106 #define stop_rt_timer()
107
108 #endif
109 #ifdef CONFIG_COMEDI_RTAI
110 #include <rtai.h>
111 #include <rtai_sched.h>
112 #endif
113
114
115 /* This defines the fastest speed we will emulate.  Note that
116  * without a watchdog (like in RTAI), we could easily overrun our
117  * task period because analog input tends to be slow. */
118 #define SPEED_LIMIT 100000      /* in nanoseconds */
119
120 static int timer_attach(comedi_device *dev,comedi_devconfig *it);
121 static int timer_detach(comedi_device *dev);
122 static int timer_inttrig(comedi_device *dev, comedi_subdevice *s, unsigned int trig_num);
123 static int timer_start_cmd(comedi_device *dev, comedi_subdevice *s);
124
125 static comedi_driver driver_timer={
126         module:         THIS_MODULE,
127         driver_name:    "comedi_rt_timer",
128         attach:         timer_attach,
129         detach:         timer_detach,
130 };
131 COMEDI_INITCLEANUP(driver_timer);
132
133
134 typedef struct{
135         comedi_t *device;       // device we are emulating commands for
136         int subd;               // subdevice we are emulating commands for
137         RT_TASK *rt_task;       // rt task that starts scans
138         RT_TASK *scan_task;     // rt task that controls conversion timing in a scan
139         /* io_function can point to either an input or output function
140          * depending on what kind of subdevice we are emulating for */
141         int (*io_function)(comedi_device *dev, comedi_cmd *cmd, unsigned int index);
142         // RTIME has units of 1 = 838 nanoseconds
143         // time at which first scan started, used to check scan timing
144         RTIME start;
145         // time between scans
146         RTIME scan_period;
147         // time between conversions in a scan
148         RTIME convert_period;
149         // flags
150         volatile int stop;      // indicates we should stop
151         volatile int rt_task_active;    // indicates rt_task is servicing a comedi_cmd
152         volatile int scan_task_active;  // indicates scan_task is servicing a comedi_cmd
153         unsigned timer_running : 1;
154 }timer_private;
155 #define devpriv ((timer_private *)dev->private)
156
157 static int timer_cancel(comedi_device *dev,comedi_subdevice *s)
158 {
159         devpriv->stop = 1;
160
161         return 0;
162 }
163
164 // checks for scan timing error
165 inline static int check_scan_timing(comedi_device *dev,
166         unsigned long long scan)
167 {
168         RTIME now, timing_error;
169
170         now = rt_get_time();
171         timing_error = now - (devpriv->start + scan * devpriv->scan_period);
172         if(timing_error > devpriv->scan_period){
173                 comedi_error(dev, "timing error");
174                 rt_printk("scan started %i ns late\n", timing_error * 838);
175                 return -1;
176         }
177
178         return 0;
179 }
180
181 // checks for conversion timing error
182 inline static int check_conversion_timing(comedi_device *dev,
183         RTIME scan_start, unsigned int conversion)
184 {
185         RTIME now, timing_error;
186
187         now = rt_get_time();
188         timing_error = now - (scan_start + conversion * devpriv->convert_period);
189         if(timing_error > devpriv->convert_period){
190                 comedi_error(dev, "timing error");
191                 rt_printk("conversion started %i ns late\n", timing_error * 838);
192                 return -1;
193         }
194
195         return 0;
196 }
197
198 // devpriv->io_function for an input subdevice
199 static int timer_data_read(comedi_device *dev, comedi_cmd *cmd,
200         unsigned int index)
201 {
202         comedi_subdevice *s = dev->read_subdev;
203         int ret;
204         lsampl_t data;
205
206         ret = comedi_data_read(devpriv->device, devpriv->subd,
207                 CR_CHAN(cmd->chanlist[index]),
208                 CR_RANGE(cmd->chanlist[index]),
209                 CR_AREF(cmd->chanlist[index]),
210                 &data);
211         if(ret<0){
212                 comedi_error(dev, "read error");
213                 return -EIO;
214         }
215         if( s->flags % SDF_LSAMPL )
216                 cfc_write_long_to_buffer( s, data );
217         else
218                 cfc_write_to_buffer( s, data );
219
220         return 0;
221 }
222
223 // devpriv->io_function for an output subdevice
224 static int timer_data_write(comedi_device *dev, comedi_cmd *cmd,
225         unsigned int index)
226 {
227         comedi_subdevice *s = dev->write_subdev;
228         unsigned int num_bytes;
229         sampl_t data;
230         lsampl_t long_data;
231
232         if( s->flags & SDF_LSAMPL )
233         {
234                 num_bytes = cfc_read_array_from_buffer( s, &long_data, sizeof( long_data ) );
235         }else
236                 num_bytes = cfc_read_array_from_buffer( s, &data, sizeof( data ) );
237                 long_data = data;
238         }
239
240         if(num_bytes == 0)
241         {
242                 comedi_error(dev, "buffer underrun");
243                 return -EAGAIN;
244         }
245         ret = comedi_data_write(devpriv->device, devpriv->subd,
246                 CR_CHAN(cmd->chanlist[index]),
247                 CR_RANGE(cmd->chanlist[index]),
248                 CR_AREF(cmd->chanlist[index]),
249                 long_data);
250         if(ret<0){
251                 comedi_error(dev, "write error");
252                 return -EIO;
253         }
254
255         return 0;
256 }
257
258 // devpriv->io_function for DIO subdevices
259 static int timer_dio_read(comedi_device *dev, comedi_cmd *cmd,
260         unsigned int index)
261 {
262         comedi_subdevice *s = dev->read_subdev;
263         int ret;
264         lsampl_t data;
265
266         ret = comedi_dio_bitfield(devpriv->device, devpriv->subd, 0, &data);
267         if(ret<0){
268                 comedi_error(dev, "read error");
269                 return -EIO;
270         }
271
272         if( s->flags % SDF_LSAMPL )
273                 cfc_write_long_to_buffer( s, data );
274         else
275                 cfc_write_to_buffer( s, data );
276
277         return 0;
278 }
279
280 // performs scans
281 static void scan_task_func(int d)
282 {
283         comedi_device *dev=(comedi_device *)d;
284         comedi_subdevice *s = dev->subdevices + 0;
285         comedi_async *async = s->async;
286         comedi_cmd *cmd = &async->cmd;
287         int i, ret;
288         unsigned long long n;
289         RTIME scan_start;
290
291         // every comedi_cmd causes one execution of while loop
292         while(1){
293                 devpriv->scan_task_active = 1;
294                 // each for loop completes one scan
295                 for(n = 0; n < cmd->stop_arg || cmd->stop_src == TRIG_NONE; n++){
296                         if(n){
297                                 // suspend task until next scan
298                                 ret = rt_task_suspend(devpriv->scan_task);
299                                 if(ret < 0){
300                                         comedi_error(dev, "error suspending scan task");
301                                         async->events |= COMEDI_CB_ERROR;
302                                         goto cleanup;
303                                 }
304                         }
305                         // check if stop flag was set (by timer_cancel())
306                         if(devpriv->stop)
307                                 goto cleanup;
308                         ret = check_scan_timing(dev, n);
309                         if(ret < 0){
310                                 async->events |= COMEDI_CB_ERROR;
311                                 goto cleanup;
312                         }
313                         scan_start = rt_get_time();
314                         for(i = 0; i < cmd->scan_end_arg; i++){
315                                 // conversion timing
316                                 if(cmd->convert_src == TRIG_TIMER && i){
317                                         rt_task_wait_period();
318                                         ret = check_conversion_timing(dev, scan_start, i);
319                                         if(ret < 0){
320                                                 async->events |= COMEDI_CB_ERROR;
321                                                 goto cleanup;
322                                         }
323                                 }
324                                 ret = devpriv->io_function(dev, cmd, i);
325                                 if(ret < 0){
326                                         async->events |= COMEDI_CB_ERROR;
327                                         goto cleanup;
328                                 }
329                         }
330                         s->async->events |= COMEDI_CB_BLOCK;
331                         comedi_event(dev,s,s->async->events);
332                         s->async->events = 0;
333                 }
334
335 cleanup:
336
337                 comedi_unlock(devpriv->device,devpriv->subd);
338                 async->events |= COMEDI_CB_EOA;
339                 comedi_event(dev, s, async->events);
340                 async->events = 0;
341                 devpriv->scan_task_active = 0;
342                 // suspend task until next comedi_cmd
343                 rt_task_suspend(devpriv->scan_task);
344         }
345 }
346
347 static void timer_task_func(int d)
348 {
349         comedi_device *dev=(comedi_device *)d;
350         comedi_subdevice *s = dev->subdevices + 0;
351         comedi_cmd *cmd=&s->async->cmd;
352         int ret;
353         unsigned long long n;
354
355         // every comedi_cmd causes one execution of while loop
356         while(1){
357                 devpriv->rt_task_active = 1;
358                 devpriv->scan_task_active = 1;
359                 devpriv->start = rt_get_time();
360
361                 for(n = 0; n < cmd->stop_arg || cmd->stop_src == TRIG_NONE; n++){
362                         // scan timing
363                         if(n)
364                                 rt_task_wait_period();
365                         if(devpriv->scan_task_active == 0){
366                                 goto cleanup;
367                         }
368                         ret = rt_task_make_periodic(devpriv->scan_task,
369                                 devpriv->start + devpriv->scan_period * n,
370                                 devpriv->convert_period);
371                         if(ret < 0){
372                                 comedi_error(dev, "bug!");
373                         }
374                 }
375
376 cleanup:
377
378                 devpriv->rt_task_active = 0;
379                 // suspend until next comedi_cmd
380                 rt_task_suspend(devpriv->rt_task);
381         }
382 }
383
384 static int timer_insn(comedi_device *dev,comedi_subdevice *s,
385         comedi_insn *insn,lsampl_t *data)
386 {
387         comedi_insn xinsn = *insn;
388
389         xinsn.data = data;
390         xinsn.subdev = devpriv->subd;
391
392         return comedi_do_insn(devpriv->device,&xinsn);
393 }
394
395 static int cmdtest_helper(comedi_cmd *cmd,
396         unsigned int start_src,
397         unsigned int scan_begin_src,
398         unsigned int convert_src,
399         unsigned int scan_end_src,
400         unsigned int stop_src)
401 {
402         int err = 0;
403         int tmp;
404
405         tmp = cmd->start_src;
406         cmd->start_src &= start_src;
407         if(!cmd->start_src || tmp!=cmd->start_src)err++;
408
409         tmp = cmd->scan_begin_src;
410         cmd->scan_begin_src &= scan_begin_src;
411         if(!cmd->scan_begin_src || tmp!=cmd->scan_begin_src)err++;
412
413         tmp = cmd->convert_src;
414         cmd->convert_src &= convert_src;
415         if(!cmd->convert_src || tmp!=cmd->convert_src)err++;
416
417         tmp = cmd->scan_end_src;
418         cmd->scan_end_src &= scan_end_src;
419         if(!cmd->scan_end_src || tmp!=cmd->scan_end_src)err++;
420
421         tmp = cmd->stop_src;
422         cmd->stop_src &= stop_src;
423         if(!cmd->stop_src || tmp!=cmd->stop_src)err++;
424
425         return err;
426 }
427
428 static int timer_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd)
429 {
430         int err = 0;
431         unsigned int start_src = 0;
432
433         if(s->type == COMEDI_SUBD_AO)
434                 start_src = TRIG_INT;
435         else
436                 start_src = TRIG_NOW;
437
438         err = cmdtest_helper(cmd,
439                 start_src,      /* start_src */
440                 TRIG_TIMER | TRIG_FOLLOW,       /* scan_begin_src */
441                 TRIG_NOW | TRIG_TIMER,  /* convert_src */
442                 TRIG_COUNT,     /* scan_end_src */
443                 TRIG_COUNT | TRIG_NONE);        /* stop_src */
444         if(err)return 1;
445
446         /* step 2: make sure trigger sources are unique and mutually
447          * compatible */
448
449         if(cmd->start_src != TRIG_NOW &&
450                 cmd->start_src != TRIG_INT)
451                 err++;
452         if(cmd->scan_begin_src != TRIG_TIMER &&
453                 cmd->scan_begin_src != TRIG_FOLLOW)
454                 err++;
455         if(cmd->convert_src != TRIG_TIMER &&
456                 cmd->convert_src != TRIG_NOW)
457                 err++;
458         if(cmd->stop_src != TRIG_COUNT &&
459                 cmd->stop_src != TRIG_NONE)
460                 err++;
461         if(cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src != TRIG_TIMER)
462                 err++;
463         if(cmd->convert_src == TRIG_NOW && cmd->scan_begin_src != TRIG_TIMER)
464                 err++;
465
466         if(err)return 2;
467
468         /* step 3: make sure arguments are trivially compatible */
469         // limit frequency, this is fairly arbitrary
470         if(cmd->scan_begin_src == TRIG_TIMER){
471                 if(cmd->scan_begin_arg<SPEED_LIMIT){
472                         cmd->scan_begin_arg=SPEED_LIMIT;
473                         err++;
474                 }
475         }
476         if(cmd->convert_src == TRIG_TIMER){
477                 if(cmd->convert_arg<SPEED_LIMIT){
478                         cmd->convert_arg=SPEED_LIMIT;
479                         err++;
480                 }
481         }
482         // make sure conversion and scan frequencies are compatible
483         if(cmd->convert_src == TRIG_TIMER && cmd->scan_begin_src == TRIG_TIMER){
484                 if(cmd->convert_arg * cmd->scan_end_arg > cmd->scan_begin_arg){
485                         cmd->scan_begin_arg = cmd->convert_arg * cmd->scan_end_arg;
486                         err++;
487                 }
488         }
489         if(err)return 3;
490
491         /* step 4: fix up and arguments */
492         if(err)return 4;
493
494         return 0;
495 }
496
497 static int timer_cmd(comedi_device *dev,comedi_subdevice *s)
498 {
499         int ret;
500         comedi_cmd *cmd = &s->async->cmd;
501
502         /* hack attack: drivers are not supposed to do this: */
503         dev->rt = 1;
504
505         // make sure tasks have finished cleanup of last comedi_cmd
506         if(devpriv->rt_task_active || devpriv->scan_task_active)
507                 return -EBUSY;
508
509         ret = comedi_lock(devpriv->device,devpriv->subd);
510         if(ret<0)
511         {
512                 comedi_error(dev, "failed to obtain lock");
513                 return ret;
514         }
515         switch(cmd->scan_begin_src){
516                 case TRIG_TIMER:
517                         devpriv->scan_period = nano2count(cmd->scan_begin_arg);
518                         break;
519                 case TRIG_FOLLOW:
520                         devpriv->scan_period = nano2count(cmd->convert_arg * cmd->scan_end_arg);
521                         break;
522                 default:
523                         comedi_error(dev, "bug setting scan period!");
524                         return -1;
525                         break;
526         }
527         switch(cmd->convert_src){
528                 case TRIG_TIMER:
529                         devpriv->convert_period = nano2count(cmd->convert_arg);
530                         break;
531                 case TRIG_NOW:
532                         devpriv->convert_period = 1;
533                         break;
534                 default:
535                         comedi_error(dev, "bug setting conversion period!");
536                         return -1;
537                         break;
538         }
539
540         if(cmd->start_src == TRIG_NOW)
541                 return timer_start_cmd(dev, s);
542
543         s->async->inttrig = timer_inttrig;
544
545         return 0;
546 }
547
548 static int timer_inttrig(comedi_device *dev, comedi_subdevice *s, unsigned int trig_num)
549 {
550         if(trig_num != 0)
551                 return -EINVAL;
552
553         s->async->inttrig = NULL;
554
555         return timer_start_cmd(dev, s);
556 }
557
558 static int timer_start_cmd(comedi_device *dev, comedi_subdevice *s)
559 {
560         comedi_async *async = s->async;
561         comedi_cmd *cmd = &async->cmd;
562         RTIME now, delay, period;
563         int ret;
564
565         devpriv->stop = 0;
566         s->async->events = 0;
567
568
569         if(cmd->start_src == TRIG_NOW)
570                 delay = nano2count(cmd->start_arg);
571         else
572                 delay = 0;
573
574         now=rt_get_time();
575         /* Using 'period' this way gets around some weird bug in gcc-2.95.2
576          * that generates the compile error 'internal error--unrecognizable insn'
577          * when rt_task_make_period() is called (observed with rtlinux-3.1, linux-2.2.19).
578          *  - fmhess */
579         period = devpriv->scan_period;
580         ret = rt_task_make_periodic(devpriv->rt_task, now
581                 + delay, period);
582         if(ret < 0)
583         {
584                 comedi_error(dev, "error starting rt_task");
585                 return ret;
586         }
587         return 0;
588 }
589
590 static int timer_attach(comedi_device *dev,comedi_devconfig *it)
591 {
592         int ret;
593         comedi_subdevice *s, *emul_s;
594         comedi_device *emul_dev;
595         /* These should probably be devconfig options[] */
596         const int timer_priority = 4;
597         const int scan_priority = timer_priority + 1;
598         char path[20];
599
600         printk("comedi%d: timer: ",dev->minor);
601
602         dev->board_name="timer";
603
604         dev->n_subdevices=1;
605         if((ret=alloc_subdevices(dev))<0)
606                 return ret;
607         if((ret=alloc_private(dev,sizeof(timer_private)))<0)
608                 return ret;
609
610         sprintf(path,"/dev/comedi%d",it->options[0]);
611         devpriv->device = comedi_open(path);
612         devpriv->subd=it->options[1];
613
614         printk("emulating commands for minor %i, subdevice %d\n", it->options[0], devpriv->subd);
615
616         emul_dev = devpriv->device;
617         emul_s = emul_dev->subdevices+devpriv->subd;
618
619         // input or output subdevice
620         s=dev->subdevices+0;
621         s->type=emul_s->type;
622         s->subdev_flags = emul_s->subdev_flags;
623         s->n_chan=emul_s->n_chan;
624         s->len_chanlist=1024;
625         s->do_cmd=timer_cmd;
626         s->do_cmdtest=timer_cmdtest;
627         s->cancel=timer_cancel;
628         s->maxdata=emul_s->maxdata;
629         s->range_table=emul_s->range_table;
630         s->range_table_list=emul_s->range_table_list;
631         switch(emul_s->type){
632         case COMEDI_SUBD_AI:
633                 s->insn_read=timer_insn;
634                 dev->read_subdev = s;
635                 devpriv->io_function = timer_data_read;
636                 break;
637         case COMEDI_SUBD_AO:
638                 s->insn_write=timer_insn;
639                 s->insn_read=timer_insn;
640                 dev->write_subdev = s;
641                 devpriv->io_function = timer_data_write;
642                 break;
643         case COMEDI_SUBD_DIO:
644                 s->insn_write=timer_insn;
645                 s->insn_read=timer_insn;
646                 s->insn_bits=timer_insn;
647                 dev->read_subdev = s;
648                 devpriv->io_function = timer_dio_read;
649                 break;
650         default:
651                 comedi_error(dev, "failed to determine subdevice type!");
652                 return -EINVAL;
653         }
654
655         rt_set_oneshot_mode();
656         start_rt_timer( 1 );
657         devpriv->timer_running = 1;
658
659         devpriv->rt_task = kmalloc(sizeof(RT_TASK),GFP_KERNEL);
660         memset(devpriv->rt_task,0,sizeof(RT_TASK));
661
662         // initialize real-time tasks
663         ret = rt_task_init(devpriv->rt_task, timer_task_func,(int)dev, 3000,
664                 timer_priority, 0, 0);
665         if(ret < 0)     {
666                 comedi_error(dev, "error initalizing rt_task");
667                 kfree(devpriv->rt_task);
668                 devpriv->rt_task = 0;
669                 return ret;
670         }
671
672         devpriv->scan_task = kmalloc(sizeof(RT_TASK),GFP_KERNEL);
673         memset(devpriv->scan_task,0,sizeof(RT_TASK));
674
675         ret = rt_task_init(devpriv->scan_task, scan_task_func,
676                 (int)dev, 3000, scan_priority, 0, 0);
677         if(ret < 0){
678                 comedi_error(dev, "error initalizing scan_task");
679                 kfree(devpriv->scan_task);
680                 devpriv->scan_task = 0;
681                 return ret;
682         }
683
684         return 1;
685 }
686
687 // free allocated resources
688 static int timer_detach(comedi_device *dev)
689 {
690         printk("comedi%d: timer: remove\n",dev->minor);
691
692         if(devpriv){
693                 if(devpriv->rt_task){
694                         rt_task_delete(devpriv->rt_task);
695                         kfree(devpriv->rt_task);
696                 }
697                 if(devpriv->scan_task){
698                         rt_task_delete(devpriv->scan_task);
699                         kfree(devpriv->scan_task);
700                 }
701                 if( devpriv->timer_running )
702                         stop_rt_timer();
703         }
704
705         return 0;
706 }
707
708