123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * Copyright © 2019 Collabora Ltd
- */
- #include <config.h>
- #include <command.h>
- #include <fs.h>
- #include <log.h>
- #include <mapmem.h>
- #include <memalign.h>
- #include <part.h>
- struct persistent_ram_buffer {
- u32 sig;
- u32 start;
- u32 size;
- u8 data[0];
- };
- #define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */
- #define RAMOOPS_KERNMSG_HDR "===="
- #define PSTORE_TYPE_DMESG 0
- #define PSTORE_TYPE_CONSOLE 2
- #define PSTORE_TYPE_FTRACE 3
- #define PSTORE_TYPE_PMSG 7
- #define PSTORE_TYPE_ALL 255
- static phys_addr_t pstore_addr = CONFIG_CMD_PSTORE_MEM_ADDR;
- static phys_size_t pstore_length = CONFIG_CMD_PSTORE_MEM_SIZE;
- static unsigned int pstore_record_size = CONFIG_CMD_PSTORE_RECORD_SIZE;
- static unsigned int pstore_console_size = CONFIG_CMD_PSTORE_CONSOLE_SIZE;
- static unsigned int pstore_ftrace_size = CONFIG_CMD_PSTORE_FTRACE_SIZE;
- static unsigned int pstore_pmsg_size = CONFIG_CMD_PSTORE_PMSG_SIZE;
- static unsigned int pstore_ecc_size = CONFIG_CMD_PSTORE_ECC_SIZE;
- static unsigned int buffer_size;
- /**
- * pstore_read_kmsg_hdr() - Check kernel header and get compression flag if
- * available.
- * @buffer: Kernel messages buffer.
- * @compressed: Returns TRUE if kernel buffer is compressed, else FALSE.
- *
- * Check if buffer starts with a kernel header of the form:
- * ====<secs>.<nsecs>[-<compression>]\n
- * If <compression> is equal to 'C' then the buffer is compressed, else iter
- * should be 'D'.
- *
- * Return: Length of kernel header.
- */
- static int pstore_read_kmsg_hdr(char *buffer, bool *compressed)
- {
- char *ptr = buffer;
- *compressed = false;
- if (strncmp(RAMOOPS_KERNMSG_HDR, ptr, strlen(RAMOOPS_KERNMSG_HDR)) != 0)
- return 0;
- ptr += strlen(RAMOOPS_KERNMSG_HDR);
- ptr = strchr(ptr, '\n');
- if (!ptr)
- return 0;
- if (ptr[-2] == '-' && ptr[-1] == 'C')
- *compressed = true;
- return ptr - buffer + 1;
- }
- /**
- * pstore_get_buffer() - Get unwrapped record buffer
- * @sig: Signature to check
- * @buffer: Buffer containing wrapped record
- * @size: wrapped record size
- * @dest: Buffer used to store unwrapped record
- *
- * The record starts with <signature><start><size> header.
- * The signature is 'DBGC' for all records except for Ftrace's record(s) wich
- * use LINUX_VERSION_CODE ^ 'DBGC'.
- * Use 0 for @sig to prevent checking signature.
- * Start and size are 4 bytes long.
- *
- * Return: record's length
- */
- static u32 pstore_get_buffer(u32 sig, phys_addr_t buffer, u32 size, char *dest)
- {
- struct persistent_ram_buffer *prb =
- (struct persistent_ram_buffer *)map_sysmem(buffer, size);
- u32 dest_size;
- if (sig == 0 || prb->sig == sig) {
- if (prb->size == 0) {
- log_debug("found existing empty buffer\n");
- return 0;
- }
- if (prb->size > size) {
- log_debug("found existing invalid buffer, size %u, start %u\n",
- prb->size, prb->start);
- return 0;
- }
- } else {
- log_debug("no valid data in buffer (sig = 0x%08x)\n", prb->sig);
- return 0;
- }
- log_debug("found existing buffer, size %u, start %u\n",
- prb->size, prb->start);
- memcpy(dest, &prb->data[prb->start], prb->size - prb->start);
- memcpy(dest + prb->size - prb->start, &prb->data[0], prb->start);
- dest_size = prb->size;
- unmap_sysmem(prb);
- return dest_size;
- }
- /**
- * pstore_init_buffer_size() - Init buffer size to largest record size
- *
- * Records, console, FTrace and user logs can use different buffer sizes.
- * This function allows to retrieve the biggest one.
- */
- static void pstore_init_buffer_size(void)
- {
- if (pstore_record_size > buffer_size)
- buffer_size = pstore_record_size;
- if (pstore_console_size > buffer_size)
- buffer_size = pstore_console_size;
- if (pstore_ftrace_size > buffer_size)
- buffer_size = pstore_ftrace_size;
- if (pstore_pmsg_size > buffer_size)
- buffer_size = pstore_pmsg_size;
- }
- /**
- * pstore_set() - Initialize PStore settings from command line arguments
- * @cmdtp: Command data struct pointer
- * @flag: Command flag
- * @argc: Command-line argument count
- * @argv: Array of command-line arguments
- *
- * Set pstore reserved memory info, starting at 'addr' for 'len' bytes.
- * Default length for records is 4K.
- * Mandatory arguments:
- * - addr: ramoops starting address
- * - len: ramoops total length
- * Optional arguments:
- * - record-size: size of one panic or oops record ('dump' type)
- * - console-size: size of the kernel logs record
- * - ftrace-size: size of the ftrace record(s), this can be a single record or
- * divided in parts based on number of CPUs
- * - pmsg-size: size of the user space logs record
- * - ecc-size: enables/disables ECC support and specifies ECC buffer size in
- * bytes (0 disables it, 1 is a special value, means 16 bytes ECC)
- *
- * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
- * on error.
- */
- static int pstore_set(struct cmd_tbl *cmdtp, int flag, int argc,
- char * const argv[])
- {
- if (argc < 3)
- return CMD_RET_USAGE;
- /* Address is specified since argc > 2
- */
- pstore_addr = simple_strtoul(argv[1], NULL, 16);
- /* Length is specified since argc > 2
- */
- pstore_length = simple_strtoul(argv[2], NULL, 16);
- if (argc > 3)
- pstore_record_size = simple_strtoul(argv[3], NULL, 16);
- if (argc > 4)
- pstore_console_size = simple_strtoul(argv[4], NULL, 16);
- if (argc > 5)
- pstore_ftrace_size = simple_strtoul(argv[5], NULL, 16);
- if (argc > 6)
- pstore_pmsg_size = simple_strtoul(argv[6], NULL, 16);
- if (argc > 7)
- pstore_ecc_size = simple_strtoul(argv[7], NULL, 16);
- if (pstore_length < (pstore_record_size + pstore_console_size
- + pstore_ftrace_size + pstore_pmsg_size)) {
- printf("pstore <len> should be larger than the sum of all records sizes\n");
- pstore_length = 0;
- }
- log_debug("pstore set done: start 0x%08llx - length 0x%llx\n",
- (unsigned long long)pstore_addr,
- (unsigned long long)pstore_length);
- return 0;
- }
- /**
- * pstore_print_buffer() - Print buffer
- * @type: buffer type
- * @buffer: buffer to print
- * @size: buffer size
- *
- * Print buffer type and content
- */
- static void pstore_print_buffer(char *type, char *buffer, u32 size)
- {
- u32 i = 0;
- printf("**** %s\n", type);
- while (i < size && buffer[i] != 0) {
- putc(buffer[i]);
- i++;
- }
- }
- /**
- * pstore_display() - Display existing records in pstore reserved memory
- * @cmdtp: Command data struct pointer
- * @flag: Command flag
- * @argc: Command-line argument count
- * @argv: Array of command-line arguments
- *
- * A 'record-type' can be given to only display records of this kind.
- * If no 'record-type' is given, all valid records are dispayed.
- * 'record-type' can be one of 'dump', 'console', 'ftrace' or 'user'. For 'dump'
- * and 'ftrace' types, a 'nb' can be given to only display one record.
- *
- * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
- * on error.
- */
- static int pstore_display(struct cmd_tbl *cmdtp, int flag, int argc,
- char * const argv[])
- {
- int type = PSTORE_TYPE_ALL;
- phys_addr_t ptr;
- char *buffer;
- u32 size;
- int header_len = 0;
- bool compressed;
- if (argc > 1) {
- if (!strcmp(argv[1], "dump"))
- type = PSTORE_TYPE_DMESG;
- else if (!strcmp(argv[1], "console"))
- type = PSTORE_TYPE_CONSOLE;
- else if (!strcmp(argv[1], "ftrace"))
- type = PSTORE_TYPE_FTRACE;
- else if (!strcmp(argv[1], "user"))
- type = PSTORE_TYPE_PMSG;
- else
- return CMD_RET_USAGE;
- }
- if (pstore_length == 0) {
- printf("Please set PStore configuration\n");
- return CMD_RET_USAGE;
- }
- if (buffer_size == 0)
- pstore_init_buffer_size();
- buffer = malloc_cache_aligned(buffer_size);
- if (type == PSTORE_TYPE_DMESG || type == PSTORE_TYPE_ALL) {
- ptr = pstore_addr;
- phys_addr_t ptr_end = ptr + pstore_length - pstore_pmsg_size
- - pstore_ftrace_size - pstore_console_size;
- if (argc > 2) {
- ptr += simple_strtoul(argv[2], NULL, 10)
- * pstore_record_size;
- ptr_end = ptr + pstore_record_size;
- }
- while (ptr < ptr_end) {
- size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
- pstore_record_size, buffer);
- ptr += pstore_record_size;
- if (size == 0)
- continue;
- header_len = pstore_read_kmsg_hdr(buffer, &compressed);
- if (header_len == 0) {
- log_debug("no valid kernel header\n");
- continue;
- }
- if (compressed) {
- printf("Compressed buffer, display not available\n");
- continue;
- }
- pstore_print_buffer("Dump", buffer + header_len,
- size - header_len);
- }
- }
- if (type == PSTORE_TYPE_CONSOLE || type == PSTORE_TYPE_ALL) {
- ptr = pstore_addr + pstore_length - pstore_pmsg_size
- - pstore_ftrace_size - pstore_console_size;
- size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
- pstore_console_size, buffer);
- if (size != 0)
- pstore_print_buffer("Console", buffer, size);
- }
- if (type == PSTORE_TYPE_FTRACE || type == PSTORE_TYPE_ALL) {
- ptr = pstore_addr + pstore_length - pstore_pmsg_size
- - pstore_ftrace_size;
- /* The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC'
- * signature, pass 0 to pstore_get_buffer to prevent
- * checking it
- */
- size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer);
- if (size != 0)
- pstore_print_buffer("FTrace", buffer, size);
- }
- if (type == PSTORE_TYPE_PMSG || type == PSTORE_TYPE_ALL) {
- ptr = pstore_addr + pstore_length - pstore_pmsg_size;
- size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
- pstore_pmsg_size, buffer);
- if (size != 0)
- pstore_print_buffer("User", buffer, size);
- }
- free(buffer);
- return 0;
- }
- /**
- * pstore_save() - Save existing records from pstore reserved memory
- * @cmdtp: Command data struct pointer
- * @flag: Command flag
- * @argc: Command-line argument count
- * @argv: Array of command-line arguments
- *
- * the records are saved under 'directory path', which should already exist,
- * to partition 'part' on device type 'interface' instance 'dev'
- * Filenames are automatically generated, depending on record type, like in
- * /sys/fs/pstore under Linux
- *
- * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
- * on error.
- */
- static int pstore_save(struct cmd_tbl *cmdtp, int flag, int argc,
- char * const argv[])
- {
- phys_addr_t ptr, ptr_end;
- char *buffer;
- char *save_argv[6];
- char addr[19], length[19];
- char path[256];
- u32 size;
- unsigned int index;
- int header_len = 0;
- bool compressed;
- if (argc < 4)
- return CMD_RET_USAGE;
- if (pstore_length == 0) {
- printf("Please set PStore configuration\n");
- return CMD_RET_USAGE;
- }
- if (buffer_size == 0)
- pstore_init_buffer_size();
- buffer = malloc_cache_aligned(buffer_size);
- sprintf(addr, "0x%p", buffer);
- save_argv[0] = argv[0];
- save_argv[1] = argv[1];
- save_argv[2] = argv[2];
- save_argv[3] = addr;
- save_argv[4] = path;
- save_argv[5] = length;
- /* Save all Dump records */
- ptr = pstore_addr;
- ptr_end = ptr + pstore_length - pstore_pmsg_size - pstore_ftrace_size
- - pstore_console_size;
- index = 0;
- while (ptr < ptr_end) {
- size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
- pstore_record_size, buffer);
- ptr += pstore_record_size;
- if (size == 0)
- continue;
- header_len = pstore_read_kmsg_hdr(buffer, &compressed);
- if (header_len == 0) {
- log_debug("no valid kernel header\n");
- continue;
- }
- sprintf(addr, "0x%08lx", (ulong)map_to_sysmem(buffer + header_len));
- sprintf(length, "0x%X", size - header_len);
- sprintf(path, "%s/dmesg-ramoops-%u%s", argv[3], index,
- compressed ? ".enc.z" : "");
- do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
- index++;
- }
- sprintf(addr, "0x%08lx", (ulong)map_to_sysmem(buffer));
- /* Save Console record */
- size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_console_size,
- buffer);
- if (size != 0) {
- sprintf(length, "0x%X", size);
- sprintf(path, "%s/console-ramoops-0", argv[3]);
- do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
- }
- ptr += pstore_console_size;
- /* Save FTrace record(s)
- * The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC' signature,
- * pass 0 to pstore_get_buffer to prevent checking it
- */
- size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer);
- if (size != 0) {
- sprintf(length, "0x%X", size);
- sprintf(path, "%s/ftrace-ramoops-0", argv[3]);
- do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
- }
- ptr += pstore_ftrace_size;
- /* Save Console record */
- size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_pmsg_size,
- buffer);
- if (size != 0) {
- sprintf(length, "0x%X", size);
- sprintf(path, "%s/pmsg-ramoops-0", argv[3]);
- do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY);
- }
- free(buffer);
- return 0;
- }
- static struct cmd_tbl cmd_pstore_sub[] = {
- U_BOOT_CMD_MKENT(set, 8, 0, pstore_set, "", ""),
- U_BOOT_CMD_MKENT(display, 3, 0, pstore_display, "", ""),
- U_BOOT_CMD_MKENT(save, 4, 0, pstore_save, "", ""),
- };
- static int do_pstore(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
- {
- struct cmd_tbl *c;
- if (argc < 2)
- return CMD_RET_USAGE;
- /* Strip off leading argument */
- argc--;
- argv++;
- c = find_cmd_tbl(argv[0], cmd_pstore_sub, ARRAY_SIZE(cmd_pstore_sub));
- if (!c)
- return CMD_RET_USAGE;
- return c->cmd(cmdtp, flag, argc, argv);
- }
- void fdt_fixup_pstore(void *blob)
- {
- char node[32];
- int nodeoffset; /* node offset from libfdt */
- nodeoffset = fdt_path_offset(blob, "/");
- if (nodeoffset < 0) {
- /* Not found or something else bad happened. */
- log_err("fdt_path_offset() returned %s\n", fdt_strerror(nodeoffset));
- return;
- }
- nodeoffset = fdt_add_subnode(blob, nodeoffset, "reserved-memory");
- if (nodeoffset < 0) {
- log_err("Add 'reserved-memory' node failed: %s\n",
- fdt_strerror(nodeoffset));
- return;
- }
- fdt_setprop_u32(blob, nodeoffset, "#address-cells", 2);
- fdt_setprop_u32(blob, nodeoffset, "#size-cells", 2);
- fdt_setprop_empty(blob, nodeoffset, "ranges");
- sprintf(node, "ramoops@%llx", (unsigned long long)pstore_addr);
- nodeoffset = fdt_add_subnode(blob, nodeoffset, node);
- if (nodeoffset < 0) {
- log_err("Add '%s' node failed: %s\n", node, fdt_strerror(nodeoffset));
- return;
- }
- fdt_setprop_string(blob, nodeoffset, "compatible", "ramoops");
- fdt_setprop_u64(blob, nodeoffset, "reg", pstore_addr);
- fdt_appendprop_u64(blob, nodeoffset, "reg", pstore_length);
- fdt_setprop_u32(blob, nodeoffset, "record-size", pstore_record_size);
- fdt_setprop_u32(blob, nodeoffset, "console-size", pstore_console_size);
- fdt_setprop_u32(blob, nodeoffset, "ftrace-size", pstore_ftrace_size);
- fdt_setprop_u32(blob, nodeoffset, "pmsg-size", pstore_pmsg_size);
- fdt_setprop_u32(blob, nodeoffset, "ecc-size", pstore_ecc_size);
- }
- U_BOOT_CMD(pstore, 10, 0, do_pstore,
- "Manage Linux Persistent Storage",
- "set <addr> <len> [record-size] [console-size] [ftrace-size] [pmsg_size] [ecc-size]\n"
- "- Set pstore reserved memory info, starting at 'addr' for 'len' bytes.\n"
- " Default length for records is 4K.\n"
- " 'record-size' is the size of one panic or oops record ('dump' type).\n"
- " 'console-size' is the size of the kernel logs record.\n"
- " 'ftrace-size' is the size of the ftrace record(s), this can be a single\n"
- " record or divided in parts based on number of CPUs.\n"
- " 'pmsg-size' is the size of the user space logs record.\n"
- " 'ecc-size' enables/disables ECC support and specifies ECC buffer size in\n"
- " bytes (0 disables it, 1 is a special value, means 16 bytes ECC).\n"
- "pstore display [record-type] [nb]\n"
- "- Display existing records in pstore reserved memory. A 'record-type' can\n"
- " be given to only display records of this kind. 'record-type' can be one\n"
- " of 'dump', 'console', 'ftrace' or 'user'. For 'dump' and 'ftrace' types,\n"
- " a 'nb' can be given to only display one record.\n"
- "pstore save <interface> <dev[:part]> <directory-path>\n"
- "- Save existing records in pstore reserved memory under 'directory path'\n"
- " to partition 'part' on device type 'interface' instance 'dev'.\n"
- " Filenames are automatically generated, depending on record type, like\n"
- " in /sys/fs/pstore under Linux.\n"
- " The 'directory-path' should already exist.\n"
- );
|