diff options
author | Jim Keniston <jkenisto@us.ibm.com> | 2011-07-25 03:54:50 -0400 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2011-09-19 19:19:46 -0400 |
commit | 6c493685f1b209dd4ae41eb52c818cf12da20def (patch) | |
tree | e524f18276205cecb1552baca95cb5dc7d981b2b /arch/powerpc/platforms/pseries | |
parent | 7392769365f32c82340f184f93408b12dc3da4dc (diff) |
powerpc/nvram: Add compression to fit more oops output into NVRAM
Capture more than twice as much text from the printk buffer, and
compress it to fit it in the lnx,oops-log NVRAM partition. You
can view the compressed text using the new (as of July 20) --unzip
option of the nvram command in the powerpc-utils package.
[BenH: Added select of ZLIB_DEFLATE]
Signed-off-by: Jim Keniston <jkenisto@us.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/platforms/pseries')
-rw-r--r-- | arch/powerpc/platforms/pseries/Kconfig | 1 | ||||
-rw-r--r-- | arch/powerpc/platforms/pseries/nvram.c | 171 |
2 files changed, 165 insertions, 7 deletions
diff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig index 05cf4769b88c..c81f6bb9c10f 100644 --- a/arch/powerpc/platforms/pseries/Kconfig +++ b/arch/powerpc/platforms/pseries/Kconfig | |||
@@ -15,6 +15,7 @@ config PPC_PSERIES | |||
15 | select PPC_UDBG_16550 | 15 | select PPC_UDBG_16550 |
16 | select PPC_NATIVE | 16 | select PPC_NATIVE |
17 | select PPC_PCI_CHOICE if EXPERT | 17 | select PPC_PCI_CHOICE if EXPERT |
18 | select ZLIB_DEFLATE | ||
18 | default y | 19 | default y |
19 | 20 | ||
20 | config PPC_SPLPAR | 21 | config PPC_SPLPAR |
diff --git a/arch/powerpc/platforms/pseries/nvram.c b/arch/powerpc/platforms/pseries/nvram.c index 00cc3a094885..a76b22844d18 100644 --- a/arch/powerpc/platforms/pseries/nvram.c +++ b/arch/powerpc/platforms/pseries/nvram.c | |||
@@ -18,6 +18,8 @@ | |||
18 | #include <linux/spinlock.h> | 18 | #include <linux/spinlock.h> |
19 | #include <linux/slab.h> | 19 | #include <linux/slab.h> |
20 | #include <linux/kmsg_dump.h> | 20 | #include <linux/kmsg_dump.h> |
21 | #include <linux/ctype.h> | ||
22 | #include <linux/zlib.h> | ||
21 | #include <asm/uaccess.h> | 23 | #include <asm/uaccess.h> |
22 | #include <asm/nvram.h> | 24 | #include <asm/nvram.h> |
23 | #include <asm/rtas.h> | 25 | #include <asm/rtas.h> |
@@ -78,8 +80,41 @@ static struct kmsg_dumper nvram_kmsg_dumper = { | |||
78 | #define NVRAM_RTAS_READ_TIMEOUT 5 /* seconds */ | 80 | #define NVRAM_RTAS_READ_TIMEOUT 5 /* seconds */ |
79 | static unsigned long last_unread_rtas_event; /* timestamp */ | 81 | static unsigned long last_unread_rtas_event; /* timestamp */ |
80 | 82 | ||
81 | /* We preallocate oops_buf during init to avoid kmalloc during oops/panic. */ | 83 | /* |
82 | static char *oops_buf; | 84 | * For capturing and compressing an oops or panic report... |
85 | |||
86 | * big_oops_buf[] holds the uncompressed text we're capturing. | ||
87 | * | ||
88 | * oops_buf[] holds the compressed text, preceded by a prefix. | ||
89 | * The prefix is just a u16 holding the length of the compressed* text. | ||
90 | * (*Or uncompressed, if compression fails.) oops_buf[] gets written | ||
91 | * to NVRAM. | ||
92 | * | ||
93 | * oops_len points to the prefix. oops_data points to the compressed text. | ||
94 | * | ||
95 | * +- oops_buf | ||
96 | * | +- oops_data | ||
97 | * v v | ||
98 | * +------------+-----------------------------------------------+ | ||
99 | * | length | text | | ||
100 | * | (2 bytes) | (oops_data_sz bytes) | | ||
101 | * +------------+-----------------------------------------------+ | ||
102 | * ^ | ||
103 | * +- oops_len | ||
104 | * | ||
105 | * We preallocate these buffers during init to avoid kmalloc during oops/panic. | ||
106 | */ | ||
107 | static size_t big_oops_buf_sz; | ||
108 | static char *big_oops_buf, *oops_buf; | ||
109 | static u16 *oops_len; | ||
110 | static char *oops_data; | ||
111 | static size_t oops_data_sz; | ||
112 | |||
113 | /* Compression parameters */ | ||
114 | #define COMPR_LEVEL 6 | ||
115 | #define WINDOW_BITS 12 | ||
116 | #define MEM_LEVEL 4 | ||
117 | static struct z_stream_s stream; | ||
83 | 118 | ||
84 | static ssize_t pSeries_nvram_read(char *buf, size_t count, loff_t *index) | 119 | static ssize_t pSeries_nvram_read(char *buf, size_t count, loff_t *index) |
85 | { | 120 | { |
@@ -387,11 +422,44 @@ static void __init nvram_init_oops_partition(int rtas_partition_exists) | |||
387 | sizeof(rtas_log_partition)); | 422 | sizeof(rtas_log_partition)); |
388 | } | 423 | } |
389 | oops_buf = kmalloc(oops_log_partition.size, GFP_KERNEL); | 424 | oops_buf = kmalloc(oops_log_partition.size, GFP_KERNEL); |
425 | if (!oops_buf) { | ||
426 | pr_err("nvram: No memory for %s partition\n", | ||
427 | oops_log_partition.name); | ||
428 | return; | ||
429 | } | ||
430 | oops_len = (u16*) oops_buf; | ||
431 | oops_data = oops_buf + sizeof(u16); | ||
432 | oops_data_sz = oops_log_partition.size - sizeof(u16); | ||
433 | |||
434 | /* | ||
435 | * Figure compression (preceded by elimination of each line's <n> | ||
436 | * severity prefix) will reduce the oops/panic report to at most | ||
437 | * 45% of its original size. | ||
438 | */ | ||
439 | big_oops_buf_sz = (oops_data_sz * 100) / 45; | ||
440 | big_oops_buf = kmalloc(big_oops_buf_sz, GFP_KERNEL); | ||
441 | if (big_oops_buf) { | ||
442 | stream.workspace = kmalloc(zlib_deflate_workspacesize( | ||
443 | WINDOW_BITS, MEM_LEVEL), GFP_KERNEL); | ||
444 | if (!stream.workspace) { | ||
445 | pr_err("nvram: No memory for compression workspace; " | ||
446 | "skipping compression of %s partition data\n", | ||
447 | oops_log_partition.name); | ||
448 | kfree(big_oops_buf); | ||
449 | big_oops_buf = NULL; | ||
450 | } | ||
451 | } else { | ||
452 | pr_err("No memory for uncompressed %s data; " | ||
453 | "skipping compression\n", oops_log_partition.name); | ||
454 | stream.workspace = NULL; | ||
455 | } | ||
456 | |||
390 | rc = kmsg_dump_register(&nvram_kmsg_dumper); | 457 | rc = kmsg_dump_register(&nvram_kmsg_dumper); |
391 | if (rc != 0) { | 458 | if (rc != 0) { |
392 | pr_err("nvram: kmsg_dump_register() failed; returned %d\n", rc); | 459 | pr_err("nvram: kmsg_dump_register() failed; returned %d\n", rc); |
393 | kfree(oops_buf); | 460 | kfree(oops_buf); |
394 | return; | 461 | kfree(big_oops_buf); |
462 | kfree(stream.workspace); | ||
395 | } | 463 | } |
396 | } | 464 | } |
397 | 465 | ||
@@ -473,7 +541,83 @@ static int clobbering_unread_rtas_event(void) | |||
473 | NVRAM_RTAS_READ_TIMEOUT); | 541 | NVRAM_RTAS_READ_TIMEOUT); |
474 | } | 542 | } |
475 | 543 | ||
476 | /* our kmsg_dump callback */ | 544 | /* Squeeze out each line's <n> severity prefix. */ |
545 | static size_t elide_severities(char *buf, size_t len) | ||
546 | { | ||
547 | char *in, *out, *buf_end = buf + len; | ||
548 | /* Assume a <n> at the very beginning marks the start of a line. */ | ||
549 | int newline = 1; | ||
550 | |||
551 | in = out = buf; | ||
552 | while (in < buf_end) { | ||
553 | if (newline && in+3 <= buf_end && | ||
554 | *in == '<' && isdigit(in[1]) && in[2] == '>') { | ||
555 | in += 3; | ||
556 | newline = 0; | ||
557 | } else { | ||
558 | newline = (*in == '\n'); | ||
559 | *out++ = *in++; | ||
560 | } | ||
561 | } | ||
562 | return out - buf; | ||
563 | } | ||
564 | |||
565 | /* Derived from logfs_compress() */ | ||
566 | static int nvram_compress(const void *in, void *out, size_t inlen, | ||
567 | size_t outlen) | ||
568 | { | ||
569 | int err, ret; | ||
570 | |||
571 | ret = -EIO; | ||
572 | err = zlib_deflateInit2(&stream, COMPR_LEVEL, Z_DEFLATED, WINDOW_BITS, | ||
573 | MEM_LEVEL, Z_DEFAULT_STRATEGY); | ||
574 | if (err != Z_OK) | ||
575 | goto error; | ||
576 | |||
577 | stream.next_in = in; | ||
578 | stream.avail_in = inlen; | ||
579 | stream.total_in = 0; | ||
580 | stream.next_out = out; | ||
581 | stream.avail_out = outlen; | ||
582 | stream.total_out = 0; | ||
583 | |||
584 | err = zlib_deflate(&stream, Z_FINISH); | ||
585 | if (err != Z_STREAM_END) | ||
586 | goto error; | ||
587 | |||
588 | err = zlib_deflateEnd(&stream); | ||
589 | if (err != Z_OK) | ||
590 | goto error; | ||
591 | |||
592 | if (stream.total_out >= stream.total_in) | ||
593 | goto error; | ||
594 | |||
595 | ret = stream.total_out; | ||
596 | error: | ||
597 | return ret; | ||
598 | } | ||
599 | |||
600 | /* Compress the text from big_oops_buf into oops_buf. */ | ||
601 | static int zip_oops(size_t text_len) | ||
602 | { | ||
603 | int zipped_len = nvram_compress(big_oops_buf, oops_data, text_len, | ||
604 | oops_data_sz); | ||
605 | if (zipped_len < 0) { | ||
606 | pr_err("nvram: compression failed; returned %d\n", zipped_len); | ||
607 | pr_err("nvram: logging uncompressed oops/panic report\n"); | ||
608 | return -1; | ||
609 | } | ||
610 | *oops_len = (u16) zipped_len; | ||
611 | return 0; | ||
612 | } | ||
613 | |||
614 | /* | ||
615 | * This is our kmsg_dump callback, called after an oops or panic report | ||
616 | * has been written to the printk buffer. We want to capture as much | ||
617 | * of the printk buffer as possible. First, capture as much as we can | ||
618 | * that we think will compress sufficiently to fit in the lnx,oops-log | ||
619 | * partition. If that's too much, go back and capture uncompressed text. | ||
620 | */ | ||
477 | static void oops_to_nvram(struct kmsg_dumper *dumper, | 621 | static void oops_to_nvram(struct kmsg_dumper *dumper, |
478 | enum kmsg_dump_reason reason, | 622 | enum kmsg_dump_reason reason, |
479 | const char *old_msgs, unsigned long old_len, | 623 | const char *old_msgs, unsigned long old_len, |
@@ -482,6 +626,8 @@ static void oops_to_nvram(struct kmsg_dumper *dumper, | |||
482 | static unsigned int oops_count = 0; | 626 | static unsigned int oops_count = 0; |
483 | static bool panicking = false; | 627 | static bool panicking = false; |
484 | size_t text_len; | 628 | size_t text_len; |
629 | unsigned int err_type = ERR_TYPE_KERNEL_PANIC_GZ; | ||
630 | int rc = -1; | ||
485 | 631 | ||
486 | switch (reason) { | 632 | switch (reason) { |
487 | case KMSG_DUMP_RESTART: | 633 | case KMSG_DUMP_RESTART: |
@@ -509,8 +655,19 @@ static void oops_to_nvram(struct kmsg_dumper *dumper, | |||
509 | if (clobbering_unread_rtas_event()) | 655 | if (clobbering_unread_rtas_event()) |
510 | return; | 656 | return; |
511 | 657 | ||
512 | text_len = capture_last_msgs(old_msgs, old_len, new_msgs, new_len, | 658 | if (big_oops_buf) { |
513 | oops_buf, oops_log_partition.size); | 659 | text_len = capture_last_msgs(old_msgs, old_len, |
660 | new_msgs, new_len, big_oops_buf, big_oops_buf_sz); | ||
661 | text_len = elide_severities(big_oops_buf, text_len); | ||
662 | rc = zip_oops(text_len); | ||
663 | } | ||
664 | if (rc != 0) { | ||
665 | text_len = capture_last_msgs(old_msgs, old_len, | ||
666 | new_msgs, new_len, oops_data, oops_data_sz); | ||
667 | err_type = ERR_TYPE_KERNEL_PANIC; | ||
668 | *oops_len = (u16) text_len; | ||
669 | } | ||
670 | |||
514 | (void) nvram_write_os_partition(&oops_log_partition, oops_buf, | 671 | (void) nvram_write_os_partition(&oops_log_partition, oops_buf, |
515 | (int) text_len, ERR_TYPE_KERNEL_PANIC, ++oops_count); | 672 | (int) (sizeof(*oops_len) + *oops_len), err_type, ++oops_count); |
516 | } | 673 | } |