pstore: add lzo/lz4 compression support
[cascardo/linux.git] / fs / pstore / platform.c
index e8c17af..16ecca5 100644 (file)
 #include <linux/console.h>
 #include <linux/module.h>
 #include <linux/pstore.h>
+#ifdef CONFIG_PSTORE_ZLIB_COMPRESS
 #include <linux/zlib.h>
+#endif
+#ifdef CONFIG_PSTORE_LZO_COMPRESS
+#include <linux/lzo.h>
+#endif
+#ifdef CONFIG_PSTORE_LZ4_COMPRESS
+#include <linux/lz4.h>
+#endif
 #include <linux/string.h>
 #include <linux/timer.h>
 #include <linux/slab.h>
@@ -69,10 +77,23 @@ struct pstore_info *psinfo;
 static char *backend;
 
 /* Compression parameters */
+#ifdef CONFIG_PSTORE_ZLIB_COMPRESS
 #define COMPR_LEVEL 6
 #define WINDOW_BITS 12
 #define MEM_LEVEL 4
 static struct z_stream_s stream;
+#else
+static unsigned char *workspace;
+#endif
+
+struct pstore_zbackend {
+       int (*compress)(const void *in, void *out, size_t inlen, size_t outlen);
+       int (*decompress)(void *in, void *out, size_t inlen, size_t outlen);
+       void (*allocate)(void);
+       void (*free)(void);
+
+       const char *name;
+};
 
 static char *big_oops_buf;
 static size_t big_oops_buf_sz;
@@ -129,9 +150,9 @@ bool pstore_cannot_block_path(enum kmsg_dump_reason reason)
 }
 EXPORT_SYMBOL_GPL(pstore_cannot_block_path);
 
+#ifdef CONFIG_PSTORE_ZLIB_COMPRESS
 /* Derived from logfs_compress() */
-static int pstore_compress(const void *in, void *out, size_t inlen,
-                                                       size_t outlen)
+static int compress_zlib(const void *in, void *out, size_t inlen, size_t outlen)
 {
        int err, ret;
 
@@ -165,7 +186,7 @@ error:
 }
 
 /* Derived from logfs_uncompress */
-static int pstore_decompress(void *in, void *out, size_t inlen, size_t outlen)
+static int decompress_zlib(void *in, void *out, size_t inlen, size_t outlen)
 {
        int err, ret;
 
@@ -194,7 +215,7 @@ error:
        return ret;
 }
 
-static void allocate_buf_for_compression(void)
+static void allocate_zlib(void)
 {
        size_t size;
        size_t cmpr;
@@ -237,12 +258,190 @@ static void allocate_buf_for_compression(void)
 
 }
 
-static void free_buf_for_compression(void)
+static void free_zlib(void)
 {
        kfree(stream.workspace);
        stream.workspace = NULL;
        kfree(big_oops_buf);
        big_oops_buf = NULL;
+       big_oops_buf_sz = 0;
+}
+
+static struct pstore_zbackend backend_zlib = {
+       .compress       = compress_zlib,
+       .decompress     = decompress_zlib,
+       .allocate       = allocate_zlib,
+       .free           = free_zlib,
+       .name           = "zlib",
+};
+#endif
+
+#ifdef CONFIG_PSTORE_LZO_COMPRESS
+static int compress_lzo(const void *in, void *out, size_t inlen, size_t outlen)
+{
+       int ret;
+
+       ret = lzo1x_1_compress(in, inlen, out, &outlen, workspace);
+       if (ret != LZO_E_OK) {
+               pr_err("lzo_compress error, ret = %d!\n", ret);
+               return -EIO;
+       }
+
+       return outlen;
+}
+
+static int decompress_lzo(void *in, void *out, size_t inlen, size_t outlen)
+{
+       int ret;
+
+       ret = lzo1x_decompress_safe(in, inlen, out, &outlen);
+       if (ret != LZO_E_OK) {
+               pr_err("lzo_decompress error, ret = %d!\n", ret);
+               return -EIO;
+       }
+
+       return outlen;
+}
+
+static void allocate_lzo(void)
+{
+       big_oops_buf_sz = lzo1x_worst_compress(psinfo->bufsize);
+       big_oops_buf = kmalloc(big_oops_buf_sz, GFP_KERNEL);
+       if (big_oops_buf) {
+               workspace = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL);
+               if (!workspace) {
+                       pr_err("No memory for compression workspace; skipping compression\n");
+                       kfree(big_oops_buf);
+                       big_oops_buf = NULL;
+               }
+       } else {
+               pr_err("No memory for uncompressed data; skipping compression\n");
+               workspace = NULL;
+       }
+}
+
+static void free_lzo(void)
+{
+       kfree(workspace);
+       kfree(big_oops_buf);
+       big_oops_buf = NULL;
+       big_oops_buf_sz = 0;
+}
+
+static struct pstore_zbackend backend_lzo = {
+       .compress       = compress_lzo,
+       .decompress     = decompress_lzo,
+       .allocate       = allocate_lzo,
+       .free           = free_lzo,
+       .name           = "lzo",
+};
+#endif
+
+#ifdef CONFIG_PSTORE_LZ4_COMPRESS
+static int compress_lz4(const void *in, void *out, size_t inlen, size_t outlen)
+{
+       int ret;
+
+       ret = lz4_compress(in, inlen, out, &outlen, workspace);
+       if (ret) {
+               pr_err("lz4_compress error, ret = %d!\n", ret);
+               return -EIO;
+       }
+
+       return outlen;
+}
+
+static int decompress_lz4(void *in, void *out, size_t inlen, size_t outlen)
+{
+       int ret;
+
+       ret = lz4_decompress_unknownoutputsize(in, inlen, out, &outlen);
+       if (ret) {
+               pr_err("lz4_decompress error, ret = %d!\n", ret);
+               return -EIO;
+       }
+
+       return outlen;
+}
+
+static void allocate_lz4(void)
+{
+       big_oops_buf_sz = lz4_compressbound(psinfo->bufsize);
+       big_oops_buf = kmalloc(big_oops_buf_sz, GFP_KERNEL);
+       if (big_oops_buf) {
+               workspace = kmalloc(LZ4_MEM_COMPRESS, GFP_KERNEL);
+               if (!workspace) {
+                       pr_err("No memory for compression workspace; skipping compression\n");
+                       kfree(big_oops_buf);
+                       big_oops_buf = NULL;
+               }
+       } else {
+               pr_err("No memory for uncompressed data; skipping compression\n");
+               workspace = NULL;
+       }
+}
+
+static void free_lz4(void)
+{
+       kfree(workspace);
+       kfree(big_oops_buf);
+       big_oops_buf = NULL;
+       big_oops_buf_sz = 0;
+}
+
+static struct pstore_zbackend backend_lz4 = {
+       .compress       = compress_lz4,
+       .decompress     = decompress_lz4,
+       .allocate       = allocate_lz4,
+       .free           = free_lz4,
+       .name           = "lz4",
+};
+#endif
+
+static struct pstore_zbackend *zbackend =
+#if defined(CONFIG_PSTORE_ZLIB_COMPRESS)
+       &backend_zlib;
+#elif defined(CONFIG_PSTORE_LZO_COMPRESS)
+       &backend_lzo;
+#elif defined(CONFIG_PSTORE_LZ4_COMPRESS)
+       &backend_lz4;
+#else
+       NULL;
+#endif
+
+static int pstore_compress(const void *in, void *out,
+                          size_t inlen, size_t outlen)
+{
+       if (zbackend)
+               return zbackend->compress(in, out, inlen, outlen);
+       else
+               return -EIO;
+}
+
+static int pstore_decompress(void *in, void *out, size_t inlen, size_t outlen)
+{
+       if (zbackend)
+               return zbackend->decompress(in, out, inlen, outlen);
+       else
+               return -EIO;
+}
+
+static void allocate_buf_for_compression(void)
+{
+       if (zbackend) {
+               pr_info("using %s compression\n", zbackend->name);
+               zbackend->allocate();
+       } else {
+               pr_err("allocate compression buffer error!\n");
+       }
+}
+
+static void free_buf_for_compression(void)
+{
+       if (zbackend)
+               zbackend->free();
+       else
+               pr_err("free compression buffer error!\n");
 }
 
 /*
@@ -284,7 +483,7 @@ static void pstore_dump(struct kmsg_dumper *dumper,
        u64             id;
        unsigned int    part = 1;
        unsigned long   flags = 0;
-       int             is_locked = 0;
+       int             is_locked;
        int             ret;
 
        why = get_reason_str(reason);
@@ -295,8 +494,10 @@ static void pstore_dump(struct kmsg_dumper *dumper,
                        pr_err("pstore dump routine blocked in %s path, may corrupt error record\n"
                                       , in_nmi() ? "NMI" : why);
                }
-       } else
+       } else {
                spin_lock_irqsave(&psinfo->buf_lock, flags);
+               is_locked = 1;
+       }
        oopscount++;
        while (total < kmsg_bytes) {
                char *dst;
@@ -304,19 +505,25 @@ static void pstore_dump(struct kmsg_dumper *dumper,
                int hsize;
                int zipped_len = -1;
                size_t len;
-               bool compressed;
+               bool compressed = false;
                size_t total_len;
 
                if (big_oops_buf && is_locked) {
                        dst = big_oops_buf;
-                       hsize = sprintf(dst, "%s#%d Part%u\n", why,
-                                                       oopscount, part);
-                       size = big_oops_buf_sz - hsize;
+                       size = big_oops_buf_sz;
+               } else {
+                       dst = psinfo->buf;
+                       size = psinfo->bufsize;
+               }
 
-                       if (!kmsg_dump_get_buffer(dumper, true, dst + hsize,
-                                                               size, &len))
-                               break;
+               hsize = sprintf(dst, "%s#%d Part%u\n", why, oopscount, part);
+               size -= hsize;
+
+               if (!kmsg_dump_get_buffer(dumper, true, dst + hsize,
+                                         size, &len))
+                       break;
 
+               if (big_oops_buf && is_locked) {
                        zipped_len = pstore_compress(dst, psinfo->buf,
                                                hsize + len, psinfo->bufsize);
 
@@ -324,21 +531,9 @@ static void pstore_dump(struct kmsg_dumper *dumper,
                                compressed = true;
                                total_len = zipped_len;
                        } else {
-                               compressed = false;
                                total_len = copy_kmsg_to_buffer(hsize, len);
                        }
                } else {
-                       dst = psinfo->buf;
-                       hsize = sprintf(dst, "%s#%d Part%u\n", why, oopscount,
-                                                                       part);
-                       size = psinfo->bufsize - hsize;
-                       dst += hsize;
-
-                       if (!kmsg_dump_get_buffer(dumper, true, dst,
-                                                               size, &len))
-                               break;
-
-                       compressed = false;
                        total_len = hsize + len;
                }
 
@@ -350,10 +545,7 @@ static void pstore_dump(struct kmsg_dumper *dumper,
                total += total_len;
                part++;
        }
-       if (pstore_cannot_block_path(reason)) {
-               if (is_locked)
-                       spin_unlock_irqrestore(&psinfo->buf_lock, flags);
-       } else
+       if (is_locked)
                spin_unlock_irqrestore(&psinfo->buf_lock, flags);
 }
 
@@ -529,6 +721,7 @@ void pstore_get_records(int quiet)
        int                     failed = 0, rc;
        bool                    compressed;
        int                     unzipped_len = -1;
+       ssize_t                 ecc_notice_size = 0;
 
        if (!psi)
                return;
@@ -538,7 +731,7 @@ void pstore_get_records(int quiet)
                goto out;
 
        while ((size = psi->read(&id, &type, &count, &time, &buf, &compressed,
-                               psi)) > 0) {
+                                &ecc_notice_size, psi)) > 0) {
                if (compressed && (type == PSTORE_TYPE_DMESG)) {
                        if (big_oops_buf)
                                unzipped_len = pstore_decompress(buf,
@@ -546,6 +739,9 @@ void pstore_get_records(int quiet)
                                                        big_oops_buf_sz);
 
                        if (unzipped_len > 0) {
+                               if (ecc_notice_size)
+                                       memcpy(big_oops_buf + unzipped_len,
+                                              buf + size, ecc_notice_size);
                                kfree(buf);
                                buf = big_oops_buf;
                                size = unzipped_len;
@@ -557,7 +753,8 @@ void pstore_get_records(int quiet)
                        }
                }
                rc = pstore_mkfile(type, psi->name, id, count, buf,
-                                 compressed, (size_t)size, time, psi);
+                                  compressed, size + ecc_notice_size,
+                                  time, psi);
                if (unzipped_len < 0) {
                        /* Free buffer other than big oops */
                        kfree(buf);