/ payloads / coreinfo / bootlog_module.c
bootlog_module.c
  1  /* SPDX-License-Identifier: GPL-2.0-only */
  2  
  3  #include "coreinfo.h"
  4  
  5  #if CONFIG(MODULE_BOOTLOG)
  6  
  7  #define LINES_SHOWN 19
  8  #define TAB_WIDTH 2
  9  
 10  /* Globals that are used for tracking screen state */
 11  static char *g_buf = NULL;
 12  static s32 g_line = 0;
 13  static s32 g_lines_count = 0;
 14  static s32 g_max_cursor_line = 0;
 15  
 16  /* Copied from libpayload/drivers/cbmem_console.c */
 17  struct cbmem_console {
 18  	u32 size;
 19  	u32 cursor;
 20  	u8 body[];
 21  } __packed;
 22  
 23  #define CURSOR_MASK ((1 << 28) - 1)
 24  #define OVERFLOW (1 << 31)
 25  
 26  static u32 char_width(char c, u32 cursor, u32 screen_width)
 27  {
 28  	if (c == '\n') {
 29  		return screen_width - (cursor % screen_width);
 30  	} else if (c == '\t') {
 31  		return TAB_WIDTH;
 32  	} else if (isprint(c)) {
 33  		return 1;
 34  	}
 35  
 36  	return 0;
 37  }
 38  
 39  static u32 calculate_chars_count(char *str, u32 str_len, u32 screen_width, u32 screen_height)
 40  {
 41  	u32 i, count = 0;
 42  
 43  	for (i = 0; i < str_len; i++) {
 44  		count += char_width(str[i], count, screen_width);
 45  	}
 46  
 47  	/* Ensure that 'count' can occupy at least the whole screen */
 48  	if (count < screen_width * screen_height) {
 49  		count = screen_width * screen_height;
 50  	}
 51  
 52  	/* Pad to line end */
 53  	if (count % screen_width != 0) {
 54  		count += screen_width - (count % screen_width);
 55  	}
 56  
 57  	return count;
 58  }
 59  
 60  /*
 61   * This method takes an input buffer and sanitizes it for display, which means:
 62   *  - '\n' is converted to spaces until end of line
 63   *  - Tabs are converted to spaces of size TAB_WIDTH
 64   *  - Only printable characters are preserved
 65   */
 66  static int sanitize_buffer_for_display(char *str, u32 str_len, char *out, u32 out_len, u32 screen_width)
 67  {
 68  	u32 cursor = 0;
 69  	u32 i;
 70  
 71  	for (i = 0; i < str_len && cursor < out_len; i++) {
 72  		u32 width = char_width(str[i], cursor, screen_width);
 73  		if (width == 1) {
 74  			out[cursor++] = str[i];
 75  		} else if (width > 1) {
 76  			while (width-- && cursor < out_len) {
 77  				out[cursor++] = ' ';
 78  			}
 79  		}
 80  	}
 81  
 82  	/* Fill the rest of the out buffer with spaces */
 83  	while (cursor < out_len) {
 84  		out[cursor++] = ' ';
 85  	}
 86  
 87  	return 0;
 88  }
 89  
 90  static int bootlog_module_init(void)
 91  {
 92  	/* Make sure that lib_sysinfo is initialized */
 93  	int ret = lib_get_sysinfo();
 94  	if (ret) {
 95  		return -1;
 96  	}
 97  
 98  	struct cbmem_console *console = phys_to_virt(lib_sysinfo.cbmem_cons);
 99  	if (console == NULL) {
100  		return -1;
101  	}
102  	/* Extract console information */
103  	char *buffer = (char *)(&(console->body));
104  	u32 size = console->size;
105  	u32 cursor = console->cursor & CURSOR_MASK;
106  
107  	/* The cursor may be bigger than buffer size with older console code */
108  	if (cursor >= size) {
109  		cursor = size - 1;
110  	}
111  
112  	/* Calculate how much characters will be displayed on screen */
113  	u32 chars_count = calculate_chars_count(buffer, cursor, SCREEN_X, LINES_SHOWN);
114  	u32 overflow_chars_count = 0;
115  	if (console->cursor & OVERFLOW) {
116  		overflow_chars_count = calculate_chars_count(buffer + cursor,
117  			size - cursor, SCREEN_X, LINES_SHOWN);
118  	}
119  
120  	/* Sanity check, chars_count must be padded to full line */
121  	if (chars_count % SCREEN_X || overflow_chars_count % SCREEN_X) {
122  		return -2;
123  	}
124  
125  	g_lines_count = (chars_count + overflow_chars_count) / SCREEN_X;
126  	g_max_cursor_line = MAX(g_lines_count - 1 - LINES_SHOWN, 0);
127  
128  	g_buf = malloc(chars_count);
129  	if (!g_buf) {
130  		return -3;
131  	}
132  
133  	if (console->cursor & OVERFLOW) {
134  		if (sanitize_buffer_for_display(buffer + cursor, size - cursor,
135  				g_buf, overflow_chars_count, SCREEN_X) < 0) {
136  			goto err_free;
137  		}
138  	}
139  	if (sanitize_buffer_for_display(buffer, cursor,
140  					g_buf + overflow_chars_count,
141  					chars_count, SCREEN_X) < 0) {
142  		goto err_free;
143  	}
144  
145  	/* TODO: Maybe a _cleanup hook where we call free()? */
146  
147  	return 0;
148  
149  err_free:
150  	free(g_buf);
151  	g_buf = NULL;
152  	return -4;
153  }
154  
155  static int bootlog_module_redraw(WINDOW *win)
156  {
157  	print_module_title(win, "coreboot Bootlog");
158  
159  	if (!g_buf) {
160  		return -1;
161  	}
162  
163  	int x = 0, y = 0;
164  	char *tmp = g_buf + g_line * SCREEN_X;
165  
166  	for (y = 0; y < LINES_SHOWN; y++) {
167  		for (x = 0; x < SCREEN_X; x++) {
168  			mvwaddch(win, y + 2, x, *tmp);
169  			tmp++;
170  		}
171  
172  	}
173  
174  	return 0;
175  }
176  
177  static int bootlog_module_handle(int key)
178  {
179  	if (!g_buf) {
180  		return 0;
181  	}
182  
183  	switch (key) {
184  	case KEY_DOWN:
185  		g_line++;
186  		break;
187  	case KEY_UP:
188  		g_line--;
189  		break;
190  	case KEY_NPAGE: /* Page up */
191  		g_line -= LINES_SHOWN;
192  		break;
193  	case KEY_PPAGE: /* Page down */
194  		g_line += LINES_SHOWN;
195  		break;
196  	}
197  
198  	if (g_line < 0)
199  		g_line = 0;
200  
201  	if (g_line > g_max_cursor_line)
202  		g_line = g_max_cursor_line;
203  
204  	return 1;
205  }
206  
207  struct coreinfo_module bootlog_module = {
208  	.name = "Bootlog",
209  	.init = bootlog_module_init,
210  	.redraw = bootlog_module_redraw,
211  	.handle = bootlog_module_handle,
212  };
213  
214  #else
215  
216  struct coreinfo_module bootlog_module = {
217  };
218  
219  #endif