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 
15 struct 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 
31 static phys_addr_t pstore_addr = CONFIG_CMD_PSTORE_MEM_ADDR;
32 static phys_size_t pstore_length = CONFIG_CMD_PSTORE_MEM_SIZE;
33 static unsigned int pstore_record_size = CONFIG_CMD_PSTORE_RECORD_SIZE;
34 static unsigned int pstore_console_size = CONFIG_CMD_PSTORE_CONSOLE_SIZE;
35 static unsigned int pstore_ftrace_size = CONFIG_CMD_PSTORE_FTRACE_SIZE;
36 static unsigned int pstore_pmsg_size = CONFIG_CMD_PSTORE_PMSG_SIZE;
37 static unsigned int pstore_ecc_size = CONFIG_CMD_PSTORE_ECC_SIZE;
38 static 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   */
pstore_read_kmsg_hdr(char * buffer,bool * compressed)53 static 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  */
pstore_get_buffer(u32 sig,phys_addr_t buffer,u32 size,char * dest)88 static 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  */
pstore_init_buffer_size(void)128 static 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  */
pstore_set(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])167 static 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 = simple_strtoul(argv[1], NULL, 16);
176 
177 	/* Length is specified since argc > 2
178 	 */
179 	pstore_length = simple_strtoul(argv[2], NULL, 16);
180 
181 	if (argc > 3)
182 		pstore_record_size = simple_strtoul(argv[3], NULL, 16);
183 
184 	if (argc > 4)
185 		pstore_console_size = simple_strtoul(argv[4], NULL, 16);
186 
187 	if (argc > 5)
188 		pstore_ftrace_size = simple_strtoul(argv[5], NULL, 16);
189 
190 	if (argc > 6)
191 		pstore_pmsg_size = simple_strtoul(argv[6], NULL, 16);
192 
193 	if (argc > 7)
194 		pstore_ecc_size = simple_strtoul(argv[7], NULL, 16);
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  */
pstore_print_buffer(char * type,char * buffer,u32 size)217 static 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  */
pstore_display(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])243 static 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 += simple_strtoul(argv[2], NULL, 10)
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  */
pstore_save(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])360 static 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 
459 static 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 
do_pstore(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])465 static 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 
fdt_fixup_pstore(void * blob)484 void 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 
522 U_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