Convert CONFIG_SYS_MAXARGS to Kconfig
[platform/kernel/u-boot.git] / cmd / pstore.c
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
16 struct 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
32 static phys_addr_t pstore_addr = CONFIG_CMD_PSTORE_MEM_ADDR;
33 static phys_size_t pstore_length = CONFIG_CMD_PSTORE_MEM_SIZE;
34 static unsigned int pstore_record_size = CONFIG_CMD_PSTORE_RECORD_SIZE;
35 static unsigned int pstore_console_size = CONFIG_CMD_PSTORE_CONSOLE_SIZE;
36 static unsigned int pstore_ftrace_size = CONFIG_CMD_PSTORE_FTRACE_SIZE;
37 static unsigned int pstore_pmsg_size = CONFIG_CMD_PSTORE_PMSG_SIZE;
38 static unsigned int pstore_ecc_size = CONFIG_CMD_PSTORE_ECC_SIZE;
39 static 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   */
54 static 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  */
89 static 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  */
129 static 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  */
168 static 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  */
218 static 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  */
244 static 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  */
361 static 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
460 static 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
466 static 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
485 void fdt_fixup_pstore(void *blob)
486 {
487         char node[32];
488         int  nodeoffset;        /* node offset from libfdt */
489         u32 addr_cells;
490         u32 size_cells;
491
492         nodeoffset = fdt_path_offset(blob, "/");
493         if (nodeoffset < 0) {
494                 /* Not found or something else bad happened. */
495                 log_err("fdt_path_offset() returned %s\n", fdt_strerror(nodeoffset));
496                 return;
497         }
498
499         nodeoffset = fdt_find_or_add_subnode(blob, nodeoffset, "reserved-memory");
500         if (nodeoffset < 0) {
501                 log_err("Add 'reserved-memory' node failed: %s\n",
502                                 fdt_strerror(nodeoffset));
503                 return;
504         }
505
506         addr_cells = fdt_getprop_u32_default_node(blob, nodeoffset, 0, "#address-cells", 2);
507         size_cells = fdt_getprop_u32_default_node(blob, nodeoffset, 0, "#size-cells", 2);
508         fdt_setprop_u32(blob, nodeoffset, "#address-cells", addr_cells);
509         fdt_setprop_u32(blob, nodeoffset, "#size-cells", size_cells);
510
511         fdt_setprop_empty(blob, nodeoffset, "ranges");
512
513         sprintf(node, "ramoops@%llx", (unsigned long long)pstore_addr);
514         nodeoffset = fdt_add_subnode(blob, nodeoffset, node);
515         if (nodeoffset < 0) {
516                 log_err("Add '%s' node failed: %s\n", node, fdt_strerror(nodeoffset));
517                 return;
518         }
519
520         fdt_setprop_string(blob, nodeoffset, "compatible", "ramoops");
521
522         if (addr_cells == 1) {
523                 fdt_setprop_u32(blob, nodeoffset, "reg", pstore_addr);
524         } else if (addr_cells == 2) {
525                 fdt_setprop_u64(blob, nodeoffset, "reg", pstore_addr);
526         } else {
527                 log_err("Unsupported #address-cells: %u\n", addr_cells);
528                 goto clean_ramoops;
529         }
530
531         if (size_cells == 1) {
532                 // Let's consider that the pstore_length fits in a 32 bits value
533                 fdt_appendprop_u32(blob, nodeoffset, "reg", pstore_length);
534         } else if (size_cells == 2) {
535                 fdt_appendprop_u64(blob, nodeoffset, "reg", pstore_length);
536         } else {
537                 log_err("Unsupported #size-cells: %u\n", addr_cells);
538                 goto clean_ramoops;
539         }
540
541         fdt_setprop_u32(blob, nodeoffset, "record-size", pstore_record_size);
542         fdt_setprop_u32(blob, nodeoffset, "console-size", pstore_console_size);
543         fdt_setprop_u32(blob, nodeoffset, "ftrace-size", pstore_ftrace_size);
544         fdt_setprop_u32(blob, nodeoffset, "pmsg-size", pstore_pmsg_size);
545         fdt_setprop_u32(blob, nodeoffset, "ecc-size", pstore_ecc_size);
546
547 clean_ramoops:
548         fdt_del_node_and_alias(blob, node);
549 }
550
551 U_BOOT_CMD(pstore, 10, 0, do_pstore,
552            "Manage Linux Persistent Storage",
553            "set <addr> <len> [record-size] [console-size] [ftrace-size] [pmsg_size] [ecc-size]\n"
554            "- Set pstore reserved memory info, starting at 'addr' for 'len' bytes.\n"
555            "  Default length for records is 4K.\n"
556            "  'record-size' is the size of one panic or oops record ('dump' type).\n"
557            "  'console-size' is the size of the kernel logs record.\n"
558            "  'ftrace-size' is the size of the ftrace record(s), this can be a single\n"
559            "  record or divided in parts based on number of CPUs.\n"
560            "  'pmsg-size' is the size of the user space logs record.\n"
561            "  'ecc-size' enables/disables ECC support and specifies ECC buffer size in\n"
562            "  bytes (0 disables it, 1 is a special value, means 16 bytes ECC).\n"
563            "pstore display [record-type] [nb]\n"
564            "- Display existing records in pstore reserved memory. A 'record-type' can\n"
565            "  be given to only display records of this kind. 'record-type' can be one\n"
566            "  of 'dump', 'console', 'ftrace' or 'user'. For 'dump' and 'ftrace' types,\n"
567            "  a 'nb' can be given to only display one record.\n"
568            "pstore save <interface> <dev[:part]> <directory-path>\n"
569            "- Save existing records in pstore reserved memory under 'directory path'\n"
570            "  to partition 'part' on device type 'interface' instance 'dev'.\n"
571            "  Filenames are automatically generated, depending on record type, like\n"
572            "  in /sys/fs/pstore under Linux.\n"
573            "  The 'directory-path' should already exist.\n"
574 );