xref: /haiku/src/add-ons/kernel/drivers/common/console.cpp (revision 239222b2369c39dc52df52b0a7cdd6cc0a91bc92)
1 /*
2  * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
6  * Distributed under the terms of the NewOS License.
7  */
8 
9 
10 #include <Drivers.h>
11 #include <KernelExport.h>
12 
13 #include <console.h>
14 #include <lock.h>
15 
16 #include <string.h>
17 #include <stdio.h>
18 #include <termios.h>
19 
20 
21 #define DEVICE_NAME "console"
22 
23 #define TAB_SIZE 8
24 #define TAB_MASK 7
25 
26 #define FMASK 0x0f
27 #define BMASK 0x70
28 
29 typedef enum {
30 	CONSOLE_STATE_NORMAL = 0,
31 	CONSOLE_STATE_GOT_ESCAPE,
32 	CONSOLE_STATE_SEEN_BRACKET,
33 	CONSOLE_STATE_NEW_ARG,
34 	CONSOLE_STATE_PARSING_ARG,
35 } console_state;
36 
37 typedef enum {
38 	SCREEN_ERASE_WHOLE,
39 	SCREEN_ERASE_UP,
40 	SCREEN_ERASE_DOWN
41 } erase_screen_mode;
42 
43 typedef enum {
44 	LINE_ERASE_WHOLE,
45 	LINE_ERASE_LEFT,
46 	LINE_ERASE_RIGHT
47 } erase_line_mode;
48 
49 #define MAX_ARGS 8
50 
51 static struct console_desc {
52 	mutex	lock;
53 
54 	int32	lines;
55 	int32	columns;
56 
57 	uint8	attr;
58 	uint8	saved_attr;
59 	bool	bright_attr;
60 	bool	reverse_attr;
61 
62 	int32	x;						/* current x coordinate */
63 	int32	y;						/* current y coordinate */
64 	int32	saved_x;				/* used to save x and y */
65 	int32	saved_y;
66 
67 	int32	scroll_top;	/* top of the scroll region */
68 	int32	scroll_bottom;	/* bottom of the scroll region */
69 
70 	/* state machine */
71 	console_state state;
72 	int32	arg_count;
73 	int32	args[MAX_ARGS];
74 
75 	char	module_name[B_FILE_NAME_LENGTH];
76 	console_module_info *module;
77 } sConsole;
78 
79 int32 api_version = B_CUR_DRIVER_API_VERSION;
80 
81 
82 static inline void
83 update_cursor(struct console_desc *console, int x, int y)
84 {
85 	console->module->move_cursor(x, y);
86 }
87 
88 
89 static void
90 gotoxy(struct console_desc *console, int new_x, int new_y)
91 {
92 	if (new_x >= console->columns)
93 		new_x = console->columns - 1;
94 	if (new_x < 0)
95 		new_x = 0;
96 	if (new_y >= console->lines)
97 		new_y = console->lines - 1;
98 	if (new_y < 0)
99 		new_y = 0;
100 
101 	console->x = new_x;
102 	console->y = new_y;
103 }
104 
105 
106 static void
107 reset_console(struct console_desc *console)
108 {
109 	console->attr = 0x0f;
110 	console->scroll_top = 0;
111 	console->scroll_bottom = console->lines - 1;
112 	console->bright_attr = true;
113 	console->reverse_attr = false;
114 }
115 
116 
117 /** scroll from the cursor line up to the top of the scroll region up one line */
118 
119 static void
120 scrup(struct console_desc *console)
121 {
122 	// see if cursor is outside of scroll region
123 	if (console->y < console->scroll_top || console->y > console->scroll_bottom)
124 		return;
125 
126 	if (console->y - console->scroll_top > 1) {
127 		// move the screen up one
128 		console->module->blit(0, console->scroll_top + 1, console->columns,
129 			console->y - console->scroll_top, 0, console->scroll_top);
130 	}
131 
132 	// clear the bottom line
133 	console->module->fill_glyph(0, console->y, console->columns, 1, ' ', console->attr);
134 }
135 
136 
137 /** scroll from the cursor line down to the bottom of the scroll region down one line */
138 
139 static void
140 scrdown(struct console_desc *console)
141 {
142 	// see if cursor is outside of scroll region
143 	if (console->y < console->scroll_top || console->y > console->scroll_bottom)
144 		return;
145 
146 	if (console->scroll_bottom - console->y > 1) {
147 		// move the screen down one
148 		console->module->blit(0, console->y, console->columns,
149 			console->scroll_bottom - console->y, 0, console->y + 1);
150 	}
151 
152 	// clear the top line
153 	console->module->fill_glyph(0, console->y, console->columns, 1, ' ', console->attr);
154 }
155 
156 
157 static void
158 lf(struct console_desc *console)
159 {
160 	//dprintf("lf: y %d x %d scroll_top %d scoll_bottom %d\n", console->y, console->x, console->scroll_top, console->scroll_bottom);
161 
162 	if (console->y == console->scroll_bottom ) {
163  		// we hit the bottom of our scroll region
164  		scrup(console);
165 	} else if(console->y < console->scroll_bottom) {
166 		console->y++;
167 	}
168 }
169 
170 
171 static void
172 rlf(struct console_desc *console)
173 {
174 	if (console->y == console->scroll_top) {
175  		// we hit the top of our scroll region
176  		scrdown(console);
177 	} else if (console->y > console->scroll_top) {
178 		console->y--;
179 	}
180 }
181 
182 
183 static void
184 cr(struct console_desc *console)
185 {
186 	console->x = 0;
187 }
188 
189 
190 static void
191 del(struct console_desc *console)
192 {
193 	if (console->x > 0) {
194 		console->x--;
195 	} else if (console->y > 0) {
196         console->y--;
197         console->x = console->columns - 1;
198     } else {
199         //This doesn't work...
200         //scrdown(console);
201         //console->y--;
202         //console->x = console->columns - 1;
203         return;
204     }
205 	console->module->put_glyph(console->x, console->y, ' ', console->attr);
206 }
207 
208 
209 static void
210 erase_line(struct console_desc *console, erase_line_mode mode)
211 {
212 	switch (mode) {
213 		case LINE_ERASE_WHOLE:
214 			console->module->fill_glyph(0, console->y, console->columns, 1, ' ', console->attr);
215 			break;
216 		case LINE_ERASE_LEFT:
217 			console->module->fill_glyph(0, console->y, console->x+1, 1, ' ', console->attr);
218 			break;
219 		case LINE_ERASE_RIGHT:
220 			console->module->fill_glyph(console->x, console->y,
221 				console->columns - console->x, 1, ' ', console->attr);
222 			break;
223 		default:
224 			return;
225 	}
226 }
227 
228 
229 static void
230 erase_screen(struct console_desc *console, erase_screen_mode mode)
231 {
232 	switch (mode) {
233 		case SCREEN_ERASE_WHOLE:
234 			console->module->clear(console->attr);
235 			break;
236 		case SCREEN_ERASE_UP:
237 			console->module->fill_glyph(0, 0, console->columns, console->y + 1, ' ', console->attr);
238 			break;
239 		case SCREEN_ERASE_DOWN:
240 			console->module->fill_glyph(console->y, 0,
241 				console->columns, console->lines - console->y, ' ', console->attr);
242 			break;
243 		default:
244 			return;
245 	}
246 }
247 
248 
249 static void
250 save_cur(struct console_desc *console, bool save_attrs)
251 {
252 	console->saved_x = console->x;
253 	console->saved_y = console->y;
254 	if (save_attrs)
255 		console->saved_attr = console->attr;
256 }
257 
258 
259 static void
260 restore_cur(struct console_desc *console, bool restore_attrs)
261 {
262 	console->x = console->saved_x;
263 	console->y = console->saved_y;
264 	if (restore_attrs)
265 		console->attr = console->saved_attr;
266 }
267 
268 
269 static char
270 console_putch(struct console_desc *console, const char c)
271 {
272 	if (++console->x >= console->columns) {
273 		cr(console);
274 		lf(console);
275 	}
276 	console->module->put_glyph(console->x-1, console->y, c, console->attr);
277 	return c;
278 }
279 
280 
281 static void
282 tab(struct console_desc *console)
283 {
284 	console->x = (console->x + TAB_SIZE) & ~TAB_MASK;
285 	if (console->x >= console->columns) {
286 		console->x -= console->columns;
287 		lf(console);
288 	}
289 }
290 
291 
292 static void
293 set_scroll_region(struct console_desc *console, int top, int bottom)
294 {
295 	if (top < 0)
296 		top = 0;
297 	if (bottom >= console->lines)
298 		bottom = console->lines - 1;
299 	if (top > bottom)
300 		return;
301 
302 	console->scroll_top = top;
303 	console->scroll_bottom = bottom;
304 }
305 
306 
307 static void
308 set_vt100_attributes(struct console_desc *console, int32 *args, int32 argCount)
309 {
310 	if (argCount == 0) {
311 		// that's the default (attributes off)
312 		argCount++;
313 		args[0] = 0;
314 	}
315 
316 	for (int32 i = 0; i < argCount; i++) {
317 		//dprintf("set_vt100_attributes: %ld\n", args[i]);
318 		switch (args[i]) {
319 			case 0: // reset
320 				console->attr = 0x0f;
321 				console->bright_attr = true;
322 				console->reverse_attr = false;
323 				break;
324 			case 1: // bright
325 				console->bright_attr = true;
326 				console->attr |= 0x08; // set the bright bit
327 				break;
328 			case 2: // dim
329 				console->bright_attr = false;
330 				console->attr &= ~0x08; // unset the bright bit
331 				break;
332 			case 4: // underscore we can't do
333 				break;
334 			case 5: // blink
335 				console->attr |= 0x80; // set the blink bit
336 				break;
337 			case 7: // reverse
338 				console->reverse_attr = true;
339 				console->attr = ((console->attr & BMASK) >> 4) | ((console->attr & FMASK) << 4);
340 				if (console->bright_attr)
341 					console->attr |= 0x08;
342 				break;
343 			case 8: // hidden?
344 				break;
345 
346 			/* foreground colors */
347 			case 30: console->attr = (console->attr & ~FMASK) | 0 | (console->bright_attr ? 0x08 : 0); break; // black
348 			case 31: console->attr = (console->attr & ~FMASK) | 4 | (console->bright_attr ? 0x08 : 0); break; // red
349 			case 32: console->attr = (console->attr & ~FMASK) | 2 | (console->bright_attr ? 0x08 : 0); break; // green
350 			case 33: console->attr = (console->attr & ~FMASK) | 6 | (console->bright_attr ? 0x08 : 0); break; // yellow
351 			case 34: console->attr = (console->attr & ~FMASK) | 1 | (console->bright_attr ? 0x08 : 0); break; // blue
352 			case 35: console->attr = (console->attr & ~FMASK) | 5 | (console->bright_attr ? 0x08 : 0); break; // magenta
353 			case 36: console->attr = (console->attr & ~FMASK) | 3 | (console->bright_attr ? 0x08 : 0); break; // cyan
354 			case 37: console->attr = (console->attr & ~FMASK) | 7 | (console->bright_attr ? 0x08 : 0); break; // white
355 
356 			/* background colors */
357 			case 40: console->attr = (console->attr & ~BMASK) | (0 << 4); break; // black
358 			case 41: console->attr = (console->attr & ~BMASK) | (4 << 4); break; // red
359 			case 42: console->attr = (console->attr & ~BMASK) | (2 << 4); break; // green
360 			case 43: console->attr = (console->attr & ~BMASK) | (6 << 4); break; // yellow
361 			case 44: console->attr = (console->attr & ~BMASK) | (1 << 4); break; // blue
362 			case 45: console->attr = (console->attr & ~BMASK) | (5 << 4); break; // magenta
363 			case 46: console->attr = (console->attr & ~BMASK) | (3 << 4); break; // cyan
364 			case 47: console->attr = (console->attr & ~BMASK) | (7 << 4); break; // white
365 		}
366 	}
367 }
368 
369 
370 static bool
371 process_vt100_command(struct console_desc *console, const char c,
372 	bool seenBracket, int32 *args, int32 argCount)
373 {
374 	bool ret = true;
375 
376 	//dprintf("process_vt100_command: c '%c', argCount %ld, arg[0] %ld, arg[1] %ld, seenBracket %d\n",
377 	//	c, argCount, args[0], args[1], seenBracket);
378 
379 	if (seenBracket) {
380 		switch(c) {
381 			case 'H': /* set cursor position */
382 			case 'f': {
383 				int32 row = argCount > 0 ? args[0] : 1;
384 				int32 col = argCount > 1 ? args[1] : 1;
385 				if (row > 0)
386 					row--;
387 				if (col > 0)
388 					col--;
389 				gotoxy(console, col, row);
390 				break;
391 			}
392 			case 'A': { /* move up */
393 				int32 deltay = argCount > 0 ? -args[0] : -1;
394 				if (deltay == 0)
395 					deltay = -1;
396 				gotoxy(console, console->x, console->y + deltay);
397 				break;
398 			}
399 			case 'e':
400 			case 'B': { /* move down */
401 				int32 deltay = argCount > 0 ? args[0] : 1;
402 				if (deltay == 0)
403 					deltay = 1;
404 				gotoxy(console, console->x, console->y + deltay);
405 				break;
406 			}
407 			case 'D': { /* move left */
408 				int32 deltax = argCount > 0 ? -args[0] : -1;
409 				if (deltax == 0)
410 					deltax = -1;
411 				gotoxy(console, console->x + deltax, console->y);
412 				break;
413 			}
414 			case 'a':
415 			case 'C': { /* move right */
416 				int32 deltax = argCount > 0 ? args[0] : 1;
417 				if (deltax == 0)
418 					deltax = 1;
419 				gotoxy(console, console->x + deltax, console->y);
420 				break;
421 			}
422 			case '`':
423 			case 'G': { /* set X position */
424 				int32 newx = argCount > 0 ? args[0] : 1;
425 				if (newx > 0)
426 					newx--;
427 				gotoxy(console, newx, console->y);
428 				break;
429 			}
430 			case 'd': { /* set y position */
431 				int32 newy = argCount > 0 ? args[0] : 1;
432 				if (newy > 0)
433 					newy--;
434 				gotoxy(console, console->x, newy);
435 				break;
436 			}
437 			case 's': /* save current cursor */
438 				save_cur(console, false);
439 				break;
440 			case 'u': /* restore cursor */
441 				restore_cur(console, false);
442 				break;
443 			case 'r': { /* set scroll region */
444 				int32 low = argCount > 0 ? args[0] : 1;
445 				int32 high = argCount > 1 ? args[1] : console->lines;
446 				if (low <= high)
447 					set_scroll_region(console, low - 1, high - 1);
448 				break;
449 			}
450 			case 'L': { /* scroll virtual down at cursor */
451 				int32 lines = argCount > 0 ? args[0] : 1;
452 				while (lines > 0) {
453 					scrdown(console);
454 					lines--;
455 				}
456 				break;
457 			}
458 			case 'M': { /* scroll virtual up at cursor */
459 				int32 lines = argCount > 0 ? args[0] : 1;
460 				while (lines > 0) {
461 					scrup(console);
462 					lines--;
463 				}
464 				break;
465 			}
466 			case 'K':
467 				if (argCount == 0 || args[0] == 0) {
468 					// erase to end of line
469 					erase_line(console, LINE_ERASE_RIGHT);
470 				} else if (argCount > 0) {
471 					if (args[0] == 1)
472 						erase_line(console, LINE_ERASE_LEFT);
473 					else if (args[0] == 2)
474 						erase_line(console, LINE_ERASE_WHOLE);
475 				}
476 				break;
477 			case 'J':
478 				if (argCount == 0 || args[0] == 0) {
479 					// erase to end of screen
480 					erase_screen(console, SCREEN_ERASE_DOWN);
481 				} else {
482 					if (args[0] == 1)
483 						erase_screen(console, SCREEN_ERASE_UP);
484 					else if (args[0] == 2)
485 						erase_screen(console, SCREEN_ERASE_WHOLE);
486 				}
487 				break;
488 			case 'm':
489 				if (argCount >= 0)
490 					set_vt100_attributes(console, args, argCount);
491 				break;
492 			default:
493 				ret = false;
494 		}
495 	} else {
496 		switch (c) {
497 			case 'c':
498 				reset_console(console);
499 				break;
500 			case 'D':
501 				rlf(console);
502 				break;
503 			case 'M':
504 				lf(console);
505 				break;
506 			case '7':
507 				save_cur(console, true);
508 				break;
509 			case '8':
510 				restore_cur(console, true);
511 				break;
512 			default:
513 				ret = false;
514 		}
515 	}
516 
517 	return ret;
518 }
519 
520 
521 static ssize_t
522 _console_write(struct console_desc *console, const void *buf, size_t len)
523 {
524 	const char *c;
525 	size_t pos = 0;
526 
527 	while (pos < len) {
528 		c = &((const char *)buf)[pos++];
529 
530 		switch (console->state) {
531 			case CONSOLE_STATE_NORMAL:
532 				// just output the stuff
533 				switch (*c) {
534 					case '\n':
535 						lf(console);
536 						break;
537 					case '\r':
538 						cr(console);
539 						break;
540 					case 0x8: // backspace
541 						del(console);
542 						break;
543 					case '\t':
544 						tab(console);
545 						break;
546 					case '\a':
547 						// beep
548 						dprintf("<BEEP>\n");
549 						break;
550 					case '\0':
551 						break;
552 					case 0x1b:
553 						// escape character
554 						console->arg_count = -1;
555 						console->state = CONSOLE_STATE_GOT_ESCAPE;
556 						break;
557 					default:
558 						console_putch(console, *c);
559 				}
560 				break;
561 			case CONSOLE_STATE_GOT_ESCAPE:
562 				// look for either commands with no argument, or the '[' character
563 				switch (*c) {
564 					case '[':
565 						console->state = CONSOLE_STATE_SEEN_BRACKET;
566 						break;
567 					default:
568 						console->args[console->arg_count] = 0;
569 						process_vt100_command(console, *c, false, console->args, console->arg_count + 1);
570 						console->state = CONSOLE_STATE_NORMAL;
571 				}
572 				break;
573 			case CONSOLE_STATE_SEEN_BRACKET:
574 				switch (*c) {
575 					case '0'...'9':
576 						console->arg_count = 0;
577 						console->args[console->arg_count] = *c - '0';
578 						console->state = CONSOLE_STATE_PARSING_ARG;
579 						break;
580 					case '?':
581 						// private DEC mode parameter follows - we ignore those anyway
582 						// ToDo: check if it was really used in combination with a mode command
583 						break;
584 					default:
585 						process_vt100_command(console, *c, true, console->args, console->arg_count + 1);
586 						console->state = CONSOLE_STATE_NORMAL;
587 				}
588 				break;
589 			case CONSOLE_STATE_NEW_ARG:
590 				switch (*c) {
591 					case '0'...'9':
592 						console->arg_count++;
593 						if (console->arg_count == MAX_ARGS) {
594 							console->state = CONSOLE_STATE_NORMAL;
595 							break;
596 						}
597 						console->args[console->arg_count] = *c - '0';
598 						console->state = CONSOLE_STATE_PARSING_ARG;
599 						break;
600 					default:
601 						process_vt100_command(console, *c, true, console->args, console->arg_count + 1);
602 						console->state = CONSOLE_STATE_NORMAL;
603 				}
604 				break;
605 			case CONSOLE_STATE_PARSING_ARG:
606 				// parse args
607 				switch (*c) {
608 					case '0'...'9':
609 						console->args[console->arg_count] *= 10;
610 						console->args[console->arg_count] += *c - '0';
611 						break;
612 					case ';':
613 						console->state = CONSOLE_STATE_NEW_ARG;
614 						break;
615 					default:
616 						process_vt100_command(console, *c, true, console->args, console->arg_count + 1);
617 						console->state = CONSOLE_STATE_NORMAL;
618 				}
619 		}
620 	}
621 
622 	return pos;
623 }
624 
625 
626 //	#pragma mark -
627 
628 
629 static status_t
630 console_open(const char *name, uint32 flags, void **cookie)
631 {
632 	*cookie = &sConsole;
633 
634 	status_t status = get_module(sConsole.module_name, (module_info **)&sConsole.module);
635 	if (status == B_OK)
636 		sConsole.module->clear(0x0f);
637 
638 	return status;
639 }
640 
641 
642 static status_t
643 console_freecookie(void *cookie)
644 {
645 	if (sConsole.module != NULL) {
646 		put_module(sConsole.module_name);
647 		sConsole.module = NULL;
648 	}
649 
650 	return B_OK;
651 }
652 
653 
654 static status_t
655 console_close(void *cookie)
656 {
657 //	dprintf("console_close: entry\n");
658 
659 	return 0;
660 }
661 
662 
663 static status_t
664 console_read(void *cookie, off_t pos, void *buffer, size_t *_length)
665 {
666 	return B_NOT_ALLOWED;
667 }
668 
669 
670 static status_t
671 console_write(void *cookie, off_t pos, const void *buffer, size_t *_length)
672 {
673 	struct console_desc *console = (struct console_desc *)cookie;
674 	ssize_t written;
675 
676 #if 0
677 {
678 	const char *input = (const char *)buffer;
679 	dprintf("console_write (%lu bytes): \"", *_length);
680 	for (uint32 i = 0; i < *_length; i++) {
681 		if (input[i] < ' ')
682 			dprintf("(%d:0x%x)", input[i], input[i]);
683 		else
684 			dprintf("%c", input[i]);
685 	}
686 	dprintf("\"\n");
687 }
688 #endif
689 
690 	mutex_lock(&console->lock);
691 
692 	update_cursor(console, -1, -1); // hide it
693 	written = _console_write(console, buffer, *_length);
694 	update_cursor(console, console->x, console->y);
695 
696 	mutex_unlock(&console->lock);
697 
698 	if (written >= 0) {
699 		*_length = written;
700 		return B_OK;
701 	}
702 	return written;
703 }
704 
705 
706 static status_t
707 console_ioctl(void *cookie, uint32 op, void *buffer, size_t length)
708 {
709 	struct console_desc *console = (struct console_desc *)cookie;
710 
711 	if (op == TIOCGWINSZ) {
712 		struct winsize size;
713 		size.ws_xpixel = size.ws_col = console->columns;
714 		size.ws_ypixel = size.ws_row = console->lines;
715 
716 		return user_memcpy(buffer, &size, sizeof(struct winsize));
717 	}
718 
719 	return B_BAD_VALUE;
720 }
721 
722 
723 //	#pragma mark -
724 
725 
726 status_t
727 init_hardware(void)
728 {
729 	// iterate through the list of console modules until we find one that accepts the job
730 	void *cookie = open_module_list("console");
731 	if (cookie == NULL)
732 		return B_ERROR;
733 
734 	bool found = false;
735 
736 	char buffer[B_FILE_NAME_LENGTH];
737 	size_t bufferSize = sizeof(buffer);
738 
739 	while (read_next_module_name(cookie, buffer, &bufferSize) == B_OK) {
740 		dprintf("con_init: trying module %s\n", buffer);
741 		if (get_module(buffer, (module_info **)&sConsole.module) == B_OK) {
742 			strlcpy(sConsole.module_name, buffer, sizeof(sConsole.module_name));
743 			put_module(buffer);
744 			found = true;
745 			break;
746 		}
747 
748 		bufferSize = sizeof(buffer);
749 	}
750 
751 	if (found) {
752 		// set up the console structure
753 		mutex_init(&sConsole.lock, "console lock");
754 		sConsole.module->get_size(&sConsole.columns, &sConsole.lines);
755 
756 		reset_console(&sConsole);
757 		gotoxy(&sConsole, 0, 0);
758 		save_cur(&sConsole, true);
759 	}
760 
761 	close_module_list(cookie);
762 	return found ? B_OK : B_ERROR;
763 }
764 
765 
766 const char **
767 publish_devices(void)
768 {
769 	static const char *devices[] = {
770 		DEVICE_NAME,
771 		NULL
772 	};
773 
774 	return devices;
775 }
776 
777 
778 device_hooks *
779 find_device(const char *name)
780 {
781 	static device_hooks hooks = {
782 		&console_open,
783 		&console_close,
784 		&console_freecookie,
785 		&console_ioctl,
786 		&console_read,
787 		&console_write,
788 		/* Leave select/deselect/readv/writev undefined. The kernel will
789 		 * use its own default implementation. The basic hooks above this
790 		 * line MUST be defined, however. */
791 		NULL,
792 		NULL,
793 		NULL,
794 		NULL
795 	};
796 
797 	if (!strcmp(name, DEVICE_NAME))
798 		return &hooks;
799 
800 	return NULL;
801 }
802 
803 
804 status_t
805 init_driver(void)
806 {
807 	return B_OK;
808 }
809 
810 
811 void
812 uninit_driver(void)
813 {
814 }
815 
816