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