blob: 9a8b38c7f26ef3aff169253b2c9df6474c25409c [file] [log] [blame]
Frédéric Danised2e8e42020-03-20 10:59:22 +01001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright © 2019 Collabora Ltd
4 */
5
6#include <config.h>
7#include <command.h>
8#include <fs.h>
9#include <log.h>
10#include <mapmem.h>
11#include <memalign.h>
12#include <part.h>
13
14struct persistent_ram_buffer {
15 u32 sig;
16 u32 start;
17 u32 size;
18 u8 data[0];
19};
20
21#define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */
22#define RAMOOPS_KERNMSG_HDR "===="
23
24#define PSTORE_TYPE_DMESG 0
25#define PSTORE_TYPE_CONSOLE 2
26#define PSTORE_TYPE_FTRACE 3
27#define PSTORE_TYPE_PMSG 7
28#define PSTORE_TYPE_ALL 255
29
30static phys_addr_t pstore_addr = CONFIG_CMD_PSTORE_MEM_ADDR;
31static phys_size_t pstore_length = CONFIG_CMD_PSTORE_MEM_SIZE;
32static unsigned int pstore_record_size = CONFIG_CMD_PSTORE_RECORD_SIZE;
33static unsigned int pstore_console_size = CONFIG_CMD_PSTORE_CONSOLE_SIZE;
34static unsigned int pstore_ftrace_size = CONFIG_CMD_PSTORE_FTRACE_SIZE;
35static unsigned int pstore_pmsg_size = CONFIG_CMD_PSTORE_PMSG_SIZE;
36static unsigned int pstore_ecc_size = CONFIG_CMD_PSTORE_ECC_SIZE;
37static unsigned int buffer_size;
38
39 /**
40 * pstore_read_kmsg_hdr() - Check kernel header and get compression flag if
41 * available.
42 * @buffer: Kernel messages buffer.
43 * @compressed: Returns TRUE if kernel buffer is compressed, else FALSE.
44 *
45 * Check if buffer starts with a kernel header of the form:
46 * ====<secs>.<nsecs>[-<compression>]\n
47 * If <compression> is equal to 'C' then the buffer is compressed, else iter
48 * should be 'D'.
49 *
50 * Return: Length of kernel header.
51 */
52static int pstore_read_kmsg_hdr(char *buffer, bool *compressed)
53{
54 char *ptr = buffer;
55 *compressed = false;
56
57 if (strncmp(RAMOOPS_KERNMSG_HDR, ptr, strlen(RAMOOPS_KERNMSG_HDR)) != 0)
58 return 0;
59
60 ptr += strlen(RAMOOPS_KERNMSG_HDR);
61
62 ptr = strchr(ptr, '\n');
63 if (!ptr)
64 return 0;
65
66 if (ptr[-2] == '-' && ptr[-1] == 'C')
67 *compressed = true;
68
69 return ptr - buffer + 1;
70}
71
72/**
73 * pstore_get_buffer() - Get unwrapped record buffer
74 * @sig: Signature to check
75 * @buffer: Buffer containing wrapped record
76 * @size: wrapped record size
77 * @dest: Buffer used to store unwrapped record
78 *
79 * The record starts with <signature><start><size> header.
80 * The signature is 'DBGC' for all records except for Ftrace's record(s) wich
81 * use LINUX_VERSION_CODE ^ 'DBGC'.
82 * Use 0 for @sig to prevent checking signature.
83 * Start and size are 4 bytes long.
84 *
85 * Return: record's length
86 */
87static u32 pstore_get_buffer(u32 sig, phys_addr_t buffer, u32 size, char *dest)
88{
89 struct persistent_ram_buffer *prb =
90 (struct persistent_ram_buffer *)map_sysmem(buffer, size);
91 u32 dest_size;
92
93 if (sig == 0 || prb->sig == sig) {
94 if (prb->size == 0) {
95 log_debug("found existing empty buffer\n");
96 return 0;
97 }
98
99 if (prb->size > size) {
100 log_debug("found existing invalid buffer, size %u, start %u\n",
101 prb->size, prb->start);
102 return 0;
103 }
104 } else {
105 log_debug("no valid data in buffer (sig = 0x%08x)\n", prb->sig);
106 return 0;
107 }
108
109 log_debug("found existing buffer, size %u, start %u\n",
110 prb->size, prb->start);
111
112 memcpy(dest, &prb->data[prb->start], prb->size - prb->start);
113 memcpy(dest + prb->size - prb->start, &prb->data[0], prb->start);
114
115 dest_size = prb->size;
116 unmap_sysmem(prb);
117
118 return dest_size;
119}
120
121/**
122 * pstore_init_buffer_size() - Init buffer size to largest record size
123 *
124 * Records, console, FTrace and user logs can use different buffer sizes.
125 * This function allows to retrieve the biggest one.
126 */
127static void pstore_init_buffer_size(void)
128{
129 if (pstore_record_size > buffer_size)
130 buffer_size = pstore_record_size;
131
132 if (pstore_console_size > buffer_size)
133 buffer_size = pstore_console_size;
134
135 if (pstore_ftrace_size > buffer_size)
136 buffer_size = pstore_ftrace_size;
137
138 if (pstore_pmsg_size > buffer_size)
139 buffer_size = pstore_pmsg_size;
140}
141
142/**
143 * pstore_set() - Initialize PStore settings from command line arguments
144 * @cmdtp: Command data struct pointer
145 * @flag: Command flag
146 * @argc: Command-line argument count
147 * @argv: Array of command-line arguments
148 *
149 * Set pstore reserved memory info, starting at 'addr' for 'len' bytes.
150 * Default length for records is 4K.
151 * Mandatory arguments:
152 * - addr: ramoops starting address
153 * - len: ramoops total length
154 * Optional arguments:
155 * - record-size: size of one panic or oops record ('dump' type)
156 * - console-size: size of the kernel logs record
157 * - ftrace-size: size of the ftrace record(s), this can be a single record or
158 * divided in parts based on number of CPUs
159 * - pmsg-size: size of the user space logs record
160 * - ecc-size: enables/disables ECC support and specifies ECC buffer size in
161 * bytes (0 disables it, 1 is a special value, means 16 bytes ECC)
162 *
163 * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
164 * on error.
165 */
166static int pstore_set(struct cmd_tbl *cmdtp, int flag, int argc,
167 char * const argv[])
168{
169 if (argc < 3)
170 return CMD_RET_USAGE;
171
172 /* Address is specified since argc > 2
173 */
174 pstore_addr = simple_strtoul(argv[1], NULL, 16);
175
176 /* Length is specified since argc > 2
177 */
178 pstore_length = simple_strtoul(argv[2], NULL, 16);
179
180 if (argc > 3)
181 pstore_record_size = simple_strtoul(argv[3], NULL, 16);
182
183 if (argc > 4)
184 pstore_console_size = simple_strtoul(argv[4], NULL, 16);
185
186 if (argc > 5)
187 pstore_ftrace_size = simple_strtoul(argv[5], NULL, 16);
188
189 if (argc > 6)
190 pstore_pmsg_size = simple_strtoul(argv[6], NULL, 16);
191
192 if (argc > 7)
193 pstore_ecc_size = simple_strtoul(argv[7], NULL, 16);
194
195 if (pstore_length < (pstore_record_size + pstore_console_size
196 + pstore_ftrace_size + pstore_pmsg_size)) {
197 printf("pstore <len> should be larger than the sum of all records sizes\n");
198 pstore_length = 0;
199 }
200
201 log_debug("pstore set done: start 0x%08llx - length 0x%llx\n",
202 (unsigned long long)pstore_addr,
203 (unsigned long long)pstore_length);
204
205 return 0;
206}
207
208/**
209 * pstore_print_buffer() - Print buffer
210 * @type: buffer type
211 * @buffer: buffer to print
212 * @size: buffer size
213 *
214 * Print buffer type and content
215 */
216static void pstore_print_buffer(char *type, char *buffer, u32 size)
217{
218 u32 i = 0;
219
220 printf("**** %s\n", type);
221 while (i < size && buffer[i] != 0) {
222 putc(buffer[i]);
223 i++;
224 }
225}
226
227/**
228 * pstore_display() - Display existing records in pstore reserved memory
229 * @cmdtp: Command data struct pointer
230 * @flag: Command flag
231 * @argc: Command-line argument count
232 * @argv: Array of command-line arguments
233 *
234 * A 'record-type' can be given to only display records of this kind.
235 * If no 'record-type' is given, all valid records are dispayed.
236 * 'record-type' can be one of 'dump', 'console', 'ftrace' or 'user'. For 'dump'
237 * and 'ftrace' types, a 'nb' can be given to only display one record.
238 *
239 * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
240 * on error.
241 */
242static int pstore_display(struct cmd_tbl *cmdtp, int flag, int argc,
243 char * const argv[])
244{
245 int type = PSTORE_TYPE_ALL;
246 phys_addr_t ptr;
247 char *buffer;
248 u32 size;
249 int header_len = 0;
250 bool compressed;
251
252 if (argc > 1) {
253 if (!strcmp(argv[1], "dump"))
254 type = PSTORE_TYPE_DMESG;
255 else if (!strcmp(argv[1], "console"))
256 type = PSTORE_TYPE_CONSOLE;
257 else if (!strcmp(argv[1], "ftrace"))
258 type = PSTORE_TYPE_FTRACE;
259 else if (!strcmp(argv[1], "user"))
260 type = PSTORE_TYPE_PMSG;
261 else
262 return CMD_RET_USAGE;
263 }
264
265 if (pstore_length == 0) {
266 printf("Please set PStore configuration\n");
267 return CMD_RET_USAGE;
268 }
269
270 if (buffer_size == 0)
271 pstore_init_buffer_size();
272
273 buffer = malloc_cache_aligned(buffer_size);
274
275 if (type == PSTORE_TYPE_DMESG || type == PSTORE_TYPE_ALL) {
276 ptr = pstore_addr;
277 phys_addr_t ptr_end = ptr + pstore_length - pstore_pmsg_size
278 - pstore_ftrace_size - pstore_console_size;
279
280 if (argc > 2) {
281 ptr += simple_strtoul(argv[2], NULL, 10)
282 * pstore_record_size;
283 ptr_end = ptr + pstore_record_size;
284 }
285
286 while (ptr < ptr_end) {
287 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
288 pstore_record_size, buffer);
289 ptr += pstore_record_size;
290
291 if (size == 0)
292 continue;
293
294 header_len = pstore_read_kmsg_hdr(buffer, &compressed);
295 if (header_len == 0) {
296 log_debug("no valid kernel header\n");
297 continue;
298 }
299
300 if (compressed) {
301 printf("Compressed buffer, display not available\n");
302 continue;
303 }
304
305 pstore_print_buffer("Dump", buffer + header_len,
306 size - header_len);
307 }
308 }
309
310 if (type == PSTORE_TYPE_CONSOLE || type == PSTORE_TYPE_ALL) {
311 ptr = pstore_addr + pstore_length - pstore_pmsg_size
312 - pstore_ftrace_size - pstore_console_size;
313 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
314 pstore_console_size, buffer);
315 if (size != 0)
316 pstore_print_buffer("Console", buffer, size);
317 }
318
319 if (type == PSTORE_TYPE_FTRACE || type == PSTORE_TYPE_ALL) {
320 ptr = pstore_addr + pstore_length - pstore_pmsg_size
321 - pstore_ftrace_size;
322 /* The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC'
323 * signature, pass 0 to pstore_get_buffer to prevent
324 * checking it
325 */
326 size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer);
327 if (size != 0)
328 pstore_print_buffer("FTrace", buffer, size);
329 }
330
331 if (type == PSTORE_TYPE_PMSG || type == PSTORE_TYPE_ALL) {
332 ptr = pstore_addr + pstore_length - pstore_pmsg_size;
333 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
334 pstore_pmsg_size, buffer);
335 if (size != 0)
336 pstore_print_buffer("User", buffer, size);
337 }
338
339 free(buffer);
340
341 return 0;
342}
343
344/**
345 * pstore_save() - Save existing records from pstore reserved memory
346 * @cmdtp: Command data struct pointer
347 * @flag: Command flag
348 * @argc: Command-line argument count
349 * @argv: Array of command-line arguments
350 *
351 * the records are saved under 'directory path', which should already exist,
352 * to partition 'part' on device type 'interface' instance 'dev'
353 * Filenames are automatically generated, depending on record type, like in
354 * /sys/fs/pstore under Linux
355 *
356 * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
357 * on error.
358 */
359static int pstore_save(struct cmd_tbl *cmdtp, int flag, int argc,
360 char * const argv[])
361{
362 phys_addr_t ptr, ptr_end;
363 char *buffer;
364 char *save_argv[6];
365 char addr[19], length[19];
366 char path[256];
367 u32 size;
368 unsigned int index;
369 int header_len = 0;
370 bool compressed;
371
372 if (argc < 4)
373 return CMD_RET_USAGE;
374
375 if (pstore_length == 0) {
376 printf("Please set PStore configuration\n");
377 return CMD_RET_USAGE;
378 }
379
380 if (buffer_size == 0)
381 pstore_init_buffer_size();
382
383 buffer = malloc_cache_aligned(buffer_size);
384 sprintf(addr, "0x%p", buffer);
385
386 save_argv[0] = argv[0];
387 save_argv[1] = argv[1];
388 save_argv[2] = argv[2];
389 save_argv[3] = addr;
390 save_argv[4] = path;
391 save_argv[5] = length;
392
393 /* Save all Dump records */
394 ptr = pstore_addr;
395 ptr_end = ptr + pstore_length - pstore_pmsg_size - pstore_ftrace_size
396 - pstore_console_size;
397 index = 0;
398 while (ptr < ptr_end) {
399 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
400 pstore_record_size, buffer);
401 ptr += pstore_record_size;
402
403 if (size == 0)
404 continue;
405
406 header_len = pstore_read_kmsg_hdr(buffer, &compressed);
407 if (header_len == 0) {
408 log_debug("no valid kernel header\n");
409 continue;
410 }
411
412 sprintf(addr, "0x%08lx", (ulong)map_to_sysmem(buffer + header_len));
413 sprintf(length, "0x%X", size - header_len);
414 sprintf(path, "%s/dmesg-ramoops-%u%s", argv[3], index,
415 compressed ? ".enc.z" : "");
416 do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
417 index++;
418 }
419
420 sprintf(addr, "0x%08lx", (ulong)map_to_sysmem(buffer));
421
422 /* Save Console record */
423 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_console_size,
424 buffer);
425 if (size != 0) {
426 sprintf(length, "0x%X", size);
427 sprintf(path, "%s/console-ramoops-0", argv[3]);
428 do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
429 }
430 ptr += pstore_console_size;
431
432 /* Save FTrace record(s)
433 * The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC' signature,
434 * pass 0 to pstore_get_buffer to prevent checking it
435 */
436 size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer);
437 if (size != 0) {
438 sprintf(length, "0x%X", size);
439 sprintf(path, "%s/ftrace-ramoops-0", argv[3]);
440 do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
441 }
442 ptr += pstore_ftrace_size;
443
444 /* Save Console record */
445 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_pmsg_size,
446 buffer);
447 if (size != 0) {
448 sprintf(length, "0x%X", size);
449 sprintf(path, "%s/pmsg-ramoops-0", argv[3]);
450 do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
451 }
452
453 free(buffer);
454
455 return 0;
456}
457
458static struct cmd_tbl cmd_pstore_sub[] = {
459 U_BOOT_CMD_MKENT(set, 8, 0, pstore_set, "", ""),
460 U_BOOT_CMD_MKENT(display, 3, 0, pstore_display, "", ""),
461 U_BOOT_CMD_MKENT(save, 4, 0, pstore_save, "", ""),
462};
463
464static int do_pstore(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
465{
466 struct cmd_tbl *c;
467
468 if (argc < 2)
469 return CMD_RET_USAGE;
470
471 /* Strip off leading argument */
472 argc--;
473 argv++;
474
475 c = find_cmd_tbl(argv[0], cmd_pstore_sub, ARRAY_SIZE(cmd_pstore_sub));
476
477 if (!c)
478 return CMD_RET_USAGE;
479
480 return c->cmd(cmdtp, flag, argc, argv);
481}
482
Frédéric Daniscdd6a6d2020-03-20 10:59:24 +0100483void fdt_fixup_pstore(void *blob)
484{
485 char node[32];
486 int nodeoffset; /* node offset from libfdt */
487
488 nodeoffset = fdt_path_offset(blob, "/");
489 if (nodeoffset < 0) {
490 /* Not found or something else bad happened. */
491 log_err("fdt_path_offset() returned %s\n", fdt_strerror(nodeoffset));
492 return;
493 }
494
495 nodeoffset = fdt_add_subnode(blob, nodeoffset, "reserved-memory");
496 if (nodeoffset < 0) {
497 log_err("Add 'reserved-memory' node failed: %s\n",
498 fdt_strerror(nodeoffset));
499 return;
500 }
501 fdt_setprop_u32(blob, nodeoffset, "#address-cells", 2);
502 fdt_setprop_u32(blob, nodeoffset, "#size-cells", 2);
503 fdt_setprop_empty(blob, nodeoffset, "ranges");
504
505 sprintf(node, "ramoops@%llx", (unsigned long long)pstore_addr);
506 nodeoffset = fdt_add_subnode(blob, nodeoffset, node);
507 if (nodeoffset < 0) {
508 log_err("Add '%s' node failed: %s\n", node, fdt_strerror(nodeoffset));
509 return;
510 }
511 fdt_setprop_string(blob, nodeoffset, "compatible", "ramoops");
512 fdt_setprop_u64(blob, nodeoffset, "reg", pstore_addr);
513 fdt_appendprop_u64(blob, nodeoffset, "reg", pstore_length);
514 fdt_setprop_u32(blob, nodeoffset, "record-size", pstore_record_size);
515 fdt_setprop_u32(blob, nodeoffset, "console-size", pstore_console_size);
516 fdt_setprop_u32(blob, nodeoffset, "ftrace-size", pstore_ftrace_size);
517 fdt_setprop_u32(blob, nodeoffset, "pmsg-size", pstore_pmsg_size);
518 fdt_setprop_u32(blob, nodeoffset, "ecc-size", pstore_ecc_size);
519}
520
Frédéric Danised2e8e42020-03-20 10:59:22 +0100521U_BOOT_CMD(pstore, 10, 0, do_pstore,
522 "Manage Linux Persistent Storage",
523 "set <addr> <len> [record-size] [console-size] [ftrace-size] [pmsg_size] [ecc-size]\n"
524 "- Set pstore reserved memory info, starting at 'addr' for 'len' bytes.\n"
525 " Default length for records is 4K.\n"
526 " 'record-size' is the size of one panic or oops record ('dump' type).\n"
527 " 'console-size' is the size of the kernel logs record.\n"
528 " 'ftrace-size' is the size of the ftrace record(s), this can be a single\n"
529 " record or divided in parts based on number of CPUs.\n"
530 " 'pmsg-size' is the size of the user space logs record.\n"
531 " 'ecc-size' enables/disables ECC support and specifies ECC buffer size in\n"
532 " bytes (0 disables it, 1 is a special value, means 16 bytes ECC).\n"
533 "pstore display [record-type] [nb]\n"
534 "- Display existing records in pstore reserved memory. A 'record-type' can\n"
535 " be given to only display records of this kind. 'record-type' can be one\n"
536 " of 'dump', 'console', 'ftrace' or 'user'. For 'dump' and 'ftrace' types,\n"
537 " a 'nb' can be given to only display one record.\n"
538 "pstore save <interface> <dev[:part]> <directory-path>\n"
539 "- Save existing records in pstore reserved memory under 'directory path'\n"
540 " to partition 'part' on device type 'interface' instance 'dev'.\n"
541 " Filenames are automatically generated, depending on record type, like\n"
542 " in /sys/fs/pstore under Linux.\n"
543 " The 'directory-path' should already exist.\n"
544);