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