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