| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/10/16 08:10 | flow | patch-triage |
4mArgs:null Results: {
"EnableConfigs": [
"ZRAM",
"ZRAM_BACKEND_LZ4",
"ZRAM_BACKEND_LZ4HC",
"ZRAM_BACKEND_ZSTD",
"ZRAM_BACKEND_DEFLATE",
"ZRAM_BACKEND_842",
"ZRAM_BACKEND_LZO",
"ZRAM_WRITEBACK",
"ZRAM_MULTI_COMP"
],
"FocusSymbols": [
"zcomp_compress",
"zcomp_decompress",
"zcomp_cstrm_get",
"zcomp_dstrm_get",
"zcomp_shared_decomp",
"zcomp_create",
"read_compressed_page",
"zram_write_page",
"recompress_slot",
"decompress_bdev_page"
],
"KMSANReasoning": "The patch series refactors zram compression backends and stream management by separating unified compression/decompression streams into dedicated compression (`zcomp_cstrm`) and decompression (`zcomp_dstrm`) streams, supporting concurrent shared decompression for stateless backends, and dedicating a single stream for recompression.\n\nFrom a memory safety and sanitizer perspective:\n1. Uninitialized memory risks:\n - All stream, context, and workspace allocations use zero-initializing allocators (`kzalloc_obj`, `vzalloc`, `__GFP_ZERO`, and `alloc_percpu` which zeroes memory).\n - In `backend_lz4` and `backend_lz4hc` where `vmalloc` is used for compression contexts, `LZ4_compress_fast` / `LZ4_compress_HC` explicitly resets and initializes the working memory (`LZ4_resetStream`, `LZ4HC_init`) or copies from the zero-initialized dictionary stream before execution.\n - In `zcomp_decompress()`, the dynamic bounce buffer allocated via `kmalloc()` is completely populated with `req.src_len` bytes by `sg_copy_to_buffer()` before being passed as `req.src` to the decompressor.\n - No kernel structures, padding, or uninitialized buffers are copied to user space (no `copy_to_user`, ioctl outputs, or netlink attributes involved).\n2. Primary bug risks:\n - The refactoring primarily introduces risks related to locking/concurrency (the new rwsem and mutex synchronization model between CPU hotplug and shared/exclusive decompression modes), lifetime management (CPU hotplug state callbacks), and potential out-of-bounds memory accesses or use-after-free.\n - These risks are fully covered by standard debugging facilities and sanitizers enabled in standard KASAN fuzzing builds (specifically KASAN and LOCKDEP).\n\nBecause no uninitialized memory usage or kernel-to-user info-leak risks are introduced, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false,
"Reasoning": "The patch series refactors zram's compression frontend and backend subsystem, separating compression and decompression streams (cstream vs dstream), introducing shared vs exclusive decompression concurrency with read/write semaphores and mutexes, adding a dedicated per-device recompression stream, and restructuring context allocation across compression backends (lz4, lz4hc, deflate, zstd, etc.). It also adds new lockdep assertions and fallback bounce-buffer logic in zcomp_decompress. The code is entirely reachable via standard virtualized block operations and zram sysfs interfaces, making it an excellent target for fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/10/16 08:10 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 8c8370b2ce1d31f392641b412dca2a03280f3d38\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Oct 16 08:10:28 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c\nindex 3846a04c69d71..f911cd1ca72e4 100644\n--- a/drivers/block/zram/backend_842.c\n+++ b/drivers/block/zram/backend_842.c\n@@ -1,6 +1,6 @@\n // SPDX-License-Identifier: GPL-2.0-or-later\n \n-#define pr_fmt(fmt) \"842: \" fmt\n+#define pr_fmt(fmt) \"zram: 842: \" fmt\n \n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/slab.h\u003e\n@@ -63,8 +63,8 @@ static int decompress_842(struct zcomp_params *params, struct zcomp_ctx *ctx,\n const struct zcomp_ops backend_842 = {\n \t.compress\t= compress_842,\n \t.decompress\t= decompress_842,\n-\t.create_ctx\t= create_842,\n-\t.destroy_ctx\t= destroy_842,\n+\t.create_cctx\t= create_842,\n+\t.destroy_cctx\t= destroy_842,\n \t.setup_params\t= setup_params_842,\n \t.release_params\t= release_params_842,\n \t.name\t\t= \"842\",\ndiff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c\nindex f71b11bcac788..5ffdaf80d8cff 100644\n--- a/drivers/block/zram/backend_deflate.c\n+++ b/drivers/block/zram/backend_deflate.c\n@@ -1,6 +1,6 @@\n // SPDX-License-Identifier: GPL-2.0-or-later\n \n-#define pr_fmt(fmt) \"deflate: \" fmt\n+#define pr_fmt(fmt) \"zram: deflate: \" fmt\n \n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/slab.h\u003e\n@@ -13,11 +13,6 @@\n #define DEFLATE_DEF_WINBITS\t\t(-11)\n #define DEFLATE_DEF_MEMLEVEL\t\tMAX_MEM_LEVEL\n \n-struct deflate_ctx {\n-\tstruct z_stream_s cctx;\n-\tstruct z_stream_s dctx;\n-};\n-\n static void deflate_release_params(struct zcomp_params *params)\n {\n }\n@@ -51,70 +46,98 @@ static int deflate_setup_params(struct zcomp_params *params)\n \treturn 0;\n }\n \n-static void deflate_destroy(struct zcomp_ctx *ctx)\n+static void deflate_destroy_cctx(struct zcomp_ctx *ctx)\n {\n-\tstruct deflate_ctx *zctx = ctx-\u003econtext;\n+\tstruct z_stream_s *cctx = ctx-\u003econtext;\n \n-\tif (!zctx)\n+\tif (!cctx)\n \t\treturn;\n \n-\tif (zctx-\u003ecctx.workspace) {\n-\t\tzlib_deflateEnd(\u0026zctx-\u003ecctx);\n-\t\tvfree(zctx-\u003ecctx.workspace);\n-\t}\n-\tif (zctx-\u003edctx.workspace) {\n-\t\tzlib_inflateEnd(\u0026zctx-\u003edctx);\n-\t\tvfree(zctx-\u003edctx.workspace);\n+\tif (cctx-\u003eworkspace) {\n+\t\tzlib_deflateEnd(cctx);\n+\t\tvfree(cctx-\u003eworkspace);\n \t}\n-\tkfree(zctx);\n+\tkfree(cctx);\n }\n \n-static int deflate_create(struct zcomp_params *params, struct zcomp_ctx *ctx)\n+static int deflate_create_cctx(struct zcomp_params *params,\n+\t\t\t struct zcomp_ctx *ctx)\n {\n-\tstruct deflate_ctx *zctx;\n+\tstruct z_stream_s *cctx;\n \tsize_t sz;\n \tint ret;\n \n-\tzctx = kzalloc_obj(*zctx);\n-\tif (!zctx)\n+\tcctx = kzalloc_obj(*cctx);\n+\tif (!cctx)\n \t\treturn -ENOMEM;\n \n-\tctx-\u003econtext = zctx;\n+\tctx-\u003econtext = cctx;\n \tsz = zlib_deflate_workspacesize(params-\u003edeflate.winbits, MAX_MEM_LEVEL);\n-\tzctx-\u003ecctx.workspace = vzalloc(sz);\n-\tif (!zctx-\u003ecctx.workspace)\n+\tcctx-\u003eworkspace = vzalloc(sz);\n+\tif (!cctx-\u003eworkspace)\n \t\tgoto error;\n \n-\tret = zlib_deflateInit2(\u0026zctx-\u003ecctx, params-\u003elevel, Z_DEFLATED,\n+\tret = zlib_deflateInit2(cctx, params-\u003elevel, Z_DEFLATED,\n \t\t\t\tparams-\u003edeflate.winbits, DEFLATE_DEF_MEMLEVEL,\n \t\t\t\tZ_DEFAULT_STRATEGY);\n \tif (ret != Z_OK)\n \t\tgoto error;\n \n+\treturn 0;\n+\n+error:\n+\tdeflate_destroy_cctx(ctx);\n+\treturn -EINVAL;\n+}\n+\n+static void deflate_destroy_dctx(struct zcomp_ctx *ctx)\n+{\n+\tstruct z_stream_s *dctx = ctx-\u003econtext;\n+\n+\tif (!dctx)\n+\t\treturn;\n+\n+\tif (dctx-\u003eworkspace) {\n+\t\tzlib_inflateEnd(dctx);\n+\t\tvfree(dctx-\u003eworkspace);\n+\t}\n+\tkfree(dctx);\n+}\n+\n+static int deflate_create_dctx(struct zcomp_params *params,\n+\t\t\t struct zcomp_ctx *ctx)\n+{\n+\tstruct z_stream_s *dctx;\n+\tsize_t sz;\n+\tint ret;\n+\n+\tdctx = kzalloc_obj(*dctx);\n+\tif (!dctx)\n+\t\treturn -ENOMEM;\n+\n+\tctx-\u003econtext = dctx;\n \tsz = zlib_inflate_workspacesize();\n-\tzctx-\u003edctx.workspace = vzalloc(sz);\n-\tif (!zctx-\u003edctx.workspace)\n+\tdctx-\u003eworkspace = vzalloc(sz);\n+\tif (!dctx-\u003eworkspace)\n \t\tgoto error;\n \n-\tret = zlib_inflateInit2(\u0026zctx-\u003edctx, params-\u003edeflate.winbits);\n+\tret = zlib_inflateInit2(dctx, params-\u003edeflate.winbits);\n \tif (ret != Z_OK)\n \t\tgoto error;\n \n \treturn 0;\n \n error:\n-\tdeflate_destroy(ctx);\n+\tdeflate_destroy_dctx(ctx);\n \treturn -EINVAL;\n }\n \n static int deflate_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n \t\t\t struct zcomp_req *req)\n {\n-\tstruct deflate_ctx *zctx = ctx-\u003econtext;\n-\tstruct z_stream_s *deflate;\n+\tstruct z_stream_s *deflate = ctx-\u003econtext;\n \tint ret;\n \n-\tdeflate = \u0026zctx-\u003ecctx;\n \tret = zlib_deflateReset(deflate);\n \tif (ret != Z_OK)\n \t\treturn -EINVAL;\n@@ -136,12 +159,9 @@ static int deflate_decompress(struct zcomp_params *params,\n \t\t\t struct zcomp_ctx *ctx,\n \t\t\t struct zcomp_req *req)\n {\n-\tstruct deflate_ctx *zctx = ctx-\u003econtext;\n-\tstruct z_stream_s *inflate;\n+\tstruct z_stream_s *inflate = ctx-\u003econtext;\n \tint ret;\n \n-\tinflate = \u0026zctx-\u003edctx;\n-\n \tret = zlib_inflateReset(inflate);\n \tif (ret != Z_OK)\n \t\treturn -EINVAL;\n@@ -161,8 +181,10 @@ static int deflate_decompress(struct zcomp_params *params,\n const struct zcomp_ops backend_deflate = {\n \t.compress\t= deflate_compress,\n \t.decompress\t= deflate_decompress,\n-\t.create_ctx\t= deflate_create,\n-\t.destroy_ctx\t= deflate_destroy,\n+\t.create_cctx\t= deflate_create_cctx,\n+\t.destroy_cctx\t= deflate_destroy_cctx,\n+\t.create_dctx\t= deflate_create_dctx,\n+\t.destroy_dctx\t= deflate_destroy_dctx,\n \t.setup_params\t= deflate_setup_params,\n \t.release_params\t= deflate_release_params,\n \t.name\t\t= \"deflate\",\ndiff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c\nindex 1e4ad31d39a63..9767482a2fea0 100644\n--- a/drivers/block/zram/backend_lz4.c\n+++ b/drivers/block/zram/backend_lz4.c\n@@ -1,21 +1,13 @@\n // SPDX-License-Identifier: GPL-2.0-or-later\n \n-#define pr_fmt(fmt) \"lz4: \" fmt\n+#define pr_fmt(fmt) \"zram: lz4: \" fmt\n \n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/lz4.h\u003e\n-#include \u003clinux/slab.h\u003e\n #include \u003clinux/vmalloc.h\u003e\n \n #include \"backend_lz4.h\"\n \n-struct lz4_ctx {\n-\tvoid *mem;\n-\n-\tLZ4_streamDecode_t *dstrm;\n-\tLZ4_stream_t *cstrm;\n-};\n-\n static void lz4_release_params(struct zcomp_params *params)\n {\n \tLZ4_stream_t *dict_stream = params-\u003edrv_data;\n@@ -24,7 +16,7 @@ static void lz4_release_params(struct zcomp_params *params)\n \tif (!dict_stream)\n \t\treturn;\n \n-\tkfree(dict_stream);\n+\tvfree(dict_stream);\n }\n \n static int lz4_setup_params(struct zcomp_params *params)\n@@ -42,14 +34,13 @@ static int lz4_setup_params(struct zcomp_params *params)\n \tif (!params-\u003edict || !params-\u003edict_sz)\n \t\treturn 0;\n \n-\tdict_stream = kzalloc_obj(*dict_stream);\n+\tdict_stream = vzalloc(sizeof(*dict_stream));\n \tif (!dict_stream)\n \t\treturn -ENOMEM;\n \n-\tret = LZ4_loadDict(dict_stream,\n-\t\t\t params-\u003edict, params-\u003edict_sz);\n+\tret = LZ4_loadDict(dict_stream, params-\u003edict, params-\u003edict_sz);\n \tif (ret != params-\u003edict_sz) {\n-\t\tkfree(dict_stream);\n+\t\tvfree(dict_stream);\n \t\treturn -EINVAL;\n \t}\n \tparams-\u003edrv_data = dict_stream;\n@@ -59,61 +50,32 @@ static int lz4_setup_params(struct zcomp_params *params)\n \n static void lz4_destroy(struct zcomp_ctx *ctx)\n {\n-\tstruct lz4_ctx *zctx = ctx-\u003econtext;\n-\n-\tif (!zctx)\n-\t\treturn;\n-\n-\tvfree(zctx-\u003emem);\n-\tkfree(zctx-\u003edstrm);\n-\tkfree(zctx-\u003ecstrm);\n-\tkfree(zctx);\n+\tvfree(ctx-\u003econtext);\n }\n \n static int lz4_create(struct zcomp_params *params, struct zcomp_ctx *ctx)\n {\n-\tstruct lz4_ctx *zctx;\n+\tBUILD_BUG_ON(sizeof(LZ4_stream_t) != LZ4_MEM_COMPRESS);\n \n-\tzctx = kzalloc_obj(*zctx);\n-\tif (!zctx)\n+\tctx-\u003econtext = vmalloc(LZ4_MEM_COMPRESS);\n+\tif (!ctx-\u003econtext)\n \t\treturn -ENOMEM;\n-\n-\tctx-\u003econtext = zctx;\n-\tif (params-\u003edict_sz == 0) {\n-\t\tzctx-\u003emem = vmalloc(LZ4_MEM_COMPRESS);\n-\t\tif (!zctx-\u003emem)\n-\t\t\tgoto error;\n-\t} else {\n-\t\tzctx-\u003edstrm = kzalloc_obj(*zctx-\u003edstrm);\n-\t\tif (!zctx-\u003edstrm)\n-\t\t\tgoto error;\n-\n-\t\tzctx-\u003ecstrm = kzalloc_obj(*zctx-\u003ecstrm);\n-\t\tif (!zctx-\u003ecstrm)\n-\t\t\tgoto error;\n-\t}\n-\n \treturn 0;\n-\n-error:\n-\tlz4_destroy(ctx);\n-\treturn -ENOMEM;\n }\n \n static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n \t\t\tstruct zcomp_req *req)\n {\n-\tstruct lz4_ctx *zctx = ctx-\u003econtext;\n \tint ret;\n \n-\tif (!zctx-\u003ecstrm) {\n+\tif (!params-\u003edrv_data) {\n \t\tret = LZ4_compress_fast(req-\u003esrc, req-\u003edst, req-\u003esrc_len,\n \t\t\t\t\treq-\u003edst_len, params-\u003elevel,\n-\t\t\t\t\tzctx-\u003emem);\n+\t\t\t\t\tctx-\u003econtext);\n \t} else {\n \t\t/* Cstrm needs to be reset */\n-\t\tmemcpy(zctx-\u003ecstrm, params-\u003edrv_data, sizeof(*zctx-\u003ecstrm));\n-\t\tret = LZ4_compress_fast_continue(zctx-\u003ecstrm, req-\u003esrc,\n+\t\tmemcpy(ctx-\u003econtext, params-\u003edrv_data, sizeof(LZ4_stream_t));\n+\t\tret = LZ4_compress_fast_continue(ctx-\u003econtext, req-\u003esrc,\n \t\t\t\t\t\t req-\u003edst, req-\u003esrc_len,\n \t\t\t\t\t\t req-\u003edst_len, params-\u003elevel);\n \t}\n@@ -126,22 +88,11 @@ static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n static int lz4_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n \t\t\t struct zcomp_req *req)\n {\n-\tstruct lz4_ctx *zctx = ctx-\u003econtext;\n \tint ret;\n \n-\tif (!zctx-\u003edstrm) {\n-\t\tret = LZ4_decompress_safe(req-\u003esrc, req-\u003edst, req-\u003esrc_len,\n-\t\t\t\t\t req-\u003edst_len);\n-\t} else {\n-\t\t/* Dstrm needs to be reset */\n-\t\tret = LZ4_setStreamDecode(zctx-\u003edstrm, params-\u003edict,\n-\t\t\t\t\t params-\u003edict_sz);\n-\t\tif (!ret)\n-\t\t\treturn -EINVAL;\n-\t\tret = LZ4_decompress_safe_continue(zctx-\u003edstrm, req-\u003esrc,\n-\t\t\t\t\t\t req-\u003edst, req-\u003esrc_len,\n-\t\t\t\t\t\t req-\u003edst_len);\n-\t}\n+\tret = LZ4_decompress_safe_usingDict(req-\u003esrc, req-\u003edst, req-\u003esrc_len,\n+\t\t\t\t\t req-\u003edst_len, params-\u003edict,\n+\t\t\t\t\t params-\u003edict_sz);\n \tif (ret \u003c 0)\n \t\treturn -EINVAL;\n \treturn 0;\n@@ -150,8 +101,8 @@ static int lz4_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n const struct zcomp_ops backend_lz4 = {\n \t.compress\t= lz4_compress,\n \t.decompress\t= lz4_decompress,\n-\t.create_ctx\t= lz4_create,\n-\t.destroy_ctx\t= lz4_destroy,\n+\t.create_cctx\t= lz4_create,\n+\t.destroy_cctx\t= lz4_destroy,\n \t.setup_params\t= lz4_setup_params,\n \t.release_params\t= lz4_release_params,\n \t.name\t\t= \"lz4\",\ndiff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c\nindex d8aa01bb258fe..2d1364a29b27b 100644\n--- a/drivers/block/zram/backend_lz4hc.c\n+++ b/drivers/block/zram/backend_lz4hc.c\n@@ -1,27 +1,29 @@\n // SPDX-License-Identifier: GPL-2.0-or-later\n \n-#define pr_fmt(fmt) \"lz4hc: \" fmt\n+#define pr_fmt(fmt) \"zram: lz4hc: \" fmt\n \n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/lz4.h\u003e\n-#include \u003clinux/slab.h\u003e\n #include \u003clinux/vmalloc.h\u003e\n \n #include \"backend_lz4hc.h\"\n \n-struct lz4hc_ctx {\n-\tvoid *mem;\n-\n-\tLZ4_streamDecode_t *dstrm;\n-\tLZ4_streamHC_t *cstrm;\n-};\n-\n static void lz4hc_release_params(struct zcomp_params *params)\n {\n+\tLZ4_streamHC_t *dict_stream = params-\u003edrv_data;\n+\n+\tparams-\u003edrv_data = NULL;\n+\tif (!dict_stream)\n+\t\treturn;\n+\n+\tvfree(dict_stream);\n }\n \n static int lz4hc_setup_params(struct zcomp_params *params)\n {\n+\tLZ4_streamHC_t *dict_stream;\n+\tint ret;\n+\n \tif (params-\u003elevel == ZCOMP_PARAM_NOT_SET) {\n \t\tparams-\u003elevel = LZ4HC_DEFAULT_CLEVEL;\n \t} else if (params-\u003elevel \u003c 1 || params-\u003elevel \u003e LZ4HC_MAX_CLEVEL) {\n@@ -35,70 +37,52 @@ static int lz4hc_setup_params(struct zcomp_params *params)\n \t\treturn -EINVAL;\n \t}\n \n+\tif (!params-\u003edict || !params-\u003edict_sz)\n+\t\treturn 0;\n+\n+\tdict_stream = vzalloc(sizeof(*dict_stream));\n+\tif (!dict_stream)\n+\t\treturn -ENOMEM;\n+\n+\tLZ4_resetStreamHC(dict_stream, params-\u003elevel);\n+\tret = LZ4_loadDictHC(dict_stream, params-\u003edict, params-\u003edict_sz);\n+\tif (ret != params-\u003edict_sz) {\n+\t\tvfree(dict_stream);\n+\t\treturn -EINVAL;\n+\t}\n+\tparams-\u003edrv_data = dict_stream;\n+\n \treturn 0;\n }\n \n static void lz4hc_destroy(struct zcomp_ctx *ctx)\n {\n-\tstruct lz4hc_ctx *zctx = ctx-\u003econtext;\n-\n-\tif (!zctx)\n-\t\treturn;\n-\n-\tkfree(zctx-\u003edstrm);\n-\tkfree(zctx-\u003ecstrm);\n-\tvfree(zctx-\u003emem);\n-\tkfree(zctx);\n+\tvfree(ctx-\u003econtext);\n }\n \n static int lz4hc_create(struct zcomp_params *params, struct zcomp_ctx *ctx)\n {\n-\tstruct lz4hc_ctx *zctx;\n+\tBUILD_BUG_ON(sizeof(LZ4_streamHC_t) != LZ4HC_MEM_COMPRESS);\n \n-\tzctx = kzalloc_obj(*zctx);\n-\tif (!zctx)\n+\tctx-\u003econtext = vmalloc(LZ4HC_MEM_COMPRESS);\n+\tif (!ctx-\u003econtext)\n \t\treturn -ENOMEM;\n-\n-\tctx-\u003econtext = zctx;\n-\tif (params-\u003edict_sz == 0) {\n-\t\tzctx-\u003emem = vmalloc(LZ4HC_MEM_COMPRESS);\n-\t\tif (!zctx-\u003emem)\n-\t\t\tgoto error;\n-\t} else {\n-\t\tzctx-\u003edstrm = kzalloc_obj(*zctx-\u003edstrm);\n-\t\tif (!zctx-\u003edstrm)\n-\t\t\tgoto error;\n-\n-\t\tzctx-\u003ecstrm = kzalloc_obj(*zctx-\u003ecstrm);\n-\t\tif (!zctx-\u003ecstrm)\n-\t\t\tgoto error;\n-\t}\n-\n \treturn 0;\n-\n-error:\n-\tlz4hc_destroy(ctx);\n-\treturn -EINVAL;\n }\n \n static int lz4hc_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n \t\t\t struct zcomp_req *req)\n {\n-\tstruct lz4hc_ctx *zctx = ctx-\u003econtext;\n \tint ret;\n \n-\tif (!zctx-\u003ecstrm) {\n+\tif (!params-\u003edrv_data) {\n \t\tret = LZ4_compress_HC(req-\u003esrc, req-\u003edst, req-\u003esrc_len,\n \t\t\t\t req-\u003edst_len, params-\u003elevel,\n-\t\t\t\t zctx-\u003emem);\n+\t\t\t\t ctx-\u003econtext);\n \t} else {\n \t\t/* Cstrm needs to be reset */\n-\t\tLZ4_resetStreamHC(zctx-\u003ecstrm, params-\u003elevel);\n-\t\tret = LZ4_loadDictHC(zctx-\u003ecstrm, params-\u003edict,\n-\t\t\t\t params-\u003edict_sz);\n-\t\tif (ret != params-\u003edict_sz)\n-\t\t\treturn -EINVAL;\n-\t\tret = LZ4_compress_HC_continue(zctx-\u003ecstrm, req-\u003esrc, req-\u003edst,\n+\t\tmemcpy(ctx-\u003econtext, params-\u003edrv_data, sizeof(LZ4_streamHC_t));\n+\t\tret = LZ4_compress_HC_continue(ctx-\u003econtext, req-\u003esrc, req-\u003edst,\n \t\t\t\t\t req-\u003esrc_len, req-\u003edst_len);\n \t}\n \tif (!ret)\n@@ -110,22 +94,11 @@ static int lz4hc_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n static int lz4hc_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n \t\t\t struct zcomp_req *req)\n {\n-\tstruct lz4hc_ctx *zctx = ctx-\u003econtext;\n \tint ret;\n \n-\tif (!zctx-\u003edstrm) {\n-\t\tret = LZ4_decompress_safe(req-\u003esrc, req-\u003edst, req-\u003esrc_len,\n-\t\t\t\t\t req-\u003edst_len);\n-\t} else {\n-\t\t/* Dstrm needs to be reset */\n-\t\tret = LZ4_setStreamDecode(zctx-\u003edstrm, params-\u003edict,\n-\t\t\t\t\t params-\u003edict_sz);\n-\t\tif (!ret)\n-\t\t\treturn -EINVAL;\n-\t\tret = LZ4_decompress_safe_continue(zctx-\u003edstrm, req-\u003esrc,\n-\t\t\t\t\t\t req-\u003edst, req-\u003esrc_len,\n-\t\t\t\t\t\t req-\u003edst_len);\n-\t}\n+\tret = LZ4_decompress_safe_usingDict(req-\u003esrc, req-\u003edst, req-\u003esrc_len,\n+\t\t\t\t\t req-\u003edst_len, params-\u003edict,\n+\t\t\t\t\t params-\u003edict_sz);\n \tif (ret \u003c 0)\n \t\treturn -EINVAL;\n \treturn 0;\n@@ -134,8 +107,8 @@ static int lz4hc_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n const struct zcomp_ops backend_lz4hc = {\n \t.compress\t= lz4hc_compress,\n \t.decompress\t= lz4hc_decompress,\n-\t.create_ctx\t= lz4hc_create,\n-\t.destroy_ctx\t= lz4hc_destroy,\n+\t.create_cctx\t= lz4hc_create,\n+\t.destroy_cctx\t= lz4hc_destroy,\n \t.setup_params\t= lz4hc_setup_params,\n \t.release_params\t= lz4hc_release_params,\n \t.name\t\t= \"lz4hc\",\ndiff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c\nindex d83f92cf757cf..e703560d9ecde 100644\n--- a/drivers/block/zram/backend_lzo.c\n+++ b/drivers/block/zram/backend_lzo.c\n@@ -1,6 +1,6 @@\n // SPDX-License-Identifier: GPL-2.0-or-later\n \n-#define pr_fmt(fmt) \"lzo: \" fmt\n+#define pr_fmt(fmt) \"zram: lzo: \" fmt\n \n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/slab.h\u003e\n@@ -61,8 +61,8 @@ static int lzo_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n const struct zcomp_ops backend_lzo = {\n \t.compress\t= lzo_compress,\n \t.decompress\t= lzo_decompress,\n-\t.create_ctx\t= lzo_create,\n-\t.destroy_ctx\t= lzo_destroy,\n+\t.create_cctx\t= lzo_create,\n+\t.destroy_cctx\t= lzo_destroy,\n \t.setup_params\t= lzo_setup_params,\n \t.release_params\t= lzo_release_params,\n \t.name\t\t= \"lzo\",\ndiff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c\nindex 1b120d062c928..e95026e992022 100644\n--- a/drivers/block/zram/backend_lzorle.c\n+++ b/drivers/block/zram/backend_lzorle.c\n@@ -1,6 +1,6 @@\n // SPDX-License-Identifier: GPL-2.0-or-later\n \n-#define pr_fmt(fmt) \"lzo-rle: \" fmt\n+#define pr_fmt(fmt) \"zram: lzo-rle: \" fmt\n \n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/slab.h\u003e\n@@ -61,8 +61,8 @@ static int lzorle_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n const struct zcomp_ops backend_lzorle = {\n \t.compress\t= lzorle_compress,\n \t.decompress\t= lzorle_decompress,\n-\t.create_ctx\t= lzorle_create,\n-\t.destroy_ctx\t= lzorle_destroy,\n+\t.create_cctx\t= lzorle_create,\n+\t.destroy_cctx\t= lzorle_destroy,\n \t.setup_params\t= lzorle_setup_params,\n \t.release_params\t= lzorle_release_params,\n \t.name\t\t= \"lzo-rle\",\ndiff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c\nindex 08da3810cffd8..d55615f343004 100644\n--- a/drivers/block/zram/backend_zstd.c\n+++ b/drivers/block/zram/backend_zstd.c\n@@ -1,6 +1,6 @@\n // SPDX-License-Identifier: GPL-2.0-or-later\n \n-#define pr_fmt(fmt) \"zstd: \" fmt\n+#define pr_fmt(fmt) \"zram: zstd: \" fmt\n \n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/slab.h\u003e\n@@ -10,10 +10,8 @@\n #include \"backend_zstd.h\"\n \n struct zstd_ctx {\n-\tzstd_cctx *cctx;\n-\tzstd_dctx *dctx;\n-\tvoid *cctx_mem;\n-\tvoid *dctx_mem;\n+\tvoid *ctx;\n+\tvoid *mem;\n };\n \n struct zstd_params {\n@@ -26,10 +24,17 @@ struct zstd_params {\n /*\n * For C/D dictionaries we need to provide zstd with zstd_custom_mem,\n * which zstd uses internally to allocate/free memory when needed.\n+ *\n+ * zstd allocations (C/D dictionaries and contexts) are large and\n+ * long-lived. Don't use kvzalloc() for them: when enough physically\n+ * contiguous memory is available it serves them from kmalloc(), which\n+ * rounds them up to a power-of-two number of pages.\n */\n static void *zstd_custom_alloc(void *opaque, size_t size)\n {\n-\treturn kvzalloc(size, GFP_NOIO | __GFP_NOWARN);\n+\tif (size \u003c= PAGE_SIZE)\n+\t\treturn kzalloc(size, GFP_NOIO | __GFP_NOWARN);\n+\treturn __vmalloc(size, GFP_NOIO | __GFP_NOWARN | __GFP_ZERO);\n }\n \n static void zstd_custom_free(void *opaque, void *address)\n@@ -76,6 +81,9 @@ static int zstd_setup_params(struct zcomp_params *params)\n \tprm = zstd_get_cparams(params-\u003elevel, PAGE_SIZE,\n \t\t\t params-\u003edict_sz);\n \n+\tif (!params-\u003edict || !params-\u003edict_sz)\n+\t\treturn 0;\n+\n \tzp-\u003ecdict = zstd_create_cdict_byreference(params-\u003edict,\n \t\t\t\t\t\t params-\u003edict_sz,\n \t\t\t\t\t\t prm,\n@@ -95,7 +103,7 @@ static int zstd_setup_params(struct zcomp_params *params)\n \treturn -EINVAL;\n }\n \n-static void zstd_destroy(struct zcomp_ctx *ctx)\n+static void zstd_destroy_cctx(struct zcomp_ctx *ctx)\n {\n \tstruct zstd_ctx *zctx = ctx-\u003econtext;\n \n@@ -103,30 +111,23 @@ static void zstd_destroy(struct zcomp_ctx *ctx)\n \t\treturn;\n \n \t/*\n-\t * If -\u003ecctx_mem and -\u003edctx_mem were allocated then we didn't use\n-\t * C/D dictionary and -\u003ecctx / -\u003edctx were \"embedded\" into these\n-\t * buffers.\n+\t * If -\u003emem was allocated then we didn't use C/D dictionary and\n+\t * -\u003ectx was \"embedded\" into this buffer.\n \t *\n-\t * If otherwise then we need to explicitly release -\u003ecctx / -\u003edctx.\n+\t * If otherwise then we need to explicitly release -\u003ectx.\n \t */\n-\tif (zctx-\u003ecctx_mem)\n-\t\tvfree(zctx-\u003ecctx_mem);\n-\telse\n-\t\tzstd_free_cctx(zctx-\u003ecctx);\n-\n-\tif (zctx-\u003edctx_mem)\n-\t\tvfree(zctx-\u003edctx_mem);\n+\tif (zctx-\u003emem)\n+\t\tvfree(zctx-\u003emem);\n \telse\n-\t\tzstd_free_dctx(zctx-\u003edctx);\n+\t\tzstd_free_cctx(zctx-\u003ectx);\n \n \tkfree(zctx);\n }\n \n-static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx)\n+static int zstd_create_cctx(struct zcomp_params *params, struct zcomp_ctx *ctx)\n {\n+\tstruct zstd_params *zp = params-\u003edrv_data;\n \tstruct zstd_ctx *zctx;\n-\tzstd_parameters prm;\n-\tsize_t sz;\n \n \tzctx = kzalloc_obj(*zctx);\n \tif (!zctx)\n@@ -134,40 +135,74 @@ static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx)\n \n \tctx-\u003econtext = zctx;\n \tif (params-\u003edict_sz == 0) {\n-\t\tprm = zstd_get_params(params-\u003elevel, PAGE_SIZE);\n-\t\tsz = zstd_cctx_workspace_bound(\u0026prm.cParams);\n-\t\tzctx-\u003ecctx_mem = vzalloc(sz);\n-\t\tif (!zctx-\u003ecctx_mem)\n-\t\t\tgoto error;\n+\t\tzstd_parameters prm = zp-\u003ecprm;\n+\t\tsize_t sz = zstd_cctx_workspace_bound(\u0026prm.cParams);\n \n-\t\tzctx-\u003ecctx = zstd_init_cctx(zctx-\u003ecctx_mem, sz);\n-\t\tif (!zctx-\u003ecctx)\n+\t\tzctx-\u003emem = vzalloc(sz);\n+\t\tif (!zctx-\u003emem)\n \t\t\tgoto error;\n \n-\t\tsz = zstd_dctx_workspace_bound();\n-\t\tzctx-\u003edctx_mem = vzalloc(sz);\n-\t\tif (!zctx-\u003edctx_mem)\n-\t\t\tgoto error;\n-\n-\t\tzctx-\u003edctx = zstd_init_dctx(zctx-\u003edctx_mem, sz);\n-\t\tif (!zctx-\u003edctx)\n+\t\tzctx-\u003ectx = zstd_init_cctx(zctx-\u003emem, sz);\n+\t\tif (!zctx-\u003ectx)\n \t\t\tgoto error;\n \t} else {\n-\t\tstruct zstd_params *zp = params-\u003edrv_data;\n+\t\tzctx-\u003ectx = zstd_create_cctx_advanced(zp-\u003ecustom_mem);\n+\t\tif (!zctx-\u003ectx)\n+\t\t\tgoto error;\n+\t}\n+\n+\treturn 0;\n+\n+error:\n+\tzstd_destroy_cctx(ctx);\n+\treturn -EINVAL;\n+}\n \n-\t\tzctx-\u003ecctx = zstd_create_cctx_advanced(zp-\u003ecustom_mem);\n-\t\tif (!zctx-\u003ecctx)\n+static void zstd_destroy_dctx(struct zcomp_ctx *ctx)\n+{\n+\tstruct zstd_ctx *zctx = ctx-\u003econtext;\n+\n+\tif (!zctx)\n+\t\treturn;\n+\n+\tif (zctx-\u003emem)\n+\t\tvfree(zctx-\u003emem);\n+\telse\n+\t\tzstd_free_dctx(zctx-\u003ectx);\n+\n+\tkfree(zctx);\n+}\n+\n+static int zstd_create_dctx(struct zcomp_params *params, struct zcomp_ctx *ctx)\n+{\n+\tstruct zstd_params *zp = params-\u003edrv_data;\n+\tstruct zstd_ctx *zctx;\n+\n+\tzctx = kzalloc_obj(*zctx);\n+\tif (!zctx)\n+\t\treturn -ENOMEM;\n+\n+\tctx-\u003econtext = zctx;\n+\tif (params-\u003edict_sz == 0) {\n+\t\tsize_t sz = zstd_dctx_workspace_bound();\n+\n+\t\tzctx-\u003emem = vzalloc(sz);\n+\t\tif (!zctx-\u003emem)\n \t\t\tgoto error;\n \n-\t\tzctx-\u003edctx = zstd_create_dctx_advanced(zp-\u003ecustom_mem);\n-\t\tif (!zctx-\u003edctx)\n+\t\tzctx-\u003ectx = zstd_init_dctx(zctx-\u003emem, sz);\n+\t\tif (!zctx-\u003ectx)\n+\t\t\tgoto error;\n+\t} else {\n+\t\tzctx-\u003ectx = zstd_create_dctx_advanced(zp-\u003ecustom_mem);\n+\t\tif (!zctx-\u003ectx)\n \t\t\tgoto error;\n \t}\n \n \treturn 0;\n \n error:\n-\tzstd_destroy(ctx);\n+\tzstd_destroy_dctx(ctx);\n \treturn -EINVAL;\n }\n \n@@ -179,10 +214,10 @@ static int zstd_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n \tsize_t ret;\n \n \tif (params-\u003edict_sz == 0)\n-\t\tret = zstd_compress_cctx(zctx-\u003ecctx, req-\u003edst, req-\u003edst_len,\n+\t\tret = zstd_compress_cctx(zctx-\u003ectx, req-\u003edst, req-\u003edst_len,\n \t\t\t\t\t req-\u003esrc, req-\u003esrc_len, \u0026zp-\u003ecprm);\n \telse\n-\t\tret = zstd_compress_using_cdict(zctx-\u003ecctx, req-\u003edst,\n+\t\tret = zstd_compress_using_cdict(zctx-\u003ectx, req-\u003edst,\n \t\t\t\t\t\treq-\u003edst_len, req-\u003esrc,\n \t\t\t\t\t\treq-\u003esrc_len,\n \t\t\t\t\t\tzp-\u003ecdict);\n@@ -200,10 +235,10 @@ static int zstd_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n \tsize_t ret;\n \n \tif (params-\u003edict_sz == 0)\n-\t\tret = zstd_decompress_dctx(zctx-\u003edctx, req-\u003edst, req-\u003edst_len,\n+\t\tret = zstd_decompress_dctx(zctx-\u003ectx, req-\u003edst, req-\u003edst_len,\n \t\t\t\t\t req-\u003esrc, req-\u003esrc_len);\n \telse\n-\t\tret = zstd_decompress_using_ddict(zctx-\u003edctx, req-\u003edst,\n+\t\tret = zstd_decompress_using_ddict(zctx-\u003ectx, req-\u003edst,\n \t\t\t\t\t\t req-\u003edst_len, req-\u003esrc,\n \t\t\t\t\t\t req-\u003esrc_len, zp-\u003eddict);\n \tif (zstd_is_error(ret))\n@@ -214,8 +249,10 @@ static int zstd_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n const struct zcomp_ops backend_zstd = {\n \t.compress\t= zstd_compress,\n \t.decompress\t= zstd_decompress,\n-\t.create_ctx\t= zstd_create,\n-\t.destroy_ctx\t= zstd_destroy,\n+\t.create_cctx\t= zstd_create_cctx,\n+\t.destroy_cctx\t= zstd_destroy_cctx,\n+\t.create_dctx\t= zstd_create_dctx,\n+\t.destroy_dctx\t= zstd_destroy_dctx,\n \t.setup_params\t= zstd_setup_params,\n \t.release_params\t= zstd_release_params,\n \t.name\t\t= \"zstd\",\ndiff --git a/drivers/block/zram/zcomp.c b/drivers/block/zram/zcomp.c\nindex 028e2f0f587e3..81f21bad787e7 100644\n--- a/drivers/block/zram/zcomp.c\n+++ b/drivers/block/zram/zcomp.c\n@@ -45,30 +45,65 @@ static const struct zcomp_ops *backends[] = {\n \tNULL\n };\n \n-static void zcomp_strm_free(struct zcomp *comp, struct zcomp_strm *zstrm)\n+static void zcomp_cstrm_free(struct zcomp *comp, struct zcomp_cstrm *zstrm)\n {\n-\tcomp-\u003eops-\u003edestroy_ctx(\u0026zstrm-\u003ectx);\n-\tvfree(zstrm-\u003elocal_copy);\n+\tif (comp-\u003eops-\u003edestroy_cctx)\n+\t\tcomp-\u003eops-\u003edestroy_cctx(\u0026zstrm-\u003ectx);\n \tvfree(zstrm-\u003ebuffer);\n \tzstrm-\u003ebuffer = NULL;\n+\tzstrm-\u003ectx.context = NULL;\n }\n \n-static int zcomp_strm_init(struct zcomp *comp, struct zcomp_strm *zstrm)\n+static int zcomp_cstrm_init(struct zcomp *comp, struct zcomp_cstrm *zstrm)\n {\n \tint ret;\n \n-\tret = comp-\u003eops-\u003ecreate_ctx(comp-\u003eparams, \u0026zstrm-\u003ectx);\n-\tif (ret)\n-\t\treturn ret;\n+\tif (comp-\u003eops-\u003ecreate_cctx) {\n+\t\tret = comp-\u003eops-\u003ecreate_cctx(comp-\u003eparams, \u0026zstrm-\u003ectx);\n+\t\tif (ret) {\n+\t\t\t/* backend has released partially created context */\n+\t\t\tzstrm-\u003ectx.context = NULL;\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n \n-\tzstrm-\u003elocal_copy = vzalloc(PAGE_SIZE);\n \t/*\n \t * allocate 2 pages. 1 for compressed data, plus 1 extra for the\n \t * case when compressed size is larger than the original one\n \t */\n \tzstrm-\u003ebuffer = vzalloc(2 * PAGE_SIZE);\n-\tif (!zstrm-\u003ebuffer || !zstrm-\u003elocal_copy) {\n-\t\tzcomp_strm_free(comp, zstrm);\n+\tif (!zstrm-\u003ebuffer) {\n+\t\tzcomp_cstrm_free(comp, zstrm);\n+\t\treturn -ENOMEM;\n+\t}\n+\treturn 0;\n+}\n+\n+static void zcomp_dstrm_free(struct zcomp *comp, struct zcomp_dstrm *zstrm)\n+{\n+\tif (comp-\u003eops-\u003edestroy_dctx)\n+\t\tcomp-\u003eops-\u003edestroy_dctx(\u0026zstrm-\u003ectx);\n+\tvfree(zstrm-\u003elocal_copy);\n+\tzstrm-\u003elocal_copy = NULL;\n+\tzstrm-\u003ectx.context = NULL;\n+}\n+\n+static int zcomp_dstrm_init(struct zcomp *comp, struct zcomp_dstrm *zstrm)\n+{\n+\tint ret;\n+\n+\tif (comp-\u003eops-\u003ecreate_dctx) {\n+\t\tret = comp-\u003eops-\u003ecreate_dctx(comp-\u003eparams, \u0026zstrm-\u003ectx);\n+\t\tif (ret) {\n+\t\t\t/* backend has released partially created context */\n+\t\t\tzstrm-\u003ectx.context = NULL;\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tzstrm-\u003elocal_copy = vzalloc(PAGE_SIZE);\n+\tif (!zstrm-\u003elocal_copy) {\n+\t\tzcomp_dstrm_free(comp, zstrm);\n \t\treturn -ENOMEM;\n \t}\n \treturn 0;\n@@ -114,10 +149,15 @@ ssize_t zcomp_available_show(const char *comp, char *buf, ssize_t at)\n \treturn at;\n }\n \n-struct zcomp_strm *zcomp_stream_get(struct zcomp *comp)\n+struct zcomp_cstrm *zcomp_cstrm_get(struct zcomp *comp)\n {\n+\tif (comp-\u003erecomp_cstream) {\n+\t\tmutex_lock(\u0026comp-\u003erecomp_cstream-\u003elock);\n+\t\treturn comp-\u003erecomp_cstream;\n+\t}\n+\n \tfor (;;) {\n-\t\tstruct zcomp_strm *zstrm = raw_cpu_ptr(comp-\u003estream);\n+\t\tstruct zcomp_cstrm *zstrm = raw_cpu_ptr(comp-\u003ecstream);\n \n \t\t/*\n \t\t * Inspired by zswap\n@@ -130,18 +170,62 @@ struct zcomp_strm *zcomp_stream_get(struct zcomp *comp)\n \t\t * so then unlock and re-try on the current CPU.\n \t\t */\n \t\tmutex_lock(\u0026zstrm-\u003elock);\n-\t\tif (likely(zstrm-\u003ebuffer))\n+\t\tif (zstrm-\u003ebuffer)\n \t\t\treturn zstrm;\n \t\tmutex_unlock(\u0026zstrm-\u003elock);\n \t}\n }\n \n-void zcomp_stream_put(struct zcomp_strm *zstrm)\n+void zcomp_cstrm_put(struct zcomp_cstrm *zstrm)\n {\n \tmutex_unlock(\u0026zstrm-\u003elock);\n }\n \n-int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm,\n+bool zcomp_shared_decomp(struct zcomp *comp)\n+{\n+\t/*\n+\t * Shared decompression is only safe when the backend has no\n+\t * mutable per-stream decompression context.\n+\t */\n+\treturn !comp-\u003eops-\u003ecreate_dctx;\n+}\n+\n+struct zcomp_dstrm *zcomp_dstrm_get(struct zcomp *comp,\n+\t\t\t\t enum zcomp_dstrm_mode mode)\n+{\n+\tfor (;;) {\n+\t\tstruct zcomp_dstrm *zstrm = raw_cpu_ptr(comp-\u003edstream);\n+\n+\t\t/*\n+\t\t * Pin the stream. The write side of -\u003elock is only taken\n+\t\t * by zcomp_cpu_dead(), so this never contends with other\n+\t\t * decompression requests, regardless of their mode.\n+\t\t */\n+\t\tdown_read(\u0026zstrm-\u003elock);\n+\t\tif (!zstrm-\u003elocal_copy) {\n+\t\t\t/* The stream has been destroyed, re-try */\n+\t\t\tup_read(\u0026zstrm-\u003elock);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Exclusive requests additionally own -\u003elocal_copy and\n+\t\t * -\u003ectx and hence can only wait for each other.\n+\t\t */\n+\t\tif (mode == ZCOMP_DSTRM_EXCLUSIVE)\n+\t\t\tmutex_lock(\u0026zstrm-\u003eexcl_lock);\n+\t\treturn zstrm;\n+\t}\n+}\n+\n+void zcomp_dstrm_put(struct zcomp_dstrm *zstrm, enum zcomp_dstrm_mode mode)\n+{\n+\tif (mode == ZCOMP_DSTRM_EXCLUSIVE)\n+\t\tmutex_unlock(\u0026zstrm-\u003eexcl_lock);\n+\tup_read(\u0026zstrm-\u003elock);\n+}\n+\n+int zcomp_compress(struct zcomp *comp, struct zcomp_cstrm *zstrm,\n \t\t const void *src, unsigned int *dst_len)\n {\n \tstruct zcomp_req req = {\n@@ -159,7 +243,16 @@ int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm,\n \treturn ret;\n }\n \n-int zcomp_decompress(struct zcomp *comp, struct zcomp_strm *zstrm,\n+static int zcomp_decompress_buf(struct zcomp *comp, struct zcomp_dstrm *zstrm,\n+\t\t\t\tstruct scatterlist *sg, void *buf,\n+\t\t\t\tstruct zcomp_req *req)\n+{\n+\tsg_copy_to_buffer(sg, 2, buf, req-\u003esrc_len);\n+\treq-\u003esrc = buf;\n+\treturn comp-\u003eops-\u003edecompress(comp-\u003eparams, \u0026zstrm-\u003ectx, req);\n+}\n+\n+int zcomp_decompress(struct zcomp *comp, struct zcomp_dstrm *zstrm,\n \t\t struct scatterlist *sg, unsigned int src_len, void *dst)\n {\n \tstruct zcomp_req req = {\n@@ -167,65 +260,138 @@ int zcomp_decompress(struct zcomp *comp, struct zcomp_strm *zstrm,\n \t\t.src_len = src_len,\n \t\t.dst_len = PAGE_SIZE,\n \t};\n-\tvoid *src = NULL;\n+\tvoid *src;\n \tint ret;\n \n \tmight_sleep();\n+\tlockdep_assert_held(\u0026zstrm-\u003elock);\n+\tif (!zcomp_shared_decomp(comp))\n+\t\tlockdep_assert_held(\u0026zstrm-\u003eexcl_lock);\n \n \tif (sg_is_last(sg)) {\n \t\t/* the object is contained within one page, read it in-place */\n \t\tsrc = kmap_local_page(sg_page(sg));\n \t\treq.src = src + sg-\u003eoffset;\n-\t} else {\n-\t\t/* the object spans two pages, linearize it into local copy */\n-\t\tsg_copy_to_buffer(sg, 2, zstrm-\u003elocal_copy, src_len);\n-\t\treq.src = zstrm-\u003elocal_copy;\n+\t\tret = comp-\u003eops-\u003edecompress(comp-\u003eparams, \u0026zstrm-\u003ectx, \u0026req);\n+\t\tkunmap_local(src);\n+\t\treturn ret;\n \t}\n \n-\tret = comp-\u003eops-\u003edecompress(comp-\u003eparams, \u0026zstrm-\u003ectx, \u0026req);\n-\tif (src)\n-\t\tkunmap_local(src);\n+\t/* exclusive mode already holds -\u003eexcl_lock */\n+\tif (!zcomp_shared_decomp(comp))\n+\t\treturn zcomp_decompress_buf(comp, zstrm, sg, zstrm-\u003elocal_copy,\n+\t\t\t\t\t \u0026req);\n+\n+\t/* shared mode: try a per-request bounce buffer first */\n+\tsrc = kmalloc(src_len, GFP_NOWAIT | __GFP_NOWARN);\n+\tif (src) {\n+\t\tret = zcomp_decompress_buf(comp, zstrm, sg, src, \u0026req);\n+\t\tkfree(src);\n+\t\treturn ret;\n+\t}\n+\n+\t/* fall back to -\u003elocal_copy serialized by -\u003eexcl_lock */\n+\tmutex_lock(\u0026zstrm-\u003eexcl_lock);\n+\tret = zcomp_decompress_buf(comp, zstrm, sg, zstrm-\u003elocal_copy, \u0026req);\n+\tmutex_unlock(\u0026zstrm-\u003eexcl_lock);\n \treturn ret;\n }\n \n int zcomp_cpu_up_prepare(unsigned int cpu, struct hlist_node *node)\n {\n \tstruct zcomp *comp = hlist_entry(node, struct zcomp, node);\n-\tstruct zcomp_strm *zstrm = per_cpu_ptr(comp-\u003estream, cpu);\n+\tstruct zcomp_cstrm *cstrm = NULL;\n+\tstruct zcomp_dstrm *dstrm;\n \tint ret;\n \n-\tret = zcomp_strm_init(comp, zstrm);\n-\tif (ret)\n-\t\tpr_err(\"Can't allocate a compression stream\\n\");\n-\treturn ret;\n+\tif (comp-\u003ecstream) {\n+\t\tcstrm = per_cpu_ptr(comp-\u003ecstream, cpu);\n+\t\tret = zcomp_cstrm_init(comp, cstrm);\n+\t\tif (ret) {\n+\t\t\tpr_err(\"Can't allocate a compression stream\\n\");\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tdstrm = per_cpu_ptr(comp-\u003edstream, cpu);\n+\tret = zcomp_dstrm_init(comp, dstrm);\n+\tif (ret) {\n+\t\tpr_err(\"Can't allocate a decompression stream\\n\");\n+\t\tif (cstrm)\n+\t\t\tzcomp_cstrm_free(comp, cstrm);\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n }\n \n int zcomp_cpu_dead(unsigned int cpu, struct hlist_node *node)\n {\n \tstruct zcomp *comp = hlist_entry(node, struct zcomp, node);\n-\tstruct zcomp_strm *zstrm = per_cpu_ptr(comp-\u003estream, cpu);\n+\tstruct zcomp_cstrm *cstrm;\n+\tstruct zcomp_dstrm *dstrm;\n+\n+\tif (comp-\u003ecstream) {\n+\t\tcstrm = per_cpu_ptr(comp-\u003ecstream, cpu);\n+\t\tmutex_lock(\u0026cstrm-\u003elock);\n+\t\tzcomp_cstrm_free(comp, cstrm);\n+\t\tmutex_unlock(\u0026cstrm-\u003elock);\n+\t}\n+\n+\tdstrm = per_cpu_ptr(comp-\u003edstream, cpu);\n+\tdown_write(\u0026dstrm-\u003elock);\n+\tzcomp_dstrm_free(comp, dstrm);\n+\tup_write(\u0026dstrm-\u003elock);\n \n-\tmutex_lock(\u0026zstrm-\u003elock);\n-\tzcomp_strm_free(comp, zstrm);\n-\tmutex_unlock(\u0026zstrm-\u003elock);\n \treturn 0;\n }\n \n-static int zcomp_init(struct zcomp *comp, struct zcomp_params *params)\n+static int zcomp_init(struct zcomp *comp, struct zcomp_params *params,\n+\t\t bool recomp)\n {\n \tint ret, cpu;\n \n-\tcomp-\u003estream = alloc_percpu(struct zcomp_strm);\n-\tif (!comp-\u003estream)\n-\t\treturn -ENOMEM;\n-\n \tcomp-\u003eparams = params;\n \tret = comp-\u003eops-\u003esetup_params(comp-\u003eparams);\n \tif (ret)\n \t\tgoto cleanup;\n \n-\tfor_each_possible_cpu(cpu)\n-\t\tmutex_init(\u0026per_cpu_ptr(comp-\u003estream, cpu)-\u003elock);\n+\tif (recomp) {\n+\t\tcomp-\u003erecomp_cstream = kzalloc_obj(*comp-\u003erecomp_cstream);\n+\t\tif (!comp-\u003erecomp_cstream) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t\tmutex_init(\u0026comp-\u003erecomp_cstream-\u003elock);\n+\t\tret = zcomp_cstrm_init(comp, comp-\u003erecomp_cstream);\n+\t\tif (ret) {\n+\t\t\t/* zcomp_cstrm_init() cleans up after itself */\n+\t\t\tkfree(comp-\u003erecomp_cstream);\n+\t\t\tcomp-\u003erecomp_cstream = NULL;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t} else {\n+\t\tcomp-\u003ecstream = alloc_percpu(struct zcomp_cstrm);\n+\t\tif (!comp-\u003ecstream) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t\tfor_each_possible_cpu(cpu)\n+\t\t\tmutex_init(\u0026per_cpu_ptr(comp-\u003ecstream, cpu)-\u003elock);\n+\t}\n+\n+\tcomp-\u003edstream = alloc_percpu(struct zcomp_dstrm);\n+\tif (!comp-\u003edstream) {\n+\t\tret = -ENOMEM;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfor_each_possible_cpu(cpu) {\n+\t\tstruct zcomp_dstrm *zstrm = per_cpu_ptr(comp-\u003edstream, cpu);\n+\n+\t\tinit_rwsem(\u0026zstrm-\u003elock);\n+\t\tmutex_init(\u0026zstrm-\u003eexcl_lock);\n+\t}\n \n \tret = cpuhp_state_add_instance(CPUHP_ZCOMP_PREPARE, \u0026comp-\u003enode);\n \tif (ret \u003c 0)\n@@ -234,20 +400,31 @@ static int zcomp_init(struct zcomp *comp, struct zcomp_params *params)\n \treturn 0;\n \n cleanup:\n+\tif (comp-\u003erecomp_cstream) {\n+\t\tzcomp_cstrm_free(comp, comp-\u003erecomp_cstream);\n+\t\tkfree(comp-\u003erecomp_cstream);\n+\t}\n \tcomp-\u003eops-\u003erelease_params(comp-\u003eparams);\n-\tfree_percpu(comp-\u003estream);\n+\tfree_percpu(comp-\u003edstream);\n+\tfree_percpu(comp-\u003ecstream);\n \treturn ret;\n }\n \n void zcomp_destroy(struct zcomp *comp)\n {\n \tcpuhp_state_remove_instance(CPUHP_ZCOMP_PREPARE, \u0026comp-\u003enode);\n+\tif (comp-\u003erecomp_cstream) {\n+\t\tzcomp_cstrm_free(comp, comp-\u003erecomp_cstream);\n+\t\tkfree(comp-\u003erecomp_cstream);\n+\t}\n \tcomp-\u003eops-\u003erelease_params(comp-\u003eparams);\n-\tfree_percpu(comp-\u003estream);\n+\tfree_percpu(comp-\u003edstream);\n+\tfree_percpu(comp-\u003ecstream);\n \tkfree(comp);\n }\n \n-struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params)\n+struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params,\n+\t\t\t bool recomp)\n {\n \tstruct zcomp *comp;\n \tint error;\n@@ -270,7 +447,7 @@ struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params)\n \t\treturn ERR_PTR(-EINVAL);\n \t}\n \n-\terror = zcomp_init(comp, params);\n+\terror = zcomp_init(comp, params, recomp);\n \tif (error) {\n \t\tkfree(comp);\n \t\treturn ERR_PTR(error);\ndiff --git a/drivers/block/zram/zcomp.h b/drivers/block/zram/zcomp.h\nindex f38fd31f9e4e0..6764818d2a9f5 100644\n--- a/drivers/block/zram/zcomp.h\n+++ b/drivers/block/zram/zcomp.h\n@@ -4,11 +4,17 @@\n #define _ZCOMP_H_\n \n #include \u003clinux/mutex.h\u003e\n+#include \u003clinux/rwsem.h\u003e\n \n struct scatterlist;\n \n #define ZCOMP_PARAM_NOT_SET\tINT_MIN\n \n+enum zcomp_dstrm_mode {\n+\tZCOMP_DSTRM_SHARED,\n+\tZCOMP_DSTRM_EXCLUSIVE,\n+};\n+\n struct deflate_params {\n \ts32 winbits;\n };\n@@ -33,16 +39,29 @@ struct zcomp_params {\n /*\n * Run-time driver context - scratch buffers, etc. It is modified during\n * request execution (compression/decompression), cannot be shared, so\n- * it's in per-CPU area.\n+ * it's in per-CPU area (except for recompression, which uses a single\n+ * per-device compression stream).\n */\n struct zcomp_ctx {\n \tvoid *context;\n };\n \n-struct zcomp_strm {\n+struct zcomp_cstrm {\n \tstruct mutex lock;\n \t/* compression buffer */\n \tvoid *buffer;\n+\tstruct zcomp_ctx ctx;\n+};\n+\n+struct zcomp_dstrm {\n+\t/*\n+\t * Pins the stream: held in read mode by every user, regardless\n+\t * of the decompression mode, and in write mode only by\n+\t * zcomp_cpu_dead().\n+\t */\n+\tstruct rw_semaphore lock;\n+\t/* Serializes users of the mutable stream state: -\u003elocal_copy, -\u003ectx */\n+\tstruct mutex excl_lock;\n \t/* local copy of handle memory */\n \tvoid *local_copy;\n \tstruct zcomp_ctx ctx;\n@@ -62,8 +81,11 @@ struct zcomp_ops {\n \tint (*decompress)(struct zcomp_params *params, struct zcomp_ctx *ctx,\n \t\t\t struct zcomp_req *req);\n \n-\tint (*create_ctx)(struct zcomp_params *params, struct zcomp_ctx *ctx);\n-\tvoid (*destroy_ctx)(struct zcomp_ctx *ctx);\n+\tint (*create_cctx)(struct zcomp_params *params, struct zcomp_ctx *ctx);\n+\tvoid (*destroy_cctx)(struct zcomp_ctx *ctx);\n+\n+\tint (*create_dctx)(struct zcomp_params *params, struct zcomp_ctx *ctx);\n+\tvoid (*destroy_dctx)(struct zcomp_ctx *ctx);\n \n \tint (*setup_params)(struct zcomp_params *params);\n \tvoid (*release_params)(struct zcomp_params *params);\n@@ -73,7 +95,9 @@ struct zcomp_ops {\n \n /* dynamic per-device compression frontend */\n struct zcomp {\n-\tstruct zcomp_strm __percpu *stream;\n+\tstruct zcomp_dstrm __percpu *dstream;\n+\tstruct zcomp_cstrm __percpu *cstream;\n+\tstruct zcomp_cstrm *recomp_cstream;\n \tconst struct zcomp_ops *ops;\n \tstruct zcomp_params *params;\n \tstruct hlist_node node;\n@@ -84,15 +108,21 @@ int zcomp_cpu_dead(unsigned int cpu, struct hlist_node *node);\n ssize_t zcomp_available_show(const char *comp, char *buf, ssize_t at);\n const char *zcomp_lookup_backend_name(const char *comp);\n \n-struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params);\n+struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params,\n+\t\t\t bool recomp);\n void zcomp_destroy(struct zcomp *comp);\n \n-struct zcomp_strm *zcomp_stream_get(struct zcomp *comp);\n-void zcomp_stream_put(struct zcomp_strm *zstrm);\n+struct zcomp_cstrm *zcomp_cstrm_get(struct zcomp *comp);\n+void zcomp_cstrm_put(struct zcomp_cstrm *zstrm);\n+\n+bool zcomp_shared_decomp(struct zcomp *comp);\n+struct zcomp_dstrm *zcomp_dstrm_get(struct zcomp *comp,\n+\t\t\t\t enum zcomp_dstrm_mode mode);\n+void zcomp_dstrm_put(struct zcomp_dstrm *zstrm, enum zcomp_dstrm_mode mode);\n \n-int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm,\n+int zcomp_compress(struct zcomp *comp, struct zcomp_cstrm *zstrm,\n \t\t const void *src, unsigned int *dst_len);\n-int zcomp_decompress(struct zcomp *comp, struct zcomp_strm *zstrm,\n+int zcomp_decompress(struct zcomp *comp, struct zcomp_dstrm *zstrm,\n \t\t struct scatterlist *sg, unsigned int src_len, void *dst);\n \n #endif /* _ZCOMP_H_ */\ndiff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c\nindex ab064c84b0922..e608769bfad56 100644\n--- a/drivers/block/zram/zram_drv.c\n+++ b/drivers/block/zram/zram_drv.c\n@@ -694,6 +694,7 @@ static void reset_bdev(struct zram *zram)\n \tzram-\u003edisk-\u003efops = \u0026zram_devops;\n \tkvfree(zram-\u003ebitmap);\n \tzram-\u003ebitmap = NULL;\n+\tzram-\u003enr_pages = 0;\n }\n \n static ssize_t backing_dev_show(struct device *dev,\n@@ -1347,7 +1348,7 @@ static ssize_t writeback_store(struct device *dev,\n static int decompress_bdev_page(struct zram *zram, struct page *page,\n \t\t\t\tunsigned long index)\n {\n-\tstruct zcomp_strm *zstrm;\n+\tstruct zcomp_dstrm *zstrm;\n \tstruct scatterlist sg[1];\n \tunsigned int size;\n \tint ret, prio;\n@@ -1372,7 +1373,7 @@ static int decompress_bdev_page(struct zram *zram, struct page *page,\n \tsg_init_table(sg, 1);\n \tsg_set_page(sg, page, size, 0);\n \n-\tzstrm = zcomp_stream_get(zram-\u003ecomps[prio]);\n+\tzstrm = zcomp_dstrm_get(zram-\u003ecomps[prio], ZCOMP_DSTRM_EXCLUSIVE);\n \tret = zcomp_decompress(zram-\u003ecomps[prio], zstrm, sg, size,\n \t\t\t zstrm-\u003elocal_copy);\n \tif (!ret) {\n@@ -1381,7 +1382,7 @@ static int decompress_bdev_page(struct zram *zram, struct page *page,\n \t\tcopy_page(dst, zstrm-\u003elocal_copy);\n \t\tkunmap_local(dst);\n \t}\n-\tzcomp_stream_put(zstrm);\n+\tzcomp_dstrm_put(zstrm, ZCOMP_DSTRM_EXCLUSIVE);\n \tslot_unlock(zram, index);\n \n \treturn ret;\n@@ -2114,7 +2115,8 @@ static int read_incompressible_page(struct zram *zram, struct page *page,\n static int read_compressed_page(struct zram *zram, struct page *page,\n \t\t\t\tunsigned long index)\n {\n-\tstruct zcomp_strm *zstrm;\n+\tenum zcomp_dstrm_mode mode = ZCOMP_DSTRM_EXCLUSIVE;\n+\tstruct zcomp_dstrm *zstrm;\n \tstruct scatterlist sg[2];\n \tunsigned long handle;\n \tunsigned int size;\n@@ -2125,13 +2127,16 @@ static int read_compressed_page(struct zram *zram, struct page *page,\n \tsize = get_slot_size(zram, index);\n \tprio = get_slot_comp_priority(zram, index);\n \n-\tzstrm = zcomp_stream_get(zram-\u003ecomps[prio]);\n \tzs_obj_read_sg_begin(zram-\u003emem_pool, handle, sg, size);\n+\tif (zcomp_shared_decomp(zram-\u003ecomps[prio]))\n+\t\tmode = ZCOMP_DSTRM_SHARED;\n+\n+\tzstrm = zcomp_dstrm_get(zram-\u003ecomps[prio], mode);\n \tdst = kmap_local_page(page);\n \tret = zcomp_decompress(zram-\u003ecomps[prio], zstrm, sg, size, dst);\n \tkunmap_local(dst);\n+\tzcomp_dstrm_put(zstrm, mode);\n \tzs_obj_read_sg_end(zram-\u003emem_pool, handle);\n-\tzcomp_stream_put(zstrm);\n \n \treturn ret;\n }\n@@ -2299,7 +2304,7 @@ static int zram_write_page(struct zram *zram, struct page *page,\n \tunsigned long handle;\n \tunsigned int comp_len;\n \tvoid *mem;\n-\tstruct zcomp_strm *zstrm;\n+\tstruct zcomp_cstrm *zstrm;\n \tunsigned long element;\n \tbool same_filled;\n \n@@ -2309,20 +2314,20 @@ static int zram_write_page(struct zram *zram, struct page *page,\n \tif (same_filled)\n \t\treturn write_same_filled_page(zram, element, index);\n \n-\tzstrm = zcomp_stream_get(zram-\u003ecomps[ZRAM_PRIMARY_COMP]);\n+\tzstrm = zcomp_cstrm_get(zram-\u003ecomps[ZRAM_PRIMARY_COMP]);\n \tmem = kmap_local_page(page);\n \tret = zcomp_compress(zram-\u003ecomps[ZRAM_PRIMARY_COMP], zstrm,\n \t\t\t mem, \u0026comp_len);\n \tkunmap_local(mem);\n \n \tif (unlikely(ret)) {\n-\t\tzcomp_stream_put(zstrm);\n+\t\tzcomp_cstrm_put(zstrm);\n \t\tpr_err(\"Compression failed! err=%d\\n\", ret);\n \t\treturn ret;\n \t}\n \n \tif (comp_len \u003e= huge_class_size) {\n-\t\tzcomp_stream_put(zstrm);\n+\t\tzcomp_cstrm_put(zstrm);\n \t\treturn write_incompressible_page(zram, page, index);\n \t}\n \n@@ -2330,18 +2335,18 @@ static int zram_write_page(struct zram *zram, struct page *page,\n \t\t\t GFP_NOIO | __GFP_NOWARN |\n \t\t\t __GFP_HIGHMEM | __GFP_MOVABLE, page_to_nid(page));\n \tif (IS_ERR_VALUE(handle)) {\n-\t\tzcomp_stream_put(zstrm);\n+\t\tzcomp_cstrm_put(zstrm);\n \t\treturn PTR_ERR((void *)handle);\n \t}\n \n \tif (!zram_can_store_page(zram)) {\n-\t\tzcomp_stream_put(zstrm);\n+\t\tzcomp_cstrm_put(zstrm);\n \t\tzs_free(zram-\u003emem_pool, handle);\n \t\treturn -ENOMEM;\n \t}\n \n \tzs_obj_write(zram-\u003emem_pool, handle, zstrm-\u003ebuffer, comp_len);\n-\tzcomp_stream_put(zstrm);\n+\tzcomp_cstrm_put(zstrm);\n \n \tslot_lock(zram, index);\n \tslot_free(zram, index);\n@@ -2450,7 +2455,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,\n \t\t\t struct page *page, u64 *num_recomp_pages,\n \t\t\t u32 threshold, u32 prio)\n {\n-\tstruct zcomp_strm *zstrm = NULL;\n+\tstruct zcomp_cstrm *zstrm = NULL;\n \tunsigned long handle_old;\n \tunsigned long handle_new;\n \tunsigned int comp_len_old;\n@@ -2482,7 +2487,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,\n \t */\n \tclear_slot_flag(zram, index, ZRAM_IDLE);\n \n-\tzstrm = zcomp_stream_get(zram-\u003ecomps[prio]);\n+\tzstrm = zcomp_cstrm_get(zram-\u003ecomps[prio]);\n \tsrc = kmap_local_page(page);\n \tret = zcomp_compress(zram-\u003ecomps[prio], zstrm, src, \u0026comp_len_new);\n \tkunmap_local(src);\n@@ -2497,7 +2502,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,\n \t\t*num_recomp_pages -= 1;\n \n \tif (ret) {\n-\t\tzcomp_stream_put(zstrm);\n+\t\tzcomp_cstrm_put(zstrm);\n \t\treturn ret;\n \t}\n \n@@ -2506,7 +2511,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,\n \n \tif (class_index_new \u003e= class_index_old ||\n \t (threshold \u0026\u0026 comp_len_new \u003e= threshold)) {\n-\t\tzcomp_stream_put(zstrm);\n+\t\tzcomp_cstrm_put(zstrm);\n \n \t\t/*\n \t\t * Secondary algorithms failed to re-compress the page\n@@ -2521,7 +2526,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,\n \t}\n \n \t/*\n-\t * We are holding per-CPU stream mutex and entry lock so better\n+\t * We are holding compression stream mutex and entry lock so better\n \t * avoid direct reclaim. Allocation error is not fatal since\n \t * we still have the old object in the mem_pool.\n \t *\n@@ -2535,12 +2540,12 @@ static int recompress_slot(struct zram *zram, unsigned long index,\n \t\t\t __GFP_HIGHMEM | __GFP_MOVABLE,\n \t\t\t page_to_nid(page));\n \tif (IS_ERR_VALUE(handle_new)) {\n-\t\tzcomp_stream_put(zstrm);\n+\t\tzcomp_cstrm_put(zstrm);\n \t\treturn PTR_ERR((void *)handle_new);\n \t}\n \n \tzs_obj_write(zram-\u003emem_pool, handle_new, zstrm-\u003ebuffer, comp_len_new);\n-\tzcomp_stream_put(zstrm);\n+\tzcomp_cstrm_put(zstrm);\n \n \tslot_free(zram, index);\n \tset_slot_handle(zram, index, handle_new);\n@@ -2881,6 +2886,12 @@ static void zram_reset_device(struct zram *zram)\n \tzram_destroy_comps(zram);\n \tmemset(\u0026zram-\u003estats, 0, sizeof(zram-\u003estats));\n \treset_bdev(zram);\n+#ifdef CONFIG_ZRAM_WRITEBACK\n+\tzram-\u003ewb_limit_enable = false;\n+\tzram-\u003ebd_wb_limit = 0;\n+\tzram-\u003ewb_batch_size = 32;\n+\tzram-\u003ecompressed_wb = false;\n+#endif\n }\n \n static ssize_t disksize_store(struct device *dev, struct device_attribute *attr,\n@@ -2917,7 +2928,8 @@ static ssize_t disksize_store(struct device *dev, struct device_attribute *attr,\n \t\t\tcontinue;\n \n \t\tcomp = zcomp_create(zram-\u003ecomp_algs[prio],\n-\t\t\t\t \u0026zram-\u003eparams[prio]);\n+\t\t\t\t \u0026zram-\u003eparams[prio],\n+\t\t\t\t prio != ZRAM_PRIMARY_COMP);\n \t\tif (IS_ERR(comp)) {\n \t\t\tpr_err(\"Cannot initialise %s compressing backend\\n\",\n \t\t\t zram-\u003ecomp_algs[prio]);\n@@ -3297,6 +3309,7 @@ static int __init zram_init(void)\n \tif (zram_major \u003c= 0) {\n \t\tpr_err(\"Unable to get major number\\n\");\n \t\tclass_unregister(\u0026zram_control_class);\n+\t\tzram_debugfs_destroy();\n \t\tcpuhp_remove_multi_state(CPUHP_ZCOMP_PREPARE);\n \t\treturn -EBUSY;\n \t}\n"
} |
| 2/1 | 2026/10/16 08:10 | agent | patch-evaluator |
1mModel:core Args: null Results: {
"EnableConfigs": [
"ZRAM",
"ZRAM_BACKEND_LZ4",
"ZRAM_BACKEND_LZ4HC",
"ZRAM_BACKEND_ZSTD",
"ZRAM_BACKEND_DEFLATE",
"ZRAM_BACKEND_842",
"ZRAM_BACKEND_LZO",
"ZRAM_WRITEBACK",
"ZRAM_MULTI_COMP"
],
"FocusSymbols": [
"zcomp_compress",
"zcomp_decompress",
"zcomp_cstrm_get",
"zcomp_dstrm_get",
"zcomp_shared_decomp",
"zcomp_create",
"read_compressed_page",
"zram_write_page",
"recompress_slot",
"decompress_bdev_page"
],
"Reasoning": "The patch series refactors zram's compression frontend and backend subsystem, separating compression and decompression streams (cstream vs dstream), introducing shared vs exclusive decompression concurrency with read/write semaphores and mutexes, adding a dedicated per-device recompression stream, and restructuring context allocation across compression backends (lz4, lz4hc, deflate, zstd, etc.). It also adds new lockdep assertions and fallback bounce-buffer logic in zcomp_decompress. The code is entirely reachable via standard virtualized block operations and zram sysfs interfaces, making it an excellent target for fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 8c8370b2ce1d31f392641b412dca2a03280f3d38
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Oct 16 08:10:28 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c
index 3846a04c69d71..f911cd1ca72e4 100644
--- a/drivers/block/zram/backend_842.c
+++ b/drivers/block/zram/backend_842.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "842: " fmt
+#define pr_fmt(fmt) "zram: 842: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -63,8 +63,8 @@ static int decompress_842(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_842 = {
.compress = compress_842,
.decompress = decompress_842,
- .create_ctx = create_842,
- .destroy_ctx = destroy_842,
+ .create_cctx = create_842,
+ .destroy_cctx = destroy_842,
.setup_params = setup_params_842,
.release_params = release_params_842,
.name = "842",
diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c
index f71b11bcac788..5ffdaf80d8cff 100644
--- a/drivers/block/zram/backend_deflate.c
+++ b/drivers/block/zram/backend_deflate.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "deflate: " fmt
+#define pr_fmt(fmt) "zram: deflate: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -13,11 +13,6 @@
#define DEFLATE_DEF_WINBITS (-11)
#define DEFLATE_DEF_MEMLEVEL MAX_MEM_LEVEL
-struct deflate_ctx {
- struct z_stream_s cctx;
- struct z_stream_s dctx;
-};
-
static void deflate_release_params(struct zcomp_params *params)
{
}
@@ -51,70 +46,98 @@ static int deflate_setup_params(struct zcomp_params *params)
return 0;
}
-static void deflate_destroy(struct zcomp_ctx *ctx)
+static void deflate_destroy_cctx(struct zcomp_ctx *ctx)
{
- struct deflate_ctx *zctx = ctx->context;
+ struct z_stream_s *cctx = ctx->context;
- if (!zctx)
+ if (!cctx)
return;
- if (zctx->cctx.workspace) {
- zlib_deflateEnd(&zctx->cctx);
- vfree(zctx->cctx.workspace);
- }
- if (zctx->dctx.workspace) {
- zlib_inflateEnd(&zctx->dctx);
- vfree(zctx->dctx.workspace);
+ if (cctx->workspace) {
+ zlib_deflateEnd(cctx);
+ vfree(cctx->workspace);
}
- kfree(zctx);
+ kfree(cctx);
}
-static int deflate_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
+static int deflate_create_cctx(struct zcomp_params *params,
+ struct zcomp_ctx *ctx)
{
- struct deflate_ctx *zctx;
+ struct z_stream_s *cctx;
size_t sz;
int ret;
- zctx = kzalloc_obj(*zctx);
- if (!zctx)
+ cctx = kzalloc_obj(*cctx);
+ if (!cctx)
return -ENOMEM;
- ctx->context = zctx;
+ ctx->context = cctx;
sz = zlib_deflate_workspacesize(params->deflate.winbits, MAX_MEM_LEVEL);
- zctx->cctx.workspace = vzalloc(sz);
- if (!zctx->cctx.workspace)
+ cctx->workspace = vzalloc(sz);
+ if (!cctx->workspace)
goto error;
- ret = zlib_deflateInit2(&zctx->cctx, params->level, Z_DEFLATED,
+ ret = zlib_deflateInit2(cctx, params->level, Z_DEFLATED,
params->deflate.winbits, DEFLATE_DEF_MEMLEVEL,
Z_DEFAULT_STRATEGY);
if (ret != Z_OK)
goto error;
+ return 0;
+
+error:
+ deflate_destroy_cctx(ctx);
+ return -EINVAL;
+}
+
+static void deflate_destroy_dctx(struct zcomp_ctx *ctx)
+{
+ struct z_stream_s *dctx = ctx->context;
+
+ if (!dctx)
+ return;
+
+ if (dctx->workspace) {
+ zlib_inflateEnd(dctx);
+ vfree(dctx->workspace);
+ }
+ kfree(dctx);
+}
+
+static int deflate_create_dctx(struct zcomp_params *params,
+ struct zcomp_ctx *ctx)
+{
+ struct z_stream_s *dctx;
+ size_t sz;
+ int ret;
+
+ dctx = kzalloc_obj(*dctx);
+ if (!dctx)
+ return -ENOMEM;
+
+ ctx->context = dctx;
sz = zlib_inflate_workspacesize();
- zctx->dctx.workspace = vzalloc(sz);
- if (!zctx->dctx.workspace)
+ dctx->workspace = vzalloc(sz);
+ if (!dctx->workspace)
goto error;
- ret = zlib_inflateInit2(&zctx->dctx, params->deflate.winbits);
+ ret = zlib_inflateInit2(dctx, params->deflate.winbits);
if (ret != Z_OK)
goto error;
return 0;
error:
- deflate_destroy(ctx);
+ deflate_destroy_dctx(ctx);
return -EINVAL;
}
static int deflate_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct deflate_ctx *zctx = ctx->context;
- struct z_stream_s *deflate;
+ struct z_stream_s *deflate = ctx->context;
int ret;
- deflate = &zctx->cctx;
ret = zlib_deflateReset(deflate);
if (ret != Z_OK)
return -EINVAL;
@@ -136,12 +159,9 @@ static int deflate_decompress(struct zcomp_params *params,
struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct deflate_ctx *zctx = ctx->context;
- struct z_stream_s *inflate;
+ struct z_stream_s *inflate = ctx->context;
int ret;
- inflate = &zctx->dctx;
-
ret = zlib_inflateReset(inflate);
if (ret != Z_OK)
return -EINVAL;
@@ -161,8 +181,10 @@ static int deflate_decompress(struct zcomp_params *params,
const struct zcomp_ops backend_deflate = {
.compress = deflate_compress,
.decompress = deflate_decompress,
- .create_ctx = deflate_create,
- .destroy_ctx = deflate_destroy,
+ .create_cctx = deflate_create_cctx,
+ .destroy_cctx = deflate_destroy_cctx,
+ .create_dctx = deflate_create_dctx,
+ .destroy_dctx = deflate_destroy_dctx,
.setup_params = deflate_setup_params,
.release_params = deflate_release_params,
.name = "deflate",
diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c
index 1e4ad31d39a63..9767482a2fea0 100644
--- a/drivers/block/zram/backend_lz4.c
+++ b/drivers/block/zram/backend_lz4.c
@@ -1,21 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "lz4: " fmt
+#define pr_fmt(fmt) "zram: lz4: " fmt
#include <linux/kernel.h>
#include <linux/lz4.h>
-#include <linux/slab.h>
#include <linux/vmalloc.h>
#include "backend_lz4.h"
-struct lz4_ctx {
- void *mem;
-
- LZ4_streamDecode_t *dstrm;
- LZ4_stream_t *cstrm;
-};
-
static void lz4_release_params(struct zcomp_params *params)
{
LZ4_stream_t *dict_stream = params->drv_data;
@@ -24,7 +16,7 @@ static void lz4_release_params(struct zcomp_params *params)
if (!dict_stream)
return;
- kfree(dict_stream);
+ vfree(dict_stream);
}
static int lz4_setup_params(struct zcomp_params *params)
@@ -42,14 +34,13 @@ static int lz4_setup_params(struct zcomp_params *params)
if (!params->dict || !params->dict_sz)
return 0;
- dict_stream = kzalloc_obj(*dict_stream);
+ dict_stream = vzalloc(sizeof(*dict_stream));
if (!dict_stream)
return -ENOMEM;
- ret = LZ4_loadDict(dict_stream,
- params->dict, params->dict_sz);
+ ret = LZ4_loadDict(dict_stream, params->dict, params->dict_sz);
if (ret != params->dict_sz) {
- kfree(dict_stream);
+ vfree(dict_stream);
return -EINVAL;
}
params->drv_data = dict_stream;
@@ -59,61 +50,32 @@ static int lz4_setup_params(struct zcomp_params *params)
static void lz4_destroy(struct zcomp_ctx *ctx)
{
- struct lz4_ctx *zctx = ctx->context;
-
- if (!zctx)
- return;
-
- vfree(zctx->mem);
- kfree(zctx->dstrm);
- kfree(zctx->cstrm);
- kfree(zctx);
+ vfree(ctx->context);
}
static int lz4_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
{
- struct lz4_ctx *zctx;
+ BUILD_BUG_ON(sizeof(LZ4_stream_t) != LZ4_MEM_COMPRESS);
- zctx = kzalloc_obj(*zctx);
- if (!zctx)
+ ctx->context = vmalloc(LZ4_MEM_COMPRESS);
+ if (!ctx->context)
return -ENOMEM;
-
- ctx->context = zctx;
- if (params->dict_sz == 0) {
- zctx->mem = vmalloc(LZ4_MEM_COMPRESS);
- if (!zctx->mem)
- goto error;
- } else {
- zctx->dstrm = kzalloc_obj(*zctx->dstrm);
- if (!zctx->dstrm)
- goto error;
-
- zctx->cstrm = kzalloc_obj(*zctx->cstrm);
- if (!zctx->cstrm)
- goto error;
- }
-
return 0;
-
-error:
- lz4_destroy(ctx);
- return -ENOMEM;
}
static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct lz4_ctx *zctx = ctx->context;
int ret;
- if (!zctx->cstrm) {
+ if (!params->drv_data) {
ret = LZ4_compress_fast(req->src, req->dst, req->src_len,
req->dst_len, params->level,
- zctx->mem);
+ ctx->context);
} else {
/* Cstrm needs to be reset */
- memcpy(zctx->cstrm, params->drv_data, sizeof(*zctx->cstrm));
- ret = LZ4_compress_fast_continue(zctx->cstrm, req->src,
+ memcpy(ctx->context, params->drv_data, sizeof(LZ4_stream_t));
+ ret = LZ4_compress_fast_continue(ctx->context, req->src,
req->dst, req->src_len,
req->dst_len, params->level);
}
@@ -126,22 +88,11 @@ static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
static int lz4_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct lz4_ctx *zctx = ctx->context;
int ret;
- if (!zctx->dstrm) {
- ret = LZ4_decompress_safe(req->src, req->dst, req->src_len,
- req->dst_len);
- } else {
- /* Dstrm needs to be reset */
- ret = LZ4_setStreamDecode(zctx->dstrm, params->dict,
- params->dict_sz);
- if (!ret)
- return -EINVAL;
- ret = LZ4_decompress_safe_continue(zctx->dstrm, req->src,
- req->dst, req->src_len,
- req->dst_len);
- }
+ ret = LZ4_decompress_safe_usingDict(req->src, req->dst, req->src_len,
+ req->dst_len, params->dict,
+ params->dict_sz);
if (ret < 0)
return -EINVAL;
return 0;
@@ -150,8 +101,8 @@ static int lz4_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_lz4 = {
.compress = lz4_compress,
.decompress = lz4_decompress,
- .create_ctx = lz4_create,
- .destroy_ctx = lz4_destroy,
+ .create_cctx = lz4_create,
+ .destroy_cctx = lz4_destroy,
.setup_params = lz4_setup_params,
.release_params = lz4_release_params,
.name = "lz4",
diff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c
index d8aa01bb258fe..2d1364a29b27b 100644
--- a/drivers/block/zram/backend_lz4hc.c
+++ b/drivers/block/zram/backend_lz4hc.c
@@ -1,27 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "lz4hc: " fmt
+#define pr_fmt(fmt) "zram: lz4hc: " fmt
#include <linux/kernel.h>
#include <linux/lz4.h>
-#include <linux/slab.h>
#include <linux/vmalloc.h>
#include "backend_lz4hc.h"
-struct lz4hc_ctx {
- void *mem;
-
- LZ4_streamDecode_t *dstrm;
- LZ4_streamHC_t *cstrm;
-};
-
static void lz4hc_release_params(struct zcomp_params *params)
{
+ LZ4_streamHC_t *dict_stream = params->drv_data;
+
+ params->drv_data = NULL;
+ if (!dict_stream)
+ return;
+
+ vfree(dict_stream);
}
static int lz4hc_setup_params(struct zcomp_params *params)
{
+ LZ4_streamHC_t *dict_stream;
+ int ret;
+
if (params->level == ZCOMP_PARAM_NOT_SET) {
params->level = LZ4HC_DEFAULT_CLEVEL;
} else if (params->level < 1 || params->level > LZ4HC_MAX_CLEVEL) {
@@ -35,70 +37,52 @@ static int lz4hc_setup_params(struct zcomp_params *params)
return -EINVAL;
}
+ if (!params->dict || !params->dict_sz)
+ return 0;
+
+ dict_stream = vzalloc(sizeof(*dict_stream));
+ if (!dict_stream)
+ return -ENOMEM;
+
+ LZ4_resetStreamHC(dict_stream, params->level);
+ ret = LZ4_loadDictHC(dict_stream, params->dict, params->dict_sz);
+ if (ret != params->dict_sz) {
+ vfree(dict_stream);
+ return -EINVAL;
+ }
+ params->drv_data = dict_stream;
+
return 0;
}
static void lz4hc_destroy(struct zcomp_ctx *ctx)
{
- struct lz4hc_ctx *zctx = ctx->context;
-
- if (!zctx)
- return;
-
- kfree(zctx->dstrm);
- kfree(zctx->cstrm);
- vfree(zctx->mem);
- kfree(zctx);
+ vfree(ctx->context);
}
static int lz4hc_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
{
- struct lz4hc_ctx *zctx;
+ BUILD_BUG_ON(sizeof(LZ4_streamHC_t) != LZ4HC_MEM_COMPRESS);
- zctx = kzalloc_obj(*zctx);
- if (!zctx)
+ ctx->context = vmalloc(LZ4HC_MEM_COMPRESS);
+ if (!ctx->context)
return -ENOMEM;
-
- ctx->context = zctx;
- if (params->dict_sz == 0) {
- zctx->mem = vmalloc(LZ4HC_MEM_COMPRESS);
- if (!zctx->mem)
- goto error;
- } else {
- zctx->dstrm = kzalloc_obj(*zctx->dstrm);
- if (!zctx->dstrm)
- goto error;
-
- zctx->cstrm = kzalloc_obj(*zctx->cstrm);
- if (!zctx->cstrm)
- goto error;
- }
-
return 0;
-
-error:
- lz4hc_destroy(ctx);
- return -EINVAL;
}
static int lz4hc_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct lz4hc_ctx *zctx = ctx->context;
int ret;
- if (!zctx->cstrm) {
+ if (!params->drv_data) {
ret = LZ4_compress_HC(req->src, req->dst, req->src_len,
req->dst_len, params->level,
- zctx->mem);
+ ctx->context);
} else {
/* Cstrm needs to be reset */
- LZ4_resetStreamHC(zctx->cstrm, params->level);
- ret = LZ4_loadDictHC(zctx->cstrm, params->dict,
- params->dict_sz);
- if (ret != params->dict_sz)
- return -EINVAL;
- ret = LZ4_compress_HC_continue(zctx->cstrm, req->src, req->dst,
+ memcpy(ctx->context, params->drv_data, sizeof(LZ4_streamHC_t));
+ ret = LZ4_compress_HC_continue(ctx->context, req->src, req->dst,
req->src_len, req->dst_len);
}
if (!ret)
@@ -110,22 +94,11 @@ static int lz4hc_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
static int lz4hc_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct lz4hc_ctx *zctx = ctx->context;
int ret;
- if (!zctx->dstrm) {
- ret = LZ4_decompress_safe(req->src, req->dst, req->src_len,
- req->dst_len);
- } else {
- /* Dstrm needs to be reset */
- ret = LZ4_setStreamDecode(zctx->dstrm, params->dict,
- params->dict_sz);
- if (!ret)
- return -EINVAL;
- ret = LZ4_decompress_safe_continue(zctx->dstrm, req->src,
- req->dst, req->src_len,
- req->dst_len);
- }
+ ret = LZ4_decompress_safe_usingDict(req->src, req->dst, req->src_len,
+ req->dst_len, params->dict,
+ params->dict_sz);
if (ret < 0)
return -EINVAL;
return 0;
@@ -134,8 +107,8 @@ static int lz4hc_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_lz4hc = {
.compress = lz4hc_compress,
.decompress = lz4hc_decompress,
- .create_ctx = lz4hc_create,
- .destroy_ctx = lz4hc_destroy,
+ .create_cctx = lz4hc_create,
+ .destroy_cctx = lz4hc_destroy,
.setup_params = lz4hc_setup_params,
.release_params = lz4hc_release_params,
.name = "lz4hc",
diff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c
index d83f92cf757cf..e703560d9ecde 100644
--- a/drivers/block/zram/backend_lzo.c
+++ b/drivers/block/zram/backend_lzo.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "lzo: " fmt
+#define pr_fmt(fmt) "zram: lzo: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -61,8 +61,8 @@ static int lzo_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_lzo = {
.compress = lzo_compress,
.decompress = lzo_decompress,
- .create_ctx = lzo_create,
- .destroy_ctx = lzo_destroy,
+ .create_cctx = lzo_create,
+ .destroy_cctx = lzo_destroy,
.setup_params = lzo_setup_params,
.release_params = lzo_release_params,
.name = "lzo",
diff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c
index 1b120d062c928..e95026e992022 100644
--- a/drivers/block/zram/backend_lzorle.c
+++ b/drivers/block/zram/backend_lzorle.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "lzo-rle: " fmt
+#define pr_fmt(fmt) "zram: lzo-rle: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -61,8 +61,8 @@ static int lzorle_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_lzorle = {
.compress = lzorle_compress,
.decompress = lzorle_decompress,
- .create_ctx = lzorle_create,
- .destroy_ctx = lzorle_destroy,
+ .create_cctx = lzorle_create,
+ .destroy_cctx = lzorle_destroy,
.setup_params = lzorle_setup_params,
.release_params = lzorle_release_params,
.name = "lzo-rle",
diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c
index 08da3810cffd8..d55615f343004 100644
--- a/drivers/block/zram/backend_zstd.c
+++ b/drivers/block/zram/backend_zstd.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "zstd: " fmt
+#define pr_fmt(fmt) "zram: zstd: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -10,10 +10,8 @@
#include "backend_zstd.h"
struct zstd_ctx {
- zstd_cctx *cctx;
- zstd_dctx *dctx;
- void *cctx_mem;
- void *dctx_mem;
+ void *ctx;
+ void *mem;
};
struct zstd_params {
@@ -26,10 +24,17 @@ struct zstd_params {
/*
* For C/D dictionaries we need to provide zstd with zstd_custom_mem,
* which zstd uses internally to allocate/free memory when needed.
+ *
+ * zstd allocations (C/D dictionaries and contexts) are large and
+ * long-lived. Don't use kvzalloc() for them: when enough physically
+ * contiguous memory is available it serves them from kmalloc(), which
+ * rounds them up to a power-of-two number of pages.
*/
static void *zstd_custom_alloc(void *opaque, size_t size)
{
- return kvzalloc(size, GFP_NOIO | __GFP_NOWARN);
+ if (size <= PAGE_SIZE)
+ return kzalloc(size, GFP_NOIO | __GFP_NOWARN);
+ return __vmalloc(size, GFP_NOIO | __GFP_NOWARN | __GFP_ZERO);
}
static void zstd_custom_free(void *opaque, void *address)
@@ -76,6 +81,9 @@ static int zstd_setup_params(struct zcomp_params *params)
prm = zstd_get_cparams(params->level, PAGE_SIZE,
params->dict_sz);
+ if (!params->dict || !params->dict_sz)
+ return 0;
+
zp->cdict = zstd_create_cdict_byreference(params->dict,
params->dict_sz,
prm,
@@ -95,7 +103,7 @@ static int zstd_setup_params(struct zcomp_params *params)
return -EINVAL;
}
-static void zstd_destroy(struct zcomp_ctx *ctx)
+static void zstd_destroy_cctx(struct zcomp_ctx *ctx)
{
struct zstd_ctx *zctx = ctx->context;
@@ -103,30 +111,23 @@ static void zstd_destroy(struct zcomp_ctx *ctx)
return;
/*
- * If ->cctx_mem and ->dctx_mem were allocated then we didn't use
- * C/D dictionary and ->cctx / ->dctx were "embedded" into these
- * buffers.
+ * If ->mem was allocated then we didn't use C/D dictionary and
+ * ->ctx was "embedded" into this buffer.
*
- * If otherwise then we need to explicitly release ->cctx / ->dctx.
+ * If otherwise then we need to explicitly release ->ctx.
*/
- if (zctx->cctx_mem)
- vfree(zctx->cctx_mem);
- else
- zstd_free_cctx(zctx->cctx);
-
- if (zctx->dctx_mem)
- vfree(zctx->dctx_mem);
+ if (zctx->mem)
+ vfree(zctx->mem);
else
- zstd_free_dctx(zctx->dctx);
+ zstd_free_cctx(zctx->ctx);
kfree(zctx);
}
-static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
+static int zstd_create_cctx(struct zcomp_params *params, struct zcomp_ctx *ctx)
{
+ struct zstd_params *zp = params->drv_data;
struct zstd_ctx *zctx;
- zstd_parameters prm;
- size_t sz;
zctx = kzalloc_obj(*zctx);
if (!zctx)
@@ -134,40 +135,74 @@ static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
ctx->context = zctx;
if (params->dict_sz == 0) {
- prm = zstd_get_params(params->level, PAGE_SIZE);
- sz = zstd_cctx_workspace_bound(&prm.cParams);
- zctx->cctx_mem = vzalloc(sz);
- if (!zctx->cctx_mem)
- goto error;
+ zstd_parameters prm = zp->cprm;
+ size_t sz = zstd_cctx_workspace_bound(&prm.cParams);
- zctx->cctx = zstd_init_cctx(zctx->cctx_mem, sz);
- if (!zctx->cctx)
+ zctx->mem = vzalloc(sz);
+ if (!zctx->mem)
goto error;
- sz = zstd_dctx_workspace_bound();
- zctx->dctx_mem = vzalloc(sz);
- if (!zctx->dctx_mem)
- goto error;
-
- zctx->dctx = zstd_init_dctx(zctx->dctx_mem, sz);
- if (!zctx->dctx)
+ zctx->ctx = zstd_init_cctx(zctx->mem, sz);
+ if (!zctx->ctx)
goto error;
} else {
- struct zstd_params *zp = params->drv_data;
+ zctx->ctx = zstd_create_cctx_advanced(zp->custom_mem);
+ if (!zctx->ctx)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ zstd_destroy_cctx(ctx);
+ return -EINVAL;
+}
- zctx->cctx = zstd_create_cctx_advanced(zp->custom_mem);
- if (!zctx->cctx)
+static void zstd_destroy_dctx(struct zcomp_ctx *ctx)
+{
+ struct zstd_ctx *zctx = ctx->context;
+
+ if (!zctx)
+ return;
+
+ if (zctx->mem)
+ vfree(zctx->mem);
+ else
+ zstd_free_dctx(zctx->ctx);
+
+ kfree(zctx);
+}
+
+static int zstd_create_dctx(struct zcomp_params *params, struct zcomp_ctx *ctx)
+{
+ struct zstd_params *zp = params->drv_data;
+ struct zstd_ctx *zctx;
+
+ zctx = kzalloc_obj(*zctx);
+ if (!zctx)
+ return -ENOMEM;
+
+ ctx->context = zctx;
+ if (params->dict_sz == 0) {
+ size_t sz = zstd_dctx_workspace_bound();
+
+ zctx->mem = vzalloc(sz);
+ if (!zctx->mem)
goto error;
- zctx->dctx = zstd_create_dctx_advanced(zp->custom_mem);
- if (!zctx->dctx)
+ zctx->ctx = zstd_init_dctx(zctx->mem, sz);
+ if (!zctx->ctx)
+ goto error;
+ } else {
+ zctx->ctx = zstd_create_dctx_advanced(zp->custom_mem);
+ if (!zctx->ctx)
goto error;
}
return 0;
error:
- zstd_destroy(ctx);
+ zstd_destroy_dctx(ctx);
return -EINVAL;
}
@@ -179,10 +214,10 @@ static int zstd_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
size_t ret;
if (params->dict_sz == 0)
- ret = zstd_compress_cctx(zctx->cctx, req->dst, req->dst_len,
+ ret = zstd_compress_cctx(zctx->ctx, req->dst, req->dst_len,
req->src, req->src_len, &zp->cprm);
else
- ret = zstd_compress_using_cdict(zctx->cctx, req->dst,
+ ret = zstd_compress_using_cdict(zctx->ctx, req->dst,
req->dst_len, req->src,
req->src_len,
zp->cdict);
@@ -200,10 +235,10 @@ static int zstd_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
size_t ret;
if (params->dict_sz == 0)
- ret = zstd_decompress_dctx(zctx->dctx, req->dst, req->dst_len,
+ ret = zstd_decompress_dctx(zctx->ctx, req->dst, req->dst_len,
req->src, req->src_len);
else
- ret = zstd_decompress_using_ddict(zctx->dctx, req->dst,
+ ret = zstd_decompress_using_ddict(zctx->ctx, req->dst,
req->dst_len, req->src,
req->src_len, zp->ddict);
if (zstd_is_error(ret))
@@ -214,8 +249,10 @@ static int zstd_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_zstd = {
.compress = zstd_compress,
.decompress = zstd_decompress,
- .create_ctx = zstd_create,
- .destroy_ctx = zstd_destroy,
+ .create_cctx = zstd_create_cctx,
+ .destroy_cctx = zstd_destroy_cctx,
+ .create_dctx = zstd_create_dctx,
+ .destroy_dctx = zstd_destroy_dctx,
.setup_params = zstd_setup_params,
.release_params = zstd_release_params,
.name = "zstd",
diff --git a/drivers/block/zram/zcomp.c b/drivers/block/zram/zcomp.c
index 028e2f0f587e3..81f21bad787e7 100644
--- a/drivers/block/zram/zcomp.c
+++ b/drivers/block/zram/zcomp.c
@@ -45,30 +45,65 @@ static const struct zcomp_ops *backends[] = {
NULL
};
-static void zcomp_strm_free(struct zcomp *comp, struct zcomp_strm *zstrm)
+static void zcomp_cstrm_free(struct zcomp *comp, struct zcomp_cstrm *zstrm)
{
- comp->ops->destroy_ctx(&zstrm->ctx);
- vfree(zstrm->local_copy);
+ if (comp->ops->destroy_cctx)
+ comp->ops->destroy_cctx(&zstrm->ctx);
vfree(zstrm->buffer);
zstrm->buffer = NULL;
+ zstrm->ctx.context = NULL;
}
-static int zcomp_strm_init(struct zcomp *comp, struct zcomp_strm *zstrm)
+static int zcomp_cstrm_init(struct zcomp *comp, struct zcomp_cstrm *zstrm)
{
int ret;
- ret = comp->ops->create_ctx(comp->params, &zstrm->ctx);
- if (ret)
- return ret;
+ if (comp->ops->create_cctx) {
+ ret = comp->ops->create_cctx(comp->params, &zstrm->ctx);
+ if (ret) {
+ /* backend has released partially created context */
+ zstrm->ctx.context = NULL;
+ return ret;
+ }
+ }
- zstrm->local_copy = vzalloc(PAGE_SIZE);
/*
* allocate 2 pages. 1 for compressed data, plus 1 extra for the
* case when compressed size is larger than the original one
*/
zstrm->buffer = vzalloc(2 * PAGE_SIZE);
- if (!zstrm->buffer || !zstrm->local_copy) {
- zcomp_strm_free(comp, zstrm);
+ if (!zstrm->buffer) {
+ zcomp_cstrm_free(comp, zstrm);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void zcomp_dstrm_free(struct zcomp *comp, struct zcomp_dstrm *zstrm)
+{
+ if (comp->ops->destroy_dctx)
+ comp->ops->destroy_dctx(&zstrm->ctx);
+ vfree(zstrm->local_copy);
+ zstrm->local_copy = NULL;
+ zstrm->ctx.context = NULL;
+}
+
+static int zcomp_dstrm_init(struct zcomp *comp, struct zcomp_dstrm *zstrm)
+{
+ int ret;
+
+ if (comp->ops->create_dctx) {
+ ret = comp->ops->create_dctx(comp->params, &zstrm->ctx);
+ if (ret) {
+ /* backend has released partially created context */
+ zstrm->ctx.context = NULL;
+ return ret;
+ }
+ }
+
+ zstrm->local_copy = vzalloc(PAGE_SIZE);
+ if (!zstrm->local_copy) {
+ zcomp_dstrm_free(comp, zstrm);
return -ENOMEM;
}
return 0;
@@ -114,10 +149,15 @@ ssize_t zcomp_available_show(const char *comp, char *buf, ssize_t at)
return at;
}
-struct zcomp_strm *zcomp_stream_get(struct zcomp *comp)
+struct zcomp_cstrm *zcomp_cstrm_get(struct zcomp *comp)
{
+ if (comp->recomp_cstream) {
+ mutex_lock(&comp->recomp_cstream->lock);
+ return comp->recomp_cstream;
+ }
+
for (;;) {
- struct zcomp_strm *zstrm = raw_cpu_ptr(comp->stream);
+ struct zcomp_cstrm *zstrm = raw_cpu_ptr(comp->cstream);
/*
* Inspired by zswap
@@ -130,18 +170,62 @@ struct zcomp_strm *zcomp_stream_get(struct zcomp *comp)
* so then unlock and re-try on the current CPU.
*/
mutex_lock(&zstrm->lock);
- if (likely(zstrm->buffer))
+ if (zstrm->buffer)
return zstrm;
mutex_unlock(&zstrm->lock);
}
}
-void zcomp_stream_put(struct zcomp_strm *zstrm)
+void zcomp_cstrm_put(struct zcomp_cstrm *zstrm)
{
mutex_unlock(&zstrm->lock);
}
-int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm,
+bool zcomp_shared_decomp(struct zcomp *comp)
+{
+ /*
+ * Shared decompression is only safe when the backend has no
+ * mutable per-stream decompression context.
+ */
+ return !comp->ops->create_dctx;
+}
+
+struct zcomp_dstrm *zcomp_dstrm_get(struct zcomp *comp,
+ enum zcomp_dstrm_mode mode)
+{
+ for (;;) {
+ struct zcomp_dstrm *zstrm = raw_cpu_ptr(comp->dstream);
+
+ /*
+ * Pin the stream. The write side of ->lock is only taken
+ * by zcomp_cpu_dead(), so this never contends with other
+ * decompression requests, regardless of their mode.
+ */
+ down_read(&zstrm->lock);
+ if (!zstrm->local_copy) {
+ /* The stream has been destroyed, re-try */
+ up_read(&zstrm->lock);
+ continue;
+ }
+
+ /*
+ * Exclusive requests additionally own ->local_copy and
+ * ->ctx and hence can only wait for each other.
+ */
+ if (mode == ZCOMP_DSTRM_EXCLUSIVE)
+ mutex_lock(&zstrm->excl_lock);
+ return zstrm;
+ }
+}
+
+void zcomp_dstrm_put(struct zcomp_dstrm *zstrm, enum zcomp_dstrm_mode mode)
+{
+ if (mode == ZCOMP_DSTRM_EXCLUSIVE)
+ mutex_unlock(&zstrm->excl_lock);
+ up_read(&zstrm->lock);
+}
+
+int zcomp_compress(struct zcomp *comp, struct zcomp_cstrm *zstrm,
const void *src, unsigned int *dst_len)
{
struct zcomp_req req = {
@@ -159,7 +243,16 @@ int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm,
return ret;
}
-int zcomp_decompress(struct zcomp *comp, struct zcomp_strm *zstrm,
+static int zcomp_decompress_buf(struct zcomp *comp, struct zcomp_dstrm *zstrm,
+ struct scatterlist *sg, void *buf,
+ struct zcomp_req *req)
+{
+ sg_copy_to_buffer(sg, 2, buf, req->src_len);
+ req->src = buf;
+ return comp->ops->decompress(comp->params, &zstrm->ctx, req);
+}
+
+int zcomp_decompress(struct zcomp *comp, struct zcomp_dstrm *zstrm,
struct scatterlist *sg, unsigned int src_len, void *dst)
{
struct zcomp_req req = {
@@ -167,65 +260,138 @@ int zcomp_decompress(struct zcomp *comp, struct zcomp_strm *zstrm,
.src_len = src_len,
.dst_len = PAGE_SIZE,
};
- void *src = NULL;
+ void *src;
int ret;
might_sleep();
+ lockdep_assert_held(&zstrm->lock);
+ if (!zcomp_shared_decomp(comp))
+ lockdep_assert_held(&zstrm->excl_lock);
if (sg_is_last(sg)) {
/* the object is contained within one page, read it in-place */
src = kmap_local_page(sg_page(sg));
req.src = src + sg->offset;
- } else {
- /* the object spans two pages, linearize it into local copy */
- sg_copy_to_buffer(sg, 2, zstrm->local_copy, src_len);
- req.src = zstrm->local_copy;
+ ret = comp->ops->decompress(comp->params, &zstrm->ctx, &req);
+ kunmap_local(src);
+ return ret;
}
- ret = comp->ops->decompress(comp->params, &zstrm->ctx, &req);
- if (src)
- kunmap_local(src);
+ /* exclusive mode already holds ->excl_lock */
+ if (!zcomp_shared_decomp(comp))
+ return zcomp_decompress_buf(comp, zstrm, sg, zstrm->local_copy,
+ &req);
+
+ /* shared mode: try a per-request bounce buffer first */
+ src = kmalloc(src_len, GFP_NOWAIT | __GFP_NOWARN);
+ if (src) {
+ ret = zcomp_decompress_buf(comp, zstrm, sg, src, &req);
+ kfree(src);
+ return ret;
+ }
+
+ /* fall back to ->local_copy serialized by ->excl_lock */
+ mutex_lock(&zstrm->excl_lock);
+ ret = zcomp_decompress_buf(comp, zstrm, sg, zstrm->local_copy, &req);
+ mutex_unlock(&zstrm->excl_lock);
return ret;
}
int zcomp_cpu_up_prepare(unsigned int cpu, struct hlist_node *node)
{
struct zcomp *comp = hlist_entry(node, struct zcomp, node);
- struct zcomp_strm *zstrm = per_cpu_ptr(comp->stream, cpu);
+ struct zcomp_cstrm *cstrm = NULL;
+ struct zcomp_dstrm *dstrm;
int ret;
- ret = zcomp_strm_init(comp, zstrm);
- if (ret)
- pr_err("Can't allocate a compression stream\n");
- return ret;
+ if (comp->cstream) {
+ cstrm = per_cpu_ptr(comp->cstream, cpu);
+ ret = zcomp_cstrm_init(comp, cstrm);
+ if (ret) {
+ pr_err("Can't allocate a compression stream\n");
+ return ret;
+ }
+ }
+
+ dstrm = per_cpu_ptr(comp->dstream, cpu);
+ ret = zcomp_dstrm_init(comp, dstrm);
+ if (ret) {
+ pr_err("Can't allocate a decompression stream\n");
+ if (cstrm)
+ zcomp_cstrm_free(comp, cstrm);
+ return ret;
+ }
+
+ return 0;
}
int zcomp_cpu_dead(unsigned int cpu, struct hlist_node *node)
{
struct zcomp *comp = hlist_entry(node, struct zcomp, node);
- struct zcomp_strm *zstrm = per_cpu_ptr(comp->stream, cpu);
+ struct zcomp_cstrm *cstrm;
+ struct zcomp_dstrm *dstrm;
+
+ if (comp->cstream) {
+ cstrm = per_cpu_ptr(comp->cstream, cpu);
+ mutex_lock(&cstrm->lock);
+ zcomp_cstrm_free(comp, cstrm);
+ mutex_unlock(&cstrm->lock);
+ }
+
+ dstrm = per_cpu_ptr(comp->dstream, cpu);
+ down_write(&dstrm->lock);
+ zcomp_dstrm_free(comp, dstrm);
+ up_write(&dstrm->lock);
- mutex_lock(&zstrm->lock);
- zcomp_strm_free(comp, zstrm);
- mutex_unlock(&zstrm->lock);
return 0;
}
-static int zcomp_init(struct zcomp *comp, struct zcomp_params *params)
+static int zcomp_init(struct zcomp *comp, struct zcomp_params *params,
+ bool recomp)
{
int ret, cpu;
- comp->stream = alloc_percpu(struct zcomp_strm);
- if (!comp->stream)
- return -ENOMEM;
-
comp->params = params;
ret = comp->ops->setup_params(comp->params);
if (ret)
goto cleanup;
- for_each_possible_cpu(cpu)
- mutex_init(&per_cpu_ptr(comp->stream, cpu)->lock);
+ if (recomp) {
+ comp->recomp_cstream = kzalloc_obj(*comp->recomp_cstream);
+ if (!comp->recomp_cstream) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ mutex_init(&comp->recomp_cstream->lock);
+ ret = zcomp_cstrm_init(comp, comp->recomp_cstream);
+ if (ret) {
+ /* zcomp_cstrm_init() cleans up after itself */
+ kfree(comp->recomp_cstream);
+ comp->recomp_cstream = NULL;
+ goto cleanup;
+ }
+ } else {
+ comp->cstream = alloc_percpu(struct zcomp_cstrm);
+ if (!comp->cstream) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ for_each_possible_cpu(cpu)
+ mutex_init(&per_cpu_ptr(comp->cstream, cpu)->lock);
+ }
+
+ comp->dstream = alloc_percpu(struct zcomp_dstrm);
+ if (!comp->dstream) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ for_each_possible_cpu(cpu) {
+ struct zcomp_dstrm *zstrm = per_cpu_ptr(comp->dstream, cpu);
+
+ init_rwsem(&zstrm->lock);
+ mutex_init(&zstrm->excl_lock);
+ }
ret = cpuhp_state_add_instance(CPUHP_ZCOMP_PREPARE, &comp->node);
if (ret < 0)
@@ -234,20 +400,31 @@ static int zcomp_init(struct zcomp *comp, struct zcomp_params *params)
return 0;
cleanup:
+ if (comp->recomp_cstream) {
+ zcomp_cstrm_free(comp, comp->recomp_cstream);
+ kfree(comp->recomp_cstream);
+ }
comp->ops->release_params(comp->params);
- free_percpu(comp->stream);
+ free_percpu(comp->dstream);
+ free_percpu(comp->cstream);
return ret;
}
void zcomp_destroy(struct zcomp *comp)
{
cpuhp_state_remove_instance(CPUHP_ZCOMP_PREPARE, &comp->node);
+ if (comp->recomp_cstream) {
+ zcomp_cstrm_free(comp, comp->recomp_cstream);
+ kfree(comp->recomp_cstream);
+ }
comp->ops->release_params(comp->params);
- free_percpu(comp->stream);
+ free_percpu(comp->dstream);
+ free_percpu(comp->cstream);
kfree(comp);
}
-struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params)
+struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params,
+ bool recomp)
{
struct zcomp *comp;
int error;
@@ -270,7 +447,7 @@ struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params)
return ERR_PTR(-EINVAL);
}
- error = zcomp_init(comp, params);
+ error = zcomp_init(comp, params, recomp);
if (error) {
kfree(comp);
return ERR_PTR(error);
diff --git a/drivers/block/zram/zcomp.h b/drivers/block/zram/zcomp.h
index f38fd31f9e4e0..6764818d2a9f5 100644
--- a/drivers/block/zram/zcomp.h
+++ b/drivers/block/zram/zcomp.h
@@ -4,11 +4,17 @@
#define _ZCOMP_H_
#include <linux/mutex.h>
+#include <linux/rwsem.h>
struct scatterlist;
#define ZCOMP_PARAM_NOT_SET INT_MIN
+enum zcomp_dstrm_mode {
+ ZCOMP_DSTRM_SHARED,
+ ZCOMP_DSTRM_EXCLUSIVE,
+};
+
struct deflate_params {
s32 winbits;
};
@@ -33,16 +39,29 @@ struct zcomp_params {
/*
* Run-time driver context - scratch buffers, etc. It is modified during
* request execution (compression/decompression), cannot be shared, so
- * it's in per-CPU area.
+ * it's in per-CPU area (except for recompression, which uses a single
+ * per-device compression stream).
*/
struct zcomp_ctx {
void *context;
};
-struct zcomp_strm {
+struct zcomp_cstrm {
struct mutex lock;
/* compression buffer */
void *buffer;
+ struct zcomp_ctx ctx;
+};
+
+struct zcomp_dstrm {
+ /*
+ * Pins the stream: held in read mode by every user, regardless
+ * of the decompression mode, and in write mode only by
+ * zcomp_cpu_dead().
+ */
+ struct rw_semaphore lock;
+ /* Serializes users of the mutable stream state: ->local_copy, ->ctx */
+ struct mutex excl_lock;
/* local copy of handle memory */
void *local_copy;
struct zcomp_ctx ctx;
@@ -62,8 +81,11 @@ struct zcomp_ops {
int (*decompress)(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req);
- int (*create_ctx)(struct zcomp_params *params, struct zcomp_ctx *ctx);
- void (*destroy_ctx)(struct zcomp_ctx *ctx);
+ int (*create_cctx)(struct zcomp_params *params, struct zcomp_ctx *ctx);
+ void (*destroy_cctx)(struct zcomp_ctx *ctx);
+
+ int (*create_dctx)(struct zcomp_params *params, struct zcomp_ctx *ctx);
+ void (*destroy_dctx)(struct zcomp_ctx *ctx);
int (*setup_params)(struct zcomp_params *params);
void (*release_params)(struct zcomp_params *params);
@@ -73,7 +95,9 @@ struct zcomp_ops {
/* dynamic per-device compression frontend */
struct zcomp {
- struct zcomp_strm __percpu *stream;
+ struct zcomp_dstrm __percpu *dstream;
+ struct zcomp_cstrm __percpu *cstream;
+ struct zcomp_cstrm *recomp_cstream;
const struct zcomp_ops *ops;
struct zcomp_params *params;
struct hlist_node node;
@@ -84,15 +108,21 @@ int zcomp_cpu_dead(unsigned int cpu, struct hlist_node *node);
ssize_t zcomp_available_show(const char *comp, char *buf, ssize_t at);
const char *zcomp_lookup_backend_name(const char *comp);
-struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params);
+struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params,
+ bool recomp);
void zcomp_destroy(struct zcomp *comp);
-struct zcomp_strm *zcomp_stream_get(struct zcomp *comp);
-void zcomp_stream_put(struct zcomp_strm *zstrm);
+struct zcomp_cstrm *zcomp_cstrm_get(struct zcomp *comp);
+void zcomp_cstrm_put(struct zcomp_cstrm *zstrm);
+
+bool zcomp_shared_decomp(struct zcomp *comp);
+struct zcomp_dstrm *zcomp_dstrm_get(struct zcomp *comp,
+ enum zcomp_dstrm_mode mode);
+void zcomp_dstrm_put(struct zcomp_dstrm *zstrm, enum zcomp_dstrm_mode mode);
-int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm,
+int zcomp_compress(struct zcomp *comp, struct zcomp_cstrm *zstrm,
const void *src, unsigned int *dst_len);
-int zcomp_decompress(struct zcomp *comp, struct zcomp_strm *zstrm,
+int zcomp_decompress(struct zcomp *comp, struct zcomp_dstrm *zstrm,
struct scatterlist *sg, unsigned int src_len, void *dst);
#endif /* _ZCOMP_H_ */
diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c
index ab064c84b0922..e608769bfad56 100644
--- a/drivers/block/zram/zram_drv.c
+++ b/drivers/block/zram/zram_drv.c
@@ -694,6 +694,7 @@ static void reset_bdev(struct zram *zram)
zram->disk->fops = &zram_devops;
kvfree(zram->bitmap);
zram->bitmap = NULL;
+ zram->nr_pages = 0;
}
static ssize_t backing_dev_show(struct device *dev,
@@ -1347,7 +1348,7 @@ static ssize_t writeback_store(struct device *dev,
static int decompress_bdev_page(struct zram *zram, struct page *page,
unsigned long index)
{
- struct zcomp_strm *zstrm;
+ struct zcomp_dstrm *zstrm;
struct scatterlist sg[1];
unsigned int size;
int ret, prio;
@@ -1372,7 +1373,7 @@ static int decompress_bdev_page(struct zram *zram, struct page *page,
sg_init_table(sg, 1);
sg_set_page(sg, page, size, 0);
- zstrm = zcomp_stream_get(zram->comps[prio]);
+ zstrm = zcomp_dstrm_get(zram->comps[prio], ZCOMP_DSTRM_EXCLUSIVE);
ret = zcomp_decompress(zram->comps[prio], zstrm, sg, size,
zstrm->local_copy);
if (!ret) {
@@ -1381,7 +1382,7 @@ static int decompress_bdev_page(struct zram *zram, struct page *page,
copy_page(dst, zstrm->local_copy);
kunmap_local(dst);
}
- zcomp_stream_put(zstrm);
+ zcomp_dstrm_put(zstrm, ZCOMP_DSTRM_EXCLUSIVE);
slot_unlock(zram, index);
return ret;
@@ -2114,7 +2115,8 @@ static int read_incompressible_page(struct zram *zram, struct page *page,
static int read_compressed_page(struct zram *zram, struct page *page,
unsigned long index)
{
- struct zcomp_strm *zstrm;
+ enum zcomp_dstrm_mode mode = ZCOMP_DSTRM_EXCLUSIVE;
+ struct zcomp_dstrm *zstrm;
struct scatterlist sg[2];
unsigned long handle;
unsigned int size;
@@ -2125,13 +2127,16 @@ static int read_compressed_page(struct zram *zram, struct page *page,
size = get_slot_size(zram, index);
prio = get_slot_comp_priority(zram, index);
- zstrm = zcomp_stream_get(zram->comps[prio]);
zs_obj_read_sg_begin(zram->mem_pool, handle, sg, size);
+ if (zcomp_shared_decomp(zram->comps[prio]))
+ mode = ZCOMP_DSTRM_SHARED;
+
+ zstrm = zcomp_dstrm_get(zram->comps[prio], mode);
dst = kmap_local_page(page);
ret = zcomp_decompress(zram->comps[prio], zstrm, sg, size, dst);
kunmap_local(dst);
+ zcomp_dstrm_put(zstrm, mode);
zs_obj_read_sg_end(zram->mem_pool, handle);
- zcomp_stream_put(zstrm);
return ret;
}
@@ -2299,7 +2304,7 @@ static int zram_write_page(struct zram *zram, struct page *page,
unsigned long handle;
unsigned int comp_len;
void *mem;
- struct zcomp_strm *zstrm;
+ struct zcomp_cstrm *zstrm;
unsigned long element;
bool same_filled;
@@ -2309,20 +2314,20 @@ static int zram_write_page(struct zram *zram, struct page *page,
if (same_filled)
return write_same_filled_page(zram, element, index);
- zstrm = zcomp_stream_get(zram->comps[ZRAM_PRIMARY_COMP]);
+ zstrm = zcomp_cstrm_get(zram->comps[ZRAM_PRIMARY_COMP]);
mem = kmap_local_page(page);
ret = zcomp_compress(zram->comps[ZRAM_PRIMARY_COMP], zstrm,
mem, &comp_len);
kunmap_local(mem);
if (unlikely(ret)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
pr_err("Compression failed! err=%d\n", ret);
return ret;
}
if (comp_len >= huge_class_size) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
return write_incompressible_page(zram, page, index);
}
@@ -2330,18 +2335,18 @@ static int zram_write_page(struct zram *zram, struct page *page,
GFP_NOIO | __GFP_NOWARN |
__GFP_HIGHMEM | __GFP_MOVABLE, page_to_nid(page));
if (IS_ERR_VALUE(handle)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
return PTR_ERR((void *)handle);
}
if (!zram_can_store_page(zram)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
zs_free(zram->mem_pool, handle);
return -ENOMEM;
}
zs_obj_write(zram->mem_pool, handle, zstrm->buffer, comp_len);
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
slot_lock(zram, index);
slot_free(zram, index);
@@ -2450,7 +2455,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
struct page *page, u64 *num_recomp_pages,
u32 threshold, u32 prio)
{
- struct zcomp_strm *zstrm = NULL;
+ struct zcomp_cstrm *zstrm = NULL;
unsigned long handle_old;
unsigned long handle_new;
unsigned int comp_len_old;
@@ -2482,7 +2487,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
*/
clear_slot_flag(zram, index, ZRAM_IDLE);
- zstrm = zcomp_stream_get(zram->comps[prio]);
+ zstrm = zcomp_cstrm_get(zram->comps[prio]);
src = kmap_local_page(page);
ret = zcomp_compress(zram->comps[prio], zstrm, src, &comp_len_new);
kunmap_local(src);
@@ -2497,7 +2502,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
*num_recomp_pages -= 1;
if (ret) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
return ret;
}
@@ -2506,7 +2511,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
if (class_index_new >= class_index_old ||
(threshold && comp_len_new >= threshold)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
/*
* Secondary algorithms failed to re-compress the page
@@ -2521,7 +2526,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
}
/*
- * We are holding per-CPU stream mutex and entry lock so better
+ * We are holding compression stream mutex and entry lock so better
* avoid direct reclaim. Allocation error is not fatal since
* we still have the old object in the mem_pool.
*
@@ -2535,12 +2540,12 @@ static int recompress_slot(struct zram *zram, unsigned long index,
__GFP_HIGHMEM | __GFP_MOVABLE,
page_to_nid(page));
if (IS_ERR_VALUE(handle_new)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
return PTR_ERR((void *)handle_new);
}
zs_obj_write(zram->mem_pool, handle_new, zstrm->buffer, comp_len_new);
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
slot_free(zram, index);
set_slot_handle(zram, index, handle_new);
@@ -2881,6 +2886,12 @@ static void zram_reset_device(struct zram *zram)
zram_destroy_comps(zram);
memset(&zram->stats, 0, sizeof(zram->stats));
reset_bdev(zram);
+#ifdef CONFIG_ZRAM_WRITEBACK
+ zram->wb_limit_enable = false;
+ zram->bd_wb_limit = 0;
+ zram->wb_batch_size = 32;
+ zram->compressed_wb = false;
+#endif
}
static ssize_t disksize_store(struct device *dev, struct device_attribute *attr,
@@ -2917,7 +2928,8 @@ static ssize_t disksize_store(struct device *dev, struct device_attribute *attr,
continue;
comp = zcomp_create(zram->comp_algs[prio],
- &zram->params[prio]);
+ &zram->params[prio],
+ prio != ZRAM_PRIMARY_COMP);
if (IS_ERR(comp)) {
pr_err("Cannot initialise %s compressing backend\n",
zram->comp_algs[prio]);
@@ -3297,6 +3309,7 @@ static int __init zram_init(void)
if (zram_major <= 0) {
pr_err("Unable to get major number\n");
class_unregister(&zram_control_class);
+ zram_debugfs_destroy();
cpuhp_remove_multi_state(CPUHP_ZCOMP_PREPARE);
return -EBUSY;
}
|
| 3/2 | 2026/10/16 08:10 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 21669 output: 31 thoughts: 1062 Thoughts: **Zram Patch Evaluation - Deep Dive** Okay, so I've got a patch series here applied as HEAD in the kernel workspace, aimed at zram. My initial thought is to dive deep into what's actually changed. I see a fairly extensive touch on several files: `drivers/block/zram/` is the epicenter, specifically impacting various backend compression algorithms and the core zram driver itself. The files are: - `drivers/block/zram/backend_842.c` - `drivers/block/zram/backend_deflate.c` - `drivers/block/zram/backend_lz4.c` - `drivers/block/zram/backend_lz4hc.c` - `drivers/block/zram/backend_lzo.c` - `drivers/block/zram/backend_lzorle.c` - `drivers/block/zram/backend_zstd.c` - `drivers/block/zram/zcomp.c` - `drivers/block/zram/zcomp.h` - `drivers/block/zram/zram_drv.c` I'm thinking, "Is zram easily reachable?" Since it's a software block device, yeah, it's easily reachable in standard virtualized environments like QEMU and GCE. Good, it's a good place to test. Syzkaller is a fantastic fuzzer for testing things and it does seem that syzkaller frequently fuzzed zram via `/dev/zram*` or block ioctl, I'll double-check syzkaller coverage, yep, I see pseudo-syscalls and descriptions for it. So, that's positive. Now, let's get into the guts of the patch. The biggest takeaway is a pretty substantial refactoring. It looks like it's separated compression and decompression streams within zcomp – `zcomp_cstrm` and `zcomp_dstrm` – and added shared decompression vs. exclusive decompression support. This is a locking change with `ZCOMP_DSTRM_SHARED`, `ZCOMP_DSTRM_EXCLUSIVE`, rwsem vs mutex locks, and `zcomp_shared_decomp`. There's also a new `recomp_cstream` for secondary algorithms and modified backend ops. It's essentially splitting `create_ctx`/`destroy_ctx` into dedicated functions for compression and decompression. Looking deeper at the backends, I see these changes: * **842:** only `create_cctx`/`destroy_cctx` * **deflate:** separate `create_cctx`/`destroy_cctx` and `create_dctx`/`destroy_dctx` * **lz4:** uses Dict, `create_cctx`/`destroy_cctx`, vmalloc instead of kzalloc, direct decompress_safe_usingDict without dctx! * **lz4hc:** same as lz4, direct `decompress_safe_usingDict` * **lzo, lzorle:** only `create_cctx`/`destroy_cctx` * **zstd:** separate cctx / dctx create/destroy, custom_alloc uses vmalloc when > PAGE_SIZE instead of kvzalloc. In `zcomp_decompress()`, I'm noting lockdep assertions, fallback buffer with `kmalloc`, or `->local_copy` serialized by `excl_lock`. And there are a bunch of changes in `zram_drv.c` – reset of `nr_pages`, usage of `zcomp_dstrm_get` with `SHARED` or `EXCLUSIVE`, `recompress_slot` using `zcomp_cstrm_get`, and error handling fix in `zram_init`. My expert opinion is that this is a significant refactoring! This hits concurrency, memory management, locking, and backend drivers for compression and decompression. New locking with `rwsem` and `mutex`, lockdep assertions, buffer handling, and shared vs exclusive decompression concurrency. I think, "Is this WorthFuzzing?" Yes, absolutely. This falls squarely into "Core Subsystems / Refactorings and Code Cleanups / Runtime Assertions and Defensive Checks / Reachable Drivers and Protocols". Zram is reachable, and the changes are substantial. No hardware required, standard virtual machines are perfect. This warrants a thorough fuzzing campaign. I'm going to look at the Kconfig dependencies next with grepper or read-file. |
| 4/2 | 2026/10/16 08:10 | tool | grepper |
0mArgs:{
"Expression": "config ZRAM",
"PathPrefixes": [
"drivers/block/zram/"
]
}Results: {
"Output": "drivers/block/zram/Kconfig-1-# SPDX-License-Identifier: GPL-2.0\ndrivers/block/zram/Kconfig:2:config ZRAM\ndrivers/block/zram/Kconfig-3-\ttristate \"Compressed RAM block device support\"\n--\ndrivers/block/zram/Kconfig-16-\ndrivers/block/zram/Kconfig:17:config ZRAM_BACKEND_LZ4\ndrivers/block/zram/Kconfig-18-\tbool \"lz4 compression support\"\n--\ndrivers/block/zram/Kconfig-22-\ndrivers/block/zram/Kconfig:23:config ZRAM_BACKEND_LZ4HC\ndrivers/block/zram/Kconfig-24-\tbool \"lz4hc compression support\"\n--\ndrivers/block/zram/Kconfig-28-\ndrivers/block/zram/Kconfig:29:config ZRAM_BACKEND_ZSTD\ndrivers/block/zram/Kconfig-30-\tbool \"zstd compression support\"\n--\ndrivers/block/zram/Kconfig-34-\ndrivers/block/zram/Kconfig:35:config ZRAM_BACKEND_DEFLATE\ndrivers/block/zram/Kconfig-36-\tbool \"deflate compression support\"\n--\ndrivers/block/zram/Kconfig-40-\ndrivers/block/zram/Kconfig:41:config ZRAM_BACKEND_842\ndrivers/block/zram/Kconfig-42-\tbool \"842 compression support\"\n--\ndrivers/block/zram/Kconfig-46-\ndrivers/block/zram/Kconfig:47:config ZRAM_BACKEND_FORCE_LZO\ndrivers/block/zram/Kconfig-48-\tdepends on ZRAM\n--\ndrivers/block/zram/Kconfig-52-\ndrivers/block/zram/Kconfig:53:config ZRAM_BACKEND_LZO\ndrivers/block/zram/Kconfig-54-\tbool \"lzo and lzo-rle compression support\" if !ZRAM_BACKEND_FORCE_LZO\n--\ndrivers/block/zram/Kconfig=60=choice\n--\ndrivers/block/zram/Kconfig-64-\ndrivers/block/zram/Kconfig:65:config ZRAM_DEF_COMP_LZORLE\ndrivers/block/zram/Kconfig-66-\tbool \"lzo-rle\"\n--\ndrivers/block/zram/Kconfig-68-\ndrivers/block/zram/Kconfig:69:config ZRAM_DEF_COMP_LZO\ndrivers/block/zram/Kconfig-70-\tbool \"lzo\"\n--\ndrivers/block/zram/Kconfig-72-\ndrivers/block/zram/Kconfig:73:config ZRAM_DEF_COMP_LZ4\ndrivers/block/zram/Kconfig-74-\tbool \"lz4\"\n--\ndrivers/block/zram/Kconfig-76-\ndrivers/block/zram/Kconfig:77:config ZRAM_DEF_COMP_LZ4HC\ndrivers/block/zram/Kconfig-78-\tbool \"lz4hc\"\n--\ndrivers/block/zram/Kconfig-80-\ndrivers/block/zram/Kconfig:81:config ZRAM_DEF_COMP_ZSTD\ndrivers/block/zram/Kconfig-82-\tbool \"zstd\"\n--\ndrivers/block/zram/Kconfig-84-\ndrivers/block/zram/Kconfig:85:config ZRAM_DEF_COMP_DEFLATE\ndrivers/block/zram/Kconfig-86-\tbool \"deflate\"\n--\ndrivers/block/zram/Kconfig-88-\ndrivers/block/zram/Kconfig:89:config ZRAM_DEF_COMP_842\ndrivers/block/zram/Kconfig-90-\tbool \"842\"\n--\ndrivers/block/zram/Kconfig=93=endchoice\ndrivers/block/zram/Kconfig-94-\ndrivers/block/zram/Kconfig:95:config ZRAM_DEF_COMP\ndrivers/block/zram/Kconfig-96-\tstring\n--\ndrivers/block/zram/Kconfig-106-\ndrivers/block/zram/Kconfig:107:config ZRAM_WRITEBACK\ndrivers/block/zram/Kconfig-108- bool \"Write back incompressible or idle page to backing device\"\n--\ndrivers/block/zram/Kconfig-120-\ndrivers/block/zram/Kconfig:121:config ZRAM_TRACK_ENTRY_ACTIME\ndrivers/block/zram/Kconfig-122-\tbool \"Track access time of zram entries\"\n--\ndrivers/block/zram/Kconfig-128-\ndrivers/block/zram/Kconfig:129:config ZRAM_MEMORY_TRACKING\ndrivers/block/zram/Kconfig-130-\tbool \"Track zRam block status\"\n--\ndrivers/block/zram/Kconfig-139-\ndrivers/block/zram/Kconfig:140:config ZRAM_MULTI_COMP\ndrivers/block/zram/Kconfig-141-\tbool \"Enable multiple compression streams\"\n"
} |
| 5/2 | 2026/10/16 08:10 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 25047 output: 35 thoughts: 20 Thoughts: Okay, let's dive into this. Here's my thought process on this: **Analyzing Kernel Configuration Dependencies** Alright, I need to understand the dependencies for `drivers/block/zram/Kconfig`. Time to get my hands dirty. I'm going to start by reading the `Kconfig` file itself. This is the definitive source for figuring out what options and prerequisites are involved in enabling or disabling the zram driver. It's the standard practice, and honestly, the most efficient way to get a clear picture of the build-time configuration landscape. |
| 6/2 | 2026/10/16 08:10 | tool | read-file |
0mArgs:{
"File": "drivers/block/zram/Kconfig",
"FirstLine": 1,
"LineCount": 60
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0\n 2:\tconfig ZRAM\n 3:\t\ttristate \"Compressed RAM block device support\"\n 4:\t\tdepends on BLOCK \u0026\u0026 SYSFS \u0026\u0026 MMU\n 5:\t\tselect ZSMALLOC\n 6:\t\thelp\n 7:\t\t Creates virtual block devices called /dev/zramX (X = 0, 1, ...).\n 8:\t\t Pages written to these disks are compressed and stored in memory\n 9:\t\t itself. These disks allow very fast I/O and compression provides\n 10:\t\t good amounts of memory savings.\n 11:\t\n 12:\t\t It has several use cases, for example: /tmp storage, use as swap\n 13:\t\t disks and maybe many more.\n 14:\t\n 15:\t\t See Documentation/admin-guide/blockdev/zram.rst for more information.\n 16:\t\n 17:\tconfig ZRAM_BACKEND_LZ4\n 18:\t\tbool \"lz4 compression support\"\n 19:\t\tdepends on ZRAM\n 20:\t\tselect LZ4_COMPRESS\n 21:\t\tselect LZ4_DECOMPRESS\n 22:\t\n 23:\tconfig ZRAM_BACKEND_LZ4HC\n 24:\t\tbool \"lz4hc compression support\"\n 25:\t\tdepends on ZRAM\n 26:\t\tselect LZ4HC_COMPRESS\n 27:\t\tselect LZ4_DECOMPRESS\n 28:\t\n 29:\tconfig ZRAM_BACKEND_ZSTD\n 30:\t\tbool \"zstd compression support\"\n 31:\t\tdepends on ZRAM\n 32:\t\tselect ZSTD_COMPRESS\n 33:\t\tselect ZSTD_DECOMPRESS\n 34:\t\n 35:\tconfig ZRAM_BACKEND_DEFLATE\n 36:\t\tbool \"deflate compression support\"\n 37:\t\tdepends on ZRAM\n 38:\t\tselect ZLIB_DEFLATE\n 39:\t\tselect ZLIB_INFLATE\n 40:\t\n 41:\tconfig ZRAM_BACKEND_842\n 42:\t\tbool \"842 compression support\"\n 43:\t\tdepends on ZRAM\n 44:\t\tselect 842_COMPRESS\n 45:\t\tselect 842_DECOMPRESS\n 46:\t\n 47:\tconfig ZRAM_BACKEND_FORCE_LZO\n 48:\t\tdepends on ZRAM\n 49:\t\tdef_bool !ZRAM_BACKEND_LZ4 \u0026\u0026 !ZRAM_BACKEND_LZ4HC \u0026\u0026 \\\n 50:\t\t\t!ZRAM_BACKEND_ZSTD \u0026\u0026 !ZRAM_BACKEND_DEFLATE \u0026\u0026 \\\n 51:\t\t\t!ZRAM_BACKEND_842\n 52:\t\n 53:\tconfig ZRAM_BACKEND_LZO\n 54:\t\tbool \"lzo and lzo-rle compression support\" if !ZRAM_BACKEND_FORCE_LZO\n 55:\t\tdepends on ZRAM\n 56:\t\tdefault ZRAM_BACKEND_FORCE_LZO\n 57:\t\tselect LZO_COMPRESS\n 58:\t\tselect LZO_DECOMPRESS\n 59:\t\n 60:\tchoice\n"
} |
| 7/2 | 2026/10/16 08:10 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 26037 output: 36 thoughts: 27 Thoughts: Okay, here's my take on those lines of the Kconfig file, framed as my own thought process: **Diving into `drivers/block/zram/Kconfig`** Alright, time to get a bit more granular. I've been working with zram, and to really understand the configuration options, I should probably revisit those Kconfig settings. Specifically, lines 60 through 145 of `drivers/block/zram/Kconfig`. Let's see... what's in this section? Ah, looks like this covers a lot of the core features and tuning parameters. I need to refresh my memory on the trade-offs involved in some of these. I suspect there are some important considerations for different workload profiles that I’ll want to review. Perhaps there are some newly added or modified configuration options, let’s see what's changed since the last time I looked. I'll mentally map these options to potential performance implications. I'll need to remember to relate these to the kernel documentation, of course. Right, let's start reading... |
| 8/2 | 2026/10/16 08:10 | tool | read-file |
0mArgs:{
"File": "drivers/block/zram/Kconfig",
"FirstLine": 61,
"LineCount": 85
}Results: {
"Contents": " 61:\t\tprompt \"Default zram compressor\"\n 62:\t\tdefault ZRAM_DEF_COMP_LZORLE\n 63:\t\tdepends on ZRAM\n 64:\t\n 65:\tconfig ZRAM_DEF_COMP_LZORLE\n 66:\t\tbool \"lzo-rle\"\n 67:\t\tdepends on ZRAM_BACKEND_LZO\n 68:\t\n 69:\tconfig ZRAM_DEF_COMP_LZO\n 70:\t\tbool \"lzo\"\n 71:\t\tdepends on ZRAM_BACKEND_LZO\n 72:\t\n 73:\tconfig ZRAM_DEF_COMP_LZ4\n 74:\t\tbool \"lz4\"\n 75:\t\tdepends on ZRAM_BACKEND_LZ4\n 76:\t\n 77:\tconfig ZRAM_DEF_COMP_LZ4HC\n 78:\t\tbool \"lz4hc\"\n 79:\t\tdepends on ZRAM_BACKEND_LZ4HC\n 80:\t\n 81:\tconfig ZRAM_DEF_COMP_ZSTD\n 82:\t\tbool \"zstd\"\n 83:\t\tdepends on ZRAM_BACKEND_ZSTD\n 84:\t\n 85:\tconfig ZRAM_DEF_COMP_DEFLATE\n 86:\t\tbool \"deflate\"\n 87:\t\tdepends on ZRAM_BACKEND_DEFLATE\n 88:\t\n 89:\tconfig ZRAM_DEF_COMP_842\n 90:\t\tbool \"842\"\n 91:\t\tdepends on ZRAM_BACKEND_842\n 92:\t\n 93:\tendchoice\n 94:\t\n 95:\tconfig ZRAM_DEF_COMP\n 96:\t\tstring\n 97:\t\tdepends on ZRAM\n 98:\t\tdefault \"lzo-rle\" if ZRAM_DEF_COMP_LZORLE\n 99:\t\tdefault \"lzo\" if ZRAM_DEF_COMP_LZO\n 100:\t\tdefault \"lz4\" if ZRAM_DEF_COMP_LZ4\n 101:\t\tdefault \"lz4hc\" if ZRAM_DEF_COMP_LZ4HC\n 102:\t\tdefault \"zstd\" if ZRAM_DEF_COMP_ZSTD\n 103:\t\tdefault \"deflate\" if ZRAM_DEF_COMP_DEFLATE\n 104:\t\tdefault \"842\" if ZRAM_DEF_COMP_842\n 105:\t\tdefault \"unset-value\"\n 106:\t\n 107:\tconfig ZRAM_WRITEBACK\n 108:\t bool \"Write back incompressible or idle page to backing device\"\n 109:\t depends on ZRAM\n 110:\t help\n 111:\t\t This lets zram entries (incompressible or idle pages) be written\n 112:\t\t back to a backing device, helping save memory.\n 113:\t\t For this feature, admin should set up backing device via\n 114:\t\t /sys/block/zramX/backing_dev.\n 115:\t\n 116:\t\t With /sys/block/zramX/{idle,writeback}, application could ask\n 117:\t\t idle page's writeback to the backing device to save in memory.\n 118:\t\n 119:\t\t See Documentation/admin-guide/blockdev/zram.rst for more information.\n 120:\t\n 121:\tconfig ZRAM_TRACK_ENTRY_ACTIME\n 122:\t\tbool \"Track access time of zram entries\"\n 123:\t\tdepends on ZRAM\n 124:\t\thelp\n 125:\t\t With this feature zram tracks access time of every stored\n 126:\t\t entry (page), which can be used for a more fine grained IDLE\n 127:\t\t pages writeback.\n 128:\t\n 129:\tconfig ZRAM_MEMORY_TRACKING\n 130:\t\tbool \"Track zRam block status\"\n 131:\t\tdepends on ZRAM \u0026\u0026 DEBUG_FS\n 132:\t\tselect ZRAM_TRACK_ENTRY_ACTIME\n 133:\t\thelp\n 134:\t\t With this feature, admin can track the state of allocated blocks\n 135:\t\t of zRAM. Admin could see the information via\n 136:\t\t /sys/kernel/debug/zram/zramX/block_state.\n 137:\t\n 138:\t\t See Documentation/admin-guide/blockdev/zram.rst for more information.\n 139:\t\n 140:\tconfig ZRAM_MULTI_COMP\n 141:\t\tbool \"Enable multiple compression streams\"\n 142:\t\tdepends on ZRAM\n 143:\t\thelp\n 144:\t\t This will enable multi-compression streams, so that ZRAM can\n 145:\t\t re-compress pages using a potentially slower but more effective\n"
} |
| 9/2 | 2026/10/16 08:10 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27565 output: 36 thoughts: 181 Thoughts: **Analyzing ZRAM Configuration Options** Okay, let's dive into these ZRAM configuration options and see what we're dealing with. First up, we've got the core `CONFIG_ZRAM` enabling the whole shebang. Then, we see the compression backend choices: `CONFIG_ZRAM_BACKEND_LZ4`, `CONFIG_ZRAM_BACKEND_LZ4HC`, `CONFIG_ZRAM_BACKEND_ZSTD`, `CONFIG_ZRAM_BACKEND_DEFLATE`, `CONFIG_ZRAM_BACKEND_842`, and `CONFIG_ZRAM_BACKEND_LZO`. Interesting mix, covering a range of performance and compression ratios. I'd need to consider the target use case to choose the best one. `CONFIG_ZRAM_WRITEBACK` is an important one, as it would enable writeback functionality. Now, the juicy bit: `CONFIG_ZRAM_MULTI_COMP`. This suggests we can configure multiple compression algorithms and the mention of `recompress_slot` and secondary algorithms hints at a priority system with a primary and potentially several secondary compression algorithms that are available, such as `zcomp_create(..., recomp)` and `prio != ZRAM_PRIMARY_COMP`. This looks like a smart optimization – the system probably tries the faster primary algorithm first, and falls back to others if needed. I need to get into `zram_drv.c` to see the actual implementation to see how the secondary comps are *actually* configured. That's where the magic probably happens. Let's see how that priority assignment works. |
| 10/2 | 2026/10/16 08:10 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_ZRAM_MULTI_COMP",
"PathPrefixes": [
"drivers/block/zram/"
]
}Results: {
"Output": "drivers/block/zram/zram_drv.c=219=static inline bool is_partial_io(struct bio_vec *bvec)\n--\ndrivers/block/zram/zram_drv.c-224-\ndrivers/block/zram/zram_drv.c:225:#if defined CONFIG_ZRAM_WRITEBACK || defined CONFIG_ZRAM_MULTI_COMP\ndrivers/block/zram/zram_drv.c-226-struct zram_pp_slot {\n--\ndrivers/block/zram/zram_drv.c=1833=static ssize_t comp_algorithm_store(struct device *dev,\n--\ndrivers/block/zram/zram_drv.c-1844-\ndrivers/block/zram/zram_drv.c:1845:#ifdef CONFIG_ZRAM_MULTI_COMP\ndrivers/block/zram/zram_drv.c-1846-static ssize_t recomp_algorithm_show(struct device *dev,\n--\ndrivers/block/zram/zram_drv.c=2385=static int zram_bvec_write(struct zram *zram, struct bio_vec *bvec,\n--\ndrivers/block/zram/zram_drv.c-2392-\ndrivers/block/zram/zram_drv.c:2393:#ifdef CONFIG_ZRAM_MULTI_COMP\ndrivers/block/zram/zram_drv.c-2394-#define RECOMPRESS_IDLE\t\t(1 \u003c\u003c 0)\n--\ndrivers/block/zram/zram_drv.c=3030=static DEVICE_ATTR_RW(compressed_writeback);\ndrivers/block/zram/zram_drv.c-3031-#endif\ndrivers/block/zram/zram_drv.c:3032:#ifdef CONFIG_ZRAM_MULTI_COMP\ndrivers/block/zram/zram_drv.c-3033-static DEVICE_ATTR_RW(recomp_algorithm);\n--\ndrivers/block/zram/zram_drv.c=3038=static struct attribute *zram_disk_attrs[] = {\n--\ndrivers/block/zram/zram_drv.c-3058-\t\u0026dev_attr_debug_stat.attr,\ndrivers/block/zram/zram_drv.c:3059:#ifdef CONFIG_ZRAM_MULTI_COMP\ndrivers/block/zram/zram_drv.c-3060-\t\u0026dev_attr_recomp_algorithm.attr,\n--\ndrivers/block/zram/zram_drv.h=93=struct zram_stats {\n--\ndrivers/block/zram/zram_drv.h-110-\ndrivers/block/zram/zram_drv.h:111:#ifdef CONFIG_ZRAM_MULTI_COMP\ndrivers/block/zram/zram_drv.h-112-#define ZRAM_PRIMARY_COMP\t0U\n"
} |
| 11/2 | 2026/10/16 08:10 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 28862 output: 56 thoughts: 1193 Thoughts: **ZRAM Multi-Compression and Writeback Analysis** Okay, so `CONFIG_ZRAM_MULTI_COMP` is definitely the key here, it's enabling the multi-stream compression and the `recomp_algorithm` stuff, which means I should be looking closely at `recomp_cstream` and `recompress_slot` in `zram_drv.c` and `zcomp.c`. Also, `CONFIG_ZRAM_WRITEBACK` is bringing in writeback functionality and `decompress_bdev_page`. Let me quickly review the relevant configurations just to make sure I'm on the right track: `CONFIG_ZRAM`, `CONFIG_ZRAM_MULTI_COMP`, `CONFIG_ZRAM_WRITEBACK`, and the backend compression algorithms: `CONFIG_ZRAM_BACKEND_LZ4`, `CONFIG_ZRAM_BACKEND_LZ4HC`, `CONFIG_ZRAM_BACKEND_ZSTD`, `CONFIG_ZRAM_BACKEND_DEFLATE`, `CONFIG_ZRAM_BACKEND_842`, and `CONFIG_ZRAM_BACKEND_LZO`. Now, the important part: the symbols. I need to get a handle on the changes. In `zcomp.c`, I've got a whole bunch of new initialization and freeing functions for compression/decompression streams, specifically `zcomp_cstrm_init`, `zcomp_cstrm_free`, `zcomp_dstrm_init`, `zcomp_dstrm_free`, along with the stream management functions: `zcomp_cstrm_get`, `zcomp_cstrm_put`, `zcomp_dstrm_get`, and `zcomp_dstrm_put`. There are also the core `zcomp_compress` and `zcomp_decompress` functions, and new `zcomp_shared_decomp`, `zcomp_create`, and `zcomp_destroy` functions. In `zram_drv.c`, I see `read_compressed_page`, `zram_write_page`, `recompress_slot`, `decompress_bdev_page`, and `zram_reset_device` as the modifications to the existing device driver. Then, there are the backend changes. Each backend (deflate, lz4, zstd, etc.) has its own set of create, destroy, compress, and decompress functions. They all seem consistent with providing the backing compression capabilities, though I want to see if any are non-static. Okay, let's step back and think about which symbols are *important* for analysis, focusing on the guidelines. I need to avoid listing everything and prioritize feature-specific logic and entry points. I should focus on the non-static functions. So, let's check the exported or non-static functions in `zcomp.c` and `zram_drv.c`. I need to start with: `zcomp_compress`, `zcomp_decompress`, `zcomp_cstrm_get`, `zcomp_dstrm_get`, `zcomp_shared_decomp`, `zcomp_create`, `read_compressed_page`, `zram_write_page`, `recompress_slot`, and `decompress_bdev_page`. Now, the crucial question: are *any* of those backend compress/decompress functions non-static? Let me quickly scan each `backend_*.c` file. Yes, they are static... but they are being used within those files! But wait... in `drivers/block/zram/zcomp.h`, `zcomp_compress`, `zcomp_decompress`, `zcomp_cstrm_get`, `zcomp_cstrm_put`, `zcomp_dstrm_get`, `zcomp_dstrm_put`, `zcomp_shared_decomp`, `zcomp_create`, and `zcomp_destroy` are *not* static, so they *are* exported. Time to go back and double check in `drivers/block/zram/zcomp.c` and `drivers/block/zram/zram_drv.c` to confirm! |
| 12/2 | 2026/10/16 08:11 | tool | grepper |
0mArgs:{
"Expression": "^int zcomp_|^struct zcomp_.*zcomp_|^void zcomp_|^bool zcomp_",
"PathPrefixes": [
"drivers/block/zram/zcomp.c"
]
}Results: {
"Output": "drivers/block/zram/zcomp.c=135=ssize_t zcomp_available_show(const char *comp, char *buf, ssize_t at)\n--\ndrivers/block/zram/zcomp.c-151-\ndrivers/block/zram/zcomp.c:152:struct zcomp_cstrm *zcomp_cstrm_get(struct zcomp *comp)\ndrivers/block/zram/zcomp.c-153-{\n--\ndrivers/block/zram/zcomp.c-178-\ndrivers/block/zram/zcomp.c:179:void zcomp_cstrm_put(struct zcomp_cstrm *zstrm)\ndrivers/block/zram/zcomp.c-180-{\n--\ndrivers/block/zram/zcomp.c-183-\ndrivers/block/zram/zcomp.c:184:bool zcomp_shared_decomp(struct zcomp *comp)\ndrivers/block/zram/zcomp.c-185-{\n--\ndrivers/block/zram/zcomp.c-192-\ndrivers/block/zram/zcomp.c:193:struct zcomp_dstrm *zcomp_dstrm_get(struct zcomp *comp,\ndrivers/block/zram/zcomp.c-194-\t\t\t\t enum zcomp_dstrm_mode mode)\n--\ndrivers/block/zram/zcomp.c-220-\ndrivers/block/zram/zcomp.c:221:void zcomp_dstrm_put(struct zcomp_dstrm *zstrm, enum zcomp_dstrm_mode mode)\ndrivers/block/zram/zcomp.c-222-{\n--\ndrivers/block/zram/zcomp.c-227-\ndrivers/block/zram/zcomp.c:228:int zcomp_compress(struct zcomp *comp, struct zcomp_cstrm *zstrm,\ndrivers/block/zram/zcomp.c-229-\t\t const void *src, unsigned int *dst_len)\n--\ndrivers/block/zram/zcomp.c=246=static int zcomp_decompress_buf(struct zcomp *comp, struct zcomp_dstrm *zstrm,\n--\ndrivers/block/zram/zcomp.c-254-\ndrivers/block/zram/zcomp.c:255:int zcomp_decompress(struct zcomp *comp, struct zcomp_dstrm *zstrm,\ndrivers/block/zram/zcomp.c-256-\t\t struct scatterlist *sg, unsigned int src_len, void *dst)\n--\ndrivers/block/zram/zcomp.c-299-\ndrivers/block/zram/zcomp.c:300:int zcomp_cpu_up_prepare(unsigned int cpu, struct hlist_node *node)\ndrivers/block/zram/zcomp.c-301-{\n--\ndrivers/block/zram/zcomp.c-327-\ndrivers/block/zram/zcomp.c:328:int zcomp_cpu_dead(unsigned int cpu, struct hlist_node *node)\ndrivers/block/zram/zcomp.c-329-{\n--\ndrivers/block/zram/zcomp.c=349=static int zcomp_init(struct zcomp *comp, struct zcomp_params *params,\n--\ndrivers/block/zram/zcomp.c-412-\ndrivers/block/zram/zcomp.c:413:void zcomp_destroy(struct zcomp *comp)\ndrivers/block/zram/zcomp.c-414-{\n"
} |
| 13/2 | 2026/10/16 08:11 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 31940 output: 63 thoughts: 251 Thoughts: **Investigating Symbol Visibility in the Linux Kernel** Okay, so I need to figure out the symbol visibility of certain functions within the kernel, specifically concerning compression. My immediate thought is to determine if static functions within either backend files or `zcomp.c` are represented as symbols in the `vmlinux` image. I know that in the Linux kernel, static functions declared in `.c` files, assuming they aren't inlined or compiled with Link Time Optimization or whole-program optimization, often *do* have symbols listed within `kallsyms`, though this is not a guaranteed visibility. Non-static symbols are, of course, definitely going to be in symbol tables. I need to focus on relevant and less-common symbols to pinpoint the details. I'll consider these symbols: * `zcomp_compress` * `zcomp_decompress` * `zcomp_cstrm_get` * `zcomp_dstrm_get` * `zcomp_shared_decomp` * `zcomp_create` * `read_compressed_page` * `zram_write_page` * `recompress_slot` * `decompress_bdev_page` My next step is to quickly determine if the functions `read_compressed_page`, `zram_write_page`, `recompress_slot`, and `decompress_bdev_page` are declared as static within `zram_drv.c`. I will have to check the code directly in `drivers/block/zram/zram_drv.c`. |
| 14/2 | 2026/10/16 08:11 | tool | grepper |
0mArgs:{
"Expression": "static int (read_compressed_page|zram_write_page|recompress_slot|decompress_bdev_page)",
"PathPrefixes": [
"drivers/block/zram/zram_drv.c"
]
}Results: {
"Output": "drivers/block/zram/zram_drv.c=1242=static ssize_t writeback_store(struct device *dev,\n--\ndrivers/block/zram/zram_drv.c-1347-\ndrivers/block/zram/zram_drv.c:1348:static int decompress_bdev_page(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-1349-\t\t\t\tunsigned long index)\n--\ndrivers/block/zram/zram_drv.c=2095=static int read_incompressible_page(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2114-\ndrivers/block/zram/zram_drv.c:2115:static int read_compressed_page(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-2116-\t\t\t\tunsigned long index)\n--\ndrivers/block/zram/zram_drv.c=2259=static int write_incompressible_page(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2299-\ndrivers/block/zram/zram_drv.c:2300:static int zram_write_page(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-2301-\t\t\t unsigned long index)\n--\ndrivers/block/zram/zram_drv.c=2409=static void scan_slots_for_recompress(struct zram *zram, u32 mode, u32 prio,\n--\ndrivers/block/zram/zram_drv.c-2453- */\ndrivers/block/zram/zram_drv.c:2454:static int recompress_slot(struct zram *zram, unsigned long index,\ndrivers/block/zram/zram_drv.c-2455-\t\t\t struct page *page, u64 *num_recomp_pages,\n"
} |
| 15/2 | 2026/10/16 08:11 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 33152 output: 59 thoughts: 38 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing ZRAM Compression Logic** Alright, let's dive in. My immediate focus is on the core compression/decompression path within the `zram_drv.c` module. I need a clear understanding of data flow, so I'm drilling down on the key functions. I want to see precisely *where* `read_compressed_page` gets called. Understanding the contexts of the decompression path is critical for identifying potential bottlenecks or optimization opportunities. Then I need to follow the same logic for `zram_write_page`, that should be relatively straightforward: I'll be looking at how data gets compressed and written. Finally, to complete my understanding of the compression lifecycle, I need to see where `recompress_slot` comes into play. Perhaps there are opportunities there to improve efficiency or reduce fragmentation. The full picture needs these three main actors mapped out. |
| 16/2 | 2026/10/16 08:11 | tool | grepper |
0mArgs:{
"Expression": "read_compressed_page|zram_write_page|recompress_slot|decompress_bdev_page",
"PathPrefixes": [
"drivers/block/zram/zram_drv.c"
]
}Results: {
"Output": "drivers/block/zram/zram_drv.c=920=static int zram_writeback_complete(struct zram *zram, struct zram_wb_req *req)\n--\ndrivers/block/zram/zram_drv.c-939-\t * We release slot lock during writeback so slot can change under us:\ndrivers/block/zram/zram_drv.c:940:\t * slot_free() or slot_free() and zram_write_page(). In both cases\ndrivers/block/zram/zram_drv.c-941-\t * slot loses ZRAM_PP_SLOT flag. No concurrent post-processing can\n--\ndrivers/block/zram/zram_drv.c=1242=static ssize_t writeback_store(struct device *dev,\n--\ndrivers/block/zram/zram_drv.c-1347-\ndrivers/block/zram/zram_drv.c:1348:static int decompress_bdev_page(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-1349-\t\t\t\tunsigned long index)\n--\ndrivers/block/zram/zram_drv.c=1391=static void zram_deferred_decompress(struct work_struct *w)\n--\ndrivers/block/zram/zram_drv.c-1398-\ndrivers/block/zram/zram_drv.c:1399:\tret = decompress_bdev_page(zram, page, index);\ndrivers/block/zram/zram_drv.c-1400-\tif (ret)\n--\ndrivers/block/zram/zram_drv.c=1497=static int read_from_bdev_sync(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-1513-\ndrivers/block/zram/zram_drv.c:1514:\treturn decompress_bdev_page(zram, page, index);\ndrivers/block/zram/zram_drv.c-1515-}\n--\ndrivers/block/zram/zram_drv.c=2095=static int read_incompressible_page(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2114-\ndrivers/block/zram/zram_drv.c:2115:static int read_compressed_page(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-2116-\t\t\t\tunsigned long index)\n--\ndrivers/block/zram/zram_drv.c=2176=static int read_from_zspool(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2183-\tif (!test_slot_flag(zram, index, ZRAM_HUGE))\ndrivers/block/zram/zram_drv.c:2184:\t\treturn read_compressed_page(zram, page, index);\ndrivers/block/zram/zram_drv.c-2185-\telse\n--\ndrivers/block/zram/zram_drv.c=2259=static int write_incompressible_page(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2299-\ndrivers/block/zram/zram_drv.c:2300:static int zram_write_page(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-2301-\t\t\t unsigned long index)\n--\ndrivers/block/zram/zram_drv.c=2367=static int zram_bvec_write_partial(struct zram *zram, struct bio_vec *bvec,\n--\ndrivers/block/zram/zram_drv.c-2378-\t\tmemcpy_from_bvec(page_address(page) + offset, bvec);\ndrivers/block/zram/zram_drv.c:2379:\t\tret = zram_write_page(zram, page, index);\ndrivers/block/zram/zram_drv.c-2380-\t}\n--\ndrivers/block/zram/zram_drv.c=2385=static int zram_bvec_write(struct zram *zram, struct bio_vec *bvec,\n--\ndrivers/block/zram/zram_drv.c-2389-\t\treturn zram_bvec_write_partial(zram, bvec, index, offset);\ndrivers/block/zram/zram_drv.c:2390:\treturn zram_write_page(zram, bvec-\u003ebv_page, index);\ndrivers/block/zram/zram_drv.c-2391-}\n--\ndrivers/block/zram/zram_drv.c=2409=static void scan_slots_for_recompress(struct zram *zram, u32 mode, u32 prio,\n--\ndrivers/block/zram/zram_drv.c-2453- */\ndrivers/block/zram/zram_drv.c:2454:static int recompress_slot(struct zram *zram, unsigned long index,\ndrivers/block/zram/zram_drv.c-2455-\t\t\t struct page *page, u64 *num_recomp_pages,\n--\ndrivers/block/zram/zram_drv.c=2561=static ssize_t recompress_store(struct device *dev,\n--\ndrivers/block/zram/zram_drv.c-2682-\ndrivers/block/zram/zram_drv.c:2683:\t\terr = recompress_slot(zram, pps-\u003eindex, page,\ndrivers/block/zram/zram_drv.c-2684-\t\t\t\t \u0026num_recomp_pages, threshold, prio);\n"
} |
| 17/2 | 2026/10/16 08:11 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 34983 output: 331 thoughts: 1768 Thoughts: **Fuzzing ZRAM: A Deep Dive into the Code** Okay, let's break this down. My initial thought process is to assess the fuzzing potential of this code. First, I need to identify the key functions and how they interact. Looking at `zcomp.h` and `zcomp.c`, I've got a good handle on the primary entry points: `zcomp_compress`, `zcomp_decompress`, `zcomp_cstrm_get`, `zcomp_dstrm_get`, `zcomp_shared_decomp`, `zcomp_create`, and `zcomp_destroy`. Then there are the critical functions from `zram_drv.c`: `read_compressed_page`, `zram_write_page`, `recompress_slot`, and `decompress_bdev_page`. Next, I need to consider the backend implementations. Examining `backend_*.c` reveals the `zcomp_ops` structures for different compression algorithms (`lz4`, `lz4hc`, `deflate`, `zstd`, `lzo`, `lzorle`, `842`). These appear to be the actual compression engines, and these are defined by the `struct zcomp_ops` structure. Now, I'm digging deeper into the changes. The diff clearly highlights extensive refactoring, specifically focusing on the compression/decompression contexts, stream allocation, concurrency, locking, and memory management across `zcomp` and all those backends. I'm seeing renaming of context creation and destruction functions (e.g., `create_ctx` to `create_cctx`), splitting of functions (e.g., `deflate_destroy` into `deflate_destroy_cctx` and `deflate_destroy_dctx`), and the introduction of various lock types and assertions using `lockdep_assert_held`. This screams "potential bugs." My evaluation of the modifications suggests a high potential for fuzzing because of: 1. **Accessibility**: ZRAM is easily reproducible in standard virtualized environments. 2. **Scope**: Significant refactoring across the core and all backends. 3. **Concurrency Issues**: The new locking introduces opportunities for concurrency problems. 4. **Memory corruption**: The use of different contexts (cctx/dctx) potentially creates new allocation bugs. Based on the analysis, this is definitely worth fuzzing. I'll need to enable these configurations: `ZRAM`, `ZRAM_BACKEND_LZ4`, `ZRAM_BACKEND_LZ4HC`, `ZRAM_BACKEND_ZSTD`, `ZRAM_BACKEND_DEFLATE`, `ZRAM_BACKEND_842`, `ZRAM_BACKEND_LZO`, `ZRAM_WRITEBACK`, and `ZRAM_MULTI_COMP`. I want to select the crucial functions as the focus symbols: `zcomp_compress`, `zcomp_decompress`, `zcomp_cstrm_get`, `zcomp_dstrm_get`, `zcomp_shared_decomp`, `zcomp_create`, `read_compressed_page`, `zram_write_page`, `recompress_slot`, `decompress_bdev_page`, along with `lz4_compress`, `lz4_decompress`, `lz4hc_compress`, `lz4hc_decompress`, `zstd_compress`, `zstd_decompress`, `deflate_compress`, and `deflate_decompress`. These all directly trigger the new code paths. I'm confident these symbols are appropriate to expose the new stream model, locking, assertion checks, buffer allocation, and de/compression calls. I'm ready to make the tool call, this is solid. |
| 18/2 | 2026/10/16 08:11 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"ZRAM",
"ZRAM_BACKEND_LZ4",
"ZRAM_BACKEND_LZ4HC",
"ZRAM_BACKEND_ZSTD",
"ZRAM_BACKEND_DEFLATE",
"ZRAM_BACKEND_842",
"ZRAM_BACKEND_LZO",
"ZRAM_WRITEBACK",
"ZRAM_MULTI_COMP"
],
"FocusSymbols": [
"zcomp_compress",
"zcomp_decompress",
"zcomp_cstrm_get",
"zcomp_dstrm_get",
"zcomp_shared_decomp",
"zcomp_create",
"read_compressed_page",
"zram_write_page",
"recompress_slot",
"decompress_bdev_page"
],
"Reasoning": "The patch series refactors zram's compression frontend and backend subsystem, separating compression and decompression streams (cstream vs dstream), introducing shared vs exclusive decompression concurrency with read/write semaphores and mutexes, adding a dedicated per-device recompression stream, and restructuring context allocation across compression backends (lz4, lz4hc, deflate, zstd, etc.). It also adds new lockdep assertions and fallback bounce-buffer logic in zcomp_decompress. The code is entirely reachable via standard virtualized block operations and zram sysfs interfaces, making it an excellent target for fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"ZRAM",
"ZRAM_BACKEND_LZ4",
"ZRAM_BACKEND_LZ4HC",
"ZRAM_BACKEND_ZSTD",
"ZRAM_BACKEND_DEFLATE",
"ZRAM_BACKEND_842",
"ZRAM_BACKEND_LZO",
"ZRAM_WRITEBACK",
"ZRAM_MULTI_COMP"
],
"FocusSymbols": [
"zcomp_compress",
"zcomp_decompress",
"zcomp_cstrm_get",
"zcomp_dstrm_get",
"zcomp_shared_decomp",
"zcomp_create",
"read_compressed_page",
"zram_write_page",
"recompress_slot",
"decompress_bdev_page"
],
"Reasoning": "The patch series refactors zram's compression frontend and backend subsystem, separating compression and decompression streams (cstream vs dstream), introducing shared vs exclusive decompression concurrency with read/write semaphores and mutexes, adding a dedicated per-device recompression stream, and restructuring context allocation across compression backends (lz4, lz4hc, deflate, zstd, etc.). It also adds new lockdep assertions and fallback bounce-buffer logic in zcomp_decompress. The code is entirely reachable via standard virtualized block operations and zram sysfs interfaces, making it an excellent target for fuzzing.",
"WorthFuzzing": true
} |
| 19/1 | 2026/10/16 08:11 | action | If |
3mArgs:{
"WorthFuzzing": true
}Results: null |
| 20/2 | 2026/10/16 08:11 | agent | kmsan-evaluator |
3mModel:core Args: null Results: {
"KMSANReasoning": "The patch series refactors zram compression backends and stream management by separating unified compression/decompression streams into dedicated compression (`zcomp_cstrm`) and decompression (`zcomp_dstrm`) streams, supporting concurrent shared decompression for stateless backends, and dedicating a single stream for recompression.\n\nFrom a memory safety and sanitizer perspective:\n1. Uninitialized memory risks:\n - All stream, context, and workspace allocations use zero-initializing allocators (`kzalloc_obj`, `vzalloc`, `__GFP_ZERO`, and `alloc_percpu` which zeroes memory).\n - In `backend_lz4` and `backend_lz4hc` where `vmalloc` is used for compression contexts, `LZ4_compress_fast` / `LZ4_compress_HC` explicitly resets and initializes the working memory (`LZ4_resetStream`, `LZ4HC_init`) or copies from the zero-initialized dictionary stream before execution.\n - In `zcomp_decompress()`, the dynamic bounce buffer allocated via `kmalloc()` is completely populated with `req.src_len` bytes by `sg_copy_to_buffer()` before being passed as `req.src` to the decompressor.\n - No kernel structures, padding, or uninitialized buffers are copied to user space (no `copy_to_user`, ioctl outputs, or netlink attributes involved).\n2. Primary bug risks:\n - The refactoring primarily introduces risks related to locking/concurrency (the new rwsem and mutex synchronization model between CPU hotplug and shared/exclusive decompression modes), lifetime management (CPU hotplug state callbacks), and potential out-of-bounds memory accesses or use-after-free.\n - These risks are fully covered by standard debugging facilities and sanitizers enabled in standard KASAN fuzzing builds (specifically KASAN and LOCKDEP).\n\nBecause no uninitialized memory usage or kernel-to-user info-leak risks are introduced, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 8c8370b2ce1d31f392641b412dca2a03280f3d38
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Oct 16 08:10:28 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c
index 3846a04c69d71..f911cd1ca72e4 100644
--- a/drivers/block/zram/backend_842.c
+++ b/drivers/block/zram/backend_842.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "842: " fmt
+#define pr_fmt(fmt) "zram: 842: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -63,8 +63,8 @@ static int decompress_842(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_842 = {
.compress = compress_842,
.decompress = decompress_842,
- .create_ctx = create_842,
- .destroy_ctx = destroy_842,
+ .create_cctx = create_842,
+ .destroy_cctx = destroy_842,
.setup_params = setup_params_842,
.release_params = release_params_842,
.name = "842",
diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c
index f71b11bcac788..5ffdaf80d8cff 100644
--- a/drivers/block/zram/backend_deflate.c
+++ b/drivers/block/zram/backend_deflate.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "deflate: " fmt
+#define pr_fmt(fmt) "zram: deflate: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -13,11 +13,6 @@
#define DEFLATE_DEF_WINBITS (-11)
#define DEFLATE_DEF_MEMLEVEL MAX_MEM_LEVEL
-struct deflate_ctx {
- struct z_stream_s cctx;
- struct z_stream_s dctx;
-};
-
static void deflate_release_params(struct zcomp_params *params)
{
}
@@ -51,70 +46,98 @@ static int deflate_setup_params(struct zcomp_params *params)
return 0;
}
-static void deflate_destroy(struct zcomp_ctx *ctx)
+static void deflate_destroy_cctx(struct zcomp_ctx *ctx)
{
- struct deflate_ctx *zctx = ctx->context;
+ struct z_stream_s *cctx = ctx->context;
- if (!zctx)
+ if (!cctx)
return;
- if (zctx->cctx.workspace) {
- zlib_deflateEnd(&zctx->cctx);
- vfree(zctx->cctx.workspace);
- }
- if (zctx->dctx.workspace) {
- zlib_inflateEnd(&zctx->dctx);
- vfree(zctx->dctx.workspace);
+ if (cctx->workspace) {
+ zlib_deflateEnd(cctx);
+ vfree(cctx->workspace);
}
- kfree(zctx);
+ kfree(cctx);
}
-static int deflate_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
+static int deflate_create_cctx(struct zcomp_params *params,
+ struct zcomp_ctx *ctx)
{
- struct deflate_ctx *zctx;
+ struct z_stream_s *cctx;
size_t sz;
int ret;
- zctx = kzalloc_obj(*zctx);
- if (!zctx)
+ cctx = kzalloc_obj(*cctx);
+ if (!cctx)
return -ENOMEM;
- ctx->context = zctx;
+ ctx->context = cctx;
sz = zlib_deflate_workspacesize(params->deflate.winbits, MAX_MEM_LEVEL);
- zctx->cctx.workspace = vzalloc(sz);
- if (!zctx->cctx.workspace)
+ cctx->workspace = vzalloc(sz);
+ if (!cctx->workspace)
goto error;
- ret = zlib_deflateInit2(&zctx->cctx, params->level, Z_DEFLATED,
+ ret = zlib_deflateInit2(cctx, params->level, Z_DEFLATED,
params->deflate.winbits, DEFLATE_DEF_MEMLEVEL,
Z_DEFAULT_STRATEGY);
if (ret != Z_OK)
goto error;
+ return 0;
+
+error:
+ deflate_destroy_cctx(ctx);
+ return -EINVAL;
+}
+
+static void deflate_destroy_dctx(struct zcomp_ctx *ctx)
+{
+ struct z_stream_s *dctx = ctx->context;
+
+ if (!dctx)
+ return;
+
+ if (dctx->workspace) {
+ zlib_inflateEnd(dctx);
+ vfree(dctx->workspace);
+ }
+ kfree(dctx);
+}
+
+static int deflate_create_dctx(struct zcomp_params *params,
+ struct zcomp_ctx *ctx)
+{
+ struct z_stream_s *dctx;
+ size_t sz;
+ int ret;
+
+ dctx = kzalloc_obj(*dctx);
+ if (!dctx)
+ return -ENOMEM;
+
+ ctx->context = dctx;
sz = zlib_inflate_workspacesize();
- zctx->dctx.workspace = vzalloc(sz);
- if (!zctx->dctx.workspace)
+ dctx->workspace = vzalloc(sz);
+ if (!dctx->workspace)
goto error;
- ret = zlib_inflateInit2(&zctx->dctx, params->deflate.winbits);
+ ret = zlib_inflateInit2(dctx, params->deflate.winbits);
if (ret != Z_OK)
goto error;
return 0;
error:
- deflate_destroy(ctx);
+ deflate_destroy_dctx(ctx);
return -EINVAL;
}
static int deflate_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct deflate_ctx *zctx = ctx->context;
- struct z_stream_s *deflate;
+ struct z_stream_s *deflate = ctx->context;
int ret;
- deflate = &zctx->cctx;
ret = zlib_deflateReset(deflate);
if (ret != Z_OK)
return -EINVAL;
@@ -136,12 +159,9 @@ static int deflate_decompress(struct zcomp_params *params,
struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct deflate_ctx *zctx = ctx->context;
- struct z_stream_s *inflate;
+ struct z_stream_s *inflate = ctx->context;
int ret;
- inflate = &zctx->dctx;
-
ret = zlib_inflateReset(inflate);
if (ret != Z_OK)
return -EINVAL;
@@ -161,8 +181,10 @@ static int deflate_decompress(struct zcomp_params *params,
const struct zcomp_ops backend_deflate = {
.compress = deflate_compress,
.decompress = deflate_decompress,
- .create_ctx = deflate_create,
- .destroy_ctx = deflate_destroy,
+ .create_cctx = deflate_create_cctx,
+ .destroy_cctx = deflate_destroy_cctx,
+ .create_dctx = deflate_create_dctx,
+ .destroy_dctx = deflate_destroy_dctx,
.setup_params = deflate_setup_params,
.release_params = deflate_release_params,
.name = "deflate",
diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c
index 1e4ad31d39a63..9767482a2fea0 100644
--- a/drivers/block/zram/backend_lz4.c
+++ b/drivers/block/zram/backend_lz4.c
@@ -1,21 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "lz4: " fmt
+#define pr_fmt(fmt) "zram: lz4: " fmt
#include <linux/kernel.h>
#include <linux/lz4.h>
-#include <linux/slab.h>
#include <linux/vmalloc.h>
#include "backend_lz4.h"
-struct lz4_ctx {
- void *mem;
-
- LZ4_streamDecode_t *dstrm;
- LZ4_stream_t *cstrm;
-};
-
static void lz4_release_params(struct zcomp_params *params)
{
LZ4_stream_t *dict_stream = params->drv_data;
@@ -24,7 +16,7 @@ static void lz4_release_params(struct zcomp_params *params)
if (!dict_stream)
return;
- kfree(dict_stream);
+ vfree(dict_stream);
}
static int lz4_setup_params(struct zcomp_params *params)
@@ -42,14 +34,13 @@ static int lz4_setup_params(struct zcomp_params *params)
if (!params->dict || !params->dict_sz)
return 0;
- dict_stream = kzalloc_obj(*dict_stream);
+ dict_stream = vzalloc(sizeof(*dict_stream));
if (!dict_stream)
return -ENOMEM;
- ret = LZ4_loadDict(dict_stream,
- params->dict, params->dict_sz);
+ ret = LZ4_loadDict(dict_stream, params->dict, params->dict_sz);
if (ret != params->dict_sz) {
- kfree(dict_stream);
+ vfree(dict_stream);
return -EINVAL;
}
params->drv_data = dict_stream;
@@ -59,61 +50,32 @@ static int lz4_setup_params(struct zcomp_params *params)
static void lz4_destroy(struct zcomp_ctx *ctx)
{
- struct lz4_ctx *zctx = ctx->context;
-
- if (!zctx)
- return;
-
- vfree(zctx->mem);
- kfree(zctx->dstrm);
- kfree(zctx->cstrm);
- kfree(zctx);
+ vfree(ctx->context);
}
static int lz4_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
{
- struct lz4_ctx *zctx;
+ BUILD_BUG_ON(sizeof(LZ4_stream_t) != LZ4_MEM_COMPRESS);
- zctx = kzalloc_obj(*zctx);
- if (!zctx)
+ ctx->context = vmalloc(LZ4_MEM_COMPRESS);
+ if (!ctx->context)
return -ENOMEM;
-
- ctx->context = zctx;
- if (params->dict_sz == 0) {
- zctx->mem = vmalloc(LZ4_MEM_COMPRESS);
- if (!zctx->mem)
- goto error;
- } else {
- zctx->dstrm = kzalloc_obj(*zctx->dstrm);
- if (!zctx->dstrm)
- goto error;
-
- zctx->cstrm = kzalloc_obj(*zctx->cstrm);
- if (!zctx->cstrm)
- goto error;
- }
-
return 0;
-
-error:
- lz4_destroy(ctx);
- return -ENOMEM;
}
static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct lz4_ctx *zctx = ctx->context;
int ret;
- if (!zctx->cstrm) {
+ if (!params->drv_data) {
ret = LZ4_compress_fast(req->src, req->dst, req->src_len,
req->dst_len, params->level,
- zctx->mem);
+ ctx->context);
} else {
/* Cstrm needs to be reset */
- memcpy(zctx->cstrm, params->drv_data, sizeof(*zctx->cstrm));
- ret = LZ4_compress_fast_continue(zctx->cstrm, req->src,
+ memcpy(ctx->context, params->drv_data, sizeof(LZ4_stream_t));
+ ret = LZ4_compress_fast_continue(ctx->context, req->src,
req->dst, req->src_len,
req->dst_len, params->level);
}
@@ -126,22 +88,11 @@ static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
static int lz4_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct lz4_ctx *zctx = ctx->context;
int ret;
- if (!zctx->dstrm) {
- ret = LZ4_decompress_safe(req->src, req->dst, req->src_len,
- req->dst_len);
- } else {
- /* Dstrm needs to be reset */
- ret = LZ4_setStreamDecode(zctx->dstrm, params->dict,
- params->dict_sz);
- if (!ret)
- return -EINVAL;
- ret = LZ4_decompress_safe_continue(zctx->dstrm, req->src,
- req->dst, req->src_len,
- req->dst_len);
- }
+ ret = LZ4_decompress_safe_usingDict(req->src, req->dst, req->src_len,
+ req->dst_len, params->dict,
+ params->dict_sz);
if (ret < 0)
return -EINVAL;
return 0;
@@ -150,8 +101,8 @@ static int lz4_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_lz4 = {
.compress = lz4_compress,
.decompress = lz4_decompress,
- .create_ctx = lz4_create,
- .destroy_ctx = lz4_destroy,
+ .create_cctx = lz4_create,
+ .destroy_cctx = lz4_destroy,
.setup_params = lz4_setup_params,
.release_params = lz4_release_params,
.name = "lz4",
diff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c
index d8aa01bb258fe..2d1364a29b27b 100644
--- a/drivers/block/zram/backend_lz4hc.c
+++ b/drivers/block/zram/backend_lz4hc.c
@@ -1,27 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "lz4hc: " fmt
+#define pr_fmt(fmt) "zram: lz4hc: " fmt
#include <linux/kernel.h>
#include <linux/lz4.h>
-#include <linux/slab.h>
#include <linux/vmalloc.h>
#include "backend_lz4hc.h"
-struct lz4hc_ctx {
- void *mem;
-
- LZ4_streamDecode_t *dstrm;
- LZ4_streamHC_t *cstrm;
-};
-
static void lz4hc_release_params(struct zcomp_params *params)
{
+ LZ4_streamHC_t *dict_stream = params->drv_data;
+
+ params->drv_data = NULL;
+ if (!dict_stream)
+ return;
+
+ vfree(dict_stream);
}
static int lz4hc_setup_params(struct zcomp_params *params)
{
+ LZ4_streamHC_t *dict_stream;
+ int ret;
+
if (params->level == ZCOMP_PARAM_NOT_SET) {
params->level = LZ4HC_DEFAULT_CLEVEL;
} else if (params->level < 1 || params->level > LZ4HC_MAX_CLEVEL) {
@@ -35,70 +37,52 @@ static int lz4hc_setup_params(struct zcomp_params *params)
return -EINVAL;
}
+ if (!params->dict || !params->dict_sz)
+ return 0;
+
+ dict_stream = vzalloc(sizeof(*dict_stream));
+ if (!dict_stream)
+ return -ENOMEM;
+
+ LZ4_resetStreamHC(dict_stream, params->level);
+ ret = LZ4_loadDictHC(dict_stream, params->dict, params->dict_sz);
+ if (ret != params->dict_sz) {
+ vfree(dict_stream);
+ return -EINVAL;
+ }
+ params->drv_data = dict_stream;
+
return 0;
}
static void lz4hc_destroy(struct zcomp_ctx *ctx)
{
- struct lz4hc_ctx *zctx = ctx->context;
-
- if (!zctx)
- return;
-
- kfree(zctx->dstrm);
- kfree(zctx->cstrm);
- vfree(zctx->mem);
- kfree(zctx);
+ vfree(ctx->context);
}
static int lz4hc_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
{
- struct lz4hc_ctx *zctx;
+ BUILD_BUG_ON(sizeof(LZ4_streamHC_t) != LZ4HC_MEM_COMPRESS);
- zctx = kzalloc_obj(*zctx);
- if (!zctx)
+ ctx->context = vmalloc(LZ4HC_MEM_COMPRESS);
+ if (!ctx->context)
return -ENOMEM;
-
- ctx->context = zctx;
- if (params->dict_sz == 0) {
- zctx->mem = vmalloc(LZ4HC_MEM_COMPRESS);
- if (!zctx->mem)
- goto error;
- } else {
- zctx->dstrm = kzalloc_obj(*zctx->dstrm);
- if (!zctx->dstrm)
- goto error;
-
- zctx->cstrm = kzalloc_obj(*zctx->cstrm);
- if (!zctx->cstrm)
- goto error;
- }
-
return 0;
-
-error:
- lz4hc_destroy(ctx);
- return -EINVAL;
}
static int lz4hc_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct lz4hc_ctx *zctx = ctx->context;
int ret;
- if (!zctx->cstrm) {
+ if (!params->drv_data) {
ret = LZ4_compress_HC(req->src, req->dst, req->src_len,
req->dst_len, params->level,
- zctx->mem);
+ ctx->context);
} else {
/* Cstrm needs to be reset */
- LZ4_resetStreamHC(zctx->cstrm, params->level);
- ret = LZ4_loadDictHC(zctx->cstrm, params->dict,
- params->dict_sz);
- if (ret != params->dict_sz)
- return -EINVAL;
- ret = LZ4_compress_HC_continue(zctx->cstrm, req->src, req->dst,
+ memcpy(ctx->context, params->drv_data, sizeof(LZ4_streamHC_t));
+ ret = LZ4_compress_HC_continue(ctx->context, req->src, req->dst,
req->src_len, req->dst_len);
}
if (!ret)
@@ -110,22 +94,11 @@ static int lz4hc_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
static int lz4hc_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
- struct lz4hc_ctx *zctx = ctx->context;
int ret;
- if (!zctx->dstrm) {
- ret = LZ4_decompress_safe(req->src, req->dst, req->src_len,
- req->dst_len);
- } else {
- /* Dstrm needs to be reset */
- ret = LZ4_setStreamDecode(zctx->dstrm, params->dict,
- params->dict_sz);
- if (!ret)
- return -EINVAL;
- ret = LZ4_decompress_safe_continue(zctx->dstrm, req->src,
- req->dst, req->src_len,
- req->dst_len);
- }
+ ret = LZ4_decompress_safe_usingDict(req->src, req->dst, req->src_len,
+ req->dst_len, params->dict,
+ params->dict_sz);
if (ret < 0)
return -EINVAL;
return 0;
@@ -134,8 +107,8 @@ static int lz4hc_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_lz4hc = {
.compress = lz4hc_compress,
.decompress = lz4hc_decompress,
- .create_ctx = lz4hc_create,
- .destroy_ctx = lz4hc_destroy,
+ .create_cctx = lz4hc_create,
+ .destroy_cctx = lz4hc_destroy,
.setup_params = lz4hc_setup_params,
.release_params = lz4hc_release_params,
.name = "lz4hc",
diff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c
index d83f92cf757cf..e703560d9ecde 100644
--- a/drivers/block/zram/backend_lzo.c
+++ b/drivers/block/zram/backend_lzo.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "lzo: " fmt
+#define pr_fmt(fmt) "zram: lzo: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -61,8 +61,8 @@ static int lzo_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_lzo = {
.compress = lzo_compress,
.decompress = lzo_decompress,
- .create_ctx = lzo_create,
- .destroy_ctx = lzo_destroy,
+ .create_cctx = lzo_create,
+ .destroy_cctx = lzo_destroy,
.setup_params = lzo_setup_params,
.release_params = lzo_release_params,
.name = "lzo",
diff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c
index 1b120d062c928..e95026e992022 100644
--- a/drivers/block/zram/backend_lzorle.c
+++ b/drivers/block/zram/backend_lzorle.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "lzo-rle: " fmt
+#define pr_fmt(fmt) "zram: lzo-rle: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -61,8 +61,8 @@ static int lzorle_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_lzorle = {
.compress = lzorle_compress,
.decompress = lzorle_decompress,
- .create_ctx = lzorle_create,
- .destroy_ctx = lzorle_destroy,
+ .create_cctx = lzorle_create,
+ .destroy_cctx = lzorle_destroy,
.setup_params = lzorle_setup_params,
.release_params = lzorle_release_params,
.name = "lzo-rle",
diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c
index 08da3810cffd8..d55615f343004 100644
--- a/drivers/block/zram/backend_zstd.c
+++ b/drivers/block/zram/backend_zstd.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-#define pr_fmt(fmt) "zstd: " fmt
+#define pr_fmt(fmt) "zram: zstd: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -10,10 +10,8 @@
#include "backend_zstd.h"
struct zstd_ctx {
- zstd_cctx *cctx;
- zstd_dctx *dctx;
- void *cctx_mem;
- void *dctx_mem;
+ void *ctx;
+ void *mem;
};
struct zstd_params {
@@ -26,10 +24,17 @@ struct zstd_params {
/*
* For C/D dictionaries we need to provide zstd with zstd_custom_mem,
* which zstd uses internally to allocate/free memory when needed.
+ *
+ * zstd allocations (C/D dictionaries and contexts) are large and
+ * long-lived. Don't use kvzalloc() for them: when enough physically
+ * contiguous memory is available it serves them from kmalloc(), which
+ * rounds them up to a power-of-two number of pages.
*/
static void *zstd_custom_alloc(void *opaque, size_t size)
{
- return kvzalloc(size, GFP_NOIO | __GFP_NOWARN);
+ if (size <= PAGE_SIZE)
+ return kzalloc(size, GFP_NOIO | __GFP_NOWARN);
+ return __vmalloc(size, GFP_NOIO | __GFP_NOWARN | __GFP_ZERO);
}
static void zstd_custom_free(void *opaque, void *address)
@@ -76,6 +81,9 @@ static int zstd_setup_params(struct zcomp_params *params)
prm = zstd_get_cparams(params->level, PAGE_SIZE,
params->dict_sz);
+ if (!params->dict || !params->dict_sz)
+ return 0;
+
zp->cdict = zstd_create_cdict_byreference(params->dict,
params->dict_sz,
prm,
@@ -95,7 +103,7 @@ static int zstd_setup_params(struct zcomp_params *params)
return -EINVAL;
}
-static void zstd_destroy(struct zcomp_ctx *ctx)
+static void zstd_destroy_cctx(struct zcomp_ctx *ctx)
{
struct zstd_ctx *zctx = ctx->context;
@@ -103,30 +111,23 @@ static void zstd_destroy(struct zcomp_ctx *ctx)
return;
/*
- * If ->cctx_mem and ->dctx_mem were allocated then we didn't use
- * C/D dictionary and ->cctx / ->dctx were "embedded" into these
- * buffers.
+ * If ->mem was allocated then we didn't use C/D dictionary and
+ * ->ctx was "embedded" into this buffer.
*
- * If otherwise then we need to explicitly release ->cctx / ->dctx.
+ * If otherwise then we need to explicitly release ->ctx.
*/
- if (zctx->cctx_mem)
- vfree(zctx->cctx_mem);
- else
- zstd_free_cctx(zctx->cctx);
-
- if (zctx->dctx_mem)
- vfree(zctx->dctx_mem);
+ if (zctx->mem)
+ vfree(zctx->mem);
else
- zstd_free_dctx(zctx->dctx);
+ zstd_free_cctx(zctx->ctx);
kfree(zctx);
}
-static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
+static int zstd_create_cctx(struct zcomp_params *params, struct zcomp_ctx *ctx)
{
+ struct zstd_params *zp = params->drv_data;
struct zstd_ctx *zctx;
- zstd_parameters prm;
- size_t sz;
zctx = kzalloc_obj(*zctx);
if (!zctx)
@@ -134,40 +135,74 @@ static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
ctx->context = zctx;
if (params->dict_sz == 0) {
- prm = zstd_get_params(params->level, PAGE_SIZE);
- sz = zstd_cctx_workspace_bound(&prm.cParams);
- zctx->cctx_mem = vzalloc(sz);
- if (!zctx->cctx_mem)
- goto error;
+ zstd_parameters prm = zp->cprm;
+ size_t sz = zstd_cctx_workspace_bound(&prm.cParams);
- zctx->cctx = zstd_init_cctx(zctx->cctx_mem, sz);
- if (!zctx->cctx)
+ zctx->mem = vzalloc(sz);
+ if (!zctx->mem)
goto error;
- sz = zstd_dctx_workspace_bound();
- zctx->dctx_mem = vzalloc(sz);
- if (!zctx->dctx_mem)
- goto error;
-
- zctx->dctx = zstd_init_dctx(zctx->dctx_mem, sz);
- if (!zctx->dctx)
+ zctx->ctx = zstd_init_cctx(zctx->mem, sz);
+ if (!zctx->ctx)
goto error;
} else {
- struct zstd_params *zp = params->drv_data;
+ zctx->ctx = zstd_create_cctx_advanced(zp->custom_mem);
+ if (!zctx->ctx)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ zstd_destroy_cctx(ctx);
+ return -EINVAL;
+}
- zctx->cctx = zstd_create_cctx_advanced(zp->custom_mem);
- if (!zctx->cctx)
+static void zstd_destroy_dctx(struct zcomp_ctx *ctx)
+{
+ struct zstd_ctx *zctx = ctx->context;
+
+ if (!zctx)
+ return;
+
+ if (zctx->mem)
+ vfree(zctx->mem);
+ else
+ zstd_free_dctx(zctx->ctx);
+
+ kfree(zctx);
+}
+
+static int zstd_create_dctx(struct zcomp_params *params, struct zcomp_ctx *ctx)
+{
+ struct zstd_params *zp = params->drv_data;
+ struct zstd_ctx *zctx;
+
+ zctx = kzalloc_obj(*zctx);
+ if (!zctx)
+ return -ENOMEM;
+
+ ctx->context = zctx;
+ if (params->dict_sz == 0) {
+ size_t sz = zstd_dctx_workspace_bound();
+
+ zctx->mem = vzalloc(sz);
+ if (!zctx->mem)
goto error;
- zctx->dctx = zstd_create_dctx_advanced(zp->custom_mem);
- if (!zctx->dctx)
+ zctx->ctx = zstd_init_dctx(zctx->mem, sz);
+ if (!zctx->ctx)
+ goto error;
+ } else {
+ zctx->ctx = zstd_create_dctx_advanced(zp->custom_mem);
+ if (!zctx->ctx)
goto error;
}
return 0;
error:
- zstd_destroy(ctx);
+ zstd_destroy_dctx(ctx);
return -EINVAL;
}
@@ -179,10 +214,10 @@ static int zstd_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
size_t ret;
if (params->dict_sz == 0)
- ret = zstd_compress_cctx(zctx->cctx, req->dst, req->dst_len,
+ ret = zstd_compress_cctx(zctx->ctx, req->dst, req->dst_len,
req->src, req->src_len, &zp->cprm);
else
- ret = zstd_compress_using_cdict(zctx->cctx, req->dst,
+ ret = zstd_compress_using_cdict(zctx->ctx, req->dst,
req->dst_len, req->src,
req->src_len,
zp->cdict);
@@ -200,10 +235,10 @@ static int zstd_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
size_t ret;
if (params->dict_sz == 0)
- ret = zstd_decompress_dctx(zctx->dctx, req->dst, req->dst_len,
+ ret = zstd_decompress_dctx(zctx->ctx, req->dst, req->dst_len,
req->src, req->src_len);
else
- ret = zstd_decompress_using_ddict(zctx->dctx, req->dst,
+ ret = zstd_decompress_using_ddict(zctx->ctx, req->dst,
req->dst_len, req->src,
req->src_len, zp->ddict);
if (zstd_is_error(ret))
@@ -214,8 +249,10 @@ static int zstd_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
const struct zcomp_ops backend_zstd = {
.compress = zstd_compress,
.decompress = zstd_decompress,
- .create_ctx = zstd_create,
- .destroy_ctx = zstd_destroy,
+ .create_cctx = zstd_create_cctx,
+ .destroy_cctx = zstd_destroy_cctx,
+ .create_dctx = zstd_create_dctx,
+ .destroy_dctx = zstd_destroy_dctx,
.setup_params = zstd_setup_params,
.release_params = zstd_release_params,
.name = "zstd",
diff --git a/drivers/block/zram/zcomp.c b/drivers/block/zram/zcomp.c
index 028e2f0f587e3..81f21bad787e7 100644
--- a/drivers/block/zram/zcomp.c
+++ b/drivers/block/zram/zcomp.c
@@ -45,30 +45,65 @@ static const struct zcomp_ops *backends[] = {
NULL
};
-static void zcomp_strm_free(struct zcomp *comp, struct zcomp_strm *zstrm)
+static void zcomp_cstrm_free(struct zcomp *comp, struct zcomp_cstrm *zstrm)
{
- comp->ops->destroy_ctx(&zstrm->ctx);
- vfree(zstrm->local_copy);
+ if (comp->ops->destroy_cctx)
+ comp->ops->destroy_cctx(&zstrm->ctx);
vfree(zstrm->buffer);
zstrm->buffer = NULL;
+ zstrm->ctx.context = NULL;
}
-static int zcomp_strm_init(struct zcomp *comp, struct zcomp_strm *zstrm)
+static int zcomp_cstrm_init(struct zcomp *comp, struct zcomp_cstrm *zstrm)
{
int ret;
- ret = comp->ops->create_ctx(comp->params, &zstrm->ctx);
- if (ret)
- return ret;
+ if (comp->ops->create_cctx) {
+ ret = comp->ops->create_cctx(comp->params, &zstrm->ctx);
+ if (ret) {
+ /* backend has released partially created context */
+ zstrm->ctx.context = NULL;
+ return ret;
+ }
+ }
- zstrm->local_copy = vzalloc(PAGE_SIZE);
/*
* allocate 2 pages. 1 for compressed data, plus 1 extra for the
* case when compressed size is larger than the original one
*/
zstrm->buffer = vzalloc(2 * PAGE_SIZE);
- if (!zstrm->buffer || !zstrm->local_copy) {
- zcomp_strm_free(comp, zstrm);
+ if (!zstrm->buffer) {
+ zcomp_cstrm_free(comp, zstrm);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void zcomp_dstrm_free(struct zcomp *comp, struct zcomp_dstrm *zstrm)
+{
+ if (comp->ops->destroy_dctx)
+ comp->ops->destroy_dctx(&zstrm->ctx);
+ vfree(zstrm->local_copy);
+ zstrm->local_copy = NULL;
+ zstrm->ctx.context = NULL;
+}
+
+static int zcomp_dstrm_init(struct zcomp *comp, struct zcomp_dstrm *zstrm)
+{
+ int ret;
+
+ if (comp->ops->create_dctx) {
+ ret = comp->ops->create_dctx(comp->params, &zstrm->ctx);
+ if (ret) {
+ /* backend has released partially created context */
+ zstrm->ctx.context = NULL;
+ return ret;
+ }
+ }
+
+ zstrm->local_copy = vzalloc(PAGE_SIZE);
+ if (!zstrm->local_copy) {
+ zcomp_dstrm_free(comp, zstrm);
return -ENOMEM;
}
return 0;
@@ -114,10 +149,15 @@ ssize_t zcomp_available_show(const char *comp, char *buf, ssize_t at)
return at;
}
-struct zcomp_strm *zcomp_stream_get(struct zcomp *comp)
+struct zcomp_cstrm *zcomp_cstrm_get(struct zcomp *comp)
{
+ if (comp->recomp_cstream) {
+ mutex_lock(&comp->recomp_cstream->lock);
+ return comp->recomp_cstream;
+ }
+
for (;;) {
- struct zcomp_strm *zstrm = raw_cpu_ptr(comp->stream);
+ struct zcomp_cstrm *zstrm = raw_cpu_ptr(comp->cstream);
/*
* Inspired by zswap
@@ -130,18 +170,62 @@ struct zcomp_strm *zcomp_stream_get(struct zcomp *comp)
* so then unlock and re-try on the current CPU.
*/
mutex_lock(&zstrm->lock);
- if (likely(zstrm->buffer))
+ if (zstrm->buffer)
return zstrm;
mutex_unlock(&zstrm->lock);
}
}
-void zcomp_stream_put(struct zcomp_strm *zstrm)
+void zcomp_cstrm_put(struct zcomp_cstrm *zstrm)
{
mutex_unlock(&zstrm->lock);
}
-int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm,
+bool zcomp_shared_decomp(struct zcomp *comp)
+{
+ /*
+ * Shared decompression is only safe when the backend has no
+ * mutable per-stream decompression context.
+ */
+ return !comp->ops->create_dctx;
+}
+
+struct zcomp_dstrm *zcomp_dstrm_get(struct zcomp *comp,
+ enum zcomp_dstrm_mode mode)
+{
+ for (;;) {
+ struct zcomp_dstrm *zstrm = raw_cpu_ptr(comp->dstream);
+
+ /*
+ * Pin the stream. The write side of ->lock is only taken
+ * by zcomp_cpu_dead(), so this never contends with other
+ * decompression requests, regardless of their mode.
+ */
+ down_read(&zstrm->lock);
+ if (!zstrm->local_copy) {
+ /* The stream has been destroyed, re-try */
+ up_read(&zstrm->lock);
+ continue;
+ }
+
+ /*
+ * Exclusive requests additionally own ->local_copy and
+ * ->ctx and hence can only wait for each other.
+ */
+ if (mode == ZCOMP_DSTRM_EXCLUSIVE)
+ mutex_lock(&zstrm->excl_lock);
+ return zstrm;
+ }
+}
+
+void zcomp_dstrm_put(struct zcomp_dstrm *zstrm, enum zcomp_dstrm_mode mode)
+{
+ if (mode == ZCOMP_DSTRM_EXCLUSIVE)
+ mutex_unlock(&zstrm->excl_lock);
+ up_read(&zstrm->lock);
+}
+
+int zcomp_compress(struct zcomp *comp, struct zcomp_cstrm *zstrm,
const void *src, unsigned int *dst_len)
{
struct zcomp_req req = {
@@ -159,7 +243,16 @@ int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm,
return ret;
}
-int zcomp_decompress(struct zcomp *comp, struct zcomp_strm *zstrm,
+static int zcomp_decompress_buf(struct zcomp *comp, struct zcomp_dstrm *zstrm,
+ struct scatterlist *sg, void *buf,
+ struct zcomp_req *req)
+{
+ sg_copy_to_buffer(sg, 2, buf, req->src_len);
+ req->src = buf;
+ return comp->ops->decompress(comp->params, &zstrm->ctx, req);
+}
+
+int zcomp_decompress(struct zcomp *comp, struct zcomp_dstrm *zstrm,
struct scatterlist *sg, unsigned int src_len, void *dst)
{
struct zcomp_req req = {
@@ -167,65 +260,138 @@ int zcomp_decompress(struct zcomp *comp, struct zcomp_strm *zstrm,
.src_len = src_len,
.dst_len = PAGE_SIZE,
};
- void *src = NULL;
+ void *src;
int ret;
might_sleep();
+ lockdep_assert_held(&zstrm->lock);
+ if (!zcomp_shared_decomp(comp))
+ lockdep_assert_held(&zstrm->excl_lock);
if (sg_is_last(sg)) {
/* the object is contained within one page, read it in-place */
src = kmap_local_page(sg_page(sg));
req.src = src + sg->offset;
- } else {
- /* the object spans two pages, linearize it into local copy */
- sg_copy_to_buffer(sg, 2, zstrm->local_copy, src_len);
- req.src = zstrm->local_copy;
+ ret = comp->ops->decompress(comp->params, &zstrm->ctx, &req);
+ kunmap_local(src);
+ return ret;
}
- ret = comp->ops->decompress(comp->params, &zstrm->ctx, &req);
- if (src)
- kunmap_local(src);
+ /* exclusive mode already holds ->excl_lock */
+ if (!zcomp_shared_decomp(comp))
+ return zcomp_decompress_buf(comp, zstrm, sg, zstrm->local_copy,
+ &req);
+
+ /* shared mode: try a per-request bounce buffer first */
+ src = kmalloc(src_len, GFP_NOWAIT | __GFP_NOWARN);
+ if (src) {
+ ret = zcomp_decompress_buf(comp, zstrm, sg, src, &req);
+ kfree(src);
+ return ret;
+ }
+
+ /* fall back to ->local_copy serialized by ->excl_lock */
+ mutex_lock(&zstrm->excl_lock);
+ ret = zcomp_decompress_buf(comp, zstrm, sg, zstrm->local_copy, &req);
+ mutex_unlock(&zstrm->excl_lock);
return ret;
}
int zcomp_cpu_up_prepare(unsigned int cpu, struct hlist_node *node)
{
struct zcomp *comp = hlist_entry(node, struct zcomp, node);
- struct zcomp_strm *zstrm = per_cpu_ptr(comp->stream, cpu);
+ struct zcomp_cstrm *cstrm = NULL;
+ struct zcomp_dstrm *dstrm;
int ret;
- ret = zcomp_strm_init(comp, zstrm);
- if (ret)
- pr_err("Can't allocate a compression stream\n");
- return ret;
+ if (comp->cstream) {
+ cstrm = per_cpu_ptr(comp->cstream, cpu);
+ ret = zcomp_cstrm_init(comp, cstrm);
+ if (ret) {
+ pr_err("Can't allocate a compression stream\n");
+ return ret;
+ }
+ }
+
+ dstrm = per_cpu_ptr(comp->dstream, cpu);
+ ret = zcomp_dstrm_init(comp, dstrm);
+ if (ret) {
+ pr_err("Can't allocate a decompression stream\n");
+ if (cstrm)
+ zcomp_cstrm_free(comp, cstrm);
+ return ret;
+ }
+
+ return 0;
}
int zcomp_cpu_dead(unsigned int cpu, struct hlist_node *node)
{
struct zcomp *comp = hlist_entry(node, struct zcomp, node);
- struct zcomp_strm *zstrm = per_cpu_ptr(comp->stream, cpu);
+ struct zcomp_cstrm *cstrm;
+ struct zcomp_dstrm *dstrm;
+
+ if (comp->cstream) {
+ cstrm = per_cpu_ptr(comp->cstream, cpu);
+ mutex_lock(&cstrm->lock);
+ zcomp_cstrm_free(comp, cstrm);
+ mutex_unlock(&cstrm->lock);
+ }
+
+ dstrm = per_cpu_ptr(comp->dstream, cpu);
+ down_write(&dstrm->lock);
+ zcomp_dstrm_free(comp, dstrm);
+ up_write(&dstrm->lock);
- mutex_lock(&zstrm->lock);
- zcomp_strm_free(comp, zstrm);
- mutex_unlock(&zstrm->lock);
return 0;
}
-static int zcomp_init(struct zcomp *comp, struct zcomp_params *params)
+static int zcomp_init(struct zcomp *comp, struct zcomp_params *params,
+ bool recomp)
{
int ret, cpu;
- comp->stream = alloc_percpu(struct zcomp_strm);
- if (!comp->stream)
- return -ENOMEM;
-
comp->params = params;
ret = comp->ops->setup_params(comp->params);
if (ret)
goto cleanup;
- for_each_possible_cpu(cpu)
- mutex_init(&per_cpu_ptr(comp->stream, cpu)->lock);
+ if (recomp) {
+ comp->recomp_cstream = kzalloc_obj(*comp->recomp_cstream);
+ if (!comp->recomp_cstream) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ mutex_init(&comp->recomp_cstream->lock);
+ ret = zcomp_cstrm_init(comp, comp->recomp_cstream);
+ if (ret) {
+ /* zcomp_cstrm_init() cleans up after itself */
+ kfree(comp->recomp_cstream);
+ comp->recomp_cstream = NULL;
+ goto cleanup;
+ }
+ } else {
+ comp->cstream = alloc_percpu(struct zcomp_cstrm);
+ if (!comp->cstream) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ for_each_possible_cpu(cpu)
+ mutex_init(&per_cpu_ptr(comp->cstream, cpu)->lock);
+ }
+
+ comp->dstream = alloc_percpu(struct zcomp_dstrm);
+ if (!comp->dstream) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ for_each_possible_cpu(cpu) {
+ struct zcomp_dstrm *zstrm = per_cpu_ptr(comp->dstream, cpu);
+
+ init_rwsem(&zstrm->lock);
+ mutex_init(&zstrm->excl_lock);
+ }
ret = cpuhp_state_add_instance(CPUHP_ZCOMP_PREPARE, &comp->node);
if (ret < 0)
@@ -234,20 +400,31 @@ static int zcomp_init(struct zcomp *comp, struct zcomp_params *params)
return 0;
cleanup:
+ if (comp->recomp_cstream) {
+ zcomp_cstrm_free(comp, comp->recomp_cstream);
+ kfree(comp->recomp_cstream);
+ }
comp->ops->release_params(comp->params);
- free_percpu(comp->stream);
+ free_percpu(comp->dstream);
+ free_percpu(comp->cstream);
return ret;
}
void zcomp_destroy(struct zcomp *comp)
{
cpuhp_state_remove_instance(CPUHP_ZCOMP_PREPARE, &comp->node);
+ if (comp->recomp_cstream) {
+ zcomp_cstrm_free(comp, comp->recomp_cstream);
+ kfree(comp->recomp_cstream);
+ }
comp->ops->release_params(comp->params);
- free_percpu(comp->stream);
+ free_percpu(comp->dstream);
+ free_percpu(comp->cstream);
kfree(comp);
}
-struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params)
+struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params,
+ bool recomp)
{
struct zcomp *comp;
int error;
@@ -270,7 +447,7 @@ struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params)
return ERR_PTR(-EINVAL);
}
- error = zcomp_init(comp, params);
+ error = zcomp_init(comp, params, recomp);
if (error) {
kfree(comp);
return ERR_PTR(error);
diff --git a/drivers/block/zram/zcomp.h b/drivers/block/zram/zcomp.h
index f38fd31f9e4e0..6764818d2a9f5 100644
--- a/drivers/block/zram/zcomp.h
+++ b/drivers/block/zram/zcomp.h
@@ -4,11 +4,17 @@
#define _ZCOMP_H_
#include <linux/mutex.h>
+#include <linux/rwsem.h>
struct scatterlist;
#define ZCOMP_PARAM_NOT_SET INT_MIN
+enum zcomp_dstrm_mode {
+ ZCOMP_DSTRM_SHARED,
+ ZCOMP_DSTRM_EXCLUSIVE,
+};
+
struct deflate_params {
s32 winbits;
};
@@ -33,16 +39,29 @@ struct zcomp_params {
/*
* Run-time driver context - scratch buffers, etc. It is modified during
* request execution (compression/decompression), cannot be shared, so
- * it's in per-CPU area.
+ * it's in per-CPU area (except for recompression, which uses a single
+ * per-device compression stream).
*/
struct zcomp_ctx {
void *context;
};
-struct zcomp_strm {
+struct zcomp_cstrm {
struct mutex lock;
/* compression buffer */
void *buffer;
+ struct zcomp_ctx ctx;
+};
+
+struct zcomp_dstrm {
+ /*
+ * Pins the stream: held in read mode by every user, regardless
+ * of the decompression mode, and in write mode only by
+ * zcomp_cpu_dead().
+ */
+ struct rw_semaphore lock;
+ /* Serializes users of the mutable stream state: ->local_copy, ->ctx */
+ struct mutex excl_lock;
/* local copy of handle memory */
void *local_copy;
struct zcomp_ctx ctx;
@@ -62,8 +81,11 @@ struct zcomp_ops {
int (*decompress)(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req);
- int (*create_ctx)(struct zcomp_params *params, struct zcomp_ctx *ctx);
- void (*destroy_ctx)(struct zcomp_ctx *ctx);
+ int (*create_cctx)(struct zcomp_params *params, struct zcomp_ctx *ctx);
+ void (*destroy_cctx)(struct zcomp_ctx *ctx);
+
+ int (*create_dctx)(struct zcomp_params *params, struct zcomp_ctx *ctx);
+ void (*destroy_dctx)(struct zcomp_ctx *ctx);
int (*setup_params)(struct zcomp_params *params);
void (*release_params)(struct zcomp_params *params);
@@ -73,7 +95,9 @@ struct zcomp_ops {
/* dynamic per-device compression frontend */
struct zcomp {
- struct zcomp_strm __percpu *stream;
+ struct zcomp_dstrm __percpu *dstream;
+ struct zcomp_cstrm __percpu *cstream;
+ struct zcomp_cstrm *recomp_cstream;
const struct zcomp_ops *ops;
struct zcomp_params *params;
struct hlist_node node;
@@ -84,15 +108,21 @@ int zcomp_cpu_dead(unsigned int cpu, struct hlist_node *node);
ssize_t zcomp_available_show(const char *comp, char *buf, ssize_t at);
const char *zcomp_lookup_backend_name(const char *comp);
-struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params);
+struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params,
+ bool recomp);
void zcomp_destroy(struct zcomp *comp);
-struct zcomp_strm *zcomp_stream_get(struct zcomp *comp);
-void zcomp_stream_put(struct zcomp_strm *zstrm);
+struct zcomp_cstrm *zcomp_cstrm_get(struct zcomp *comp);
+void zcomp_cstrm_put(struct zcomp_cstrm *zstrm);
+
+bool zcomp_shared_decomp(struct zcomp *comp);
+struct zcomp_dstrm *zcomp_dstrm_get(struct zcomp *comp,
+ enum zcomp_dstrm_mode mode);
+void zcomp_dstrm_put(struct zcomp_dstrm *zstrm, enum zcomp_dstrm_mode mode);
-int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm,
+int zcomp_compress(struct zcomp *comp, struct zcomp_cstrm *zstrm,
const void *src, unsigned int *dst_len);
-int zcomp_decompress(struct zcomp *comp, struct zcomp_strm *zstrm,
+int zcomp_decompress(struct zcomp *comp, struct zcomp_dstrm *zstrm,
struct scatterlist *sg, unsigned int src_len, void *dst);
#endif /* _ZCOMP_H_ */
diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c
index ab064c84b0922..e608769bfad56 100644
--- a/drivers/block/zram/zram_drv.c
+++ b/drivers/block/zram/zram_drv.c
@@ -694,6 +694,7 @@ static void reset_bdev(struct zram *zram)
zram->disk->fops = &zram_devops;
kvfree(zram->bitmap);
zram->bitmap = NULL;
+ zram->nr_pages = 0;
}
static ssize_t backing_dev_show(struct device *dev,
@@ -1347,7 +1348,7 @@ static ssize_t writeback_store(struct device *dev,
static int decompress_bdev_page(struct zram *zram, struct page *page,
unsigned long index)
{
- struct zcomp_strm *zstrm;
+ struct zcomp_dstrm *zstrm;
struct scatterlist sg[1];
unsigned int size;
int ret, prio;
@@ -1372,7 +1373,7 @@ static int decompress_bdev_page(struct zram *zram, struct page *page,
sg_init_table(sg, 1);
sg_set_page(sg, page, size, 0);
- zstrm = zcomp_stream_get(zram->comps[prio]);
+ zstrm = zcomp_dstrm_get(zram->comps[prio], ZCOMP_DSTRM_EXCLUSIVE);
ret = zcomp_decompress(zram->comps[prio], zstrm, sg, size,
zstrm->local_copy);
if (!ret) {
@@ -1381,7 +1382,7 @@ static int decompress_bdev_page(struct zram *zram, struct page *page,
copy_page(dst, zstrm->local_copy);
kunmap_local(dst);
}
- zcomp_stream_put(zstrm);
+ zcomp_dstrm_put(zstrm, ZCOMP_DSTRM_EXCLUSIVE);
slot_unlock(zram, index);
return ret;
@@ -2114,7 +2115,8 @@ static int read_incompressible_page(struct zram *zram, struct page *page,
static int read_compressed_page(struct zram *zram, struct page *page,
unsigned long index)
{
- struct zcomp_strm *zstrm;
+ enum zcomp_dstrm_mode mode = ZCOMP_DSTRM_EXCLUSIVE;
+ struct zcomp_dstrm *zstrm;
struct scatterlist sg[2];
unsigned long handle;
unsigned int size;
@@ -2125,13 +2127,16 @@ static int read_compressed_page(struct zram *zram, struct page *page,
size = get_slot_size(zram, index);
prio = get_slot_comp_priority(zram, index);
- zstrm = zcomp_stream_get(zram->comps[prio]);
zs_obj_read_sg_begin(zram->mem_pool, handle, sg, size);
+ if (zcomp_shared_decomp(zram->comps[prio]))
+ mode = ZCOMP_DSTRM_SHARED;
+
+ zstrm = zcomp_dstrm_get(zram->comps[prio], mode);
dst = kmap_local_page(page);
ret = zcomp_decompress(zram->comps[prio], zstrm, sg, size, dst);
kunmap_local(dst);
+ zcomp_dstrm_put(zstrm, mode);
zs_obj_read_sg_end(zram->mem_pool, handle);
- zcomp_stream_put(zstrm);
return ret;
}
@@ -2299,7 +2304,7 @@ static int zram_write_page(struct zram *zram, struct page *page,
unsigned long handle;
unsigned int comp_len;
void *mem;
- struct zcomp_strm *zstrm;
+ struct zcomp_cstrm *zstrm;
unsigned long element;
bool same_filled;
@@ -2309,20 +2314,20 @@ static int zram_write_page(struct zram *zram, struct page *page,
if (same_filled)
return write_same_filled_page(zram, element, index);
- zstrm = zcomp_stream_get(zram->comps[ZRAM_PRIMARY_COMP]);
+ zstrm = zcomp_cstrm_get(zram->comps[ZRAM_PRIMARY_COMP]);
mem = kmap_local_page(page);
ret = zcomp_compress(zram->comps[ZRAM_PRIMARY_COMP], zstrm,
mem, &comp_len);
kunmap_local(mem);
if (unlikely(ret)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
pr_err("Compression failed! err=%d\n", ret);
return ret;
}
if (comp_len >= huge_class_size) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
return write_incompressible_page(zram, page, index);
}
@@ -2330,18 +2335,18 @@ static int zram_write_page(struct zram *zram, struct page *page,
GFP_NOIO | __GFP_NOWARN |
__GFP_HIGHMEM | __GFP_MOVABLE, page_to_nid(page));
if (IS_ERR_VALUE(handle)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
return PTR_ERR((void *)handle);
}
if (!zram_can_store_page(zram)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
zs_free(zram->mem_pool, handle);
return -ENOMEM;
}
zs_obj_write(zram->mem_pool, handle, zstrm->buffer, comp_len);
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
slot_lock(zram, index);
slot_free(zram, index);
@@ -2450,7 +2455,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
struct page *page, u64 *num_recomp_pages,
u32 threshold, u32 prio)
{
- struct zcomp_strm *zstrm = NULL;
+ struct zcomp_cstrm *zstrm = NULL;
unsigned long handle_old;
unsigned long handle_new;
unsigned int comp_len_old;
@@ -2482,7 +2487,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
*/
clear_slot_flag(zram, index, ZRAM_IDLE);
- zstrm = zcomp_stream_get(zram->comps[prio]);
+ zstrm = zcomp_cstrm_get(zram->comps[prio]);
src = kmap_local_page(page);
ret = zcomp_compress(zram->comps[prio], zstrm, src, &comp_len_new);
kunmap_local(src);
@@ -2497,7 +2502,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
*num_recomp_pages -= 1;
if (ret) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
return ret;
}
@@ -2506,7 +2511,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
if (class_index_new >= class_index_old ||
(threshold && comp_len_new >= threshold)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
/*
* Secondary algorithms failed to re-compress the page
@@ -2521,7 +2526,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
}
/*
- * We are holding per-CPU stream mutex and entry lock so better
+ * We are holding compression stream mutex and entry lock so better
* avoid direct reclaim. Allocation error is not fatal since
* we still have the old object in the mem_pool.
*
@@ -2535,12 +2540,12 @@ static int recompress_slot(struct zram *zram, unsigned long index,
__GFP_HIGHMEM | __GFP_MOVABLE,
page_to_nid(page));
if (IS_ERR_VALUE(handle_new)) {
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
return PTR_ERR((void *)handle_new);
}
zs_obj_write(zram->mem_pool, handle_new, zstrm->buffer, comp_len_new);
- zcomp_stream_put(zstrm);
+ zcomp_cstrm_put(zstrm);
slot_free(zram, index);
set_slot_handle(zram, index, handle_new);
@@ -2881,6 +2886,12 @@ static void zram_reset_device(struct zram *zram)
zram_destroy_comps(zram);
memset(&zram->stats, 0, sizeof(zram->stats));
reset_bdev(zram);
+#ifdef CONFIG_ZRAM_WRITEBACK
+ zram->wb_limit_enable = false;
+ zram->bd_wb_limit = 0;
+ zram->wb_batch_size = 32;
+ zram->compressed_wb = false;
+#endif
}
static ssize_t disksize_store(struct device *dev, struct device_attribute *attr,
@@ -2917,7 +2928,8 @@ static ssize_t disksize_store(struct device *dev, struct device_attribute *attr,
continue;
comp = zcomp_create(zram->comp_algs[prio],
- &zram->params[prio]);
+ &zram->params[prio],
+ prio != ZRAM_PRIMARY_COMP);
if (IS_ERR(comp)) {
pr_err("Cannot initialise %s compressing backend\n",
zram->comp_algs[prio]);
@@ -3297,6 +3309,7 @@ static int __init zram_init(void)
if (zram_major <= 0) {
pr_err("Unable to get major number\n");
class_unregister(&zram_control_class);
+ zram_debugfs_destroy();
cpuhp_remove_multi_state(CPUHP_ZCOMP_PREPARE);
return -EBUSY;
}
|
| 21/3 | 2026/10/16 08:11 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20881 output: 40 thoughts: 1151 Thoughts: **Analyzing the ZRAM Compression Stream Restructuring**
Okay, so I'm looking at a patch that's significantly restructuring the compression/decompression handling within ZRAM, specifically in the drivers/block/zram/ directory. The core of this patch seems to be about splitting and renaming the compression and decompression streams and contexts. I can see that it's touching a whole bunch of files: `backend_842.c`, `backend_deflate.c`, `backend_lz4.c`, `backend_lz4hc.c`, `backend_lzo.c`, `backend_lzorle.c`, `backend_zstd.c`, `zcomp.c`, `zcomp.h`, and `zram_drv.c`. That's a lot of ground to cover. Let's see what the changes actually mean.
From what I gather, it appears we are moving away from the single `struct zcomp_strm` structure, which housed both compression and decompression information, to separate `struct zcomp_cstrm` for compression streams and `struct zcomp_dstrm` for decompression streams. This separation is also being applied to recompression streams (`recomp_cstream`) where now it is possible to make use of a per-device compression stream instead of per-CPU streams. The patch also introduces the concept of shared versus exclusive decompression modes, defined by `zcomp_shared_decomp`, based on whether the backend has mutable decompression context.
Looking specifically at the `zcomp_decompress()` function, it seems the logic for handling decompression is changing based on the input SG (scatter-gather) list. If it's a single page, it reads in-place using `kmap_local_page()`. If the SG spans two pages, the behavior is conditional on the decompression mode: in exclusive mode it calls `zcomp_decompress_buf`, otherwise, in shared mode, it tries a per-request bounce buffer using `kmalloc`. If the memory allocation fails, it falls back to using the existing `zstrm->local_copy` serialized with a lock.
Now, let's dive into the individual backend changes. For `backend_842`, the `.create_ctx` and `.destroy_ctx` functions are now renamed to `.create_cctx` and `.destroy_cctx`. Decompression doesn't seem to have a dedicated context (it's NULL). For `backend_deflate`, we have a clear separation with distinct create/destroy functions for compression and decompression contexts (`create_cctx/destroy_cctx` and `create_dctx/destroy_dctx`). Here, the compression and decompression contexts (`cctx` and `dctx`) are now direct pointers to `struct z_stream_s *`, allocated via `kzalloc_obj()` and with workspace allocated using `vzalloc()`.
Now, things are getting interesting in `backend_lz4.c`. I see that the `lz4_create` function allocates `LZ4_MEM_COMPRESS` directly into `ctx->context` using `vmalloc`. Wait a second... Here is the code snippet I am seeing:
```c
static int lz4_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
{
BUILD_BUG_ON(sizeof(LZ4_stream_t) != LZ4_MEM_COMPRESS);
ctx->context = vmalloc(LZ4_MEM_COMPRESS);
if (!ctx->context)
return -ENOMEM;
return 0;
}
```
And in the lz4ops, no `create_dctx` or `destroy_dctx`! This is unusual. I am seeing `LZ4_decompress_safe_usingDict` being called directly on `req->src` and `req->dst` without the need for a decompression context. The LZ4 implementation is using a different approach. The compression itself relies on the context.
I am also seeing, in `lz4_compress`:
```c
static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
struct zcomp_req *req)
{
int ret;
if (!params->drv_data) {
ret = LZ4_compress_fast(req->src, req->dst, req->src_len,
req->dst_len, params->level,
ctx->context);
} else {
/* Cstrm needs to be reset */
memcpy(ctx->context, params->drv_data, sizeof(LZ4_stream_t));
ret = LZ4_compress_fast_continue(ctx->context, req
|
| 22/3 | 2026/10/16 08:11 | tool | grepper |
0mArgs:{
"Expression": "LZ4_compress_fast",
"PathPrefixes": [
"lib/",
"include/linux/lz4.h"
]
}Results: {
"Output": "include/linux/lz4.h=197=int LZ4_compress_default(const char *source, char *dest, int inputSize,\n--\ninclude/linux/lz4.h-200-/**\ninclude/linux/lz4.h:201: * LZ4_compress_fast() - As LZ4_compress_default providing an acceleration param\ninclude/linux/lz4.h-202- * @source: source address of the original data\n--\ninclude/linux/lz4.h-220- */\ninclude/linux/lz4.h:221:int LZ4_compress_fast(const char *source, char *dest, int inputSize,\ninclude/linux/lz4.h-222-\tint maxOutputSize, int acceleration, void *wrkmem);\n--\ninclude/linux/lz4.h=465=int LZ4_loadDict(LZ4_stream_t *streamPtr, const char *dictionary,\n--\ninclude/linux/lz4.h-478- * dictionary is immediately usable, you can therefore call\ninclude/linux/lz4.h:479: * LZ4_compress_fast_continue().\ninclude/linux/lz4.h-480- *\n--\ninclude/linux/lz4.h=484=int LZ4_saveDict(LZ4_stream_t *streamPtr, char *safeBuffer, int dictSize);\n--\ninclude/linux/lz4.h-486-/**\ninclude/linux/lz4.h:487: * LZ4_compress_fast_continue() - Compress 'src' using data from previously\ninclude/linux/lz4.h-488- *\tcompressed blocks as a dictionary\n--\ninclude/linux/lz4.h-506- */\ninclude/linux/lz4.h:507:int LZ4_compress_fast_continue(LZ4_stream_t *streamPtr, const char *src,\ninclude/linux/lz4.h-508-\tchar *dst, int srcSize, int maxDstSize, int acceleration);\n--\nlib/lz4/lz4_compress.c=175=static FORCE_INLINE int LZ4_compress_generic(\n--\nlib/lz4/lz4_compress.c-456-\nlib/lz4/lz4_compress.c:457:static int LZ4_compress_fast_extState(\nlib/lz4/lz4_compress.c-458-\tvoid *state,\n--\nlib/lz4/lz4_compress.c-501-\nlib/lz4/lz4_compress.c:502:int LZ4_compress_fast(const char *source, char *dest, int inputSize,\nlib/lz4/lz4_compress.c-503-\tint maxOutputSize, int acceleration, void *wrkmem)\nlib/lz4/lz4_compress.c-504-{\nlib/lz4/lz4_compress.c:505:\treturn LZ4_compress_fast_extState(wrkmem, source, dest, inputSize,\nlib/lz4/lz4_compress.c-506-\t\tmaxOutputSize, acceleration);\nlib/lz4/lz4_compress.c-507-}\nlib/lz4/lz4_compress.c:508:EXPORT_SYMBOL(LZ4_compress_fast);\nlib/lz4/lz4_compress.c-509-\nlib/lz4/lz4_compress.c=510=int LZ4_compress_default(const char *source, char *dest, int inputSize,\n--\nlib/lz4/lz4_compress.c-512-{\nlib/lz4/lz4_compress.c:513:\treturn LZ4_compress_fast(source, dest, inputSize,\nlib/lz4/lz4_compress.c-514-\t\tmaxOutputSize, LZ4_ACCELERATION_DEFAULT, wrkmem);\n--\nlib/lz4/lz4_compress.c=719=static int LZ4_compress_destSize_extState(\n--\nlib/lz4/lz4_compress.c-735-\t\t/* compression success is guaranteed */\nlib/lz4/lz4_compress.c:736:\t\treturn LZ4_compress_fast_extState(\nlib/lz4/lz4_compress.c-737-\t\t\tstate, src, dst, *srcSizePtr,\n--\nlib/lz4/lz4_compress.c=854=EXPORT_SYMBOL(LZ4_saveDict);\nlib/lz4/lz4_compress.c-855-\nlib/lz4/lz4_compress.c:856:int LZ4_compress_fast_continue(LZ4_stream_t *LZ4_stream, const char *source,\nlib/lz4/lz4_compress.c-857-\tchar *dest, int inputSize, int maxOutputSize, int acceleration)\n--\nlib/lz4/lz4_compress.c-935-}\nlib/lz4/lz4_compress.c:936:EXPORT_SYMBOL(LZ4_compress_fast_continue);\nlib/lz4/lz4_compress.c-937-\n"
} |
| 23/3 | 2026/10/16 08:11 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24368 output: 39 thoughts: 29 Thoughts: Okay, let's dive into this. Here's my thought process: **Diving into LZ4 Compression** Alright, I need to get a handle on how this compression is working under the hood. Specifically, I'm interested in the `LZ4_compress_fast_extState` function within `lib/lz4/lz4_compress.c`. This suggests to me that we're talking about a fast compression path, and the "extState" part implies that it's probably using some pre-allocated state rather than allocating it each time, which can give us some insight into performance characteristics. Let's see how that specific function is implemented and how it is employed within the library. |
| 24/3 | 2026/10/16 08:11 | tool | read-file |
0mArgs:{
"File": "lib/lz4/lz4_compress.c",
"FirstLine": 455,
"LineCount": 45
}Results: {
"Contents": " 455:\t}\n 456:\t\n 457:\tstatic int LZ4_compress_fast_extState(\n 458:\t\tvoid *state,\n 459:\t\tconst char *source,\n 460:\t\tchar *dest,\n 461:\t\tint inputSize,\n 462:\t\tint maxOutputSize,\n 463:\t\tint acceleration)\n 464:\t{\n 465:\t\tLZ4_stream_t_internal *ctx = \u0026((LZ4_stream_t *)state)-\u003einternal_donotuse;\n 466:\t#if LZ4_ARCH64\n 467:\t\tconst tableType_t tableType = byU32;\n 468:\t#else\n 469:\t\tconst tableType_t tableType = byPtr;\n 470:\t#endif\n 471:\t\n 472:\t\tLZ4_resetStream((LZ4_stream_t *)state);\n 473:\t\n 474:\t\tif (acceleration \u003c 1)\n 475:\t\t\tacceleration = LZ4_ACCELERATION_DEFAULT;\n 476:\t\n 477:\t\tif (maxOutputSize \u003e= LZ4_COMPRESSBOUND(inputSize)) {\n 478:\t\t\tif (inputSize \u003c LZ4_64Klimit)\n 479:\t\t\t\treturn LZ4_compress_generic(ctx, source,\n 480:\t\t\t\t\tdest, inputSize, 0,\n 481:\t\t\t\t\tnoLimit, byU16, noDict,\n 482:\t\t\t\t\tnoDictIssue, acceleration);\n 483:\t\t\telse\n 484:\t\t\t\treturn LZ4_compress_generic(ctx, source,\n 485:\t\t\t\t\tdest, inputSize, 0,\n 486:\t\t\t\t\tnoLimit, tableType, noDict,\n 487:\t\t\t\t\tnoDictIssue, acceleration);\n 488:\t\t} else {\n 489:\t\t\tif (inputSize \u003c LZ4_64Klimit)\n 490:\t\t\t\treturn LZ4_compress_generic(ctx, source,\n 491:\t\t\t\t\tdest, inputSize,\n 492:\t\t\t\t\tmaxOutputSize, limitedOutput, byU16, noDict,\n 493:\t\t\t\t\tnoDictIssue, acceleration);\n 494:\t\t\telse\n 495:\t\t\t\treturn LZ4_compress_generic(ctx, source,\n 496:\t\t\t\t\tdest, inputSize,\n 497:\t\t\t\t\tmaxOutputSize, limitedOutput, tableType, noDict,\n 498:\t\t\t\t\tnoDictIssue, acceleration);\n 499:\t\t}\n"
} |
| 25/3 | 2026/10/16 08:11 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 25244 output: 42 thoughts: 32 Thoughts: Here's my thought process, as if I were analyzing that code snippet: **Initialization Check** Okay, so we're looking at a line that seems to reset an LZ4 stream. `LZ4_resetStream((LZ4_stream_t *)state);` -- this looks like a critical initialization or re-initialization step. The cast to `(LZ4_stream_t *)` suggests `state` is some sort of data structure that holds the internal state of the LZ4 compressor or decompressor. The call to `LZ4_resetStream` is the key. Given the function name, I need to understand what this *reset* entails. Does it clear all internal buffers? Does it initialize some internal variables to their default values? Does it just re-point pointers? I need to know the details within the `LZ4_resetStream` function to determine its exact behavior and implication on the subsequent compression or decompression operation. Let's dig into that function's definition. |
| 26/3 | 2026/10/16 08:11 | tool | grepper |
0mArgs:{
"Expression": "LZ4_resetStream",
"PathPrefixes": [
"lib/lz4/",
"include/linux/lz4.h"
]
}Results: {
"Output": "include/linux/lz4.h=346=int LZ4_compress_HC(const char *src, char *dst, int srcSize, int dstCapacity,\n--\ninclude/linux/lz4.h-349-/**\ninclude/linux/lz4.h:350: * LZ4_resetStreamHC() - Init an allocated 'LZ4_streamHC_t' structure\ninclude/linux/lz4.h-351- * @streamHCPtr: pointer to the 'LZ4_streamHC_t' structure\n--\ninclude/linux/lz4.h-360- */\ninclude/linux/lz4.h:361:void LZ4_resetStreamHC(LZ4_streamHC_t *streamHCPtr, int compressionLevel);\ninclude/linux/lz4.h-362-\n--\ninclude/linux/lz4.h=376=int\tLZ4_loadDictHC(LZ4_streamHC_t *streamHCPtr, const char *dictionary,\n--\ninclude/linux/lz4.h-397- * Before starting compression, state must be properly initialized,\ninclude/linux/lz4.h:398: * using LZ4_resetStreamHC().\ninclude/linux/lz4.h-399- * A first \"fictional block\" can then be designated as\n--\ninclude/linux/lz4.h=434=int LZ4_saveDictHC(LZ4_streamHC_t *streamHCPtr, char *safeBuffer,\n--\ninclude/linux/lz4.h-441-/**\ninclude/linux/lz4.h:442: * LZ4_resetStream() - Init an allocated 'LZ4_stream_t' structure\ninclude/linux/lz4.h-443- * @LZ4_stream: pointer to the 'LZ4_stream_t' structure\n--\ninclude/linux/lz4.h-449- */\ninclude/linux/lz4.h:450:void LZ4_resetStream(LZ4_stream_t *LZ4_stream);\ninclude/linux/lz4.h-451-\n--\nlib/lz4/lz4_compress.c=457=static int LZ4_compress_fast_extState(\n--\nlib/lz4/lz4_compress.c-471-\nlib/lz4/lz4_compress.c:472:\tLZ4_resetStream((LZ4_stream_t *)state);\nlib/lz4/lz4_compress.c-473-\n--\nlib/lz4/lz4_compress.c=719=static int LZ4_compress_destSize_extState(\n--\nlib/lz4/lz4_compress.c-731-\nlib/lz4/lz4_compress.c:732:\tLZ4_resetStream(state);\nlib/lz4/lz4_compress.c-733-\n--\nlib/lz4/lz4_compress.c=764=EXPORT_SYMBOL(LZ4_compress_destSize);\n--\nlib/lz4/lz4_compress.c-768- ********************************/\nlib/lz4/lz4_compress.c:769:void LZ4_resetStream(LZ4_stream_t *LZ4_stream)\nlib/lz4/lz4_compress.c-770-{\n--\nlib/lz4/lz4_compress.c=774=int LZ4_loadDict(LZ4_stream_t *LZ4_dict,\n--\nlib/lz4/lz4_compress.c-784-\t\t/* Uninitialized structure, or reuse overflow */\nlib/lz4/lz4_compress.c:785:\t\tLZ4_resetStream(LZ4_dict);\nlib/lz4/lz4_compress.c-786-\t}\n--\nlib/lz4/lz4hc_compress.c=613=EXPORT_SYMBOL(LZ4_compress_HC);\n--\nlib/lz4/lz4hc_compress.c-617- **************************************/\nlib/lz4/lz4hc_compress.c:618:void LZ4_resetStreamHC(LZ4_streamHC_t *LZ4_streamHCPtr, int compressionLevel)\nlib/lz4/lz4hc_compress.c-619-{\n--\nlib/lz4/lz4hc_compress.c-622-}\nlib/lz4/lz4hc_compress.c:623:EXPORT_SYMBOL(LZ4_resetStreamHC);\nlib/lz4/lz4hc_compress.c-624-\n"
} |
| 27/3 | 2026/10/16 08:11 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 26647 output: 40 thoughts: 262 Thoughts: **Analyzing LZ4 HC Stream Reset and Context Management** Okay, so I'm digging into how LZ4 HC stream contexts are managed, specifically with an eye towards potential inefficiencies or areas for improvement. My initial thought is to look at the `LZ4_resetStream` and `LZ4_resetStreamHC` functions. I know they're using `memset` internally, which is a good starting point. That implies a complete zeroing of the context, which could be less optimal than a more targeted reset, depending on what the context actually *contains*. Let's dive into the `backend_lz4hc.c` code to see how these contexts are handled at the higher level. Ah, here we go, `lz4hc_create` uses `vmalloc(LZ4HC_MEM_COMPRESS)` to allocate the context. That's a good sign; dynamic allocation allows flexibility. But how is it being initialized and updated? Looking at `lz4hc_compress`, I see a conditional block. If `params->drv_data` is null, it's a straightforward `LZ4_compress_HC` call using the allocated context. But the interesting part is the `else` block. Here, the context is *not* initialized, a `memcpy` of the pre-saved state, `params->drv_data`, is performed. I see that this is then followed by a call to `LZ4_compress_HC_continue`. This suggests a stateful compression setup, and this `memcpy` operation could potentially be a performance bottleneck if the context is large or if we're doing it very frequently. This seems to be where the context reset is done, or at least a portion of it. Now, I need to understand what's actually in that `LZ4_streamHC_t` struct, and what exactly `LZ4_compress_HC` does. Back to the source! I'll jump into the `lz4hc_compress.c` file and dissect the `LZ4_compress_HC` function to get a handle on what the internal context initialization involves. I need to figure out if that `memset` is truly necessary, or if a more granular approach could be faster. And while I'm at it, I will also look into the implementation of `LZ4_compress_HC_continue`, since that's what's actually being called when state is passed between calls. |
| 28/3 | 2026/10/16 08:11 | tool | read-file |
0mArgs:{
"File": "lib/lz4/lz4hc_compress.c",
"FirstLine": 605,
"LineCount": 25
}Results: {
"Contents": " 605:\t}\n 606:\t\n 607:\tint LZ4_compress_HC(const char *src, char *dst, int srcSize,\n 608:\t\tint maxDstSize, int compressionLevel, void *wrkmem)\n 609:\t{\n 610:\t\treturn LZ4_compress_HC_extStateHC(wrkmem, src, dst,\n 611:\t\t\tsrcSize, maxDstSize, compressionLevel);\n 612:\t}\n 613:\tEXPORT_SYMBOL(LZ4_compress_HC);\n 614:\t\n 615:\t/**************************************\n 616:\t *\tStreaming Functions\n 617:\t **************************************/\n 618:\tvoid LZ4_resetStreamHC(LZ4_streamHC_t *LZ4_streamHCPtr, int compressionLevel)\n 619:\t{\n 620:\t\tLZ4_streamHCPtr-\u003einternal_donotuse.base = NULL;\n 621:\t\tLZ4_streamHCPtr-\u003einternal_donotuse.compressionLevel = (unsigned int)compressionLevel;\n 622:\t}\n 623:\tEXPORT_SYMBOL(LZ4_resetStreamHC);\n 624:\t\n 625:\tint LZ4_loadDictHC(LZ4_streamHC_t *LZ4_streamHCPtr,\n 626:\t\tconst char *dictionary,\n 627:\t\tint dictSize)\n 628:\t{\n 629:\t\tLZ4HC_CCtx_internal *ctxPtr = \u0026LZ4_streamHCPtr-\u003einternal_donotuse;\n"
} |
| 29/3 | 2026/10/16 08:11 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27883 output: 40 thoughts: 107 Thoughts: Here's my analysis of what's going on: **Deconstructing the LZ4 High Compression Stream Reset** Okay, hold on a minute, I need to zoom in on this `LZ4_resetStreamHC` function. Let's see... ah, right. The key initialization step here is setting `LZ4_streamHCPtr->internal_donotuse.base = NULL;`. That makes sense; it's clearing the base pointer, probably to ensure no stale data gets carried over from a previous compression context. And immediately following that, the compression level is set with `LZ4_streamHCPtr->internal_donotuse.compressionLevel = (unsigned int)compressionLevel;`. Makes sense, setting the compression level is critical for HC. The question now is, what about initial state? Does it memset any of the internal buffers? I need to verify whether `LZ4_compress_HC_extStateHC` and whatever initializes the `LZ4HC_CCtx_internal` structure does this, or if any other initialization is critical for proper operation of the HC context after a reset. I suspect I might need to look more deeply at what's being initialized during context allocation versus what's initialized during reset, to make sure there's no subtle state being carried over or left uninitialized, causing downstream problems. |
| 30/3 | 2026/10/16 08:12 | tool | read-file |
0mArgs:{
"File": "lib/lz4/lz4hc_compress.c",
"FirstLine": 585,
"LineCount": 30
}Results: {
"Contents": " 585:\t\tint maxDstSize,\n 586:\t\tint compressionLevel)\n 587:\t{\n 588:\t\tLZ4HC_CCtx_internal *ctx = \u0026((LZ4_streamHC_t *)state)-\u003einternal_donotuse;\n 589:\t\n 590:\t\tif (((size_t)(state)\u0026(sizeof(void *) - 1)) != 0) {\n 591:\t\t\t/* Error : state is not aligned\n 592:\t\t\t * for pointers (32 or 64 bits)\n 593:\t\t\t */\n 594:\t\t\treturn 0;\n 595:\t\t}\n 596:\t\n 597:\t\tLZ4HC_init(ctx, (const BYTE *)src);\n 598:\t\n 599:\t\tif (maxDstSize \u003c LZ4_compressBound(srcSize))\n 600:\t\t\treturn LZ4HC_compress_generic(ctx, src, dst,\n 601:\t\t\t\tsrcSize, maxDstSize, compressionLevel, limitedOutput);\n 602:\t\telse\n 603:\t\t\treturn LZ4HC_compress_generic(ctx, src, dst,\n 604:\t\t\t\tsrcSize, maxDstSize, compressionLevel, noLimit);\n 605:\t}\n 606:\t\n 607:\tint LZ4_compress_HC(const char *src, char *dst, int srcSize,\n 608:\t\tint maxDstSize, int compressionLevel, void *wrkmem)\n 609:\t{\n 610:\t\treturn LZ4_compress_HC_extStateHC(wrkmem, src, dst,\n 611:\t\t\tsrcSize, maxDstSize, compressionLevel);\n 612:\t}\n 613:\tEXPORT_SYMBOL(LZ4_compress_HC);\n 614:\t\n"
} |
| 31/3 | 2026/10/16 08:12 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 28787 output: 31 thoughts: 11 Thoughts: Here's my take on the thought process, as if I were the one thinking it: **Diving into LZ4HC_init** Okay, time to get my hands dirty with `LZ4HC_init`. I need to refresh my memory on what exactly this initialization function does within the LZ4 High Compression library. This is a critical starting point; understanding the setup is paramount to understanding everything that comes after. I suspect it's responsible for pre-calculating or pre-allocating any necessary internal tables, state variables, or working buffers. I'll need to confirm if it zeroes out any of these structures for a clean start. I'll probably want to double-check its interaction with memory management – are we dealing with stack allocation, heap allocation, or a combination? A quick glance at the code itself will be essential. This isn't just a casual read; I need to scrutinize the function's side effects and ensure it's doing precisely what I expect and nothing more. My gut tells me this is where the foundation for optimal compression performance is laid down, so I need to be thorough. |
| 32/3 | 2026/10/16 08:12 | tool | grepper |
0mArgs:{
"Expression": "LZ4HC_init",
"PathPrefixes": [
"lib/lz4/"
]
}Results: {
"Output": "lib/lz4/lz4hc_compress.c=52=static U32 LZ4HC_hashPtr(const void *ptr)\n--\nlib/lz4/lz4hc_compress.c-59- **************************************/\nlib/lz4/lz4hc_compress.c:60:static void LZ4HC_init(LZ4HC_CCtx_internal *hc4, const BYTE *start)\nlib/lz4/lz4hc_compress.c-61-{\n--\nlib/lz4/lz4hc_compress.c=580=static int LZ4_compress_HC_extStateHC(\n--\nlib/lz4/lz4hc_compress.c-596-\nlib/lz4/lz4hc_compress.c:597:\tLZ4HC_init(ctx, (const BYTE *)src);\nlib/lz4/lz4hc_compress.c-598-\n--\nlib/lz4/lz4hc_compress.c=625=int LZ4_loadDictHC(LZ4_streamHC_t *LZ4_streamHCPtr,\n--\nlib/lz4/lz4hc_compress.c-634-\t}\nlib/lz4/lz4hc_compress.c:635:\tLZ4HC_init(ctxPtr, (const BYTE *)dictionary);\nlib/lz4/lz4hc_compress.c-636-\tif (dictSize \u003e= 4)\n--\nlib/lz4/lz4hc_compress.c=667=static int LZ4_compressHC_continue_generic(\n--\nlib/lz4/lz4hc_compress.c-678-\tif (ctxPtr-\u003ebase == NULL)\nlib/lz4/lz4hc_compress.c:679:\t\tLZ4HC_init(ctxPtr, (const BYTE *) source);\nlib/lz4/lz4hc_compress.c-680-\n"
} |
| 33/3 | 2026/10/16 08:12 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 29534 output: 39 thoughts: 11 Thoughts: Okay, let's dive into this. Here's my take on `LZ4HC_init`, as if I'm the one thinking it through: **Deconstructing `LZ4HC_init`** Alright, let's get into this `LZ4HC_init` function. I'm assuming it's the high-compression initialization for the LZ4 algorithm, but let's see. My initial thought is to identify its role in the larger picture. Is it setting up internal data structures, allocating memory, or simply preparing the encoder for operation? From the name, I'd bet on the former. I need to figure out what parameters this function takes and what information it is responsible for preparing for subsequent high-compression calls. |
| 34/3 | 2026/10/16 08:12 | tool | read-file |
0mArgs:{
"File": "lib/lz4/lz4hc_compress.c",
"FirstLine": 60,
"LineCount": 20
}Results: {
"Contents": " 60:\tstatic void LZ4HC_init(LZ4HC_CCtx_internal *hc4, const BYTE *start)\n 61:\t{\n 62:\t\tmemset((void *)hc4-\u003ehashTable, 0, sizeof(hc4-\u003ehashTable));\n 63:\t\tmemset(hc4-\u003echainTable, 0xFF, sizeof(hc4-\u003echainTable));\n 64:\t\thc4-\u003enextToUpdate = 64 * KB;\n 65:\t\thc4-\u003ebase = start - 64 * KB;\n 66:\t\thc4-\u003eend = start;\n 67:\t\thc4-\u003edictBase = start - 64 * KB;\n 68:\t\thc4-\u003edictLimit = 64 * KB;\n 69:\t\thc4-\u003elowLimit = 64 * KB;\n 70:\t}\n 71:\t\n 72:\t/* Update chains up to ip (excluded) */\n 73:\tstatic FORCE_INLINE void LZ4HC_Insert(LZ4HC_CCtx_internal *hc4,\n 74:\t\tconst BYTE *ip)\n 75:\t{\n 76:\t\tU16 * const chainTable = hc4-\u003echainTable;\n 77:\t\tU32 * const hashTable\t= hc4-\u003ehashTable;\n 78:\t\tconst BYTE * const base = hc4-\u003ebase;\n 79:\t\tU32 const target = (U32)(ip - base);\n"
} |
| 35/3 | 2026/10/16 08:12 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 30107 output: 45 thoughts: 64 Thoughts: Here's my take on those thoughts: **Analyzing LZ4HC Initialization** Okay, so `LZ4HC_init` is responsible for setting up everything within the `LZ4HC_CCtx_internal` structure. That's straightforward enough. Now, a more pressing matter arises: I need to clarify the size discrepancy between `sizeof(LZ4_streamHC_t)` and `LZ4HC_MEM_COMPRESS`. I seem to recall some potential memory allocation details with those two, so I'll need to double-check the definition and how the memory is laid out for the HC context. Let's delve into the guts of `LZ4HC_MEM_COMPRESS` to understand its role. It probably has some bearing on memory management within the high-compression context. |
| 36/3 | 2026/10/16 08:12 | tool | grepper |
0mArgs:{
"Expression": "LZ4HC_MEM_COMPRESS|LZ4_MEM_COMPRESS",
"PathPrefixes": [
"include/linux/lz4.h"
]
}Results: {
"Output": "include/linux/lz4.h=148=typedef union {\n--\ninclude/linux/lz4.h-155- **************************************************************************/\ninclude/linux/lz4.h:156:#define LZ4_MEM_COMPRESS\tLZ4_STREAMSIZE\ninclude/linux/lz4.h:157:#define LZ4HC_MEM_COMPRESS\tLZ4_STREAMHCSIZE\ninclude/linux/lz4.h-158-\n--\ninclude/linux/lz4.h=170=static inline int LZ4_compressBound(size_t isize)\n--\ninclude/linux/lz4.h-182- * @wrkmem: address of the working memory.\ninclude/linux/lz4.h:183: *\tThis requires 'workmem' of LZ4_MEM_COMPRESS.\ninclude/linux/lz4.h-184- *\n--\ninclude/linux/lz4.h=197=int LZ4_compress_default(const char *source, char *dest, int inputSize,\n--\ninclude/linux/lz4.h-208- * @wrkmem: address of the working memory.\ninclude/linux/lz4.h:209: *\tThis requires 'workmem' of LZ4_MEM_COMPRESS.\ninclude/linux/lz4.h-210- *\n--\ninclude/linux/lz4.h=221=int LZ4_compress_fast(const char *source, char *dest, int inputSize,\n--\ninclude/linux/lz4.h-232- * @wrkmem: address of the working memory.\ninclude/linux/lz4.h:233: *\tThis requires 'workmem' of LZ4_MEM_COMPRESS.\ninclude/linux/lz4.h-234- *\n--\ninclude/linux/lz4.h=320=int LZ4_decompress_safe_partial(const char *source, char *dest,\n--\ninclude/linux/lz4.h-337- * @wrkmem: address of the working memory.\ninclude/linux/lz4.h:338: *\tThis requires 'wrkmem' of size LZ4HC_MEM_COMPRESS.\ninclude/linux/lz4.h-339- *\n"
} |
| 37/3 | 2026/10/16 08:12 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 30947 output: 59 thoughts: 1592 Thoughts: **Analyzing Potential Integer Overflow in ZRAM Decompression** Okay, let's break this down. First, the `BUILD_BUG_ON` is just a size check, no immediate concern. Now, I need to methodically check all relevant code paths. The code allocates buffers using `kzalloc`, `__vmalloc`, and `vzalloc` throughout, including for zstd and deflate contexts and workspaces. All of those allocators are zeroing the memory, which is good. I need to be sure though, so I keep checking. In `zcomp.c`, the initialization of `zstrm->buffer` and `zstrm->local_copy` with `vzalloc` also indicates zeroed memory, again a positive sign. Then I get to `zcomp_decompress`. I see a potentially tricky bit of logic. It looks like the decompression logic tries a per-request bounce buffer using `kmalloc` in "shared mode." If that fails, it falls back to `zstrm->local_copy`, protected by `excl_lock`. The critical section is within the `zcomp_decompress_buf` function. The function uses `sg_copy_to_buffer` to copy data from the scatterlist into the bounce buffer (`buf`). That's when I had the red flag go up! I need to ensure there is no integer overflow. Specifically, I need to confirm how `sg_copy_to_buffer` works in relation to `req->src_len`, and the `scatterlist`. I know `sg_copy_to_buffer` copies up to `buflen` bytes, but what if `buflen` is larger than what the `scatterlist` provides?! I will investigate this `scatterlist` in `zs_obj_read_sg_begin` and make sure it has the required length, and how `req->src_len` relates to it! |
| 38/3 | 2026/10/16 08:12 | tool | grepper |
0mArgs:{
"Expression": "zs_obj_read_sg_begin",
"PathPrefixes": [
"mm/zsmalloc.c",
"include/linux/zsmalloc.h",
"drivers/block/zram/"
]
}Results: {
"Output": "drivers/block/zram/zram_drv.c=2095=static int read_incompressible_page(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2102-\thandle = get_slot_handle(zram, index);\ndrivers/block/zram/zram_drv.c:2103:\tzs_obj_read_sg_begin(zram-\u003emem_pool, handle, sg, PAGE_SIZE);\ndrivers/block/zram/zram_drv.c-2104-\t/* an incompressible object never spans two pages */\n--\ndrivers/block/zram/zram_drv.c=2115=static int read_compressed_page(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2129-\ndrivers/block/zram/zram_drv.c:2130:\tzs_obj_read_sg_begin(zram-\u003emem_pool, handle, sg, size);\ndrivers/block/zram/zram_drv.c-2131-\tif (zcomp_shared_decomp(zram-\u003ecomps[prio]))\n--\ndrivers/block/zram/zram_drv.c=2145=static int read_from_zspool_raw(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2159-\t */\ndrivers/block/zram/zram_drv.c:2160:\tzs_obj_read_sg_begin(zram-\u003emem_pool, handle, sg, size);\ndrivers/block/zram/zram_drv.c-2161-\tdst = kmap_local_page(page);\n--\ninclude/linux/zsmalloc.h=41=void zs_pool_stats(struct zs_pool *pool, struct zs_pool_stats *stats);\ninclude/linux/zsmalloc.h-42-\ninclude/linux/zsmalloc.h:43:void zs_obj_read_sg_begin(struct zs_pool *pool, unsigned long handle,\ninclude/linux/zsmalloc.h-44-\t\t\t struct scatterlist *sg, size_t mem_len);\n--\nmm/zsmalloc.c=1123=EXPORT_SYMBOL_GPL(zs_get_total_pages);\nmm/zsmalloc.c-1124-\nmm/zsmalloc.c:1125:void zs_obj_read_sg_begin(struct zs_pool *pool, unsigned long handle,\nmm/zsmalloc.c-1126-\t\t\t struct scatterlist *sg, size_t mem_len)\n--\nmm/zsmalloc.c-1169-}\nmm/zsmalloc.c:1170:EXPORT_SYMBOL_GPL(zs_obj_read_sg_begin);\nmm/zsmalloc.c-1171-\n"
} |
| 39/3 | 2026/10/16 08:12 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 33751 output: 36 thoughts: 15 Thoughts: Okay, here's my interpretation of what's going on, framed as a first-person expert perspective: **Deep Dive into `zs_obj_read_sg_begin`** Alright, let's dissect `zs_obj_read_sg_begin`. Based on the naming convention, I'm expecting this to be the function that kicks off the process of reading a "single group" (SG) object, likely from a ZFS context given the `zs_` prefix. This suggests we're probably dealing with metadata structures or data blocks within the ZFS storage pool. My immediate questions are: What triggers this read? Is this a synchronous operation, or is it potentially initiating asynchronous I/O? What locking mechanisms, if any, are employed to protect the integrity of the data being read? We have to understand the interplay with other functions that are used to read ZFS objects. I anticipate seeing parameters that specify the object's identifier, offset within the object (if applicable, which I *strongly* suspect it is) and the size of the data to be read. I also expect a context that tracks the state of this read operation, maybe with information on where the data is read from, the status of the read, etc. The "begin" in the name suggests that this might be a multi-step operation, implying there's a corresponding "end" function or a series of functions that completes it. I should expect it to check for things like permissions, data checksums, and other integrity checks, given the potential for data corruption. Now, let’s see the code. |
| 40/3 | 2026/10/16 08:12 | tool | read-file |
0mArgs:{
"File": "mm/zsmalloc.c",
"FirstLine": 1125,
"LineCount": 45
}Results: {
"Contents": "1125:\tvoid zs_obj_read_sg_begin(struct zs_pool *pool, unsigned long handle,\n1126:\t\t\t\t struct scatterlist *sg, size_t mem_len)\n1127:\t{\n1128:\t\tstruct zspage *zspage;\n1129:\t\tstruct zpdesc *zpdesc;\n1130:\t\tunsigned long obj, off;\n1131:\t\tunsigned int obj_idx;\n1132:\t\tstruct size_class *class;\n1133:\t\n1134:\t\t/* Guarantee we can get zspage from handle safely */\n1135:\t\tread_lock(\u0026pool-\u003elock);\n1136:\t\tobj = handle_to_obj(handle);\n1137:\t\tobj_to_location(obj, \u0026zpdesc, \u0026obj_idx);\n1138:\t\tzspage = get_zspage(zpdesc);\n1139:\t\n1140:\t\t/* Make sure migration doesn't move any pages in this zspage */\n1141:\t\tzspage_read_lock(zspage);\n1142:\t\tread_unlock(\u0026pool-\u003elock);\n1143:\t\n1144:\t\tclass = zspage_class(pool, zspage);\n1145:\t\toff = offset_in_page(class-\u003esize * obj_idx);\n1146:\t\n1147:\t\tif (!ZsHugePage(zspage))\n1148:\t\t\toff += ZS_HANDLE_SIZE;\n1149:\t\n1150:\t\tif (off + mem_len \u003c= PAGE_SIZE) {\n1151:\t\t\t/* this object is contained entirely within a page */\n1152:\t\t\tsg_init_table(sg, 1);\n1153:\t\t\tsg_set_page(sg, zpdesc_page(zpdesc), mem_len, off);\n1154:\t\t} else {\n1155:\t\t\tsize_t sizes[2];\n1156:\t\n1157:\t\t\t/* this object spans two pages */\n1158:\t\t\tsizes[0] = PAGE_SIZE - off;\n1159:\t\t\tsizes[1] = mem_len - sizes[0];\n1160:\t\n1161:\t\t\tsg_init_table(sg, 2);\n1162:\t\t\tsg_set_page(sg, zpdesc_page(zpdesc), sizes[0], off);\n1163:\t\n1164:\t\t\tzpdesc = get_next_zpdesc(zpdesc);\n1165:\t\t\tsg = sg_next(sg);\n1166:\t\n1167:\t\t\tsg_set_page(sg, zpdesc_page(zpdesc), sizes[1], 0);\n1168:\t\t}\n1169:\t}\n"
} |
| 41/3 | 2026/10/16 08:12 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 34856 output: 41 thoughts: 1569 Thoughts: **Analyzing the Compression Logic: A Deep Dive** Okay, so I'm looking at this compression/decompression code, and a few things are immediately jumping out at me. First, let's dissect `zs_obj_read_sg_begin`. It's setting up the stage, where `sizes[0] + sizes[1]` (the size of the data) is equal to `mem_len`, which in turn equals `src_len`. Before this patch, we had a simple conditional: if data spanned two pages, we'd linearize it into `local_copy` using `sg_copy_to_buffer`. Now, things are more complex. The new code has three paths: exclusive mode (already holding the lock), shared mode using a per-request bounce buffer (using `kmalloc` to allocate `src`), or shared mode falling back to `local_copy` protected by a mutex lock. The critical part is `sg_copy_to_buffer(sg, 2, buf, req->src_len)`. The buffer `buf` is `src`, and it is completely filled with `src_len` bytes from the scatterlist `sg`. The decompress then runs on `req->src = buf`. But, wait! `kmalloc` doesn't zero the memory! Although `sg_copy_to_buffer` *should* copy `req->src_len` bytes, could it possibly copy less? No. `zs_obj_read_sg_begin` ensures `sg` contains precisely `src_len` bytes. Now, let's switch gears to `decompress_bdev_page`. This one is interesting. It sets up a scatterlist `sg` with a single page. It then calls `zcomp_decompress` with `zstrm->local_copy` as the *destination*. Okay, so in this case, `zcomp_decompress` is supposed to write directly into `local_copy`. Before the patch, the code was the same. The same logic holds. `ret` gets returned, no page copied if it fails. If decompress succeeds it decompressed into `dst`. Diving deeper, let's look at what's passed in: the page, the size and `zstrm->local_copy` passed to `zcomp_decompress`. `decompress_bdev_page` is similar, using the `local_copy` as a destination. If decompress fails, the original page is not modified. If the decompress function succeeds then we copy `zstrm->local_copy` to the page. `zcomp_decompress` takes care of the details. And the return value from `decompress` dictates the logic flow, that is unchanged. So, in the single page case, the code is unchanged. The decompression writes to `local_copy`, and then the page is updated from `local_copy` only if the decompression was successful. This is all consistent. Finally, what about other backends? Let's check `backend_842.c`. Specifically, let's examine the decompression function `decompress_842`! This seems important because the code's behavior can change based on the backend used. |
| 42/3 | 2026/10/16 08:12 | tool | grepper |
0mArgs:{
"Expression": "decompress_842",
"PathPrefixes": [
"drivers/block/zram/backend_842.c"
]
}Results: {
"Output": "drivers/block/zram/backend_842.c=42=static int compress_842(struct zcomp_params *params, struct zcomp_ctx *ctx,\n--\ndrivers/block/zram/backend_842.c-54-\ndrivers/block/zram/backend_842.c:55:static int decompress_842(struct zcomp_params *params, struct zcomp_ctx *ctx,\ndrivers/block/zram/backend_842.c-56-\t\t\t struct zcomp_req *req)\n--\ndrivers/block/zram/backend_842.c=63=const struct zcomp_ops backend_842 = {\ndrivers/block/zram/backend_842.c-64-\t.compress\t= compress_842,\ndrivers/block/zram/backend_842.c:65:\t.decompress\t= decompress_842,\ndrivers/block/zram/backend_842.c-66-\t.create_cctx\t= create_842,\n"
} |
| 43/3 | 2026/10/16 08:12 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 37444 output: 41 thoughts: 12 Thoughts: Okay, here's my interpretation of what's going on, formatted as you requested: **Deconstructing `decompress_842`** Alright, let's dive into this `decompress_842` function. My initial thought is to figure out the likely domain of this code. Based on the name alone, I'm already anticipating some kind of data compression or decompression routine. The number "842" doesn't immediately ring any specific bells in terms of common algorithms, which suggests this might be a custom implementation or perhaps a specific, internal version within a larger system. I'll need to look at the function's internals to confirm that. My primary focus will be on identifying the core algorithm being employed. Is it Huffman coding, Lempel-Ziv variants, Run-Length Encoding, or something entirely bespoke? I’ll need to understand how the function handles the compressed input and reconstructs the original data. I'll be looking for key indicators like: * **Input Data Structures:** How is the compressed data represented? Is it a byte array, a stream, or something else? Understanding the format of the input is crucial to tracing the decompression process. * **Compression Markers/Headers:** Are there any specific bit sequences or headers that signify the start or end of compressed blocks, or that provide metadata about the compression scheme? Those are always the giveaways. * **Decoding Logic:** This is where the core lies. I'll be looking for how the input data is mapped to the output data. Bitwise operations, lookup tables, and conditional branching are all typical techniques employed in decompression. * **Error Handling:** Does it include any error checking or data validation? A robust decompression function should be able to handle corrupted input gracefully, or at least provide meaningful error messages. I'll be specifically looking for potential inefficiencies or clever optimizations that might have been implemented. Knowing which compression algorithm is being applied allows me to determine if the implementation follows best practices, or if there are any obvious areas where performance could be improved. Once I have a good idea of the structure, I can start to dig in. |
| 44/3 | 2026/10/16 08:12 | tool | read-file |
0mArgs:{
"File": "drivers/block/zram/backend_842.c",
"FirstLine": 50,
"LineCount": 20
}Results: {
"Contents": " 50:\t\tif (ret == 0)\n 51:\t\t\treq-\u003edst_len = dlen;\n 52:\t\treturn ret;\n 53:\t}\n 54:\t\n 55:\tstatic int decompress_842(struct zcomp_params *params, struct zcomp_ctx *ctx,\n 56:\t\t\t\t struct zcomp_req *req)\n 57:\t{\n 58:\t\tunsigned int dlen = req-\u003edst_len;\n 59:\t\n 60:\t\treturn sw842_decompress(req-\u003esrc, req-\u003esrc_len, req-\u003edst, \u0026dlen);\n 61:\t}\n 62:\t\n 63:\tconst struct zcomp_ops backend_842 = {\n 64:\t\t.compress\t= compress_842,\n 65:\t\t.decompress\t= decompress_842,\n 66:\t\t.create_cctx\t= create_842,\n 67:\t\t.destroy_cctx\t= destroy_842,\n 68:\t\t.setup_params\t= setup_params_842,\n 69:\t\t.release_params\t= release_params_842,\n"
} |
| 45/3 | 2026/10/16 08:12 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 38260 output: 40 thoughts: 61 Thoughts: Here's my take on the thought process: **Diving into the Code: Decompression Deep Dive** Okay, so I'm looking at `decompress_842`. Immediately, I notice something interesting: it's not even utilizing the `ctx` variable. This raises a flag – that seems...unusual. I need to understand why. My mind then wanders to other areas that would likely also be decompression related. So, what about `backend_lzo.c` and `backend_lzorle.c`? Those filenames strongly suggest that they're dealing with LZO and LZO-RLE compression, respectively. I should probably get a look at those as well. Let's see what's in `backend_lzo.c` and `backend_lzorle.c`. Time to dig in... |
| 46/3 | 2026/10/16 08:13 | tool | read-file |
0mArgs:{
"File": "drivers/block/zram/backend_lzo.c",
"FirstLine": 40,
"LineCount": 40
}Results: {
"Contents": " 40:\t\n 41:\tstatic int lzo_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n 42:\t\t\t\tstruct zcomp_req *req)\n 43:\t{\n 44:\t\tint ret;\n 45:\t\n 46:\t\tret = lzo1x_1_compress(req-\u003esrc, req-\u003esrc_len, req-\u003edst,\n 47:\t\t\t\t \u0026req-\u003edst_len, ctx-\u003econtext);\n 48:\t\treturn ret == LZO_E_OK ? 0 : ret;\n 49:\t}\n 50:\t\n 51:\tstatic int lzo_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,\n 52:\t\t\t\t struct zcomp_req *req)\n 53:\t{\n 54:\t\tint ret;\n 55:\t\n 56:\t\tret = lzo1x_decompress_safe(req-\u003esrc, req-\u003esrc_len,\n 57:\t\t\t\t\t req-\u003edst, \u0026req-\u003edst_len);\n 58:\t\treturn ret == LZO_E_OK ? 0 : ret;\n 59:\t}\n 60:\t\n 61:\tconst struct zcomp_ops backend_lzo = {\n 62:\t\t.compress\t= lzo_compress,\n 63:\t\t.decompress\t= lzo_decompress,\n 64:\t\t.create_cctx\t= lzo_create,\n 65:\t\t.destroy_cctx\t= lzo_destroy,\n 66:\t\t.setup_params\t= lzo_setup_params,\n 67:\t\t.release_params\t= lzo_release_params,\n 68:\t\t.name\t\t= \"lzo\",\n 69:\t};\n"
} |
| 47/3 | 2026/10/16 08:13 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 39001 output: 39 thoughts: 1550 Thoughts: **Deconstructing the LZ4 Context Management** Okay, so I'm diving into this compression backend code, specifically looking at how context management is handled, and I need to be precise, as this is critical to performance and memory safety. My initial observation was that `create_dctx` (for decompression contexts) is only defined for `backend_deflate` and `backend_zstd`, but not for `backend_842`, `backend_lz4`, `backend_lz4hc`, `backend_lzo`, and `backend_lzorle`. Therefore, the `zcomp_shared_decomp` function returns true for those lacking the dedicated decompress context creation function. However, that doesn't tell the whole story. I see that `zcomp_dstrm_init` allocates a context using `create_dctx` *if* it's present; otherwise, it allocates a local copy with `vzalloc`. The critical allocation and release functions that come into play are `zcomp_cstrm_init` for the compression stream, and `zcomp_cstrm_free`, which handles the cleanup. Crucially, `zcomp_cstrm_free` checks for the presence of `destroy_cctx` before freeing the context, preventing double frees and dangling pointers via `zstrm->ctx.context = NULL`. The core issue I'm concerned about here is proper context initialization and destruction. Now, let's look at `backend_lz4.c` Specifically. I am checking `lz4_create` where the `ctx->context` is allocated. But, wait! It's `vmalloc`, not `vzalloc`, which means it's allocated in virtual memory, but not necessarily contiguous physical memory. This can lead to fragmentation issues, so I should be concerned about that. Then, there's `lz4_compress` which presents a branching logic. When `params->drv_data` is NOT NULL: The context is initialized by copying the driver data into `ctx->context`. This is initialized in `lz4_setup_params` using a `vzalloc`. This is important, as it implies a dictionary is being loaded. When `params->drv_data` IS NULL: The context uses `LZ4_compress_fast`. `LZ4_compress_fast` uses an `extState`, which in turn `LZ4_resetStream`. What is going on here? If `params->drv_data` is not NULL (we are using a dictionary), then the context is copied from the dictionary via `memcpy`. If `params->drv_data` IS NULL (no dictionary), the context is *reset* via `LZ4_resetStream`. We must check the implementation of `LZ4_resetStream`! I need to ensure context is initialized properly either way. Let's look at `lib/lz4/lz4_compress.c` line 769. I must be precise with this, to ensure the context is valid before use, and released after use. |
| 48/3 | 2026/10/16 08:13 | tool | read-file |
0mArgs:{
"File": "lib/lz4/lz4_compress.c",
"FirstLine": 768,
"LineCount": 20
}Results: {
"Contents": " 768:\t ********************************/\n 769:\tvoid LZ4_resetStream(LZ4_stream_t *LZ4_stream)\n 770:\t{\n 771:\t\tmemset(LZ4_stream, 0, sizeof(LZ4_stream_t));\n 772:\t}\n 773:\t\n 774:\tint LZ4_loadDict(LZ4_stream_t *LZ4_dict,\n 775:\t\tconst char *dictionary, int dictSize)\n 776:\t{\n 777:\t\tLZ4_stream_t_internal *dict = \u0026LZ4_dict-\u003einternal_donotuse;\n 778:\t\tconst BYTE *p = (const BYTE *)dictionary;\n 779:\t\tconst BYTE * const dictEnd = p + dictSize;\n 780:\t\tconst BYTE *base;\n 781:\t\n 782:\t\tif ((dict-\u003einitCheck)\n 783:\t\t\t|| (dict-\u003ecurrentOffset \u003e 1 * GB)) {\n 784:\t\t\t/* Uninitialized structure, or reuse overflow */\n 785:\t\t\tLZ4_resetStream(LZ4_dict);\n 786:\t\t}\n 787:\t\n"
} |
| 49/3 | 2026/10/16 08:13 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 41557 output: 46 thoughts: 796 Thoughts: **LZ4HC Context Management Analysis** Okay, so `LZ4_resetStream` zeroes the entire `LZ4_stream_t` context. That clears *everything*. Now I'm looking at `backend_lz4hc.c` and specifically how the high-compression context is managed. `lz4hc_create` allocates memory using `vmalloc` and assigns it to `ctx->context`. Crucially, `lz4hc_compress` then has a critical branching logic. If `params->drv_data` is not set, it's a straightforward `LZ4_compress_HC` call with the allocated `ctx->context`. But when `params->drv_data` *is* available, it copies the contents of `params->drv_data` into `ctx->context` using `memcpy`. This is key. Then, I went back and looked at the core `LZ4_compress_HC` function. I see that it actually calls `LZ4_compress_HC_extStateHC`, passing `ctx->context` as the `state` argument. Okay. Now the critical part! The internal workings of `LZ4_compress_HC_extStateHC` casts that state to a `LZ4_streamHC_t` and further extracts an internal context `LZ4HC_CCtx_internal`. Inside `LZ4_compress_HC_extStateHC` there's an alignment check. Then, there is the call to `LZ4HC_init`. And *that* is where things get interesting. `LZ4HC_init` *completely* zeros the `hashTable` and sets `chainTable` to all 0xFF. It then sets up all the pointers relative to the input buffer. This means that the *first* compression operation zeroes the entire `hashTable`. Subsequent operations, where `drv_data` is used, copy from `params->drv_data`. That means that if the `drv_data` has never been initialized, we could have a problem. I have to carefully evaluate how `params->drv_data` is initialized, and ensure that either it has been initialized correctly prior to the call to `lz4hc_compress`, or the `ctx->context` must have been initialized prior to the compression to avoid uninitialized memory or out-of-bounds access. I also have to investigate when `params->drv_data` is set. |
| 50/3 | 2026/10/16 08:13 | tool | grepper |
0mError:no matches Args: {
"Expression": "struct LZ4HC_CCtx_internal",
"PathPrefixes": [
"lib/lz4/",
"include/linux/lz4.h"
]
}Results: {
"error": "no matches"
} |
| 51/3 | 2026/10/16 08:13 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 42929 output: 45 thoughts: 14 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Hunting Down LZ4HC_CCtx_internal** Okay, so I need to track down the definition of `LZ4HC_CCtx_internal`. This smells like an internal structure within the LZ4 High Compression (HC) implementation. My gut tells me this is something specific to the compression context, likely holding state and working buffers for the HC variant. Given the naming convention, it's almost certainly not exposed in the public header file. Therefore, I will have to look in the implementation files of the LZ4 HC algorithm. I'll start by searching the obvious places first, the main `lz4hc.c` file and then any included supporting files. I'm assuming that it is defined with in `lz4hc.c` and would be `static` or `inline` as it has the `_internal` suffix. I'll use grep or a similar tool to pinpoint its declaration quickly, looking for its structure definition and where it is used. I anticipate it's likely involved in managing the match finder, hash tables, or lookahead buffers employed in the HC algorithm's more aggressive compression strategy. Depending on how it is implemented, it could also be a nested structure within a main compression context struct, or it could be a separate structure that is accessed within the main context. The more I consider it, it could even be a macro, but I would be surprised. Once I find it, I'll analyze the surrounding code to understand its purpose and how it influences the compression process. |
| 52/3 | 2026/10/16 08:13 | tool | grepper |
0mArgs:{
"Expression": "LZ4HC_CCtx_internal",
"PathPrefixes": [
"lib/lz4/",
"include/linux/lz4.h"
]
}Results: {
"Output": "include/linux/lz4.h=114=typedef struct {\n--\ninclude/linux/lz4.h-129-\tunsigned int\t compressionLevel;\ninclude/linux/lz4.h:130:} LZ4HC_CCtx_internal;\ninclude/linux/lz4.h-131-typedef union {\ninclude/linux/lz4.h-132-\tsize_t table[LZ4_STREAMHCSIZE_SIZET];\ninclude/linux/lz4.h:133:\tLZ4HC_CCtx_internal internal_donotuse;\ninclude/linux/lz4.h-134-} LZ4_streamHC_t;\n--\nlib/lz4/lz4hc_compress.c=52=static U32 LZ4HC_hashPtr(const void *ptr)\n--\nlib/lz4/lz4hc_compress.c-59- **************************************/\nlib/lz4/lz4hc_compress.c:60:static void LZ4HC_init(LZ4HC_CCtx_internal *hc4, const BYTE *start)\nlib/lz4/lz4hc_compress.c-61-{\n--\nlib/lz4/lz4hc_compress.c-72-/* Update chains up to ip (excluded) */\nlib/lz4/lz4hc_compress.c:73:static FORCE_INLINE void LZ4HC_Insert(LZ4HC_CCtx_internal *hc4,\nlib/lz4/lz4hc_compress.c-74-\tconst BYTE *ip)\n--\nlib/lz4/lz4hc_compress.c=98=static FORCE_INLINE int LZ4HC_InsertAndFindBestMatch(\nlib/lz4/lz4hc_compress.c:99:\tLZ4HC_CCtx_internal *hc4, /* Index table will be updated */\nlib/lz4/lz4hc_compress.c-100-\tconst BYTE *ip,\n--\nlib/lz4/lz4hc_compress.c=167=static FORCE_INLINE int LZ4HC_InsertAndGetWiderMatch(\nlib/lz4/lz4hc_compress.c:168:\tLZ4HC_CCtx_internal *hc4,\nlib/lz4/lz4hc_compress.c-169-\tconst BYTE * const ip,\n--\nlib/lz4/lz4hc_compress.c=337=static int LZ4HC_compress_generic(\nlib/lz4/lz4hc_compress.c:338:\tLZ4HC_CCtx_internal *const ctx,\nlib/lz4/lz4hc_compress.c-339-\tconst char * const source,\n--\nlib/lz4/lz4hc_compress.c=580=static int LZ4_compress_HC_extStateHC(\n--\nlib/lz4/lz4hc_compress.c-587-{\nlib/lz4/lz4hc_compress.c:588:\tLZ4HC_CCtx_internal *ctx = \u0026((LZ4_streamHC_t *)state)-\u003einternal_donotuse;\nlib/lz4/lz4hc_compress.c-589-\n--\nlib/lz4/lz4hc_compress.c=625=int LZ4_loadDictHC(LZ4_streamHC_t *LZ4_streamHCPtr,\n--\nlib/lz4/lz4hc_compress.c-628-{\nlib/lz4/lz4hc_compress.c:629:\tLZ4HC_CCtx_internal *ctxPtr = \u0026LZ4_streamHCPtr-\u003einternal_donotuse;\nlib/lz4/lz4hc_compress.c-630-\n--\nlib/lz4/lz4hc_compress.c=645=static void LZ4HC_setExternalDict(\nlib/lz4/lz4hc_compress.c:646:\tLZ4HC_CCtx_internal *ctxPtr,\nlib/lz4/lz4hc_compress.c-647-\tconst BYTE *newBlock)\n--\nlib/lz4/lz4hc_compress.c=667=static int LZ4_compressHC_continue_generic(\n--\nlib/lz4/lz4hc_compress.c-674-{\nlib/lz4/lz4hc_compress.c:675:\tLZ4HC_CCtx_internal *ctxPtr = \u0026LZ4_streamHCPtr-\u003einternal_donotuse;\nlib/lz4/lz4hc_compress.c-676-\n--\nlib/lz4/lz4hc_compress.c=734=int LZ4_saveDictHC(\n--\nlib/lz4/lz4hc_compress.c-738-{\nlib/lz4/lz4hc_compress.c:739:\tLZ4HC_CCtx_internal *const streamPtr = \u0026LZ4_streamHCPtr-\u003einternal_donotuse;\nlib/lz4/lz4hc_compress.c-740-\tint const prefixSize = (int)(streamPtr-\u003eend\n"
} |
| 53/3 | 2026/10/16 08:13 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 44555 output: 36 thoughts: 24 Thoughts: Okay, let's dive into this. I'm going to take a look at the `include/linux/lz4.h` file, specifically lines 110-140. I expect this section to be fairly critical, likely defining some core LZ4 functionality or data structures, given its location within the header file. I'm hoping to get a clearer picture of how LZ4 is implemented at the kernel level. **Deep Dive into LZ4.h: Unpacking the Core Definitions** Alright, lines 110-140… I bet this section will outline the key macros, inline functions, or perhaps even struct definitions that are absolutely central to the LZ4 compression and decompression algorithms within the Linux kernel. Given the precision required for kernel-level code, I'm anticipating elegant and efficient coding practices here. I'm looking for clear definitions of fundamental LZ4 building blocks, maybe how literals and matches are handled, or perhaps how the sliding window is managed. I also hope it clarifies any potential architecture-specific optimizations that might be in play. I'm keen to spot any nuances in the interface; after all, even small changes to a data structure in this context can have far-reaching implications. It will be interesting to see if any of these definitions differ from my understanding of the LZ4 standard implementation. I'm going to focus on clarity, efficiency, and how this relates to the overall kernel architecture. |
| 54/3 | 2026/10/16 08:13 | tool | read-file |
0mArgs:{
"File": "include/linux/lz4.h",
"FirstLine": 110,
"LineCount": 30
}Results: {
"Contents": " 110:\t\n 111:\t/*\n 112:\t * LZ4_streamHC_t - information structure to track an LZ4HC stream.\n 113:\t */\n 114:\ttypedef struct {\n 115:\t\tunsigned int\t hashTable[LZ4HC_HASHTABLESIZE];\n 116:\t\tunsigned short\t chainTable[LZ4HC_MAXD];\n 117:\t\t/* next block to continue on current prefix */\n 118:\t\tconst unsigned char *end;\n 119:\t\t/* All index relative to this position */\n 120:\t\tconst unsigned char *base;\n 121:\t\t/* alternate base for extDict */\n 122:\t\tconst unsigned char *dictBase;\n 123:\t\t/* below that point, need extDict */\n 124:\t\tunsigned int\t dictLimit;\n 125:\t\t/* below that point, no more dict */\n 126:\t\tunsigned int\t lowLimit;\n 127:\t\t/* index from which to continue dict update */\n 128:\t\tunsigned int\t nextToUpdate;\n 129:\t\tunsigned int\t compressionLevel;\n 130:\t} LZ4HC_CCtx_internal;\n 131:\ttypedef union {\n 132:\t\tsize_t table[LZ4_STREAMHCSIZE_SIZET];\n 133:\t\tLZ4HC_CCtx_internal internal_donotuse;\n 134:\t} LZ4_streamHC_t;\n 135:\t\n 136:\t/*\n 137:\t * LZ4_streamDecode_t - information structure to track an\n 138:\t *\tLZ4 stream during decompression.\n 139:\t *\n"
} |
| 55/3 | 2026/10/16 08:13 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 45358 output: 35 thoughts: 599 Thoughts: **LZ4HC Initialization and Compression Level - A Quick Investigation** Alright, let's break this down. First, I need to understand how the LZ4HC context is initialized. I look at the `LZ4HC_init` function. It seems pretty straightforward: `hashTable`, `chainTable`, and several other internal pointers and limits are being initialized or reset, `nextToUpdate`, `base`, `end`, `dictBase`, `dictLimit`, `lowLimit`. Wait... does this function actually handle the *compression level*? No, I don't see anything related to `compressionLevel` being set here. Okay, where is that set, then? Let's trace it back. Aha! I see it in `LZ4_compress_HC_extStateHC`. It takes `compressionLevel` as a *parameter*. Then the `LZ4HC_init` is called, and *then* `LZ4HC_compress_generic` is called, also passing that `compressionLevel` along. So, I need to verify: does `LZ4HC_compress_generic` *read* `ctx->compressionLevel` or not? It's passed as a parameter to the function. It seems like it must be used somehow. Now, I'll need to check the source code for `LZ4HC_compress_generic`. I'll open `lib/lz4/lz4hc_compress.c` and dig in. This should reveal the answer. |
| 56/3 | 2026/10/16 08:13 | tool | grepper |
0mArgs:{
"Expression": "compressionLevel",
"PathPrefixes": [
"lib/lz4/lz4hc_compress.c"
]
}Results: {
"Output": "lib/lz4/lz4hc_compress.c=337=static int LZ4HC_compress_generic(\n--\nlib/lz4/lz4hc_compress.c-342-\tint const maxOutputSize,\nlib/lz4/lz4hc_compress.c:343:\tint compressionLevel,\nlib/lz4/lz4hc_compress.c-344-\tlimitedOutput_directive limit\n--\nlib/lz4/lz4hc_compress.c-366-\t/* init */\nlib/lz4/lz4hc_compress.c:367:\tif (compressionLevel \u003e LZ4HC_MAX_CLEVEL)\nlib/lz4/lz4hc_compress.c:368:\t\tcompressionLevel = LZ4HC_MAX_CLEVEL;\nlib/lz4/lz4hc_compress.c:369:\tif (compressionLevel \u003c 1)\nlib/lz4/lz4hc_compress.c:370:\t\tcompressionLevel = LZ4HC_DEFAULT_CLEVEL;\nlib/lz4/lz4hc_compress.c:371:\tmaxNbAttempts = 1 \u003c\u003c (compressionLevel - 1);\nlib/lz4/lz4hc_compress.c-372-\tctx-\u003eend += inputSize;\n--\nlib/lz4/lz4hc_compress.c=580=static int LZ4_compress_HC_extStateHC(\n--\nlib/lz4/lz4hc_compress.c-585-\tint maxDstSize,\nlib/lz4/lz4hc_compress.c:586:\tint compressionLevel)\nlib/lz4/lz4hc_compress.c-587-{\n--\nlib/lz4/lz4hc_compress.c-600-\t\treturn LZ4HC_compress_generic(ctx, src, dst,\nlib/lz4/lz4hc_compress.c:601:\t\t\tsrcSize, maxDstSize, compressionLevel, limitedOutput);\nlib/lz4/lz4hc_compress.c-602-\telse\nlib/lz4/lz4hc_compress.c-603-\t\treturn LZ4HC_compress_generic(ctx, src, dst,\nlib/lz4/lz4hc_compress.c:604:\t\t\tsrcSize, maxDstSize, compressionLevel, noLimit);\nlib/lz4/lz4hc_compress.c-605-}\n--\nlib/lz4/lz4hc_compress.c=607=int LZ4_compress_HC(const char *src, char *dst, int srcSize,\nlib/lz4/lz4hc_compress.c:608:\tint maxDstSize, int compressionLevel, void *wrkmem)\nlib/lz4/lz4hc_compress.c-609-{\nlib/lz4/lz4hc_compress.c-610-\treturn LZ4_compress_HC_extStateHC(wrkmem, src, dst,\nlib/lz4/lz4hc_compress.c:611:\t\tsrcSize, maxDstSize, compressionLevel);\nlib/lz4/lz4hc_compress.c-612-}\nlib/lz4/lz4hc_compress.c=613=EXPORT_SYMBOL(LZ4_compress_HC);\n--\nlib/lz4/lz4hc_compress.c-617- **************************************/\nlib/lz4/lz4hc_compress.c:618:void LZ4_resetStreamHC(LZ4_streamHC_t *LZ4_streamHCPtr, int compressionLevel)\nlib/lz4/lz4hc_compress.c-619-{\nlib/lz4/lz4hc_compress.c-620-\tLZ4_streamHCPtr-\u003einternal_donotuse.base = NULL;\nlib/lz4/lz4hc_compress.c:621:\tLZ4_streamHCPtr-\u003einternal_donotuse.compressionLevel = (unsigned int)compressionLevel;\nlib/lz4/lz4hc_compress.c-622-}\n--\nlib/lz4/lz4hc_compress.c=667=static int LZ4_compressHC_continue_generic(\n--\nlib/lz4/lz4hc_compress.c-712-\treturn LZ4HC_compress_generic(ctxPtr, source, dest,\nlib/lz4/lz4hc_compress.c:713:\t\tinputSize, maxOutputSize, ctxPtr-\u003ecompressionLevel, limit);\nlib/lz4/lz4hc_compress.c-714-}\n"
} |
| 57/3 | 2026/10/16 08:13 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 47413 output: 39 thoughts: 2434 Thoughts: **Deep Dive into LZ4HC and ZRAM Compression** Okay, let's break this down systematically. My initial observation is about `LZ4HC_compress_generic` and how the `compressionLevel` is handled. It's passed as an argument, which is what I'd expect. However, my immediate concern is around the `ctx` struct. It seemed that `ctx` was either potentially uninitialized in some cases, or that its contents were being overwritten, and it was hard to determine whether there was a potential for undefined behavior. I dove deeper and confirmed that, inside `LZ4HC_compress_HC_continue`, the compressor *does not* read uninitialized memory from `ctx->context`. In `lz4hc_create`, if `params->drv_data` is not NULL, the entire contents of `ctx->context` are overwritten with a `memcpy` from `params->drv_data`. Crucially, `params->drv_data` comes from `vzalloc`, and its size matches the `LZ4HC_MEM_COMPRESS`, which is equal to `sizeof(LZ4_streamHC_t)`. It's a full overwrite, not a partial initialization, so there are no holes. I then started to evaluate what happens if `params->drv_data` is NULL. The compressor is called with `wrkmem = ctx->context`. The call `LZ4HC_init` initializes most of the members of the internal context, including the hashtable and chaintable. This means the context is initialized with clean memory, even if it's the first time it is used. The one field *not* initialized by `LZ4HC_init` is `compressionLevel`, which is passed directly as an argument, and this is completely safe. The `LZ4HC_CCtx_internal` struct has no other hidden fields, so the memory space is well defined, well initialized, and completely safe. Now, let's pivot to the `backend_deflate.c` code. It appears they're using `kzalloc_obj` and `vzalloc` for the compression/decompression contexts and workspaces, respectively. This is a standard and secure approach. I then reviewed the `BUILD_BUG_ON` checks in both `lz4_create` and `lz4hc_create`. They confirm that the sizes of `LZ4_stream_t` and `LZ4_streamHC_t` match their respective memory allocation sizes, which means that what I expected, that there was a memory space allocated and a corresponding struct defined. This means that these size checks are in place to ensure that the memory allocation is correct. I am now focusing on the ZRAM changes in `zram_drv.c`. The `reset_bdev` function zeroes the bitmap and the `nr_pages`, which indicates that the state of the block device is completely reset. The change to using `zcomp_dstrm` instead of `zcomp_strm` in `decompress_bdev_page` and `read_compressed_page` seems straightforward, which is not an issue. The additional `zcomp_dstrm_put` and `zcomp_dstrm_get` calls add explicit management of decompression streams. This gives a clearer indication of the resources involved, along with some more modes (shared or exclusive). The rewrite to use `zcomp_cstrm` instead of `zcomp_strm` in `zram_write_page` and `recompress_slot` aligns with the previous changes. Also, the addition of the writeback settings, which include enable flags, batch sizes and compression modes, adds some new features. I also reviewed the `zcomp_create` and `zcomp_init` functions in `zcomp.c`. The `zcomp_init` function handles the initialization of both the compression and decompression streams. The `zcomp_create` function allocates the `comp` struct, and the `zcomp_init` function initializes its members and allocates the needed structures: `cstream` and `dstream`. The use of `alloc_percpu` for both `cstream` and `dstream` indicates a per-CPU context, which is the expected behavior. The conditional allocation of a `recomp_cstream` suggests that there might be a separate path for recompression, which is also a secure option. The usage of `mutex_init` and `init_rwsem` to protect shared resources, shows a good use of standard synchronization primitives. Overall, the code changes demonstrate a careful approach to memory management, initialization, and synchronization. The changes are all reasonable, well-defined, and completely safe. |
| 58/3 | 2026/10/16 08:14 | tool | read-file |
0mArgs:{
"File": "drivers/block/zram/zcomp.c",
"FirstLine": 425,
"LineCount": 35
}Results: {
"Contents": " 425:\t\n 426:\tstruct zcomp *zcomp_create(const char *alg, struct zcomp_params *params,\n 427:\t\t\t\t bool recomp)\n 428:\t{\n 429:\t\tstruct zcomp *comp;\n 430:\t\tint error;\n 431:\t\n 432:\t\t/*\n 433:\t\t * The backends array has a sentinel NULL value, so the minimum\n 434:\t\t * size is 1. In order to be valid the array, apart from the\n 435:\t\t * sentinel NULL element, should have at least one compression\n 436:\t\t * backend selected.\n 437:\t\t */\n 438:\t\tBUILD_BUG_ON(ARRAY_SIZE(backends) \u003c= 1);\n 439:\t\n 440:\t\tcomp = kzalloc_obj(struct zcomp);\n 441:\t\tif (!comp)\n 442:\t\t\treturn ERR_PTR(-ENOMEM);\n 443:\t\n 444:\t\tcomp-\u003eops = lookup_backend_ops(alg);\n 445:\t\tif (!comp-\u003eops) {\n 446:\t\t\tkfree(comp);\n 447:\t\t\treturn ERR_PTR(-EINVAL);\n 448:\t\t}\n 449:\t\n 450:\t\terror = zcomp_init(comp, params, recomp);\n 451:\t\tif (error) {\n 452:\t\t\tkfree(comp);\n 453:\t\t\treturn ERR_PTR(error);\n 454:\t\t}\n 455:\t\treturn comp;\n 456:\t}\n"
} |
| 59/3 | 2026/10/16 08:14 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 51307 output: 37 thoughts: 1715 Thoughts: **Deconstructing the Decompression Stream Initialization** Okay, so I'm looking at how this `zcomp` structure gets initialized and how decompression streams are handled, specifically focusing on potential uninitialized memory issues. First, `kzalloc_obj` is used to allocate `comp`, which is zero-filled, so that's good. Now, during the `CPUHP_ZCOMP_PREPARE` phase, `zcomp_cpu_up_prepare` gets called. This is where things get interesting. Initially, I see it checks for `comp->cstream`. If it exists, then per-CPU compression streams (`cstrm`) are initialized using `zcomp_cstrm_init`. However, the conditional related to `comp->cstream` is confusing. *If* `recomp` is true, then `comp->cstream` is NULL. In this scenario, `comp->recomp_cstream` is initialized in `zcomp_init` instead, and then `zcomp_cstrm_init` is called on that. When `zcomp_cstrm_get` is called, it locks that shared stream. The *decompression* stream (`dstream`) is initialized per-CPU for all possible CPUs. I'm starting to get worried about uninitialized memory with respect to decompression. I look at `zcomp_dstrm_get`. This function retrieves a `zcomp_dstrm`. It uses `raw_cpu_ptr` to get the per-CPU `dstream` and then takes a read lock on `zstrm->lock`. The stream's `local_copy` is initialized. If the stream is destroyed, it re-tries. Exclusive requests also take `zstrm->excl_lock`. The corresponding `zcomp_dstrm_put` function releases the appropriate locks. Now, my main concern lies in the `zcomp_decompress` function. The code looks like it could use uninitialized memory if there is not a check. This function performs the actual decompression. It takes the lock and asserts that either the `zstrm->lock` or both `zstrm->lock` and `zstrm->excl_lock` are held, depending on the decompression mode (shared or exclusive). The code copies the source data (`sg`) to a buffer. However, let me look closer to be completely sure. If the input is contained within a single page, the data is read directly from that page. Okay, so that's fine. If not, the source is copied to a buffer. Now, there are two cases: shared and exclusive. If `!zcomp_shared_decomp(comp)`, meaning it is exclusive, the function uses `zstrm->local_copy` that was initialized by `vzalloc`. The code calls `sg_copy_to_buffer(sg, 2, buf, req->src_len);`. This copies `src_len` bytes from the scatterlist into `zstrm->local_copy`. Since `zstrm->local_copy` was `vzalloc`'d to `PAGE_SIZE`, and `src_len` is guaranteed to be less than or equal to `PAGE_SIZE`, and `src_len` is the number of bytes read, this is fine. So, no problem here, I am copying a certain amount to a memory block initialized before. If `zcomp_shared_decomp(comp)` the code uses a bounce buffer allocated with `kmalloc`. It then copies data to `src` with `sg_copy_to_buffer`, and then calls the `zcomp_decompress_buf` function to decompress. The copy also looks safe. The copy uses `sg_copy_to_buffer` where the source buffer is `src`. Now I need to be sure! Does `sg_copy_to_buffer` guarantee to initialize all bytes that are copied in the scatterlist? I'll need to check the definition of `sg_copy_to_buffer` to verify if this is the case. I need to be sure that the memory buffer is *completely* initialized, otherwise there may be issues. I will look into the source file to check this. |
| 60/3 | 2026/10/16 08:14 | tool | grepper |
0mArgs:{
"Expression": "size_t sg_copy_to_buffer",
"PathPrefixes": [
"lib/scatterlist.c"
]
}Results: {
"Output": "lib/scatterlist.c=1019=EXPORT_SYMBOL(sg_copy_from_buffer);\n--\nlib/scatterlist.c-1030- **/\nlib/scatterlist.c:1031:size_t sg_copy_to_buffer(struct scatterlist *sgl, unsigned int nents,\nlib/scatterlist.c-1032-\t\t\t void *buf, size_t buflen)\n"
} |
| 61/3 | 2026/10/16 08:14 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 54071 output: 35 thoughts: 24 Thoughts: Okay, let's dive into this. I'm going to take a look at the `lib/scatterlist.c` file, specifically lines 1030 through 1045. My expertise tells me that this is likely where we're dealing with scatter-gather lists, and those lines probably relate to the core manipulation or processing of them. I'm expecting to see operations around data movement, potentially memory allocation, or maybe even hardware interaction, depending on the kernel's design. Let's see what the code reveals. |
| 62/3 | 2026/10/16 08:14 | tool | read-file |
0mArgs:{
"File": "lib/scatterlist.c",
"FirstLine": 1030,
"LineCount": 20
}Results: {
"Contents": "1030:\t **/\n1031:\tsize_t sg_copy_to_buffer(struct scatterlist *sgl, unsigned int nents,\n1032:\t\t\t\t void *buf, size_t buflen)\n1033:\t{\n1034:\t\treturn sg_copy_buffer(sgl, nents, buf, buflen, 0, true);\n1035:\t}\n1036:\tEXPORT_SYMBOL(sg_copy_to_buffer);\n1037:\t\n1038:\t/**\n1039:\t * sg_pcopy_from_buffer - Copy from a linear buffer to an SG list\n1040:\t * @sgl:\t\t The SG list\n1041:\t * @nents:\t\t Number of SG entries\n1042:\t * @buf:\t\t Where to copy from\n1043:\t * @buflen:\t\t The number of bytes to copy\n1044:\t * @skip:\t\t Number of bytes to skip before copying\n1045:\t *\n1046:\t * Returns the number of copied bytes.\n1047:\t *\n1048:\t **/\n1049:\tsize_t sg_pcopy_from_buffer(struct scatterlist *sgl, unsigned int nents,\n"
} |
| 63/3 | 2026/10/16 08:14 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 54579 output: 34 thoughts: 15 Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person: **Diving into `sg_copy_buffer`** Alright, let's see what's going on with this `sg_copy_buffer` function. I see it's mentioned. My initial thought is to quickly refresh my memory of its purpose. Specifically, I need to understand if it's operating on scatter-gather lists. I immediately think to check where this function is implemented and how it is being used in my code. I am thinking, what are the use cases? What is it responsible for? Memory transfers, data transformation, or something else entirely? A quick glance at the definition or documentation would be in order. I need to understand its role in the overall data flow, especially in a context. Is it core to the I/O path, a utility function for managing buffers, or something more specialized? My prior work tells me this likely depends heavily on the specific hardware/platform involved. |
| 64/3 | 2026/10/16 08:14 | tool | read-file |
0mArgs:{
"File": "lib/scatterlist.c",
"FirstLine": 960,
"LineCount": 45
}Results: {
"Contents": " 960:\t * @buflen:\t\t The number of bytes to copy\n 961:\t * @skip:\t\t Number of bytes to skip before copying\n 962:\t * @to_buffer:\t\t transfer direction (true == from an sg list to a\n 963:\t *\t\t\t buffer, false == from a buffer to an sg list)\n 964:\t *\n 965:\t * Returns the number of copied bytes.\n 966:\t *\n 967:\t **/\n 968:\tsize_t sg_copy_buffer(struct scatterlist *sgl, unsigned int nents, void *buf,\n 969:\t\t\t size_t buflen, off_t skip, bool to_buffer)\n 970:\t{\n 971:\t\tunsigned int offset = 0;\n 972:\t\tstruct sg_mapping_iter miter;\n 973:\t\tunsigned int sg_flags = SG_MITER_LOCAL;\n 974:\t\n 975:\t\tif (to_buffer)\n 976:\t\t\tsg_flags |= SG_MITER_FROM_SG;\n 977:\t\telse\n 978:\t\t\tsg_flags |= SG_MITER_TO_SG;\n 979:\t\n 980:\t\tsg_miter_start(\u0026miter, sgl, nents, sg_flags);\n 981:\t\n 982:\t\tif (!sg_miter_skip(\u0026miter, skip))\n 983:\t\t\treturn 0;\n 984:\t\n 985:\t\twhile ((offset \u003c buflen) \u0026\u0026 sg_miter_next(\u0026miter)) {\n 986:\t\t\tunsigned int len;\n 987:\t\n 988:\t\t\tlen = min(miter.length, buflen - offset);\n 989:\t\n 990:\t\t\tif (to_buffer)\n 991:\t\t\t\tmemcpy(buf + offset, miter.addr, len);\n 992:\t\t\telse\n 993:\t\t\t\tmemcpy(miter.addr, buf + offset, len);\n 994:\t\n 995:\t\t\toffset += len;\n 996:\t\t}\n 997:\t\n 998:\t\tsg_miter_stop(\u0026miter);\n 999:\t\n1000:\t\treturn offset;\n1001:\t}\n1002:\tEXPORT_SYMBOL(sg_copy_buffer);\n1003:\t\n1004:\t/**\n"
} |
| 65/3 | 2026/10/16 08:14 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 55486 output: 33 thoughts: 363 Thoughts: **Deconstructing the Data Flow: A Deep Dive** Okay, so I'm looking at this `zs_obj_read_sg_begin` function, and it's making these Scatter Gather (SG) entries. Specifically, when a read spans two pages, it generates two entries. The first entry (`sizes[0]`) covers the data up to the end of the first page, calculated as `PAGE_SIZE - off`. The second entry (`sizes[1]`) covers the remainder: `mem_len - sizes[0]`. The total SG length is then `sizes[0] + sizes[1]`, which happily equals `mem_len`. And here's the crucial link: `mem_len` is equivalent to `src_len`, and the ultimate buffer length is `buflen` which also equals `src_len`. The loop in the code copies data corresponding to these sizes: first `sizes[0]`, then `sizes[1]`, and the `offset` correctly progresses to `src_len`, indicating that all `src_len` bytes from the source are transferred to `buf`. This buffer, `buf`, is where the decompression function is working with the compressed data, using `req->src_len` bytes from the source. This seems to imply that `buf` is fully populated. But hold on a second. What if `src_len` is zero? In zram, zero-length compressed pages should never happen because `kmalloc(0)` (or its equivalent) would return a null pointer and the slot size must be greater than zero. And another thing to consider: what if the decompression routine produces less than `PAGE_SIZE` bytes? That could introduce an issue, let's look at `read_compressed_page` to see how this is handled: ```c dst = kmap_local_page(page); ret = zcomp_decompress(zram->comps[prio], zstrm, sg, size, dst); kunmap_local(dst); ``` Ah! `dst` is the actual `page` itself! I missed that at first, and that's critical. But how is `read_compressed_page` called? I need to trace the callers to get a better handle on this whole process. |
| 66/3 | 2026/10/16 08:14 | tool | grepper |
0mArgs:{
"Expression": "read_compressed_page",
"PathPrefixes": [
"drivers/block/zram/"
]
}Results: {
"Output": "drivers/block/zram/zram_drv.c=2095=static int read_incompressible_page(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2114-\ndrivers/block/zram/zram_drv.c:2115:static int read_compressed_page(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-2116-\t\t\t\tunsigned long index)\n--\ndrivers/block/zram/zram_drv.c=2176=static int read_from_zspool(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2183-\tif (!test_slot_flag(zram, index, ZRAM_HUGE))\ndrivers/block/zram/zram_drv.c:2184:\t\treturn read_compressed_page(zram, page, index);\ndrivers/block/zram/zram_drv.c-2185-\telse\n"
} |
| 67/3 | 2026/10/16 08:14 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 56657 output: 35 thoughts: 15 Thoughts: Here's a summary of my thought process: **Caller Analysis for `read_from_zspool`** Okay, so I need to understand how `read_from_zspool` is being used. First thing's first: let's dive into identifying the functions that are actually *calling* this particular function. Understanding the calling context is crucial. Knowing *where* and *how* `read_from_zspool` is invoked will inform any modifications or debugging efforts I undertake. This should give me a better handle on the potential impact of any changes. This is standard practice before making any modifications to a function, especially a function that appears so central to a system. Now to get to work! |
| 68/3 | 2026/10/16 08:14 | tool | grepper |
0mArgs:{
"Expression": "read_from_zspool",
"PathPrefixes": [
"drivers/block/zram/"
]
}Results: {
"Output": "drivers/block/zram/zram_drv.c=453=static ssize_t idle_store(struct device *dev, struct device_attribute *attr,\n--\ndrivers/block/zram/zram_drv.c-493-\ndrivers/block/zram/zram_drv.c:494:static int read_from_zspool_raw(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-495-\t\t\t\tunsigned long index);\ndrivers/block/zram/zram_drv.c:496:static int read_from_zspool(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-497-\t\t\t unsigned long index);\n--\ndrivers/block/zram/zram_drv.c=1036=static int zram_writeback_slots(struct zram *zram,\n--\ndrivers/block/zram/zram_drv.c-1091-\t\tif (zram-\u003ecompressed_wb)\ndrivers/block/zram/zram_drv.c:1092:\t\t\terr = read_from_zspool_raw(zram, req-\u003epage, index);\ndrivers/block/zram/zram_drv.c-1093-\t\telse\ndrivers/block/zram/zram_drv.c:1094:\t\t\terr = read_from_zspool(zram, req-\u003epage, index);\ndrivers/block/zram/zram_drv.c-1095-\t\tif (err)\n--\ndrivers/block/zram/zram_drv.c=2115=static int read_compressed_page(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2144-#if defined CONFIG_ZRAM_WRITEBACK\ndrivers/block/zram/zram_drv.c:2145:static int read_from_zspool_raw(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-2146-\t\t\t\tunsigned long index)\n--\ndrivers/block/zram/zram_drv.c-2175- */\ndrivers/block/zram/zram_drv.c:2176:static int read_from_zspool(struct zram *zram, struct page *page,\ndrivers/block/zram/zram_drv.c-2177-\t\t\t unsigned long index)\n--\ndrivers/block/zram/zram_drv.c=2189=static int zram_read_page(struct zram *zram, struct page *page,\n--\ndrivers/block/zram/zram_drv.c-2196-\t\t/* Slot should be locked through out the function call */\ndrivers/block/zram/zram_drv.c:2197:\t\tret = read_from_zspool(zram, page, index);\ndrivers/block/zram/zram_drv.c-2198-\t\tslot_unlock(zram, index);\n--\ndrivers/block/zram/zram_drv.c=2454=static int recompress_slot(struct zram *zram, unsigned long index,\n--\ndrivers/block/zram/zram_drv.c-2478-\ndrivers/block/zram/zram_drv.c:2479:\tret = read_from_zspool(zram, page, index);\ndrivers/block/zram/zram_drv.c-2480-\tif (ret)\n"
} |
| 69/3 | 2026/10/16 08:14 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 57767 output: 40 thoughts: 194 Thoughts: Here's my analysis of what I'm seeing: **Analyzing Zram Zstd Compression Parameters** Okay, so the standard page reads within zram are still using the standard bio read, writeback, and recompression processes – nothing new there. Now I need to dig into what *has* changed. Specifically, let's start with `zstd_setup_params`. Okay, I'm looking at the `zstd_setup_params` function, and I'm interested in how it initializes Zstd parameters. I see that it calls `zstd_get_cparams` based on the compression level and dictionary size. But wait... here, in the code, I see this conditional that wasn't there before: ```c if (!params->dict || !params->dict_sz) return 0; zp->cdict = zstd_create_cdict_byreference(params->dict, ... ``` Ah! It looks like there's a new check in place. It seems to be handling cases where the dictionary is null or the dictionary size is zero. If either is true, it returns 0. *This* is interesting, because it appears that a dictionary is now optional. We should probably see how this influences the overall performance. Let me take a look at the code for that section - lines 60-100 of `drivers/block/zram/backend_zstd.c`. I have a feeling this is where the new dictionary behavior is implemented. |
| 70/3 | 2026/10/16 08:14 | tool | read-file |
0mArgs:{
"File": "drivers/block/zram/backend_zstd.c",
"FirstLine": 60,
"LineCount": 45
}Results: {
"Contents": " 60:\t\tzstd_compression_parameters prm;\n 61:\t\tstruct zstd_params *zp;\n 62:\t\n 63:\t\tzp = kzalloc_obj(*zp);\n 64:\t\tif (!zp)\n 65:\t\t\treturn -ENOMEM;\n 66:\t\n 67:\t\tparams-\u003edrv_data = zp;\n 68:\t\tif (params-\u003elevel == ZCOMP_PARAM_NOT_SET) {\n 69:\t\t\tparams-\u003elevel = zstd_default_clevel();\n 70:\t\t} else if (params-\u003elevel \u003c zstd_min_clevel() ||\n 71:\t\t\t params-\u003elevel \u003e zstd_max_clevel()) {\n 72:\t\t\tpr_err(\"invalid compression level %d\\n\", params-\u003elevel);\n 73:\t\t\tgoto error;\n 74:\t\t}\n 75:\t\n 76:\t\tzp-\u003ecprm = zstd_get_params(params-\u003elevel, PAGE_SIZE);\n 77:\t\n 78:\t\tzp-\u003ecustom_mem.customAlloc = zstd_custom_alloc;\n 79:\t\tzp-\u003ecustom_mem.customFree = zstd_custom_free;\n 80:\t\n 81:\t\tprm = zstd_get_cparams(params-\u003elevel, PAGE_SIZE,\n 82:\t\t\t\t params-\u003edict_sz);\n 83:\t\n 84:\t\tif (!params-\u003edict || !params-\u003edict_sz)\n 85:\t\t\treturn 0;\n 86:\t\n 87:\t\tzp-\u003ecdict = zstd_create_cdict_byreference(params-\u003edict,\n 88:\t\t\t\t\t\t\t params-\u003edict_sz,\n 89:\t\t\t\t\t\t\t prm,\n 90:\t\t\t\t\t\t\t zp-\u003ecustom_mem);\n 91:\t\tif (!zp-\u003ecdict)\n 92:\t\t\tgoto error;\n 93:\t\n 94:\t\tzp-\u003eddict = zstd_create_ddict_byreference(params-\u003edict,\n 95:\t\t\t\t\t\t\t params-\u003edict_sz,\n 96:\t\t\t\t\t\t\t zp-\u003ecustom_mem);\n 97:\t\tif (!zp-\u003eddict)\n 98:\t\t\tgoto error;\n 99:\t\n 100:\t\treturn 0;\n 101:\t\n 102:\terror:\n 103:\t\treturn -EINVAL;\n 104:\t}\n"
} |
| 71/3 | 2026/10/16 08:14 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 58938 output: 40 thoughts: 254 Thoughts: **My ZSTD Parameter and Context Initialization Process** Okay, so I'm looking at how we're setting up the Zstandard parameters and compression context. First, in `zstd_setup_params`, I allocate memory for the `zp` struct using `kzalloc_obj`, which I assume zeroes it out. Then, I grab the compression parameters with `zstd_get_params`, using the compression level from the input `params` and the `PAGE_SIZE`. I also deal with `zp->custom_mem...` which is nice and seems to be handling custom memory management (I hope it's well-formed). The zero return is the signal that there isn't a dictionary or no size, which is a sensible check, I suppose. Now, in `zstd_create_cctx`, I'm grabbing the pre-configured `zp` from the driver data of `params`. I have to check `params->dict_sz == 0`. Assuming it is zero, I pull the compression parameters from `zp->cprm` into a new `zstd_parameters prm` variable. I then calculate the workspace bound using `zstd_cctx_workspace_bound(&prm.cParams)`. From this, I allocate memory for the context using `vzalloc`, which I know is zeroed. Finally, I initialize the compression context `zctx->ctx` using `zstd_init_cctx` with the allocated memory. The fact that everything's zeroed out by `kzalloc_obj` and `vzalloc` is a good practice, preventing potential issues with uninitialized data. Wait, hold on...I need to make sure I'm not missing anything. I'm going to quickly check `backend_deflate.c`, just to get a perspective on how that one's doing it. Time to glance at `drivers/block/zram/backend_deflate.c`, specifically lines 40-55. Gotta make sure I'm not missing anything obvious from the other backend. |
| 72/3 | 2026/10/16 08:14 | tool | read-file |
0mArgs:{
"File": "drivers/block/zram/backend_deflate.c",
"FirstLine": 40,
"LineCount": 20
}Results: {
"Contents": " 40:\t\t\tif ((wb \u003c -15 || wb \u003e -9) \u0026\u0026 (wb \u003c 9 || wb \u003e 15)) {\n 41:\t\t\t\tpr_err(\"invalid winbits %d\\n\", wb);\n 42:\t\t\t\treturn -EINVAL;\n 43:\t\t\t}\n 44:\t\t}\n 45:\t\n 46:\t\treturn 0;\n 47:\t}\n 48:\t\n 49:\tstatic void deflate_destroy_cctx(struct zcomp_ctx *ctx)\n 50:\t{\n 51:\t\tstruct z_stream_s *cctx = ctx-\u003econtext;\n 52:\t\n 53:\t\tif (!cctx)\n 54:\t\t\treturn;\n 55:\t\n 56:\t\tif (cctx-\u003eworkspace) {\n 57:\t\t\tzlib_deflateEnd(cctx);\n 58:\t\t\tvfree(cctx-\u003eworkspace);\n 59:\t\t}\n"
} |
| 73/3 | 2026/10/16 08:14 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 59920 output: 454 thoughts: 3240 Thoughts: **KMSAN Assessment: A Deep Dive** Okay, let's break this down meticulously. My focus is on determining whether this patch series warrants a KMSAN analysis. First, I need to understand the code's functionality, and then rigorously assess whether the changes introduce any risks of uninitialized memory reads that KASAN wouldn't catch. I see the core changes are focused on creating and managing compression/decompression contexts and streams within the zram framework. The code manipulates `zlib`'s `deflate` and `inflate`, and looks to be using a dictionary in the lz4, lz4hc, and zstd backends. The code shows various allocation functions like `kzalloc_obj`, `vzalloc`, and `vmalloc`, which are all standard for kernel development. There are also per-CPU structures being allocated (`alloc_percpu`), a shared recompression stream and shared/exclusive locking implemented via rwsem and mutex. The critical KMSAN trigger is uninitialized memory *that's read*. So, let's step through the code and flag where memory is being allocated: * `deflate_create_cctx`, `deflate_create_dctx`: The code allocates `z_stream_s` with `kzalloc_obj` and the workspace with `vzalloc`. Both of these functions *zero* the allocated memory. Then `zlib_deflateInit2` or `zlib_inflateInit2` are called. * `deflate_compress` / `deflate_decompress`: The compression/decompression functions operate on `z_stream_s`. The buffers are passed in explicitly as `req->src` and `req->dst`. These buffers are set by the caller before being used. The code calls `zlib_deflateReset` or `zlib_inflateReset` to reset the context each time. No uninitialized memory is read. * `lz4_setup_params`, `lz4hc_setup_params`: Allocates a `dict_stream` with `vzalloc`. The content is reset via `LZ4_resetStreamHC` or copied/initialized by the calls to the compress/decompress functions. * `lz4_create`, `lz4hc_create`: `vmalloc` is used. However, the compression routines `lz4_compress` calls LZ4_compress_fast and LZ4HC_init which resets everything to 0. The decompressed functions are safe. * `zstd_custom_alloc`: This calls `kzalloc` which initializes memory to zero. * `zstd_setup_params`: Calls `kzalloc_obj`, initializing allocated memory to zero. * `zstd_create_cctx`, `zstd_create_dctx`: Allocates contexts and workspace with `kzalloc_obj` and `vzalloc`, both zeroing memory. * `zcomp_cstrm_init`, `zcomp_dstrm_init`: allocates memory for buffers via `vzalloc`. * `zcomp_decompress`: Allocates a stack structure `req` with initialized fields. `src` buffer allocated via `kmalloc`, and populated via `sg_copy_to_buffer`. This ensures that the decompressor reads *only* initialized bytes. The code calls kmalloc. No information is leaked. * Locking and concurrency: rwsem and mutex are added and initialized. * Allocation of percpu variables with mutexes: `alloc_percpu` allocates with `GFP_PERCPU | __GFP_ZERO`. The mutexes are initialized. My conclusion is: **No**. The code is consistently allocating memory with functions that zero it, or explicitly initializes memory before its use. There's no risk of kernel-to-user memory leaks (no `copy_to_user`, `put_user`, netlink, etc.) or conditional execution paths using uninitialized memory. All the buffers passed to compression/decompression routines are initialized before use, and the contexts are reset. Thus, this code series does not necessitate KMSAN. KASAN and standard kernel debuggers will catch any memory corruption, use-after-free, or out-of-bounds issues. The concurrency issues are caught by LOCKDEP, and the standard debuggers will handle other kernel debugging facilities. KMSAN would be overkill here. |
| 74/3 | 2026/10/16 08:15 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series refactors zram compression backends and stream management by separating unified compression/decompression streams into dedicated compression (`zcomp_cstrm`) and decompression (`zcomp_dstrm`) streams, supporting concurrent shared decompression for stateless backends, and dedicating a single stream for recompression.\n\nFrom a memory safety and sanitizer perspective:\n1. Uninitialized memory risks:\n - All stream, context, and workspace allocations use zero-initializing allocators (`kzalloc_obj`, `vzalloc`, `__GFP_ZERO`, and `alloc_percpu` which zeroes memory).\n - In `backend_lz4` and `backend_lz4hc` where `vmalloc` is used for compression contexts, `LZ4_compress_fast` / `LZ4_compress_HC` explicitly resets and initializes the working memory (`LZ4_resetStream`, `LZ4HC_init`) or copies from the zero-initialized dictionary stream before execution.\n - In `zcomp_decompress()`, the dynamic bounce buffer allocated via `kmalloc()` is completely populated with `req.src_len` bytes by `sg_copy_to_buffer()` before being passed as `req.src` to the decompressor.\n - No kernel structures, padding, or uninitialized buffers are copied to user space (no `copy_to_user`, ioctl outputs, or netlink attributes involved).\n2. Primary bug risks:\n - The refactoring primarily introduces risks related to locking/concurrency (the new rwsem and mutex synchronization model between CPU hotplug and shared/exclusive decompression modes), lifetime management (CPU hotplug state callbacks), and potential out-of-bounds memory accesses or use-after-free.\n - These risks are fully covered by standard debugging facilities and sanitizers enabled in standard KASAN fuzzing builds (specifically KASAN and LOCKDEP).\n\nBecause no uninitialized memory usage or kernel-to-user info-leak risks are introduced, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series refactors zram compression backends and stream management by separating unified compression/decompression streams into dedicated compression (`zcomp_cstrm`) and decompression (`zcomp_dstrm`) streams, supporting concurrent shared decompression for stateless backends, and dedicating a single stream for recompression.\n\nFrom a memory safety and sanitizer perspective:\n1. Uninitialized memory risks:\n - All stream, context, and workspace allocations use zero-initializing allocators (`kzalloc_obj`, `vzalloc`, `__GFP_ZERO`, and `alloc_percpu` which zeroes memory).\n - In `backend_lz4` and `backend_lz4hc` where `vmalloc` is used for compression contexts, `LZ4_compress_fast` / `LZ4_compress_HC` explicitly resets and initializes the working memory (`LZ4_resetStream`, `LZ4HC_init`) or copies from the zero-initialized dictionary stream before execution.\n - In `zcomp_decompress()`, the dynamic bounce buffer allocated via `kmalloc()` is completely populated with `req.src_len` bytes by `sg_copy_to_buffer()` before being passed as `req.src` to the decompressor.\n - No kernel structures, padding, or uninitialized buffers are copied to user space (no `copy_to_user`, ioctl outputs, or netlink attributes involved).\n2. Primary bug risks:\n - The refactoring primarily introduces risks related to locking/concurrency (the new rwsem and mutex synchronization model between CPU hotplug and shared/exclusive decompression modes), lifetime management (CPU hotplug state callbacks), and potential out-of-bounds memory accesses or use-after-free.\n - These risks are fully covered by standard debugging facilities and sanitizers enabled in standard KASAN fuzzing builds (specifically KASAN and LOCKDEP).\n\nBecause no uninitialized memory usage or kernel-to-user info-leak risks are introduced, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|