1 /*                                              -*- linux-c -*-
2  * dtlk.c - DoubleTalk PC driver for Linux
3  *
4  * Original author: Chris Pallotta <chris@allmedia.com>
5  * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>
6  *
7  * 2000-03-18 Jim Van Zandt: Fix polling.
8  *  Eliminate dtlk_timer_active flag and separate dtlk_stop_timer
9  *  function.  Don't restart timer in dtlk_timer_tick.  Restart timer
10  *  in dtlk_poll after every poll.  dtlk_poll returns mask (duh).
11  *  Eliminate unused function dtlk_write_byte.  Misc. code cleanups.
12  */
13 
14 /* This driver is for the DoubleTalk PC, a speech synthesizer
15    manufactured by RC Systems (http://www.rcsys.com/).  It was written
16    based on documentation in their User's Manual file and Developer's
17    Tools disk.
18 
19    The DoubleTalk PC contains four voice synthesizers: text-to-speech
20    (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD.  It
21    also has a tone generator.  Output data for LPC are written to the
22    LPC port, and output data for the other modes are written to the
23    TTS port.
24 
25    Two kinds of data can be read from the DoubleTalk: status
26    information (in response to the "\001?" interrogation command) is
27    read from the TTS port, and index markers (which mark the progress
28    of the speech) are read from the LPC port.  Not all models of the
29    DoubleTalk PC implement index markers.  Both the TTS and LPC ports
30    can also display status flags.
31 
32    The DoubleTalk PC generates no interrupts.
33 
34    These characteristics are mapped into the Unix stream I/O model as
35    follows:
36 
37    "write" sends bytes to the TTS port.  It is the responsibility of
38    the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
39    This driver was written for use with the text-to-speech
40    synthesizer.  If LPC output is needed some day, other minor device
41    numbers can be used to select among output modes.
42 
43    "read" gets index markers from the LPC port.  If the device does
44    not implement index markers, the read will fail with error EINVAL.
45 
46    Status information is available using the DTLK_INTERROGATE ioctl.
47 
48  */
49 
50 #include <linux/module.h>
51 
52 #define KERNEL
53 #include <linux/types.h>
54 #include <linux/fs.h>
55 #include <linux/mm.h>
56 #include <linux/errno.h>	/* for -EBUSY */
57 #include <linux/ioport.h>	/* for request_region */
58 #include <linux/delay.h>	/* for loops_per_jiffy */
59 #include <linux/sched.h>
60 #include <linux/mutex.h>
61 #include <asm/io.h>		/* for inb_p, outb_p, inb, outb, etc. */
62 #include <asm/uaccess.h>	/* for get_user, etc. */
63 #include <linux/wait.h>		/* for wait_queue */
64 #include <linux/init.h>		/* for __init, module_{init,exit} */
65 #include <linux/poll.h>		/* for POLLIN, etc. */
66 #include <linux/dtlk.h>		/* local header file for DoubleTalk values */
67 
68 #ifdef TRACING
69 #define TRACE_TEXT(str) printk(str);
70 #define TRACE_RET printk(")")
71 #else				/* !TRACING */
72 #define TRACE_TEXT(str) ((void) 0)
73 #define TRACE_RET ((void) 0)
74 #endif				/* TRACING */
75 
76 static DEFINE_MUTEX(dtlk_mutex);
77 static void dtlk_timer_tick(unsigned long data);
78 
79 static int dtlk_major;
80 static int dtlk_port_lpc;
81 static int dtlk_port_tts;
82 static int dtlk_busy;
83 static int dtlk_has_indexing;
84 static unsigned int dtlk_portlist[] =
85 {0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
86 static wait_queue_head_t dtlk_process_list;
87 static DEFINE_TIMER(dtlk_timer, dtlk_timer_tick, 0, 0);
88 
89 /* prototypes for file_operations struct */
90 static ssize_t dtlk_read(struct file *, char __user *,
91 			 size_t nbytes, loff_t * ppos);
92 static ssize_t dtlk_write(struct file *, const char __user *,
93 			  size_t nbytes, loff_t * ppos);
94 static unsigned int dtlk_poll(struct file *, poll_table *);
95 static int dtlk_open(struct inode *, struct file *);
96 static int dtlk_release(struct inode *, struct file *);
97 static long dtlk_ioctl(struct file *file,
98 		       unsigned int cmd, unsigned long arg);
99 
100 static const struct file_operations dtlk_fops =
101 {
102 	.owner		= THIS_MODULE,
103 	.read		= dtlk_read,
104 	.write		= dtlk_write,
105 	.poll		= dtlk_poll,
106 	.unlocked_ioctl	= dtlk_ioctl,
107 	.open		= dtlk_open,
108 	.release	= dtlk_release,
109 	.llseek		= no_llseek,
110 };
111 
112 /* local prototypes */
113 static int dtlk_dev_probe(void);
114 static struct dtlk_settings *dtlk_interrogate(void);
115 static int dtlk_readable(void);
116 static char dtlk_read_lpc(void);
117 static char dtlk_read_tts(void);
118 static int dtlk_writeable(void);
119 static char dtlk_write_bytes(const char *buf, int n);
120 static char dtlk_write_tts(char);
121 /*
122    static void dtlk_handle_error(char, char, unsigned int);
123  */
124 
dtlk_read(struct file * file,char __user * buf,size_t count,loff_t * ppos)125 static ssize_t dtlk_read(struct file *file, char __user *buf,
126 			 size_t count, loff_t * ppos)
127 {
128 	unsigned int minor = iminor(file->f_path.dentry->d_inode);
129 	char ch;
130 	int i = 0, retries;
131 
132 	TRACE_TEXT("(dtlk_read");
133 	/*  printk("DoubleTalk PC - dtlk_read()\n"); */
134 
135 	if (minor != DTLK_MINOR || !dtlk_has_indexing)
136 		return -EINVAL;
137 
138 	for (retries = 0; retries < loops_per_jiffy; retries++) {
139 		while (i < count && dtlk_readable()) {
140 			ch = dtlk_read_lpc();
141 			/*        printk("dtlk_read() reads 0x%02x\n", ch); */
142 			if (put_user(ch, buf++))
143 				return -EFAULT;
144 			i++;
145 		}
146 		if (i)
147 			return i;
148 		if (file->f_flags & O_NONBLOCK)
149 			break;
150 		msleep_interruptible(100);
151 	}
152 	if (retries == loops_per_jiffy)
153 		printk(KERN_ERR "dtlk_read times out\n");
154 	TRACE_RET;
155 	return -EAGAIN;
156 }
157 
dtlk_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)158 static ssize_t dtlk_write(struct file *file, const char __user *buf,
159 			  size_t count, loff_t * ppos)
160 {
161 	int i = 0, retries = 0, ch;
162 
163 	TRACE_TEXT("(dtlk_write");
164 #ifdef TRACING
165 	printk(" \"");
166 	{
167 		int i, ch;
168 		for (i = 0; i < count; i++) {
169 			if (get_user(ch, buf + i))
170 				return -EFAULT;
171 			if (' ' <= ch && ch <= '~')
172 				printk("%c", ch);
173 			else
174 				printk("\\%03o", ch);
175 		}
176 		printk("\"");
177 	}
178 #endif
179 
180 	if (iminor(file->f_path.dentry->d_inode) != DTLK_MINOR)
181 		return -EINVAL;
182 
183 	while (1) {
184 		while (i < count && !get_user(ch, buf) &&
185 		       (ch == DTLK_CLEAR || dtlk_writeable())) {
186 			dtlk_write_tts(ch);
187 			buf++;
188 			i++;
189 			if (i % 5 == 0)
190 				/* We yield our time until scheduled
191 				   again.  This reduces the transfer
192 				   rate to 500 bytes/sec, but that's
193 				   still enough to keep up with the
194 				   speech synthesizer. */
195 				msleep_interruptible(1);
196 			else {
197 				/* the RDY bit goes zero 2-3 usec
198 				   after writing, and goes 1 again
199 				   180-190 usec later.  Here, we wait
200 				   up to 250 usec for the RDY bit to
201 				   go nonzero. */
202 				for (retries = 0;
203 				     retries < loops_per_jiffy / (4000/HZ);
204 				     retries++)
205 					if (inb_p(dtlk_port_tts) &
206 					    TTS_WRITABLE)
207 						break;
208 			}
209 			retries = 0;
210 		}
211 		if (i == count)
212 			return i;
213 		if (file->f_flags & O_NONBLOCK)
214 			break;
215 
216 		msleep_interruptible(1);
217 
218 		if (++retries > 10 * HZ) { /* wait no more than 10 sec
219 					      from last write */
220 			printk("dtlk: write timeout.  "
221 			       "inb_p(dtlk_port_tts) = 0x%02x\n",
222 			       inb_p(dtlk_port_tts));
223 			TRACE_RET;
224 			return -EBUSY;
225 		}
226 	}
227 	TRACE_RET;
228 	return -EAGAIN;
229 }
230 
dtlk_poll(struct file * file,poll_table * wait)231 static unsigned int dtlk_poll(struct file *file, poll_table * wait)
232 {
233 	int mask = 0;
234 	unsigned long expires;
235 
236 	TRACE_TEXT(" dtlk_poll");
237 	/*
238 	   static long int j;
239 	   printk(".");
240 	   printk("<%ld>", jiffies-j);
241 	   j=jiffies;
242 	 */
243 	poll_wait(file, &dtlk_process_list, wait);
244 
245 	if (dtlk_has_indexing && dtlk_readable()) {
246 	        del_timer(&dtlk_timer);
247 		mask = POLLIN | POLLRDNORM;
248 	}
249 	if (dtlk_writeable()) {
250 	        del_timer(&dtlk_timer);
251 		mask |= POLLOUT | POLLWRNORM;
252 	}
253 	/* there are no exception conditions */
254 
255 	/* There won't be any interrupts, so we set a timer instead. */
256 	expires = jiffies + 3*HZ / 100;
257 	mod_timer(&dtlk_timer, expires);
258 
259 	return mask;
260 }
261 
dtlk_timer_tick(unsigned long data)262 static void dtlk_timer_tick(unsigned long data)
263 {
264 	TRACE_TEXT(" dtlk_timer_tick");
265 	wake_up_interruptible(&dtlk_process_list);
266 }
267 
dtlk_ioctl(struct file * file,unsigned int cmd,unsigned long arg)268 static long dtlk_ioctl(struct file *file,
269 		       unsigned int cmd,
270 		       unsigned long arg)
271 {
272 	char __user *argp = (char __user *)arg;
273 	struct dtlk_settings *sp;
274 	char portval;
275 	TRACE_TEXT(" dtlk_ioctl");
276 
277 	switch (cmd) {
278 
279 	case DTLK_INTERROGATE:
280 		mutex_lock(&dtlk_mutex);
281 		sp = dtlk_interrogate();
282 		mutex_unlock(&dtlk_mutex);
283 		if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
284 			return -EINVAL;
285 		return 0;
286 
287 	case DTLK_STATUS:
288 		portval = inb_p(dtlk_port_tts);
289 		return put_user(portval, argp);
290 
291 	default:
292 		return -EINVAL;
293 	}
294 }
295 
296 /* Note that nobody ever sets dtlk_busy... */
dtlk_open(struct inode * inode,struct file * file)297 static int dtlk_open(struct inode *inode, struct file *file)
298 {
299 	TRACE_TEXT("(dtlk_open");
300 
301 	nonseekable_open(inode, file);
302 	switch (iminor(inode)) {
303 	case DTLK_MINOR:
304 		if (dtlk_busy)
305 			return -EBUSY;
306 		return nonseekable_open(inode, file);
307 
308 	default:
309 		return -ENXIO;
310 	}
311 }
312 
dtlk_release(struct inode * inode,struct file * file)313 static int dtlk_release(struct inode *inode, struct file *file)
314 {
315 	TRACE_TEXT("(dtlk_release");
316 
317 	switch (iminor(inode)) {
318 	case DTLK_MINOR:
319 		break;
320 
321 	default:
322 		break;
323 	}
324 	TRACE_RET;
325 
326 	del_timer_sync(&dtlk_timer);
327 
328 	return 0;
329 }
330 
dtlk_init(void)331 static int __init dtlk_init(void)
332 {
333 	int err;
334 
335 	dtlk_port_lpc = 0;
336 	dtlk_port_tts = 0;
337 	dtlk_busy = 0;
338 	dtlk_major = register_chrdev(0, "dtlk", &dtlk_fops);
339 	if (dtlk_major < 0) {
340 		printk(KERN_ERR "DoubleTalk PC - cannot register device\n");
341 		return dtlk_major;
342 	}
343 	err = dtlk_dev_probe();
344 	if (err) {
345 		unregister_chrdev(dtlk_major, "dtlk");
346 		return err;
347 	}
348 	printk(", MAJOR %d\n", dtlk_major);
349 
350 	init_waitqueue_head(&dtlk_process_list);
351 
352 	return 0;
353 }
354 
dtlk_cleanup(void)355 static void __exit dtlk_cleanup (void)
356 {
357 	dtlk_write_bytes("goodbye", 8);
358 	msleep_interruptible(500);		/* nap 0.50 sec but
359 						   could be awakened
360 						   earlier by
361 						   signals... */
362 
363 	dtlk_write_tts(DTLK_CLEAR);
364 	unregister_chrdev(dtlk_major, "dtlk");
365 	release_region(dtlk_port_lpc, DTLK_IO_EXTENT);
366 }
367 
368 module_init(dtlk_init);
369 module_exit(dtlk_cleanup);
370 
371 /* ------------------------------------------------------------------------ */
372 
dtlk_readable(void)373 static int dtlk_readable(void)
374 {
375 #ifdef TRACING
376 	printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);
377 #endif
378 	return inb_p(dtlk_port_lpc) != 0x7f;
379 }
380 
dtlk_writeable(void)381 static int dtlk_writeable(void)
382 {
383 	/* TRACE_TEXT(" dtlk_writeable"); */
384 #ifdef TRACINGMORE
385 	printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);
386 #endif
387 	return inb_p(dtlk_port_tts) & TTS_WRITABLE;
388 }
389 
dtlk_dev_probe(void)390 static int __init dtlk_dev_probe(void)
391 {
392 	unsigned int testval = 0;
393 	int i = 0;
394 	struct dtlk_settings *sp;
395 
396 	if (dtlk_port_lpc | dtlk_port_tts)
397 		return -EBUSY;
398 
399 	for (i = 0; dtlk_portlist[i]; i++) {
400 #if 0
401 		printk("DoubleTalk PC - Port %03x = %04x\n",
402 		       dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));
403 #endif
404 
405 		if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT,
406 			       "dtlk"))
407 			continue;
408 		testval = inw_p(dtlk_portlist[i]);
409 		if ((testval &= 0xfbff) == 0x107f) {
410 			dtlk_port_lpc = dtlk_portlist[i];
411 			dtlk_port_tts = dtlk_port_lpc + 1;
412 
413 			sp = dtlk_interrogate();
414 			printk("DoubleTalk PC at %03x-%03x, "
415 			       "ROM version %s, serial number %u",
416 			       dtlk_portlist[i], dtlk_portlist[i] +
417 			       DTLK_IO_EXTENT - 1,
418 			       sp->rom_version, sp->serial_number);
419 
420                         /* put LPC port into known state, so
421 			   dtlk_readable() gives valid result */
422 			outb_p(0xff, dtlk_port_lpc);
423 
424                         /* INIT string and index marker */
425 			dtlk_write_bytes("\036\1@\0\0012I\r", 8);
426 			/* posting an index takes 18 msec.  Here, we
427 			   wait up to 100 msec to see whether it
428 			   appears. */
429 			msleep_interruptible(100);
430 			dtlk_has_indexing = dtlk_readable();
431 #ifdef TRACING
432 			printk(", indexing %d\n", dtlk_has_indexing);
433 #endif
434 #ifdef INSCOPE
435 			{
436 /* This macro records ten samples read from the LPC port, for later display */
437 #define LOOK					\
438 for (i = 0; i < 10; i++)			\
439   {						\
440     buffer[b++] = inb_p(dtlk_port_lpc);		\
441     __delay(loops_per_jiffy/(1000000/HZ));             \
442   }
443 				char buffer[1000];
444 				int b = 0, i, j;
445 
446 				LOOK
447 				outb_p(0xff, dtlk_port_lpc);
448 				buffer[b++] = 0;
449 				LOOK
450 				dtlk_write_bytes("\0012I\r", 4);
451 				buffer[b++] = 0;
452 				__delay(50 * loops_per_jiffy / (1000/HZ));
453 				outb_p(0xff, dtlk_port_lpc);
454 				buffer[b++] = 0;
455 				LOOK
456 
457 				printk("\n");
458 				for (j = 0; j < b; j++)
459 					printk(" %02x", buffer[j]);
460 				printk("\n");
461 			}
462 #endif				/* INSCOPE */
463 
464 #ifdef OUTSCOPE
465 			{
466 /* This macro records ten samples read from the TTS port, for later display */
467 #define LOOK					\
468 for (i = 0; i < 10; i++)			\
469   {						\
470     buffer[b++] = inb_p(dtlk_port_tts);		\
471     __delay(loops_per_jiffy/(1000000/HZ));  /* 1 us */ \
472   }
473 				char buffer[1000];
474 				int b = 0, i, j;
475 
476 				mdelay(10);	/* 10 ms */
477 				LOOK
478 				outb_p(0x03, dtlk_port_tts);
479 				buffer[b++] = 0;
480 				LOOK
481 				LOOK
482 
483 				printk("\n");
484 				for (j = 0; j < b; j++)
485 					printk(" %02x", buffer[j]);
486 				printk("\n");
487 			}
488 #endif				/* OUTSCOPE */
489 
490 			dtlk_write_bytes("Double Talk found", 18);
491 
492 			return 0;
493 		}
494 		release_region(dtlk_portlist[i], DTLK_IO_EXTENT);
495 	}
496 
497 	printk(KERN_INFO "DoubleTalk PC - not found\n");
498 	return -ENODEV;
499 }
500 
501 /*
502    static void dtlk_handle_error(char op, char rc, unsigned int minor)
503    {
504    printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n",
505    minor, op, rc);
506    return;
507    }
508  */
509 
510 /* interrogate the DoubleTalk PC and return its settings */
dtlk_interrogate(void)511 static struct dtlk_settings *dtlk_interrogate(void)
512 {
513 	unsigned char *t;
514 	static char buf[sizeof(struct dtlk_settings) + 1];
515 	int total, i;
516 	static struct dtlk_settings status;
517 	TRACE_TEXT("(dtlk_interrogate");
518 	dtlk_write_bytes("\030\001?", 3);
519 	for (total = 0, i = 0; i < 50; i++) {
520 		buf[total] = dtlk_read_tts();
521 		if (total > 2 && buf[total] == 0x7f)
522 			break;
523 		if (total < sizeof(struct dtlk_settings))
524 			total++;
525 	}
526 	/*
527 	   if (i==50) printk("interrogate() read overrun\n");
528 	   for (i=0; i<sizeof(buf); i++)
529 	   printk(" %02x", buf[i]);
530 	   printk("\n");
531 	 */
532 	t = buf;
533 	status.serial_number = t[0] + t[1] * 256; /* serial number is
534 						     little endian */
535 	t += 2;
536 
537 	i = 0;
538 	while (*t != '\r') {
539 		status.rom_version[i] = *t;
540 		if (i < sizeof(status.rom_version) - 1)
541 			i++;
542 		t++;
543 	}
544 	status.rom_version[i] = 0;
545 	t++;
546 
547 	status.mode = *t++;
548 	status.punc_level = *t++;
549 	status.formant_freq = *t++;
550 	status.pitch = *t++;
551 	status.speed = *t++;
552 	status.volume = *t++;
553 	status.tone = *t++;
554 	status.expression = *t++;
555 	status.ext_dict_loaded = *t++;
556 	status.ext_dict_status = *t++;
557 	status.free_ram = *t++;
558 	status.articulation = *t++;
559 	status.reverb = *t++;
560 	status.eob = *t++;
561 	status.has_indexing = dtlk_has_indexing;
562 	TRACE_RET;
563 	return &status;
564 }
565 
dtlk_read_tts(void)566 static char dtlk_read_tts(void)
567 {
568 	int portval, retries = 0;
569 	char ch;
570 	TRACE_TEXT("(dtlk_read_tts");
571 
572 	/* verify DT is ready, read char, wait for ACK */
573 	do {
574 		portval = inb_p(dtlk_port_tts);
575 	} while ((portval & TTS_READABLE) == 0 &&
576 		 retries++ < DTLK_MAX_RETRIES);
577 	if (retries > DTLK_MAX_RETRIES)
578 		printk(KERN_ERR "dtlk_read_tts() timeout\n");
579 
580 	ch = inb_p(dtlk_port_tts);	/* input from TTS port */
581 	ch &= 0x7f;
582 	outb_p(ch, dtlk_port_tts);
583 
584 	retries = 0;
585 	do {
586 		portval = inb_p(dtlk_port_tts);
587 	} while ((portval & TTS_READABLE) != 0 &&
588 		 retries++ < DTLK_MAX_RETRIES);
589 	if (retries > DTLK_MAX_RETRIES)
590 		printk(KERN_ERR "dtlk_read_tts() timeout\n");
591 
592 	TRACE_RET;
593 	return ch;
594 }
595 
dtlk_read_lpc(void)596 static char dtlk_read_lpc(void)
597 {
598 	int retries = 0;
599 	char ch;
600 	TRACE_TEXT("(dtlk_read_lpc");
601 
602 	/* no need to test -- this is only called when the port is readable */
603 
604 	ch = inb_p(dtlk_port_lpc);	/* input from LPC port */
605 
606 	outb_p(0xff, dtlk_port_lpc);
607 
608 	/* acknowledging a read takes 3-4
609 	   usec.  Here, we wait up to 20 usec
610 	   for the acknowledgement */
611 	retries = (loops_per_jiffy * 20) / (1000000/HZ);
612 	while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);
613 	if (retries == 0)
614 		printk(KERN_ERR "dtlk_read_lpc() timeout\n");
615 
616 	TRACE_RET;
617 	return ch;
618 }
619 
620 /* write n bytes to tts port */
dtlk_write_bytes(const char * buf,int n)621 static char dtlk_write_bytes(const char *buf, int n)
622 {
623 	char val = 0;
624 	/*  printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */
625 	TRACE_TEXT("(dtlk_write_bytes");
626 	while (n-- > 0)
627 		val = dtlk_write_tts(*buf++);
628 	TRACE_RET;
629 	return val;
630 }
631 
dtlk_write_tts(char ch)632 static char dtlk_write_tts(char ch)
633 {
634 	int retries = 0;
635 #ifdef TRACINGMORE
636 	printk("  dtlk_write_tts(");
637 	if (' ' <= ch && ch <= '~')
638 		printk("'%c'", ch);
639 	else
640 		printk("0x%02x", ch);
641 #endif
642 	if (ch != DTLK_CLEAR)	/* no flow control for CLEAR command */
643 		while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&
644 		       retries++ < DTLK_MAX_RETRIES)	/* DT ready? */
645 			;
646 	if (retries > DTLK_MAX_RETRIES)
647 		printk(KERN_ERR "dtlk_write_tts() timeout\n");
648 
649 	outb_p(ch, dtlk_port_tts);	/* output to TTS port */
650 	/* the RDY bit goes zero 2-3 usec after writing, and goes
651 	   1 again 180-190 usec later.  Here, we wait up to 10
652 	   usec for the RDY bit to go zero. */
653 	for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)
654 		if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)
655 			break;
656 
657 #ifdef TRACINGMORE
658 	printk(")\n");
659 #endif
660 	return 0;
661 }
662 
663 MODULE_LICENSE("GPL");
664