xref: /haiku/src/system/kernel/debug/blue_screen.cpp (revision 3dfd9cb95ce45f59160d50975210bc55e3fc0709)
1 /*
2  * Copyright 2005-2010, Axel Dörfler, axeld@pinc-software.de.
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 "blue_screen.h"
11 
12 #include <KernelExport.h>
13 #include <frame_buffer_console.h>
14 #include <console.h>
15 #include <debug.h>
16 #include <arch/debug_console.h>
17 #include <safemode.h>
18 
19 #include <string.h>
20 #include <stdio.h>
21 
22 #include "debug_commands.h"
23 
24 
25 #define USE_SCROLLING 0
26 #define NO_CLEAR 1
27 
28 #define MAX_ARGS 8
29 
30 #define FMASK 0x0f
31 #define BMASK 0x70
32 
33 typedef enum {
34 	CONSOLE_STATE_NORMAL = 0,
35 	CONSOLE_STATE_GOT_ESCAPE,
36 	CONSOLE_STATE_SEEN_BRACKET,
37 	CONSOLE_STATE_NEW_ARG,
38 	CONSOLE_STATE_PARSING_ARG,
39 } console_state;
40 
41 typedef enum {
42 	LINE_ERASE_WHOLE,
43 	LINE_ERASE_LEFT,
44 	LINE_ERASE_RIGHT
45 } erase_line_mode;
46 
47 struct screen_info {
48 	int32	columns;
49 	int32	rows;
50 	int32	x, y;
51 	uint8	attr;
52 	bool	bright_attr;
53 	bool	reverse_attr;
54 	int32	in_command_rows;
55 	bool	paging;
56 	bool	paging_timeout;
57 	bool	boot_debug_output;
58 	bool	ignore_output;
59 
60 	// state machine
61 	console_state state;
62 	int32	arg_count;
63 	int32	args[MAX_ARGS];
64 } sScreen;
65 
66 console_module_info *sModule;
67 
68 
69 static inline void
70 hide_cursor(void)
71 {
72 	sModule->move_cursor(-1, -1);
73 }
74 
75 
76 static inline void
77 update_cursor(int32 x, int32 y)
78 {
79 	sModule->move_cursor(x, y);
80 }
81 
82 
83 static inline void
84 move_cursor(int32 x, int32 y)
85 {
86 	sScreen.x = x;
87 	sScreen.y = y;
88 	update_cursor(x, y);
89 }
90 
91 
92 #if USE_SCROLLING
93 
94 /*!	Scroll from the cursor line up to the top of the scroll region up one
95 	line.
96 */
97 static void
98 scroll_up(void)
99 {
100 	// move the screen up one
101 	sModule->blit(0, 1, sScreen.columns, sScreen.rows - 1, 0, 0);
102 
103 	// clear the bottom line
104 	sModule->fill_glyph(0, 0, sScreen.columns, 1, ' ', sScreen.attr);
105 }
106 #endif
107 
108 
109 static void
110 next_line(void)
111 {
112 	bool abortCommand = false;
113 
114 #if USE_SCROLLING
115 	// TODO: scrolling is usually too slow; we could probably just remove it
116 	if (sScreen.y == sScreen.rows - 1)
117  		scroll_up();
118  	else
119  		sScreen.y++;
120 #else
121 	if (in_command_invocation())
122 		sScreen.in_command_rows++;
123 	else
124 		sScreen.in_command_rows = 0;
125 
126 	if (sScreen.paging && ((sScreen.in_command_rows > 0
127 			&& ((sScreen.in_command_rows + 3) % sScreen.rows) == 0)
128 		|| (sScreen.boot_debug_output && sScreen.y == sScreen.rows - 1))) {
129 		if (sScreen.paging_timeout)
130 			spin(1000 * 1000 * 3);
131 		else {
132 			// Use the paging mechanism: either, we're in the debugger, and a
133 			// command is being executed, or we're currently showing boot debug
134 			// output
135 			const char *text = in_command_invocation()
136 				? "Press key to continue, Q to quit, S to skip output"
137 				: "Press key to continue, S to skip output, P to disable paging";
138 			int32 length = strlen(text);
139 			if (sScreen.x + length > sScreen.columns) {
140 				// make sure we don't overwrite too much
141 				text = "P";
142 				length = 1;
143 			}
144 
145 			for (int32 i = 0; i < length; i++) {
146 				// yellow on black (or reverse, during boot)
147 				sModule->put_glyph(sScreen.columns - length + i, sScreen.y,
148 					text[i], sScreen.boot_debug_output ? 0x6f : 0xf6);
149 			}
150 
151 			char c = kgetc();
152 			if (c == 's') {
153 				sScreen.ignore_output = true;
154 			} else if (c == 'q' && in_command_invocation()) {
155 				abortCommand = true;
156 				sScreen.ignore_output = true;
157 			} else if (c == 'p' && !in_command_invocation())
158 				sScreen.paging = false;
159 			else if (c == 't' && !in_command_invocation())
160 				sScreen.paging_timeout = true;
161 
162 			// remove on screen text again
163 			sModule->fill_glyph(sScreen.columns - length, sScreen.y, length,
164 				1, ' ', sScreen.attr);
165 		}
166 
167 		if (sScreen.in_command_rows > 0)
168 			sScreen.in_command_rows += 2;
169 	}
170 	if (sScreen.y == sScreen.rows - 1) {
171 		sScreen.y = 0;
172 		sModule->fill_glyph(0, 0, sScreen.columns, 2, ' ', sScreen.attr);
173 	} else
174 		sScreen.y++;
175 #endif
176 
177 #if NO_CLEAR
178 	if (sScreen.y + 2 < sScreen.rows) {
179 		sModule->fill_glyph(0, (sScreen.y + 2) % sScreen.rows, sScreen.columns,
180 			1, ' ', sScreen.attr);
181 	}
182 #endif
183 	sScreen.x = 0;
184 
185 	if (abortCommand) {
186 		abort_debugger_command();
187 			// should not return
188 	}
189 }
190 
191 
192 static void
193 erase_line(erase_line_mode mode)
194 {
195 	switch (mode) {
196 		case LINE_ERASE_WHOLE:
197 			sModule->fill_glyph(0, sScreen.y, sScreen.columns, 1, ' ',
198 				sScreen.attr);
199 			break;
200 		case LINE_ERASE_LEFT:
201 			sModule->fill_glyph(0, sScreen.y, sScreen.x + 1, 1, ' ',
202 				sScreen.attr);
203 			break;
204 		case LINE_ERASE_RIGHT:
205 			sModule->fill_glyph(sScreen.x, sScreen.y, sScreen.columns
206 				- sScreen.x, 1, ' ', sScreen.attr);
207 			break;
208 	}
209 }
210 
211 
212 static void
213 back_space(void)
214 {
215 	if (sScreen.x <= 0)
216 		return;
217 
218 	sScreen.x--;
219 	sModule->put_glyph(sScreen.x, sScreen.y, ' ', sScreen.attr);
220 }
221 
222 
223 static void
224 put_character(char c)
225 {
226 	if (++sScreen.x >= sScreen.columns) {
227 		next_line();
228 		sScreen.x++;
229 	}
230 
231 	sModule->put_glyph(sScreen.x - 1, sScreen.y, c, sScreen.attr);
232 }
233 
234 
235 static void
236 set_vt100_attributes(int32 *args, int32 argCount)
237 {
238 	if (argCount == 0) {
239 		// that's the default (attributes off)
240 		argCount++;
241 		args[0] = 0;
242 	}
243 
244 	for (int32 i = 0; i < argCount; i++) {
245 		switch (args[i]) {
246 			case 0: // reset
247 				sScreen.attr = sScreen.boot_debug_output ? 0xf0 : 0x0f;
248 				sScreen.bright_attr = true;
249 				sScreen.reverse_attr = false;
250 				break;
251 			case 1: // bright
252 				sScreen.bright_attr = true;
253 				sScreen.attr |= 0x08; // set the bright bit
254 				break;
255 			case 2: // dim
256 				sScreen.bright_attr = false;
257 				sScreen.attr &= ~0x08; // unset the bright bit
258 				break;
259 			case 4: // underscore we can't do
260 				break;
261 			case 5: // blink
262 				sScreen.attr |= 0x80; // set the blink bit
263 				break;
264 			case 7: // reverse
265 				sScreen.reverse_attr = true;
266 				sScreen.attr = ((sScreen.attr & BMASK) >> 4)
267 					| ((sScreen.attr & FMASK) << 4);
268 				if (sScreen.bright_attr)
269 					sScreen.attr |= 0x08;
270 				break;
271 			case 8: // hidden?
272 				break;
273 
274 			/* foreground colors */
275 			case 30: // black
276 			case 31: // red
277 			case 32: // green
278 			case 33: // yellow
279 			case 34: // blue
280 			case 35: // magenta
281 			case 36: // cyan
282 			case 37: // white
283 			{
284 				const uint8 colors[] = {0, 4, 2, 6, 1, 5, 3, 7};
285 				sScreen.attr = (sScreen.attr & ~FMASK) | colors[args[i] - 30]
286 					| (sScreen.bright_attr ? 0x08 : 0);
287 				break;
288 			}
289 
290 			/* background colors */
291 			case 40: sScreen.attr = (sScreen.attr & ~BMASK) | (0 << 4); break; // black
292 			case 41: sScreen.attr = (sScreen.attr & ~BMASK) | (4 << 4); break; // red
293 			case 42: sScreen.attr = (sScreen.attr & ~BMASK) | (2 << 4); break; // green
294 			case 43: sScreen.attr = (sScreen.attr & ~BMASK) | (6 << 4); break; // yellow
295 			case 44: sScreen.attr = (sScreen.attr & ~BMASK) | (1 << 4); break; // blue
296 			case 45: sScreen.attr = (sScreen.attr & ~BMASK) | (5 << 4); break; // magenta
297 			case 46: sScreen.attr = (sScreen.attr & ~BMASK) | (3 << 4); break; // cyan
298 			case 47: sScreen.attr = (sScreen.attr & ~BMASK) | (7 << 4); break; // white
299 		}
300 	}
301 }
302 
303 
304 static bool
305 process_vt100_command(const char c, bool seenBracket, int32 *args,
306 	int32 argCount)
307 {
308 	bool ret = true;
309 
310 //	kprintf("process_vt100_command: c '%c', argCount %ld, arg[0] %ld, arg[1] %ld, seenBracket %d\n",
311 //		c, argCount, args[0], args[1], seenBracket);
312 
313 	if (seenBracket) {
314 		switch (c) {
315 			case 'H':	// set cursor position
316 			case 'f':
317 			{
318 				int32 row = argCount > 0 ? args[0] : 1;
319 				int32 col = argCount > 1 ? args[1] : 1;
320 				if (row > 0)
321 					row--;
322 				if (col > 0)
323 					col--;
324 				move_cursor(col, row);
325 				break;
326 			}
327 			case 'A':	// move up
328 			{
329 				int32 deltaY = argCount > 0 ? -args[0] : -1;
330 				if (deltaY == 0)
331 					deltaY = -1;
332 				move_cursor(sScreen.x, sScreen.y + deltaY);
333 				break;
334 			}
335 			case 'e':
336 			case 'B':	// move down
337 			{
338 				int32 deltaY = argCount > 0 ? args[0] : 1;
339 				if (deltaY == 0)
340 					deltaY = 1;
341 				move_cursor(sScreen.x, sScreen.y + deltaY);
342 				break;
343 			}
344 			case 'D':	// move left
345 			{
346 				int32 deltaX = argCount > 0 ? -args[0] : -1;
347 				if (deltaX == 0)
348 					deltaX = -1;
349 				move_cursor(sScreen.x + deltaX, sScreen.y);
350 				break;
351 			}
352 			case 'a':
353 			case 'C':	// move right
354 			{
355 				int32 deltaX = argCount > 0 ? args[0] : 1;
356 				if (deltaX == 0)
357 					deltaX = 1;
358 				move_cursor(sScreen.x + deltaX, sScreen.y);
359 				break;
360 			}
361 			case '`':
362 			case 'G':	// set X position
363 			{
364 				int32 newX = argCount > 0 ? args[0] : 1;
365 				if (newX > 0)
366 					newX--;
367 				move_cursor(newX, sScreen.y);
368 				break;
369 			}
370 			case 'd':	// set y position
371 			{
372 				int32 newY = argCount > 0 ? args[0] : 1;
373 				if (newY > 0)
374 					newY--;
375 				move_cursor(sScreen.x, newY);
376 				break;
377 			}
378 #if 0
379 			case 's':	// save current cursor
380 				save_cur(console, false);
381 				break;
382 			case 'u':	// restore cursor
383 				restore_cur(console, false);
384 				break;
385 			case 'r':	// set scroll region
386 			{
387 				int32 low = argCount > 0 ? args[0] : 1;
388 				int32 high = argCount > 1 ? args[1] : sScreen.lines;
389 				if (low <= high)
390 					set_scroll_region(console, low - 1, high - 1);
391 				break;
392 			}
393 			case 'L':	// scroll virtual down at cursor
394 			{
395 				int32 lines = argCount > 0 ? args[0] : 1;
396 				while (lines > 0) {
397 					scrdown(console);
398 					lines--;
399 				}
400 				break;
401 			}
402 			case 'M':	// scroll virtual up at cursor
403 			{
404 				int32 lines = argCount > 0 ? args[0] : 1;
405 				while (lines > 0) {
406 					scrup(console);
407 					lines--;
408 				}
409 				break;
410 			}
411 #endif
412 			case 'K':
413 				if (argCount == 0 || args[0] == 0) {
414 					// erase to end of line
415 					erase_line(LINE_ERASE_RIGHT);
416 				} else if (argCount > 0) {
417 					if (args[0] == 1)
418 						erase_line(LINE_ERASE_LEFT);
419 					else if (args[0] == 2)
420 						erase_line(LINE_ERASE_WHOLE);
421 				}
422 				break;
423 #if 0
424 			case 'J':
425 				if (argCount == 0 || args[0] == 0) {
426 					// erase to end of screen
427 					erase_screen(console, SCREEN_ERASE_DOWN);
428 				} else {
429 					if (args[0] == 1)
430 						erase_screen(console, SCREEN_ERASE_UP);
431 					else if (args[0] == 2)
432 						erase_screen(console, SCREEN_ERASE_WHOLE);
433 				}
434 				break;
435 #endif
436 			case 'm':
437 				if (argCount >= 0)
438 					set_vt100_attributes(args, argCount);
439 				break;
440 			default:
441 				ret = false;
442 		}
443 	} else {
444 		switch (c) {
445 #if 0
446 			case 'c':
447 				reset_console(console);
448 				break;
449 			case 'D':
450 				rlf(console);
451 				break;
452 			case 'M':
453 				lf(console);
454 				break;
455 			case '7':
456 				save_cur(console, true);
457 				break;
458 			case '8':
459 				restore_cur(console, true);
460 				break;
461 #endif
462 			default:
463 				ret = false;
464 		}
465 	}
466 
467 	return ret;
468 }
469 
470 
471 static void
472 parse_character(char c)
473 {
474 	switch (sScreen.state) {
475 		case CONSOLE_STATE_NORMAL:
476 			// just output the stuff
477 			switch (c) {
478 				case '\n':
479 					next_line();
480 					break;
481 				case 0x8:
482 					back_space();
483 					break;
484 				case '\t':
485 					// TODO: real tab...
486 					sScreen.x = (sScreen.x + 8) & ~7;
487 					if (sScreen.x >= sScreen.columns)
488 						next_line();
489 					break;
490 
491 				case '\r':
492 				case '\0':
493 				case '\a': // beep
494 					break;
495 
496 				case 0x1b:
497 					// escape character
498 					sScreen.arg_count = 0;
499 					sScreen.state = CONSOLE_STATE_GOT_ESCAPE;
500 					break;
501 				default:
502 					put_character(c);
503 			}
504 			break;
505 		case CONSOLE_STATE_GOT_ESCAPE:
506 			// look for either commands with no argument, or the '[' character
507 			switch (c) {
508 				case '[':
509 					sScreen.state = CONSOLE_STATE_SEEN_BRACKET;
510 					break;
511 				default:
512 					sScreen.args[0] = 0;
513 					process_vt100_command(c, false, sScreen.args, 0);
514 					sScreen.state = CONSOLE_STATE_NORMAL;
515 			}
516 			break;
517 		case CONSOLE_STATE_SEEN_BRACKET:
518 			switch (c) {
519 				case '0'...'9':
520 					sScreen.arg_count = 0;
521 					sScreen.args[0] = c - '0';
522 					sScreen.state = CONSOLE_STATE_PARSING_ARG;
523 					break;
524 				case '?':
525 					// private DEC mode parameter follows - we ignore those
526 					// anyway
527 					break;
528 				default:
529 					process_vt100_command(c, true, sScreen.args, 0);
530 					sScreen.state = CONSOLE_STATE_NORMAL;
531 					break;
532 			}
533 			break;
534 		case CONSOLE_STATE_NEW_ARG:
535 			switch (c) {
536 				case '0'...'9':
537 					if (++sScreen.arg_count == MAX_ARGS) {
538 						sScreen.state = CONSOLE_STATE_NORMAL;
539 						break;
540 					}
541 					sScreen.args[sScreen.arg_count] = c - '0';
542 					sScreen.state = CONSOLE_STATE_PARSING_ARG;
543 					break;
544 				default:
545 					process_vt100_command(c, true, sScreen.args,
546 						sScreen.arg_count + 1);
547 					sScreen.state = CONSOLE_STATE_NORMAL;
548 					break;
549 			}
550 			break;
551 		case CONSOLE_STATE_PARSING_ARG:
552 			// parse args
553 			switch (c) {
554 				case '0'...'9':
555 					sScreen.args[sScreen.arg_count] *= 10;
556 					sScreen.args[sScreen.arg_count] += c - '0';
557 					break;
558 				case ';':
559 					sScreen.state = CONSOLE_STATE_NEW_ARG;
560 					break;
561 				default:
562 					process_vt100_command(c, true, sScreen.args,
563 						sScreen.arg_count + 1);
564 					sScreen.state = CONSOLE_STATE_NORMAL;
565 					break;
566 			}
567 	}
568 }
569 
570 
571 static int
572 set_paging(int argc, char **argv)
573 {
574 	if (argc > 1 && !strcmp(argv[1], "--help")) {
575 		kprintf("usage: %s [on|off]\n", argv[0]);
576 		return 0;
577 	}
578 
579 	if (argc == 1)
580 		sScreen.paging = !sScreen.paging;
581 	else if (!strcmp(argv[1], "on"))
582 		sScreen.paging = true;
583 	else if (!strcmp(argv[1], "off"))
584 		sScreen.paging = false;
585 	else
586 		sScreen.paging = parse_expression(argv[1]) != 0;
587 
588 	kprintf("paging is turned %s now.\n", sScreen.paging ? "on" : "off");
589 	return 0;
590 }
591 
592 
593 //	#pragma mark -
594 
595 
596 status_t
597 blue_screen_init(void)
598 {
599 	extern console_module_info gFrameBufferConsoleModule;
600 
601 	// we can't use get_module() here, since it's too early in the boot process
602 
603 	if (!frame_buffer_console_available())
604 		return B_ERROR;
605 
606 	sModule = &gFrameBufferConsoleModule;
607 	sScreen.paging = !get_safemode_boolean(
608 		"disable_onscreen_paging", false);
609 	sScreen.paging_timeout = false;
610 
611 	add_debugger_command("paging", set_paging, "Enable or disable paging");
612 	return B_OK;
613 }
614 
615 
616 status_t
617 blue_screen_enter(bool debugOutput)
618 {
619 	sScreen.attr = debugOutput ? 0xf0 : 0x0f;
620 		// black on white for KDL, white on black for debug output
621 	sScreen.boot_debug_output = debugOutput;
622 	sScreen.ignore_output = false;
623 
624 	sScreen.x = sScreen.y = 0;
625 	sScreen.state = CONSOLE_STATE_NORMAL;
626 
627 	if (sModule == NULL)
628 		return B_NO_INIT;
629 
630 	sModule->get_size(&sScreen.columns, &sScreen.rows);
631 #if !NO_CLEAR
632 	sModule->clear(sScreen.attr);
633 #else
634 	sModule->fill_glyph(0, sScreen.y, sScreen.columns, 3, ' ', sScreen.attr);
635 #endif
636 	return B_OK;
637 }
638 
639 
640 bool
641 blue_screen_paging_enabled(void)
642 {
643 	return sScreen.paging;
644 }
645 
646 
647 void
648 blue_screen_set_paging(bool enabled)
649 {
650 	sScreen.paging = enabled;
651 }
652 
653 
654 void
655 blue_screen_clear_screen(void)
656 {
657 	sModule->clear(sScreen.attr);
658 	move_cursor(0, 0);
659 }
660 
661 
662 int
663 blue_screen_try_getchar(void)
664 {
665 	return arch_debug_blue_screen_try_getchar();
666 }
667 
668 
669 char
670 blue_screen_getchar(void)
671 {
672 	return arch_debug_blue_screen_getchar();
673 }
674 
675 
676 void
677 blue_screen_putchar(char c)
678 {
679 	if (sScreen.ignore_output
680 		&& (in_command_invocation() || sScreen.boot_debug_output))
681 		return;
682 
683 	sScreen.ignore_output = false;
684 	hide_cursor();
685 
686 	parse_character(c);
687 
688 	update_cursor(sScreen.x, sScreen.y);
689 }
690 
691 
692 void
693 blue_screen_puts(const char *text)
694 {
695 	if (sScreen.ignore_output
696 		&& (in_command_invocation() || sScreen.boot_debug_output))
697 		return;
698 
699 	sScreen.ignore_output = false;
700 	hide_cursor();
701 
702 	while (text[0] != '\0') {
703 		parse_character(text[0]);
704 		text++;
705 	}
706 
707 	update_cursor(sScreen.x, sScreen.y);
708 }
709