| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #include "coreinfo.h" |
| |
| #if CONFIG(MODULE_BOOTLOG) |
| |
| #define LINES_SHOWN 19 |
| #define TAB_WIDTH 2 |
| |
| /* Globals that are used for tracking screen state */ |
| static char *g_buf = NULL; |
| static s32 g_line = 0; |
| static s32 g_lines_count = 0; |
| static s32 g_max_cursor_line = 0; |
| |
| /* Copied from libpayload/drivers/cbmem_console.c */ |
| struct cbmem_console { |
| u32 size; |
| u32 cursor; |
| u8 body[]; |
| } __packed; |
| |
| #define CURSOR_MASK ((1 << 28) - 1) |
| #define OVERFLOW (1 << 31) |
| |
| static u32 char_width(char c, u32 cursor, u32 screen_width) |
| { |
| if (c == '\n') { |
| return screen_width - (cursor % screen_width); |
| } else if (c == '\t') { |
| return TAB_WIDTH; |
| } else if (isprint(c)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static u32 calculate_chars_count(char *str, u32 str_len, u32 screen_width, u32 screen_height) |
| { |
| u32 i, count = 0; |
| |
| for (i = 0; i < str_len; i++) { |
| count += char_width(str[i], count, screen_width); |
| } |
| |
| /* Ensure that 'count' can occupy at least the whole screen */ |
| if (count < screen_width * screen_height) { |
| count = screen_width * screen_height; |
| } |
| |
| /* Pad to line end */ |
| if (count % screen_width != 0) { |
| count += screen_width - (count % screen_width); |
| } |
| |
| return count; |
| } |
| |
| /* |
| * This method takes an input buffer and sanitizes it for display, which means: |
| * - '\n' is converted to spaces until end of line |
| * - Tabs are converted to spaces of size TAB_WIDTH |
| * - Only printable characters are preserved |
| */ |
| static int sanitize_buffer_for_display(char *str, u32 str_len, char *out, u32 out_len, u32 screen_width) |
| { |
| u32 cursor = 0; |
| u32 i; |
| |
| for (i = 0; i < str_len && cursor < out_len; i++) { |
| u32 width = char_width(str[i], cursor, screen_width); |
| if (width == 1) { |
| out[cursor++] = str[i]; |
| } else if (width > 1) { |
| while (width-- && cursor < out_len) { |
| out[cursor++] = ' '; |
| } |
| } |
| } |
| |
| /* Fill the rest of the out buffer with spaces */ |
| while (cursor < out_len) { |
| out[cursor++] = ' '; |
| } |
| |
| return 0; |
| } |
| |
| static int bootlog_module_init(void) |
| { |
| /* Make sure that lib_sysinfo is initialized */ |
| int ret = lib_get_sysinfo(); |
| if (ret) { |
| return -1; |
| } |
| |
| struct cbmem_console *console = phys_to_virt(lib_sysinfo.cbmem_cons); |
| if (console == NULL) { |
| return -1; |
| } |
| /* Extract console information */ |
| char *buffer = (char *)(&(console->body)); |
| u32 size = console->size; |
| u32 cursor = console->cursor & CURSOR_MASK; |
| |
| /* The cursor may be bigger than buffer size with older console code */ |
| if (cursor >= size) { |
| cursor = size - 1; |
| } |
| |
| /* Calculate how much characters will be displayed on screen */ |
| u32 chars_count = calculate_chars_count(buffer, cursor, SCREEN_X, LINES_SHOWN); |
| u32 overflow_chars_count = 0; |
| if (console->cursor & OVERFLOW) { |
| overflow_chars_count = calculate_chars_count(buffer + cursor, |
| size - cursor, SCREEN_X, LINES_SHOWN); |
| } |
| |
| /* Sanity check, chars_count must be padded to full line */ |
| if (chars_count % SCREEN_X || overflow_chars_count % SCREEN_X) { |
| return -2; |
| } |
| |
| g_lines_count = (chars_count + overflow_chars_count) / SCREEN_X; |
| g_max_cursor_line = MAX(g_lines_count - 1 - LINES_SHOWN, 0); |
| |
| g_buf = malloc(chars_count); |
| if (!g_buf) { |
| return -3; |
| } |
| |
| if (console->cursor & OVERFLOW) { |
| if (sanitize_buffer_for_display(buffer + cursor, size - cursor, |
| g_buf, overflow_chars_count, SCREEN_X) < 0) { |
| goto err_free; |
| } |
| } |
| if (sanitize_buffer_for_display(buffer, cursor, |
| g_buf + overflow_chars_count, |
| chars_count, SCREEN_X) < 0) { |
| goto err_free; |
| } |
| |
| /* TODO: Maybe a _cleanup hook where we call free()? */ |
| |
| return 0; |
| |
| err_free: |
| free(g_buf); |
| g_buf = NULL; |
| return -4; |
| } |
| |
| static int bootlog_module_redraw(WINDOW *win) |
| { |
| print_module_title(win, "coreboot Bootlog"); |
| |
| if (!g_buf) { |
| return -1; |
| } |
| |
| int x = 0, y = 0; |
| char *tmp = g_buf + g_line * SCREEN_X; |
| |
| for (y = 0; y < LINES_SHOWN; y++) { |
| for (x = 0; x < SCREEN_X; x++) { |
| mvwaddch(win, y + 2, x, *tmp); |
| tmp++; |
| } |
| |
| } |
| |
| return 0; |
| } |
| |
| static int bootlog_module_handle(int key) |
| { |
| if (!g_buf) { |
| return 0; |
| } |
| |
| switch (key) { |
| case KEY_DOWN: |
| g_line++; |
| break; |
| case KEY_UP: |
| g_line--; |
| break; |
| case KEY_NPAGE: /* Page up */ |
| g_line -= LINES_SHOWN; |
| break; |
| case KEY_PPAGE: /* Page down */ |
| g_line += LINES_SHOWN; |
| break; |
| } |
| |
| if (g_line < 0) |
| g_line = 0; |
| |
| if (g_line > g_max_cursor_line) |
| g_line = g_max_cursor_line; |
| |
| return 1; |
| } |
| |
| struct coreinfo_module bootlog_module = { |
| .name = "Bootlog", |
| .init = bootlog_module_init, |
| .redraw = bootlog_module_redraw, |
| .handle = bootlog_module_handle, |
| }; |
| |
| #else |
| |
| struct coreinfo_module bootlog_module = { |
| }; |
| |
| #endif |