blob: 9795eea2dbc3fc1adc7182e142c4fbf7d89798ce [file] [log] [blame]
Frédéric Danised2e8e42020-03-20 10:59:22 +01001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright © 2019 Collabora Ltd
4 */
5
6#include <config.h>
7#include <command.h>
Simon Glass3ba929a2020-10-30 21:38:53 -06008#include <fdtdec.h>
Frédéric Danised2e8e42020-03-20 10:59:22 +01009#include <fs.h>
10#include <log.h>
11#include <mapmem.h>
12#include <memalign.h>
13#include <part.h>
Detlev Casanova4d4795f2022-02-07 11:02:30 -050014#include <fdt_support.h>
Frédéric Danised2e8e42020-03-20 10:59:22 +010015
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 */
Simon Glass3ff49ec2021-07-24 09:03:29 -0600176 pstore_addr = hextoul(argv[1], NULL);
Frédéric Danised2e8e42020-03-20 10:59:22 +0100177
178 /* Length is specified since argc > 2
179 */
Simon Glass3ff49ec2021-07-24 09:03:29 -0600180 pstore_length = hextoul(argv[2], NULL);
Frédéric Danised2e8e42020-03-20 10:59:22 +0100181
182 if (argc > 3)
Simon Glass3ff49ec2021-07-24 09:03:29 -0600183 pstore_record_size = hextoul(argv[3], NULL);
Frédéric Danised2e8e42020-03-20 10:59:22 +0100184
185 if (argc > 4)
Simon Glass3ff49ec2021-07-24 09:03:29 -0600186 pstore_console_size = hextoul(argv[4], NULL);
Frédéric Danised2e8e42020-03-20 10:59:22 +0100187
188 if (argc > 5)
Simon Glass3ff49ec2021-07-24 09:03:29 -0600189 pstore_ftrace_size = hextoul(argv[5], NULL);
Frédéric Danised2e8e42020-03-20 10:59:22 +0100190
191 if (argc > 6)
Simon Glass3ff49ec2021-07-24 09:03:29 -0600192 pstore_pmsg_size = hextoul(argv[6], NULL);
Frédéric Danised2e8e42020-03-20 10:59:22 +0100193
194 if (argc > 7)
Simon Glass3ff49ec2021-07-24 09:03:29 -0600195 pstore_ecc_size = hextoul(argv[7], NULL);
Frédéric Danised2e8e42020-03-20 10:59:22 +0100196
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) {
Simon Glassff9b9032021-07-24 09:03:30 -0600283 ptr += dectoul(argv[2], NULL)
Frédéric Danised2e8e42020-03-20 10:59:22 +0100284 * 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
Frédéric Daniscdd6a6d2020-03-20 10:59:24 +0100485void fdt_fixup_pstore(void *blob)
486{
487 char node[32];
488 int nodeoffset; /* node offset from libfdt */
Andrey Skvortsov2f568c22023-08-26 15:16:52 +0300489 u32 addr_cells_root;
490 u32 size_cells_root;
Detlev Casanova4d4795f2022-02-07 11:02:30 -0500491 u32 addr_cells;
492 u32 size_cells;
Frédéric Daniscdd6a6d2020-03-20 10:59:24 +0100493
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 }
Andrey Skvortsov2f568c22023-08-26 15:16:52 +0300500 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);
Frédéric Daniscdd6a6d2020-03-20 10:59:24 +0100502
Detlev Casanova4d4795f2022-02-07 11:02:30 -0500503 nodeoffset = fdt_find_or_add_subnode(blob, nodeoffset, "reserved-memory");
Frédéric Daniscdd6a6d2020-03-20 10:59:24 +0100504 if (nodeoffset < 0) {
505 log_err("Add 'reserved-memory' node failed: %s\n",
506 fdt_strerror(nodeoffset));
507 return;
508 }
Detlev Casanova4d4795f2022-02-07 11:02:30 -0500509
Andrey Skvortsov2f568c22023-08-26 15:16:52 +0300510 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);
Detlev Casanova4d4795f2022-02-07 11:02:30 -0500514 fdt_setprop_u32(blob, nodeoffset, "#address-cells", addr_cells);
515 fdt_setprop_u32(blob, nodeoffset, "#size-cells", size_cells);
516
Frédéric Daniscdd6a6d2020-03-20 10:59:24 +0100517 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 }
Detlev Casanova4d4795f2022-02-07 11:02:30 -0500525
Frédéric Daniscdd6a6d2020-03-20 10:59:24 +0100526 fdt_setprop_string(blob, nodeoffset, "compatible", "ramoops");
Detlev Casanova4d4795f2022-02-07 11:02:30 -0500527
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
Frédéric Daniscdd6a6d2020-03-20 10:59:24 +0100547 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);
Detlev Casanova4d4795f2022-02-07 11:02:30 -0500552
553clean_ramoops:
554 fdt_del_node_and_alias(blob, node);
Frédéric Daniscdd6a6d2020-03-20 10:59:24 +0100555}
556
Frédéric Danised2e8e42020-03-20 10:59:22 +0100557U_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);