1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright © 2019 Collabora Ltd
15 struct persistent_ram_buffer {
22 #define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */
23 #define RAMOOPS_KERNMSG_HDR "===="
25 #define PSTORE_TYPE_DMESG 0
26 #define PSTORE_TYPE_CONSOLE 2
27 #define PSTORE_TYPE_FTRACE 3
28 #define PSTORE_TYPE_PMSG 7
29 #define PSTORE_TYPE_ALL 255
31 static phys_addr_t pstore_addr = CONFIG_CMD_PSTORE_MEM_ADDR;
32 static phys_size_t pstore_length = CONFIG_CMD_PSTORE_MEM_SIZE;
33 static unsigned int pstore_record_size = CONFIG_CMD_PSTORE_RECORD_SIZE;
34 static unsigned int pstore_console_size = CONFIG_CMD_PSTORE_CONSOLE_SIZE;
35 static unsigned int pstore_ftrace_size = CONFIG_CMD_PSTORE_FTRACE_SIZE;
36 static unsigned int pstore_pmsg_size = CONFIG_CMD_PSTORE_PMSG_SIZE;
37 static unsigned int pstore_ecc_size = CONFIG_CMD_PSTORE_ECC_SIZE;
38 static unsigned int buffer_size;
41 * pstore_read_kmsg_hdr() - Check kernel header and get compression flag if
43 * @buffer: Kernel messages buffer.
44 * @compressed: Returns TRUE if kernel buffer is compressed, else FALSE.
46 * Check if buffer starts with a kernel header of the form:
47 * ====<secs>.<nsecs>[-<compression>]\n
48 * If <compression> is equal to 'C' then the buffer is compressed, else iter
51 * Return: Length of kernel header.
53 static int pstore_read_kmsg_hdr(char *buffer, bool *compressed)
58 if (strncmp(RAMOOPS_KERNMSG_HDR, ptr, strlen(RAMOOPS_KERNMSG_HDR)) != 0)
61 ptr += strlen(RAMOOPS_KERNMSG_HDR);
63 ptr = strchr(ptr, '\n');
67 if (ptr[-2] == '-' && ptr[-1] == 'C')
70 return ptr - buffer + 1;
74 * pstore_get_buffer() - Get unwrapped record buffer
75 * @sig: Signature to check
76 * @buffer: Buffer containing wrapped record
77 * @size: wrapped record size
78 * @dest: Buffer used to store unwrapped record
80 * The record starts with <signature><start><size> header.
81 * The signature is 'DBGC' for all records except for Ftrace's record(s) wich
82 * use LINUX_VERSION_CODE ^ 'DBGC'.
83 * Use 0 for @sig to prevent checking signature.
84 * Start and size are 4 bytes long.
86 * Return: record's length
88 static u32 pstore_get_buffer(u32 sig, phys_addr_t buffer, u32 size, char *dest)
90 struct persistent_ram_buffer *prb =
91 (struct persistent_ram_buffer *)map_sysmem(buffer, size);
94 if (sig == 0 || prb->sig == sig) {
96 log_debug("found existing empty buffer\n");
100 if (prb->size > size) {
101 log_debug("found existing invalid buffer, size %u, start %u\n",
102 prb->size, prb->start);
106 log_debug("no valid data in buffer (sig = 0x%08x)\n", prb->sig);
110 log_debug("found existing buffer, size %u, start %u\n",
111 prb->size, prb->start);
113 memcpy(dest, &prb->data[prb->start], prb->size - prb->start);
114 memcpy(dest + prb->size - prb->start, &prb->data[0], prb->start);
116 dest_size = prb->size;
123 * pstore_init_buffer_size() - Init buffer size to largest record size
125 * Records, console, FTrace and user logs can use different buffer sizes.
126 * This function allows to retrieve the biggest one.
128 static void pstore_init_buffer_size(void)
130 if (pstore_record_size > buffer_size)
131 buffer_size = pstore_record_size;
133 if (pstore_console_size > buffer_size)
134 buffer_size = pstore_console_size;
136 if (pstore_ftrace_size > buffer_size)
137 buffer_size = pstore_ftrace_size;
139 if (pstore_pmsg_size > buffer_size)
140 buffer_size = pstore_pmsg_size;
144 * pstore_set() - Initialize PStore settings from command line arguments
145 * @cmdtp: Command data struct pointer
146 * @flag: Command flag
147 * @argc: Command-line argument count
148 * @argv: Array of command-line arguments
150 * Set pstore reserved memory info, starting at 'addr' for 'len' bytes.
151 * Default length for records is 4K.
152 * Mandatory arguments:
153 * - addr: ramoops starting address
154 * - len: ramoops total length
155 * Optional arguments:
156 * - record-size: size of one panic or oops record ('dump' type)
157 * - console-size: size of the kernel logs record
158 * - ftrace-size: size of the ftrace record(s), this can be a single record or
159 * divided in parts based on number of CPUs
160 * - pmsg-size: size of the user space logs record
161 * - ecc-size: enables/disables ECC support and specifies ECC buffer size in
162 * bytes (0 disables it, 1 is a special value, means 16 bytes ECC)
164 * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
167 static int pstore_set(struct cmd_tbl *cmdtp, int flag, int argc,
171 return CMD_RET_USAGE;
173 /* Address is specified since argc > 2
175 pstore_addr = hextoul(argv[1], NULL);
177 /* Length is specified since argc > 2
179 pstore_length = hextoul(argv[2], NULL);
182 pstore_record_size = hextoul(argv[3], NULL);
185 pstore_console_size = hextoul(argv[4], NULL);
188 pstore_ftrace_size = hextoul(argv[5], NULL);
191 pstore_pmsg_size = hextoul(argv[6], NULL);
194 pstore_ecc_size = hextoul(argv[7], NULL);
196 if (pstore_length < (pstore_record_size + pstore_console_size
197 + pstore_ftrace_size + pstore_pmsg_size)) {
198 printf("pstore <len> should be larger than the sum of all records sizes\n");
202 log_debug("pstore set done: start 0x%08llx - length 0x%llx\n",
203 (unsigned long long)pstore_addr,
204 (unsigned long long)pstore_length);
210 * pstore_print_buffer() - Print buffer
212 * @buffer: buffer to print
215 * Print buffer type and content
217 static void pstore_print_buffer(char *type, char *buffer, u32 size)
221 printf("**** %s\n", type);
222 while (i < size && buffer[i] != 0) {
229 * pstore_display() - Display existing records in pstore reserved memory
230 * @cmdtp: Command data struct pointer
231 * @flag: Command flag
232 * @argc: Command-line argument count
233 * @argv: Array of command-line arguments
235 * A 'record-type' can be given to only display records of this kind.
236 * If no 'record-type' is given, all valid records are dispayed.
237 * 'record-type' can be one of 'dump', 'console', 'ftrace' or 'user'. For 'dump'
238 * and 'ftrace' types, a 'nb' can be given to only display one record.
240 * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
243 static int pstore_display(struct cmd_tbl *cmdtp, int flag, int argc,
246 int type = PSTORE_TYPE_ALL;
254 if (!strcmp(argv[1], "dump"))
255 type = PSTORE_TYPE_DMESG;
256 else if (!strcmp(argv[1], "console"))
257 type = PSTORE_TYPE_CONSOLE;
258 else if (!strcmp(argv[1], "ftrace"))
259 type = PSTORE_TYPE_FTRACE;
260 else if (!strcmp(argv[1], "user"))
261 type = PSTORE_TYPE_PMSG;
263 return CMD_RET_USAGE;
266 if (pstore_length == 0) {
267 printf("Please set PStore configuration\n");
268 return CMD_RET_USAGE;
271 if (buffer_size == 0)
272 pstore_init_buffer_size();
274 buffer = malloc_cache_aligned(buffer_size);
276 if (type == PSTORE_TYPE_DMESG || type == PSTORE_TYPE_ALL) {
278 phys_addr_t ptr_end = ptr + pstore_length - pstore_pmsg_size
279 - pstore_ftrace_size - pstore_console_size;
282 ptr += dectoul(argv[2], NULL)
283 * pstore_record_size;
284 ptr_end = ptr + pstore_record_size;
287 while (ptr < ptr_end) {
288 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
289 pstore_record_size, buffer);
290 ptr += pstore_record_size;
295 header_len = pstore_read_kmsg_hdr(buffer, &compressed);
296 if (header_len == 0) {
297 log_debug("no valid kernel header\n");
302 printf("Compressed buffer, display not available\n");
306 pstore_print_buffer("Dump", buffer + header_len,
311 if (type == PSTORE_TYPE_CONSOLE || type == PSTORE_TYPE_ALL) {
312 ptr = pstore_addr + pstore_length - pstore_pmsg_size
313 - pstore_ftrace_size - pstore_console_size;
314 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
315 pstore_console_size, buffer);
317 pstore_print_buffer("Console", buffer, size);
320 if (type == PSTORE_TYPE_FTRACE || type == PSTORE_TYPE_ALL) {
321 ptr = pstore_addr + pstore_length - pstore_pmsg_size
322 - pstore_ftrace_size;
323 /* The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC'
324 * signature, pass 0 to pstore_get_buffer to prevent
327 size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer);
329 pstore_print_buffer("FTrace", buffer, size);
332 if (type == PSTORE_TYPE_PMSG || type == PSTORE_TYPE_ALL) {
333 ptr = pstore_addr + pstore_length - pstore_pmsg_size;
334 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
335 pstore_pmsg_size, buffer);
337 pstore_print_buffer("User", buffer, size);
346 * pstore_save() - Save existing records from pstore reserved memory
347 * @cmdtp: Command data struct pointer
348 * @flag: Command flag
349 * @argc: Command-line argument count
350 * @argv: Array of command-line arguments
352 * the records are saved under 'directory path', which should already exist,
353 * to partition 'part' on device type 'interface' instance 'dev'
354 * Filenames are automatically generated, depending on record type, like in
355 * /sys/fs/pstore under Linux
357 * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
360 static int pstore_save(struct cmd_tbl *cmdtp, int flag, int argc,
363 phys_addr_t ptr, ptr_end;
366 char addr[19], length[19];
374 return CMD_RET_USAGE;
376 if (pstore_length == 0) {
377 printf("Please set PStore configuration\n");
378 return CMD_RET_USAGE;
381 if (buffer_size == 0)
382 pstore_init_buffer_size();
384 buffer = malloc_cache_aligned(buffer_size);
385 sprintf(addr, "0x%p", buffer);
387 save_argv[0] = argv[0];
388 save_argv[1] = argv[1];
389 save_argv[2] = argv[2];
392 save_argv[5] = length;
394 /* Save all Dump records */
396 ptr_end = ptr + pstore_length - pstore_pmsg_size - pstore_ftrace_size
397 - pstore_console_size;
399 while (ptr < ptr_end) {
400 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
401 pstore_record_size, buffer);
402 ptr += pstore_record_size;
407 header_len = pstore_read_kmsg_hdr(buffer, &compressed);
408 if (header_len == 0) {
409 log_debug("no valid kernel header\n");
413 sprintf(addr, "0x%08lx", (ulong)map_to_sysmem(buffer + header_len));
414 sprintf(length, "0x%X", size - header_len);
415 sprintf(path, "%s/dmesg-ramoops-%u%s", argv[3], index,
416 compressed ? ".enc.z" : "");
417 do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
421 sprintf(addr, "0x%08lx", (ulong)map_to_sysmem(buffer));
423 /* Save Console record */
424 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_console_size,
427 sprintf(length, "0x%X", size);
428 sprintf(path, "%s/console-ramoops-0", argv[3]);
429 do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
431 ptr += pstore_console_size;
433 /* Save FTrace record(s)
434 * The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC' signature,
435 * pass 0 to pstore_get_buffer to prevent checking it
437 size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer);
439 sprintf(length, "0x%X", size);
440 sprintf(path, "%s/ftrace-ramoops-0", argv[3]);
441 do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
443 ptr += pstore_ftrace_size;
445 /* Save Console record */
446 size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_pmsg_size,
449 sprintf(length, "0x%X", size);
450 sprintf(path, "%s/pmsg-ramoops-0", argv[3]);
451 do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
459 static struct cmd_tbl cmd_pstore_sub[] = {
460 U_BOOT_CMD_MKENT(set, 8, 0, pstore_set, "", ""),
461 U_BOOT_CMD_MKENT(display, 3, 0, pstore_display, "", ""),
462 U_BOOT_CMD_MKENT(save, 4, 0, pstore_save, "", ""),
465 static int do_pstore(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
470 return CMD_RET_USAGE;
472 /* Strip off leading argument */
476 c = find_cmd_tbl(argv[0], cmd_pstore_sub, ARRAY_SIZE(cmd_pstore_sub));
479 return CMD_RET_USAGE;
481 return c->cmd(cmdtp, flag, argc, argv);
484 void fdt_fixup_pstore(void *blob)
487 int nodeoffset; /* node offset from libfdt */
489 nodeoffset = fdt_path_offset(blob, "/");
490 if (nodeoffset < 0) {
491 /* Not found or something else bad happened. */
492 log_err("fdt_path_offset() returned %s\n", fdt_strerror(nodeoffset));
496 nodeoffset = fdt_add_subnode(blob, nodeoffset, "reserved-memory");
497 if (nodeoffset < 0) {
498 log_err("Add 'reserved-memory' node failed: %s\n",
499 fdt_strerror(nodeoffset));
502 fdt_setprop_u32(blob, nodeoffset, "#address-cells", 2);
503 fdt_setprop_u32(blob, nodeoffset, "#size-cells", 2);
504 fdt_setprop_empty(blob, nodeoffset, "ranges");
506 sprintf(node, "ramoops@%llx", (unsigned long long)pstore_addr);
507 nodeoffset = fdt_add_subnode(blob, nodeoffset, node);
508 if (nodeoffset < 0) {
509 log_err("Add '%s' node failed: %s\n", node, fdt_strerror(nodeoffset));
512 fdt_setprop_string(blob, nodeoffset, "compatible", "ramoops");
513 fdt_setprop_u64(blob, nodeoffset, "reg", pstore_addr);
514 fdt_appendprop_u64(blob, nodeoffset, "reg", pstore_length);
515 fdt_setprop_u32(blob, nodeoffset, "record-size", pstore_record_size);
516 fdt_setprop_u32(blob, nodeoffset, "console-size", pstore_console_size);
517 fdt_setprop_u32(blob, nodeoffset, "ftrace-size", pstore_ftrace_size);
518 fdt_setprop_u32(blob, nodeoffset, "pmsg-size", pstore_pmsg_size);
519 fdt_setprop_u32(blob, nodeoffset, "ecc-size", pstore_ecc_size);
522 U_BOOT_CMD(pstore, 10, 0, do_pstore,
523 "Manage Linux Persistent Storage",
524 "set <addr> <len> [record-size] [console-size] [ftrace-size] [pmsg_size] [ecc-size]\n"
525 "- Set pstore reserved memory info, starting at 'addr' for 'len' bytes.\n"
526 " Default length for records is 4K.\n"
527 " 'record-size' is the size of one panic or oops record ('dump' type).\n"
528 " 'console-size' is the size of the kernel logs record.\n"
529 " 'ftrace-size' is the size of the ftrace record(s), this can be a single\n"
530 " record or divided in parts based on number of CPUs.\n"
531 " 'pmsg-size' is the size of the user space logs record.\n"
532 " 'ecc-size' enables/disables ECC support and specifies ECC buffer size in\n"
533 " bytes (0 disables it, 1 is a special value, means 16 bytes ECC).\n"
534 "pstore display [record-type] [nb]\n"
535 "- Display existing records in pstore reserved memory. A 'record-type' can\n"
536 " be given to only display records of this kind. 'record-type' can be one\n"
537 " of 'dump', 'console', 'ftrace' or 'user'. For 'dump' and 'ftrace' types,\n"
538 " a 'nb' can be given to only display one record.\n"
539 "pstore save <interface> <dev[:part]> <directory-path>\n"
540 "- Save existing records in pstore reserved memory under 'directory path'\n"
541 " to partition 'part' on device type 'interface' instance 'dev'.\n"
542 " Filenames are automatically generated, depending on record type, like\n"
543 " in /sys/fs/pstore under Linux.\n"
544 " The 'directory-path' should already exist.\n"