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