xref: /aosp_15_r20/external/coreboot/src/lib/cbmem_console.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #include <console/cbmem_console.h>
4 #include <console/console.h>
5 #include <console/uart.h>
6 #include <cbmem.h>
7 #include <symbols.h>
8 #include <types.h>
9 
10 /*
11  * Structure describing console buffer. It is overlaid on a flat memory area,
12  * with body covering the extent of the memory. Once the buffer is full,
13  * output will wrap back around to the start of the buffer. The high bit of the
14  * cursor field gets set to indicate that this happened. If the underlying
15  * storage allows this, the buffer will persist across multiple boots and append
16  * to the previous log.
17  *
18  * NOTE: These are known implementations accessing this console that need to be
19  * updated in case of structure/API changes:
20  *
21  * cbmem:	[coreboot]/src/util/cbmem/cbmem.c
22  * libpayload:	[coreboot]/payloads/libpayload/drivers/cbmem_console.c
23  * coreinfo:	[coreboot]/payloads/coreinfo/bootlog_module.c
24  * Linux:	drivers/firmware/google/memconsole-coreboot.c
25  * SeaBIOS:	src/firmware/coreboot.c
26  * GRUB:	grub-core/term/i386/coreboot/cbmemc.c
27  */
28 struct cbmem_console {
29 	u32 size;
30 	u32 cursor;
31 	u8  body[];
32 }  __packed;
33 
34 #define MAX_SIZE (1 << 28)	/* can't be changed without breaking readers! */
35 #define CURSOR_MASK (MAX_SIZE - 1)	/* bits 31-28 are reserved for flags */
36 #define OVERFLOW (1UL << 31)		/* set if in ring-buffer mode */
37 _Static_assert(CONFIG_CONSOLE_CBMEM_BUFFER_SIZE <= MAX_SIZE,
38 	"cbmem_console format cannot support buffers larger than 256MB!");
39 
40 static struct cbmem_console *current_console;
41 
42 static bool console_paused;
43 
44 /*
45  * While running from ROM, before DRAM is initialized, some area in cache as
46  * RAM space is used for the console buffer storage. The size and location of
47  * the area are defined by the linker script with _(e)preram_cbmem_console.
48  *
49  * When running from RAM, some console output is generated before CBMEM is
50  * reinitialized. This static buffer is used to store that output temporarily,
51  * to be concatenated with the CBMEM console buffer contents accumulated
52  * during the ROM stage, once CBMEM becomes available at RAM stage.
53  */
54 
55 #define STATIC_CONSOLE_SIZE 1024
56 static u8 static_console[STATIC_CONSOLE_SIZE];
57 
buffer_valid(struct cbmem_console * cbm_cons_p,u32 total_space)58 static int buffer_valid(struct cbmem_console *cbm_cons_p, u32 total_space)
59 {
60 	return (cbm_cons_p->cursor & CURSOR_MASK) < cbm_cons_p->size &&
61 	       cbm_cons_p->size <= MAX_SIZE &&
62 	       cbm_cons_p->size == total_space - sizeof(struct cbmem_console);
63 }
64 
init_console_ptr(void * storage,u32 total_space)65 static void init_console_ptr(void *storage, u32 total_space)
66 {
67 	struct cbmem_console *cbm_cons_p = storage;
68 
69 	if (!cbm_cons_p || total_space <= sizeof(struct cbmem_console)) {
70 		current_console = NULL;
71 		return;
72 	}
73 
74 	if (!buffer_valid(cbm_cons_p, total_space)) {
75 		cbm_cons_p->size = total_space - sizeof(struct cbmem_console);
76 		cbm_cons_p->cursor = 0;
77 	}
78 
79 	current_console = cbm_cons_p;
80 }
81 
cbmemc_init(void)82 void cbmemc_init(void)
83 {
84 	if (ENV_ROMSTAGE_OR_BEFORE) {
85 		/* Pre-RAM environments use special buffer placed by linker script. */
86 		init_console_ptr(_preram_cbmem_console, REGION_SIZE(preram_cbmem_console));
87 	} else if (ENV_SMM) {
88 		void *cbmemc = NULL;
89 		size_t cbmemc_size = 0;
90 
91 		smm_get_cbmemc_buffer(&cbmemc, &cbmemc_size);
92 
93 		init_console_ptr(cbmemc, cbmemc_size);
94 	} else {
95 		/* Post-RAM uses static (BSS) buffer before CBMEM is reinitialized. */
96 		init_console_ptr(static_console, sizeof(static_console));
97 	}
98 }
99 
cbmemc_tx_byte(unsigned char data)100 void cbmemc_tx_byte(unsigned char data)
101 {
102 	if (!current_console || !current_console->size || console_paused)
103 		return;
104 
105 	u32 flags = current_console->cursor & ~CURSOR_MASK;
106 	u32 cursor = current_console->cursor & CURSOR_MASK;
107 
108 	current_console->body[cursor++] = data;
109 	if (cursor >= current_console->size) {
110 		cursor = 0;
111 		flags |= OVERFLOW;
112 	}
113 
114 	current_console->cursor = flags | cursor;
115 }
116 
117 /*
118  * Copy the current console buffer (either from the cache as RAM area or from
119  * the static buffer, pointed at by src_cons_p) into the newly initialized CBMEM
120  * console. The use of cbmemc_tx_byte() ensures that all special cases for the
121  * target console (e.g. overflow) will be handled. If there had been an
122  * overflow in the source console, log a message to that effect.
123  */
copy_console_buffer(struct cbmem_console * src_cons_p)124 static void copy_console_buffer(struct cbmem_console *src_cons_p)
125 {
126 	u32 c;
127 
128 	if (!src_cons_p)
129 		return;
130 
131 	if (src_cons_p->cursor & OVERFLOW) {
132 		const char overflow_warning[] = "\n*** Pre-CBMEM " ENV_STRING
133 			" console overflowed, log truncated! ***\n";
134 		for (c = 0; c < sizeof(overflow_warning) - 1; c++)
135 			cbmemc_tx_byte(overflow_warning[c]);
136 		for (c = src_cons_p->cursor & CURSOR_MASK;
137 		     c < src_cons_p->size; c++)
138 			cbmemc_tx_byte(src_cons_p->body[c]);
139 	}
140 
141 	for (c = 0; c < (src_cons_p->cursor & CURSOR_MASK); c++)
142 		cbmemc_tx_byte(src_cons_p->body[c]);
143 
144 	/* Invalidate the source console, so it will be reinitialized on the
145 	   next reboot. Otherwise, we might copy the same bytes again. */
146 	src_cons_p->size = 0;
147 }
148 
cbmemc_copy_in(void * buffer,size_t size)149 void cbmemc_copy_in(void *buffer, size_t size)
150 {
151 	struct cbmem_console *previous = (void *)buffer;
152 
153 	if (!buffer_valid(previous, size))
154 		return;
155 
156 	copy_console_buffer(previous);
157 }
158 
cbmemc_reinit(int is_recovery)159 static void cbmemc_reinit(int is_recovery)
160 {
161 	const size_t size = CONFIG_CONSOLE_CBMEM_BUFFER_SIZE;
162 	/* If CBMEM entry already existed, old contents are not altered. */
163 	struct cbmem_console *cbmem_cons_p = cbmem_add(CBMEM_ID_CONSOLE, size);
164 	struct cbmem_console *previous_cons_p = current_console;
165 
166 	init_console_ptr(cbmem_cons_p, size);
167 	copy_console_buffer(previous_cons_p);
168 }
169 
170 /* Run this hook early so that the console region is one of the earliest created, and
171    therefore more likely to stay in the same place even across different boot modes where some
172    other regions may sometimes not get created (e.g. RW_MCACHE in vboot recovery mode). */
173 CBMEM_READY_HOOK_EARLY(cbmemc_reinit);
174 
175 #if CONFIG(CONSOLE_CBMEM_DUMP_TO_UART)
cbmem_dump_console_to_uart(void)176 void cbmem_dump_console_to_uart(void)
177 {
178 	u32 cursor;
179 	unsigned int console_index;
180 
181 	if (!current_console)
182 		return;
183 
184 	console_index = get_uart_for_console();
185 
186 	uart_init(console_index);
187 	if (current_console->cursor & OVERFLOW) {
188 		for (cursor = current_console->cursor & CURSOR_MASK;
189 		     cursor < current_console->size; cursor++) {
190 			if (BIOS_LOG_IS_MARKER(current_console->body[cursor]))
191 				continue;
192 			if (current_console->body[cursor] == '\n')
193 				uart_tx_byte(console_index, '\r');
194 			uart_tx_byte(console_index, current_console->body[cursor]);
195 		}
196 	}
197 	for (cursor = 0; cursor < (current_console->cursor & CURSOR_MASK); cursor++) {
198 		if (BIOS_LOG_IS_MARKER(current_console->body[cursor]))
199 			continue;
200 		if (current_console->body[cursor] == '\n')
201 			uart_tx_byte(console_index, '\r');
202 		uart_tx_byte(console_index, current_console->body[cursor]);
203 	}
204 }
205 #endif
206 
cbmem_dump_console(void)207 void cbmem_dump_console(void)
208 {
209 	u32 cursor;
210 	if (!current_console)
211 		return;
212 
213 	console_paused = true;
214 
215 	if (current_console->cursor & OVERFLOW)
216 		for (cursor = current_console->cursor & CURSOR_MASK;
217 		     cursor < current_console->size; cursor++)
218 			if (!BIOS_LOG_IS_MARKER(current_console->body[cursor]))
219 				do_putchar(current_console->body[cursor]);
220 	for (cursor = 0; cursor < (current_console->cursor & CURSOR_MASK); cursor++)
221 		if (!BIOS_LOG_IS_MARKER(current_console->body[cursor]))
222 			do_putchar(current_console->body[cursor]);
223 
224 	console_paused = false;
225 }
226