[PATCH 4/6] nvram: Add compression to fit more printk output into NVRAM
Jim Keniston
jkenisto at us.ibm.com
Sun Nov 14 15:15:33 EST 2010
Capture more than twice as much text from the printk buffer, and
compress it to fit it in the ibm,oops-log NVRAM partition.
Signed-off-by: Jim Keniston <jkenisto at us.ibm.com>
---
arch/powerpc/include/asm/rtas.h | 6 +
arch/powerpc/platforms/pseries/nvram.c | 195 ++++++++++++++++++++++++++++----
2 files changed, 177 insertions(+), 24 deletions(-)
diff --git a/arch/powerpc/include/asm/rtas.h b/arch/powerpc/include/asm/rtas.h
index 3d35f8a..6e0f4b5 100644
--- a/arch/powerpc/include/asm/rtas.h
+++ b/arch/powerpc/include/asm/rtas.h
@@ -205,10 +205,12 @@ extern void pSeries_log_error(char *buf, unsigned int err_type, int fatal);
#define ERR_FLAG_ALREADY_LOGGED 0x0
#define ERR_FLAG_BOOT 0x1 /* log was pulled from NVRAM on boot */
#define ERR_TYPE_RTAS_LOG 0x2 /* from rtas event-scan */
-#define ERR_TYPE_KERNEL_PANIC 0x4 /* from panic() */
+#define ERR_TYPE_KERNEL_PANIC 0x4 /* from die()/panic() */
+#define ERR_TYPE_KERNEL_PANIC_GZ 0x8 /* ditto, compressed */
/* All the types and not flags */
-#define ERR_TYPE_MASK (ERR_TYPE_RTAS_LOG | ERR_TYPE_KERNEL_PANIC)
+#define ERR_TYPE_MASK \
+ (ERR_TYPE_RTAS_LOG | ERR_TYPE_KERNEL_PANIC | ERR_TYPE_KERNEL_PANIC_GZ)
#define RTAS_DEBUG KERN_DEBUG "RTAS: "
diff --git a/arch/powerpc/platforms/pseries/nvram.c b/arch/powerpc/platforms/pseries/nvram.c
index e1bc1a4..8e5ed74 100644
--- a/arch/powerpc/platforms/pseries/nvram.c
+++ b/arch/powerpc/platforms/pseries/nvram.c
@@ -17,7 +17,10 @@
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/ctype.h>
#include <linux/kmsg_dump.h>
+#include <linux/zlib.h>
#include <asm/uaccess.h>
#include <asm/nvram.h>
#include <asm/rtas.h>
@@ -74,9 +77,24 @@ static struct kmsg_dumper nvram_kmsg_dumper = {
.dump = oops_to_nvram
};
-/* We preallocate oops_buf during init to avoid kmalloc during oops/panic. */
-static size_t oops_buf_sz;
-static char *oops_buf;
+/*
+ * big_oops_buf[] holds the uncompressed text we're capturing. little_oops_buf
+ * holds the compressed text, plus its length. little_oops_buf gets written
+ * to NVRAM.
+ *
+ * We preallocate these buffers during init to avoid kmalloc during oops/panic.
+ */
+static size_t big_oops_buf_sz, little_oops_buf_sz;
+static char *big_oops_buf;
+
+static struct oops_parition_data {
+#define OOPS_PTN_PREFIX_SZ sizeof(unsigned short)
+ unsigned short length;
+ char buf[0];
+} *little_oops_buf;
+
+#define COMPR_LEVEL 6
+static struct z_stream_s stream;
static ssize_t pSeries_nvram_read(char *buf, size_t count, loff_t *index)
{
@@ -374,13 +392,42 @@ static void __init nvram_init_oops_partition(int rtas_partition_exists)
memcpy(&oops_log_partition, &rtas_log_partition,
sizeof(rtas_log_partition));
}
- oops_buf_sz = oops_log_partition.size - sizeof(struct err_log_info);
- oops_buf = kmalloc(oops_buf_sz, GFP_KERNEL);
+
+ little_oops_buf_sz = oops_log_partition.size - OOPS_PTN_PREFIX_SZ;
+ little_oops_buf = kmalloc(oops_log_partition.size, GFP_KERNEL);
+ if (!little_oops_buf) {
+ pr_err("nvram: No memory for %s partition\n",
+ oops_log_partition.name);
+ return;
+ }
+ /*
+ * Figure compression (preceded by elimination of each line's <n>
+ * severity prefix) will reduce the oops/panic report to at most
+ * 45% of its original size.
+ */
+ big_oops_buf_sz = (little_oops_buf_sz * 100) / 45;
+ big_oops_buf = kmalloc(big_oops_buf_sz, GFP_KERNEL);
+ if (big_oops_buf) {
+ stream.workspace = vmalloc(zlib_deflate_workspacesize());
+ if (!stream.workspace) {
+ pr_err("nvram: No memory for compression workspace; "
+ "skipping compression of %s partition data\n",
+ oops_log_partition.name);
+ kfree(big_oops_buf);
+ big_oops_buf = NULL;
+ }
+ } else {
+ pr_err("No memory for uncompressed %s data; "
+ "skipping compression\n", oops_log_partition.name);
+ stream.workspace = NULL;
+ }
+
rc = kmsg_dump_register(&nvram_kmsg_dumper);
if (rc != 0) {
pr_err("nvram: kmsg_dump_register() failed; returned %d\n", rc);
- kfree(oops_buf);
- return;
+ kfree(little_oops_buf);
+ kfree(big_oops_buf);
+ vfree(stream.workspace);
}
}
@@ -527,7 +574,105 @@ static size_t capture_oops(const char *old_msgs, size_t old_len,
return nc1 + nc2;
}
-/* our kmsg_dump callback */
+/*
+ * For a panic, capture the last capture_len chars of the printk buffer.
+ * For an oops, ensure that we have the start of the oops report, and the
+ * message(s) leading up to it.
+ */
+static size_t capture_msgs(enum kmsg_dump_reason reason,
+ const char *old_msgs, size_t old_len,
+ const char *new_msgs, size_t new_len,
+ char *captured, size_t capture_len)
+{
+ size_t text_len;
+
+ text_len = capture_last_msgs(old_msgs, old_len, new_msgs, new_len,
+ captured, capture_len);
+ if (reason == KMSG_DUMP_OOPS) {
+ const char *poops = strnrstr(captured, OOPS_TAG, capture_len);
+ if (!poops || poops < captured + PREAMBLE_CHARS)
+ text_len = capture_oops(old_msgs, old_len, new_msgs,
+ new_len, captured, capture_len, text_len);
+ }
+ return text_len;
+}
+
+/* Squeeze out each line's <n> severity prefix. */
+static size_t elide_severities(char *buf, size_t len)
+{
+ char *in, *out, *buf_end = buf + len;
+ /* Assume a <n> at the very beginning marks the start of a line. */
+ int newline = 1;
+
+ in = out = buf;
+ while (in < buf_end) {
+ if (newline && in+3 <= buf_end &&
+ *in == '<' && isdigit(in[1]) && in[2] == '>') {
+ in += 3;
+ newline = 0;
+ } else {
+ newline = (*in == '\n');
+ *out++ = *in++;
+ }
+ }
+ return out - buf;
+}
+
+/* Derived from logfs_compress() */
+static int nvram_compress(const void *in, void *out, size_t inlen,
+ size_t outlen)
+{
+ int err, ret;
+
+ ret = -EIO;
+ err = zlib_deflateInit(&stream, COMPR_LEVEL);
+ if (err != Z_OK)
+ goto error;
+
+ stream.next_in = in;
+ stream.avail_in = inlen;
+ stream.total_in = 0;
+ stream.next_out = out;
+ stream.avail_out = outlen;
+ stream.total_out = 0;
+
+ err = zlib_deflate(&stream, Z_FINISH);
+ if (err != Z_STREAM_END)
+ goto error;
+
+ err = zlib_deflateEnd(&stream);
+ if (err != Z_OK)
+ goto error;
+
+ if (stream.total_out >= stream.total_in)
+ goto error;
+
+ ret = stream.total_out;
+error:
+ return ret;
+}
+
+/* Compress the text from big_oops_buf into little_oops_buf. */
+static int zip_oops(size_t text_len)
+{
+ int zipped_len = nvram_compress(big_oops_buf, little_oops_buf->buf,
+ text_len, little_oops_buf_sz);
+ if (zipped_len < 0) {
+ pr_err("nvram: compression failed; returned %d\n", zipped_len);
+ pr_err("nvram: logging uncompressed oops/panic report\n");
+ return -1;
+ }
+ little_oops_buf->length = (unsigned short) zipped_len;
+ return 0;
+}
+
+/*
+ * This is our kmsg_dump callback, called after an oops or panic report
+ * has been written to the printk buffer. We want to capture as much
+ * of the printk buffer as possible. First, capture as much as we can
+ * that we think will compress sufficiently to fit in the ibm,oops-log
+ * partition. If that's too much, go back and capture uncompressed text.
+ */
static void oops_to_nvram(struct kmsg_dumper *dumper,
enum kmsg_dump_reason reason,
const char *old_msgs, unsigned long old_len,
@@ -535,19 +680,25 @@ static void oops_to_nvram(struct kmsg_dumper *dumper,
{
static unsigned int oops_count = 0;
size_t text_len;
-
- text_len = capture_last_msgs(old_msgs, old_len, new_msgs, new_len,
- oops_buf, oops_buf_sz);
- if (reason == KMSG_DUMP_OOPS) {
- /*
- * Ensure that we have the start of the oops report,
- * and the message(s) leading up to it.
- */
- const char *poops = strnrstr(oops_buf, OOPS_TAG, oops_buf_sz);
- if (!poops || poops < oops_buf + PREAMBLE_CHARS)
- text_len = capture_oops(old_msgs, old_len, new_msgs,
- new_len, oops_buf, oops_buf_sz, text_len);
+ unsigned int err_type = ERR_TYPE_KERNEL_PANIC_GZ;
+ int rc = -1;
+
+ if (big_oops_buf) {
+ text_len = capture_msgs(reason, old_msgs, old_len,
+ new_msgs, new_len, big_oops_buf, big_oops_buf_sz);
+ text_len = elide_severities(big_oops_buf, text_len);
+ rc = zip_oops(text_len);
}
- (void) nvram_write_os_partition(&oops_log_partition, oops_buf,
- (int) text_len, ERR_TYPE_KERNEL_PANIC, ++oops_count);
+ if (rc != 0) {
+ text_len = capture_msgs(reason, old_msgs, old_len,
+ new_msgs, new_len, little_oops_buf->buf,
+ little_oops_buf_sz);
+ err_type = ERR_TYPE_KERNEL_PANIC;
+ little_oops_buf->length = (unsigned short) text_len;
+ }
+
+ (void) nvram_write_os_partition(&oops_log_partition,
+ (char*) little_oops_buf,
+ (int) (OOPS_PTN_PREFIX_SZ + little_oops_buf->length),
+ err_type, ++oops_count);
}
More information about the Linuxppc-dev
mailing list