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