]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
cmd: Add command to display or save Linux PStore dumps
authorFrédéric Danis <frederic.danis@collabora.com>
Fri, 20 Mar 2020 09:59:22 +0000 (10:59 +0100)
committerTom Rini <trini@konsulko.com>
Wed, 14 Oct 2020 15:16:19 +0000 (11:16 -0400)
This patch adds a new pstore command allowing to display or save ramoops
logs (oops, panic, console, ftrace and user) generated by a previous
kernel crash.
PStore parameters can be set in U-Boot configuration file, or at run-time
using "pstore set" command. Records size should be the same as the ones
used by kernel, and should be a power of 2.
This command allows:
- to display uncompressed logs
- to save compressed or uncompressed logs, compressed logs are saved as a
  compressed stream, it may need some work to be able to decompress it,
  e.g. adding a fake header:
  "printf "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00" |
  cat - dmesg-ramoops-0.enc.z | gzip -dc"
- ECC part is not used to check memory corruption
- only 1st FTrace log is displayed or saved

Signed-off-by: Frédéric Danis <frederic.danis@collabora.com>
[trini: Minor updates for current design, correct spacing in rST]
Signed-off-by: Tom Rini <trini@konsulko.com>
Cc: Heinrich Schuchardt <xypron.glpk@gmx.de>
Cc: Wolfgang Denk <wd@denx.de>
Cc: Heiko Schocher <hs@denx.de>
cmd/Kconfig
cmd/Makefile
cmd/pstore.c [new file with mode: 0644]
doc/index.rst
doc/pstore.rst [new file with mode: 0644]

index 4cb171790b34c994bd30dbbc68f6034978b98158..a3166e4f31428c5cf75fe90b3f34072a330d5cfa 100644 (file)
@@ -1751,6 +1751,77 @@ config CMD_QFW
          feature is to allow easy loading of files passed to qemu-system
          via -kernel / -initrd
 
+config CMD_PSTORE
+       bool "pstore"
+       help
+         This provides access to Linux PStore with Rammoops backend. The main
+         feature is to allow to display or save PStore records.
+
+         See doc/pstore.rst for more information.
+
+if CMD_PSTORE
+
+config CMD_PSTORE_MEM_ADDR
+       hex "Memory Address"
+       depends on CMD_PSTORE
+       help
+         Base addr used for PStore ramoops memory, should be identical to
+         ramoops.mem_address parameter used by kernel
+
+config CMD_PSTORE_MEM_SIZE
+       hex "Memory size"
+       depends on CMD_PSTORE
+       default "0x10000"
+       help
+         Size of PStore ramoops memory, should be identical to ramoops.mem_size
+         parameter used by kernel, a power of 2 and larger than the sum of the
+         record sizes
+
+config CMD_PSTORE_RECORD_SIZE
+       hex "Dump record size"
+       depends on CMD_PSTORE
+       default "0x1000"
+       help
+         Size of each dump done on oops/panic, should be identical to
+         ramoops.record_size parameter used by kernel and a power of 2
+         Must be non-zero
+
+config CMD_PSTORE_CONSOLE_SIZE
+       hex "Kernel console log size"
+       depends on CMD_PSTORE
+       default "0x1000"
+       help
+         Size of kernel console log, should be identical to
+         ramoops.console_size parameter used by kernel and a power of 2
+         Must be non-zero
+
+config CMD_PSTORE_FTRACE_SIZE
+       hex "FTrace log size"
+       depends on CMD_PSTORE
+       default "0x1000"
+       help
+         Size of ftrace log, should be identical to ramoops.ftrace_size
+         parameter used by kernel and a power of 2
+
+config CMD_PSTORE_PMSG_SIZE
+       hex "User space message log size"
+       depends on CMD_PSTORE
+       default "0x1000"
+       help
+         Size of user space message log, should be identical to
+         ramoops.pmsg_size parameter used by kernel and a power of 2
+
+config CMD_PSTORE_ECC_SIZE
+       int "ECC size"
+       depends on CMD_PSTORE
+       default "0"
+       help
+       if non-zero, the option enables ECC support and specifies ECC buffer
+       size in bytes (1 is a special value, means 16 bytes ECC), should be
+       identical to ramoops.ramoops_ecc parameter used by kernel
+
+endif
+
 source "cmd/mvebu/Kconfig"
 
 config CMD_TERMINAL
index 015b83764c68cf5390b89bb21674be038e986d1a..19a891633f67ed4a34038fc6c5c84e3c1f53cfb5 100644 (file)
@@ -116,6 +116,7 @@ obj-$(CONFIG_CMD_PCI) += pci.o
 endif
 obj-$(CONFIG_CMD_PINMUX) += pinmux.o
 obj-$(CONFIG_CMD_PMC) += pmc.o
+obj-$(CONFIG_CMD_PSTORE) += pstore.o
 obj-$(CONFIG_CMD_PXE) += pxe.o pxe_utils.o
 obj-$(CONFIG_CMD_WOL) += wol.o
 obj-$(CONFIG_CMD_QFW) += qfw.o
diff --git a/cmd/pstore.c b/cmd/pstore.c
new file mode 100644 (file)
index 0000000..530eb03
--- /dev/null
@@ -0,0 +1,506 @@
+// 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);
+}
+
+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"
+);
index fd9f10f28e4c6509101f6ee74296c25f3870254f..68a083b16ba8470142f10e34107d5a9a65ae00bb 100644 (file)
@@ -109,6 +109,13 @@ Android-specific features available in U-Boot.
 
    android/index
 
+Command line
+------------
+.. toctree::
+   :maxdepth: 2
+
+   pstore.rst
+
 Indices and tables
 ==================
 
diff --git a/doc/pstore.rst b/doc/pstore.rst
new file mode 100644 (file)
index 0000000..4fceb2d
--- /dev/null
@@ -0,0 +1,80 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+PStore command
+==============
+
+Design
+------
+
+Linux PStore and Ramoops modules (Linux config options PSTORE and PSTORE_RAM)
+allow to use memory to pass data from the dying breath of a crashing kernel to
+its successor. This command allows to read those records from U-Boot command
+line.
+
+Ramoops is an oops/panic logger that writes its logs to RAM before the system
+crashes. It works by logging oopses and panics in a circular buffer. Ramoops
+needs a system with persistent RAM so that the content of that area can survive
+after a restart.
+
+Ramoops uses a predefined memory area to store the dump.
+
+Ramoops parameters can be passed as kernel parameters or through Device Tree,
+i.e.::
+
+    ramoops.mem_address=0x30000000 ramoops.mem_size=0x100000 ramoops.record_size=0x2000 ramoops.console_size=0x2000 memmap=0x100000$0x30000000
+
+The same values should be set in U-Boot to be able to retrieve the records.
+This values can be set at build time in U-Boot configuration file, or at runtime.
+
+The PStore configuration parameters are:
+
+======================= ==========
+ Name                   Default
+======================= ==========
+CMD_PSTORE_MEM_ADDR
+CMD_PSTORE_MEM_SIZE     0x10000
+CMD_PSTORE_RECORD_SIZE  0x1000
+CMD_PSTORE_CONSOLE_SIZE 0x1000
+CMD_PSTORE_FTRACE_SIZE  0x1000
+CMD_PSTORE_PMSG_SIZE    0x1000
+CMD_PSTORE_ECC_SIZE     0
+======================= ==========
+
+Records sizes should be a power of 2.
+The memory size and the record/console size must be non-zero.
+
+Multiple 'dump' records can be stored in the memory reserved for PStore.
+The memory size has to be larger than the sum of the record sizes, i.e.::
+
+    MEM_SIZE >= RECORD_SIZE * n + CONSOLE_SIZE + FTRACE_SIZE + PMSG_SIZE
+
+Usage
+-----
+
+Generate kernel crash
+~~~~~~~~~~~~~~~~~~~~~
+
+For test purpose, you can generate a kernel crash by setting reboot timeout to
+10 seconds and trigger a panic::
+
+    $ sudo sh -c "echo 1 > /proc/sys/kernel/sysrq"
+    $ sudo sh -c "echo 10 > /proc/sys/kernel/panic"
+    $ sudo sh -c "echo c > /proc/sysrq-trigger"
+
+Retrieve logs in U-Boot
+~~~~~~~~~~~~~~~~~~~~~~~
+
+First of all, unless PStore parameters as been set during U-Boot configuration
+and match kernel ramoops parameters, it needs to be set using 'pstore set', e.g.::
+
+    => pstore set 0x30000000 0x100000 0x2000 0x2000
+
+Then all available dumps can be displayed
+using::
+
+    => pstore display
+
+Or saved to an existing directory in an Ext2 or Ext4 partition, e.g. on root
+directory of 1st partition of the 2nd MMC::
+
+    => pstore save mmc 1:1 /