uboot/cmd/pstore.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright © 2019 Collabora Ltd
   4 */
   5
   6#include <config.h>
   7#include <command.h>
   8#include <fdtdec.h>
   9#include <fs.h>
  10#include <log.h>
  11#include <mapmem.h>
  12#include <memalign.h>
  13#include <part.h>
  14
  15struct persistent_ram_buffer {
  16        u32    sig;
  17        u32    start;
  18        u32    size;
  19        u8     data[0];
  20};
  21
  22#define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */
  23#define RAMOOPS_KERNMSG_HDR "===="
  24
  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
  30
  31static phys_addr_t pstore_addr = CONFIG_CMD_PSTORE_MEM_ADDR;
  32static phys_size_t pstore_length = CONFIG_CMD_PSTORE_MEM_SIZE;
  33static unsigned int pstore_record_size = CONFIG_CMD_PSTORE_RECORD_SIZE;
  34static unsigned int pstore_console_size = CONFIG_CMD_PSTORE_CONSOLE_SIZE;
  35static unsigned int pstore_ftrace_size = CONFIG_CMD_PSTORE_FTRACE_SIZE;
  36static unsigned int pstore_pmsg_size = CONFIG_CMD_PSTORE_PMSG_SIZE;
  37static unsigned int pstore_ecc_size = CONFIG_CMD_PSTORE_ECC_SIZE;
  38static unsigned int buffer_size;
  39
  40 /**
  41  * pstore_read_kmsg_hdr() - Check kernel header and get compression flag if
  42  *                          available.
  43  * @buffer: Kernel messages buffer.
  44  * @compressed: Returns TRUE if kernel buffer is compressed, else FALSE.
  45  *
  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
  49  * should be 'D'.
  50  *
  51  * Return: Length of kernel header.
  52  */
  53static int pstore_read_kmsg_hdr(char *buffer, bool *compressed)
  54{
  55        char *ptr = buffer;
  56        *compressed = false;
  57
  58        if (strncmp(RAMOOPS_KERNMSG_HDR, ptr, strlen(RAMOOPS_KERNMSG_HDR)) != 0)
  59                return 0;
  60
  61        ptr += strlen(RAMOOPS_KERNMSG_HDR);
  62
  63        ptr = strchr(ptr, '\n');
  64        if (!ptr)
  65                return 0;
  66
  67        if (ptr[-2] == '-' && ptr[-1] == 'C')
  68                *compressed = true;
  69
  70        return ptr - buffer + 1;
  71}
  72
  73/**
  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
  79 *
  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.
  85 *
  86 * Return: record's length
  87 */
  88static u32 pstore_get_buffer(u32 sig, phys_addr_t buffer, u32 size, char *dest)
  89{
  90        struct persistent_ram_buffer *prb =
  91                (struct persistent_ram_buffer *)map_sysmem(buffer, size);
  92        u32 dest_size;
  93
  94        if (sig == 0 || prb->sig == sig) {
  95                if (prb->size == 0) {
  96                        log_debug("found existing empty buffer\n");
  97                        return 0;
  98                }
  99
 100                if (prb->size > size) {
 101                        log_debug("found existing invalid buffer, size %u, start %u\n",
 102                                  prb->size, prb->start);
 103                        return 0;
 104                }
 105        } else {
 106                log_debug("no valid data in buffer (sig = 0x%08x)\n", prb->sig);
 107                return 0;
 108        }
 109
 110        log_debug("found existing buffer, size %u, start %u\n",
 111                  prb->size, prb->start);
 112
 113        memcpy(dest, &prb->data[prb->start], prb->size - prb->start);
 114        memcpy(dest + prb->size - prb->start, &prb->data[0], prb->start);
 115
 116        dest_size = prb->size;
 117        unmap_sysmem(prb);
 118
 119        return dest_size;
 120}
 121
 122/**
 123 * pstore_init_buffer_size() - Init buffer size to largest record size
 124 *
 125 * Records, console, FTrace and user logs can use different buffer sizes.
 126 * This function allows to retrieve the biggest one.
 127 */
 128static void pstore_init_buffer_size(void)
 129{
 130        if (pstore_record_size > buffer_size)
 131                buffer_size = pstore_record_size;
 132
 133        if (pstore_console_size > buffer_size)
 134                buffer_size = pstore_console_size;
 135
 136        if (pstore_ftrace_size > buffer_size)
 137                buffer_size = pstore_ftrace_size;
 138
 139        if (pstore_pmsg_size > buffer_size)
 140                buffer_size = pstore_pmsg_size;
 141}
 142
 143/**
 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
 149 *
 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)
 163 *
 164 * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
 165 * on error.
 166 */
 167static int pstore_set(struct cmd_tbl *cmdtp, int flag,  int argc,
 168                      char * const argv[])
 169{
 170        if (argc < 3)
 171                return CMD_RET_USAGE;
 172
 173        /* Address is specified since argc > 2
 174         */
 175        pstore_addr = hextoul(argv[1], NULL);
 176
 177        /* Length is specified since argc > 2
 178         */
 179        pstore_length = hextoul(argv[2], NULL);
 180
 181        if (argc > 3)
 182                pstore_record_size = hextoul(argv[3], NULL);
 183
 184        if (argc > 4)
 185                pstore_console_size = hextoul(argv[4], NULL);
 186
 187        if (argc > 5)
 188                pstore_ftrace_size = hextoul(argv[5], NULL);
 189
 190        if (argc > 6)
 191                pstore_pmsg_size = hextoul(argv[6], NULL);
 192
 193        if (argc > 7)
 194                pstore_ecc_size = hextoul(argv[7], NULL);
 195
 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");
 199                pstore_length = 0;
 200        }
 201
 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);
 205
 206        return 0;
 207}
 208
 209/**
 210 * pstore_print_buffer() - Print buffer
 211 * @type: buffer type
 212 * @buffer: buffer to print
 213 * @size: buffer size
 214 *
 215 * Print buffer type and content
 216 */
 217static void pstore_print_buffer(char *type, char *buffer, u32 size)
 218{
 219        u32 i = 0;
 220
 221        printf("**** %s\n", type);
 222        while (i < size && buffer[i] != 0) {
 223                putc(buffer[i]);
 224                i++;
 225        }
 226}
 227
 228/**
 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
 234 *
 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.
 239 *
 240 * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
 241 * on error.
 242 */
 243static int pstore_display(struct cmd_tbl *cmdtp, int flag,  int argc,
 244                          char * const argv[])
 245{
 246        int type = PSTORE_TYPE_ALL;
 247        phys_addr_t ptr;
 248        char *buffer;
 249        u32 size;
 250        int header_len = 0;
 251        bool compressed;
 252
 253        if (argc > 1) {
 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;
 262                else
 263                        return CMD_RET_USAGE;
 264        }
 265
 266        if (pstore_length == 0) {
 267                printf("Please set PStore configuration\n");
 268                return CMD_RET_USAGE;
 269        }
 270
 271        if (buffer_size == 0)
 272                pstore_init_buffer_size();
 273
 274        buffer = malloc_cache_aligned(buffer_size);
 275
 276        if (type == PSTORE_TYPE_DMESG || type == PSTORE_TYPE_ALL) {
 277                ptr = pstore_addr;
 278                phys_addr_t ptr_end = ptr + pstore_length - pstore_pmsg_size
 279                                - pstore_ftrace_size - pstore_console_size;
 280
 281                if (argc > 2) {
 282                        ptr += dectoul(argv[2], NULL)
 283                                * pstore_record_size;
 284                        ptr_end = ptr + pstore_record_size;
 285                }
 286
 287                while (ptr < ptr_end) {
 288                        size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
 289                                                 pstore_record_size, buffer);
 290                        ptr += pstore_record_size;
 291
 292                        if (size == 0)
 293                                continue;
 294
 295                        header_len = pstore_read_kmsg_hdr(buffer, &compressed);
 296                        if (header_len == 0) {
 297                                log_debug("no valid kernel header\n");
 298                                continue;
 299                        }
 300
 301                        if (compressed) {
 302                                printf("Compressed buffer, display not available\n");
 303                                continue;
 304                        }
 305
 306                        pstore_print_buffer("Dump", buffer + header_len,
 307                                            size - header_len);
 308                }
 309        }
 310
 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);
 316                if (size != 0)
 317                        pstore_print_buffer("Console", buffer, size);
 318        }
 319
 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
 325                 * checking it
 326                 */
 327                size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer);
 328                if (size != 0)
 329                        pstore_print_buffer("FTrace", buffer, size);
 330        }
 331
 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);
 336                if (size != 0)
 337                        pstore_print_buffer("User", buffer, size);
 338        }
 339
 340        free(buffer);
 341
 342        return 0;
 343}
 344
 345/**
 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
 351 *
 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
 356 *
 357 * Return: zero on success, CMD_RET_USAGE in case of misuse and negative
 358 * on error.
 359 */
 360static int pstore_save(struct cmd_tbl *cmdtp, int flag,  int argc,
 361                       char * const argv[])
 362{
 363        phys_addr_t ptr, ptr_end;
 364        char *buffer;
 365        char *save_argv[6];
 366        char addr[19], length[19];
 367        char path[256];
 368        u32 size;
 369        unsigned int index;
 370        int header_len = 0;
 371        bool compressed;
 372
 373        if (argc < 4)
 374                return CMD_RET_USAGE;
 375
 376        if (pstore_length == 0) {
 377                printf("Please set PStore configuration\n");
 378                return CMD_RET_USAGE;
 379        }
 380
 381        if (buffer_size == 0)
 382                pstore_init_buffer_size();
 383
 384        buffer = malloc_cache_aligned(buffer_size);
 385        sprintf(addr, "0x%p", buffer);
 386
 387        save_argv[0] = argv[0];
 388        save_argv[1] = argv[1];
 389        save_argv[2] = argv[2];
 390        save_argv[3] = addr;
 391        save_argv[4] = path;
 392        save_argv[5] = length;
 393
 394        /* Save all Dump records */
 395        ptr = pstore_addr;
 396        ptr_end = ptr + pstore_length - pstore_pmsg_size - pstore_ftrace_size
 397                                - pstore_console_size;
 398        index = 0;
 399        while (ptr < ptr_end) {
 400                size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr,
 401                                         pstore_record_size, buffer);
 402                ptr += pstore_record_size;
 403
 404                if (size == 0)
 405                        continue;
 406
 407                header_len = pstore_read_kmsg_hdr(buffer, &compressed);
 408                if (header_len == 0) {
 409                        log_debug("no valid kernel header\n");
 410                        continue;
 411                }
 412
 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);
 418                index++;
 419        }
 420
 421        sprintf(addr, "0x%08lx", (ulong)map_to_sysmem(buffer));
 422
 423        /* Save Console record */
 424        size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_console_size,
 425                                 buffer);
 426        if (size != 0) {
 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);
 430        }
 431        ptr += pstore_console_size;
 432
 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
 436         */
 437        size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer);
 438        if (size != 0) {
 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);
 442        }
 443        ptr += pstore_ftrace_size;
 444
 445        /* Save Console record */
 446        size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_pmsg_size,
 447                                 buffer);
 448        if (size != 0) {
 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);
 452        }
 453
 454        free(buffer);
 455
 456        return 0;
 457}
 458
 459static 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, "", ""),
 463};
 464
 465static int do_pstore(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
 466{
 467        struct cmd_tbl *c;
 468
 469        if (argc < 2)
 470                return CMD_RET_USAGE;
 471
 472        /* Strip off leading argument */
 473        argc--;
 474        argv++;
 475
 476        c = find_cmd_tbl(argv[0], cmd_pstore_sub, ARRAY_SIZE(cmd_pstore_sub));
 477
 478        if (!c)
 479                return CMD_RET_USAGE;
 480
 481        return c->cmd(cmdtp, flag, argc, argv);
 482}
 483
 484void fdt_fixup_pstore(void *blob)
 485{
 486        char node[32];
 487        int  nodeoffset;        /* node offset from libfdt */
 488
 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));
 493                return;
 494        }
 495
 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));
 500                return;
 501        }
 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");
 505
 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));
 510                return;
 511        }
 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);
 520}
 521
 522U_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"
 545);
 546