public inbox for linux-block@vger.kernel.org
 help / color / mirror / Atom feed
From: Jihan LIN via B4 Relay <devnull+linjh22s.gmail.com@kernel.org>
To: Minchan Kim <minchan@kernel.org>,
	 Sergey Senozhatsky <senozhatsky@chromium.org>,
	Jens Axboe <axboe@kernel.dk>
Cc: linux-kernel@vger.kernel.org, linux-block@vger.kernel.org,
	 Jihan LIN <linjh22s@gmail.com>
Subject: [PATCH RFC v2 5/5] zram: Add lz4 PoC for zcomp-managed streams
Date: Mon, 09 Mar 2026 12:23:08 +0000	[thread overview]
Message-ID: <20260309-b4_zcomp_stream-v2-5-7148622326eb@gmail.com> (raw)
In-Reply-To: <20260309-b4_zcomp_stream-v2-0-7148622326eb@gmail.com>

From: Jihan LIN <linjh22s@gmail.com>

This patch provides a proof-of-concept implementation of zcomp-managed
streams for the lz4 backend, demonstrating how a hardware-accelerated
compression backend would integrate with zcomp-managed streams
introduced earlier in this series.

The PoC simulates a hardware accelerator with a fixed queue depth of
128. Global stream buffers are shared across all zram devices, while
contexts are per-device. Both are pre-allocated. During compression,
requests are submitted to a double-buffered kfifo queue and processed by
a dedicated kthread.

Known limitations:
 - The single kthread serializes all compression work.
 - Pool sizes are hard-coded.
 - Uses global mutexes; contention is expected to be high under load.
 - Assumes !HIGHMEM; kmap_local_page mappings are passed to a kthread.

Signed-off-by: Jihan LIN <linjh22s@gmail.com>
---
 drivers/block/zram/backend_lz4.c | 464 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 442 insertions(+), 22 deletions(-)

diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c
index 04e18661476086502ac41355e9cc38cc2f353d52..adf689003a62770cbbf0901ca07da84108f8d9d7 100644
--- a/drivers/block/zram/backend_lz4.c
+++ b/drivers/block/zram/backend_lz4.c
@@ -2,8 +2,12 @@
 #include <linux/lz4.h>
 #include <linux/slab.h>
 #include <linux/vmalloc.h>
+#include <linux/kthread.h>
+#include <linux/kfifo.h>
+#include <linux/completion.h>
 
 #include "backend_lz4.h"
+#include "zcomp.h"
 
 struct lz4_ctx {
 	void *mem;
@@ -12,18 +16,326 @@ struct lz4_ctx {
 	LZ4_stream_t *cstrm;
 };
 
+struct lz4_stream {
+	struct managed_zstrm zstrm;
+	struct list_head node;
+	struct completion completion;
+	int result;
+};
+
+struct lz4_req {
+	struct lz4_stream *strm;
+	struct lz4_ctx *ctx;
+	struct zcomp_params *params;
+	struct zcomp_req *zreq;
+};
+
+#define BACKEND_LZ4_STREAM_MAX 128
+#define BACKEND_LZ4_QUEUE_NR 2
+
+struct lz4_global {
+	struct list_head stream_head;
+	struct mutex stream_lock;
+	struct task_struct *tsk;
+
+	struct completion new_task_ready;
+
+	struct mutex working_lock;
+	DECLARE_KFIFO(working_queue[BACKEND_LZ4_QUEUE_NR], struct lz4_req,
+		      BACKEND_LZ4_STREAM_MAX);
+	int working_submit_idx;
+
+	struct kref ref;
+};
+
+static DEFINE_MUTEX(lz4_global_lock);
+static struct lz4_global *lz4_global_data;
+
+static void lz4_stream_free(struct lz4_stream *src)
+{
+	if (IS_ERR_OR_NULL(src))
+		return;
+
+	vfree(src->zstrm.strm.buffer);
+	vfree(src->zstrm.strm.local_copy);
+
+	kvfree(src);
+}
+
+DEFINE_FREE(lz4_stream, struct lz4_stream *, lz4_stream_free(_T));
+static struct lz4_stream *lz4_stream_alloc(void)
+{
+	struct lz4_stream *strm __free(lz4_stream) = NULL;
+	void *buffer __free(kvfree) = NULL;
+	void *local_copy __free(kvfree) = NULL;
+
+	strm = kvzalloc_obj(struct lz4_stream, GFP_KERNEL);
+	if (!strm)
+		return ERR_PTR(-ENOMEM);
+
+	buffer = vmalloc(PAGE_SIZE * 2);
+	local_copy = vmalloc(PAGE_SIZE);
+	if (!buffer || !local_copy)
+		return ERR_PTR(-ENOMEM);
+
+	strm->zstrm.strm.buffer = no_free_ptr(buffer);
+	strm->zstrm.strm.local_copy = no_free_ptr(local_copy);
+	strm->zstrm.strm.zcomp_managed = true;
+
+	return_ptr(strm);
+}
+
+static void lz4_streams_destroy(struct lz4_global *inst)
+{
+	struct lz4_stream *pos, *tmp;
+
+	list_for_each_entry_safe(pos, tmp, &inst->stream_head, node) {
+		list_del(&pos->node);
+		lz4_stream_free(pos);
+	}
+}
+
+static int lz4_streams_init(struct zcomp_params *params,
+			    struct lz4_global *inst)
+{
+	int err = 0;
+
+	INIT_LIST_HEAD(&inst->stream_head);
+	for (int i = 0; i < BACKEND_LZ4_STREAM_MAX; i++) {
+		struct lz4_stream *curr_zstrm __free(lz4_stream) = NULL;
+
+		curr_zstrm = lz4_stream_alloc();
+		if (IS_ERR(curr_zstrm)) {
+			err = PTR_ERR(curr_zstrm);
+			break;
+		}
+
+		/* lz4_ctx is linked to stream in get_stream() */
+		list_add(&curr_zstrm->node, &inst->stream_head);
+		init_completion(&curr_zstrm->completion);
+		retain_and_null_ptr(curr_zstrm);
+	}
+
+	if (err) {
+		lz4_streams_destroy(inst);
+		return err;
+	}
+
+	return 0;
+}
+
+static int __lz4_compress(struct zcomp_params *params, struct lz4_ctx *zctx,
+			  struct zcomp_req *req);
+
+static void lz4_do_compression(struct lz4_global *inst)
+{
+	struct lz4_req req;
+	int idx;
+
+	scoped_guard(mutex, &inst->working_lock)
+	{
+		idx = inst->working_submit_idx;
+		inst->working_submit_idx = (idx + 1) % BACKEND_LZ4_QUEUE_NR;
+	}
+
+	while (kfifo_get(&inst->working_queue[idx], &req)) {
+		struct lz4_stream *lz4_strm = req.strm;
+
+		lz4_strm->result =
+			__lz4_compress(req.params, req.ctx, req.zreq);
+		complete(&lz4_strm->completion);
+	}
+}
+
+static int lz4_thread_worker(void *data)
+{
+	struct lz4_global *inst = data;
+
+	while (!kthread_should_stop()) {
+		int err;
+
+		err = wait_for_completion_interruptible(&inst->new_task_ready);
+		if (err)
+			continue;
+		lz4_do_compression(inst);
+	}
+	return 0;
+}
+
+static int lz4_global_init(struct zcomp_params *params)
+{
+	int err = 0;
+	struct lz4_global *newinst = NULL;
+
+	mutex_lock(&lz4_global_lock);
+
+	if (lz4_global_data) {
+		kref_get(&lz4_global_data->ref);
+		err = 0;
+		goto out_unlock;
+	}
+
+	newinst = kvzalloc_obj(*newinst);
+	if (!newinst) {
+		err = -ENOMEM;
+		goto out_unlock;
+	}
+
+	INIT_KFIFO(newinst->working_queue[0]);
+	INIT_KFIFO(newinst->working_queue[1]);
+	newinst->working_submit_idx = 0;
+
+	mutex_init(&newinst->stream_lock);
+	mutex_init(&newinst->working_lock);
+	kref_init(&newinst->ref);
+	err = lz4_streams_init(params, newinst);
+	if (err)
+		goto err_stream_init;
+	init_completion(&newinst->new_task_ready);
+	newinst->tsk = kthread_run(lz4_thread_worker, newinst, "zcomp_lz4");
+	if (IS_ERR(newinst->tsk)) {
+		err = PTR_ERR(newinst->tsk);
+		goto err_kthread_init;
+	}
+
+	lz4_global_data = newinst;
+	mutex_unlock(&lz4_global_lock);
+	return 0;
+
+err_kthread_init:
+	lz4_streams_destroy(newinst);
+err_stream_init:
+	mutex_destroy(&newinst->working_lock);
+	mutex_destroy(&newinst->stream_lock);
+	kvfree(newinst);
+out_unlock:
+	mutex_unlock(&lz4_global_lock);
+	return err;
+}
+
+static void lz4_global_destroy(struct kref *ref)
+{
+	struct lz4_global *inst;
+
+	lockdep_assert_held(&lz4_global_lock);
+
+	if (!lz4_global_data)
+		return;
+	inst = container_of(ref, struct lz4_global, ref);
+	WARN_ON(inst != lz4_global_data);
+
+	lz4_global_data = NULL;
+	kthread_stop(inst->tsk);
+	lz4_streams_destroy(inst);
+	mutex_destroy(&inst->stream_lock);
+	mutex_destroy(&inst->working_lock);
+	kvfree(inst);
+}
+
+struct lz4_drv {
+	struct mutex lock;
+	DECLARE_KFIFO(ctxqueue, struct lz4_ctx *, BACKEND_LZ4_STREAM_MAX);
+	struct lz4_ctx ctxs[BACKEND_LZ4_STREAM_MAX];
+};
+
+static int lz4_ctx_init(struct zcomp_params *params, struct lz4_ctx *zctx);
+static void lz4_ctx_destroy(struct lz4_ctx *zctx);
+
+static void lz4_drv_free(struct lz4_drv *drv_data)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(drv_data->ctxs); i++)
+		lz4_ctx_destroy(&drv_data->ctxs[i]);
+
+	mutex_destroy(&drv_data->lock);
+
+	kvfree(drv_data);
+}
+
+static int lz4_drv_alloc(struct zcomp_params *params)
+{
+	struct lz4_drv *drv_data = NULL;
+	int i, len;
+	int err = 0;
+
+	drv_data = kvzalloc_obj(*drv_data);
+	if (!drv_data)
+		return -ENOMEM;
+	mutex_init(&drv_data->lock);
+
+	INIT_KFIFO(drv_data->ctxqueue);
+
+	len = kfifo_size(&drv_data->ctxqueue);
+
+	for (i = 0; i < min(len, ARRAY_SIZE(drv_data->ctxs)); i++) {
+		struct lz4_ctx *ctx = &drv_data->ctxs[i];
+
+		err = lz4_ctx_init(params, ctx);
+		if (err)
+			break;
+
+		kfifo_put(&drv_data->ctxqueue, ctx);
+	}
+
+	if (err) {
+		lz4_drv_free(drv_data);
+		return err;
+	}
+
+	params->drv_data = drv_data;
+
+	return 0;
+}
+
 static void lz4_release_params(struct zcomp_params *params)
 {
+	struct lz4_drv *drv_data = params->drv_data;
+
+	if (!params->zstrm_mgmt)
+		return;
+
+	lz4_drv_free(drv_data);
+
+	kref_put_mutex(&lz4_global_data->ref, lz4_global_destroy,
+		       &lz4_global_lock);
 }
 
 static int lz4_setup_params(struct zcomp_params *params)
 {
+	int err = 0;
+
 	if (params->level == ZCOMP_PARAM_NOT_SET)
 		params->level = LZ4_ACCELERATION_DEFAULT;
 
+	params->zstrm_mgmt = false;
+	err = lz4_global_init(params);
+	if (err) {
+		pr_err("lz4 global init failed: %d, managed stream disabled",
+		       err);
+		return 0;
+	}
+
+	err = lz4_drv_alloc(params);
+
+	if (err) {
+		pr_err("lz4 drv init failed: %d, managed stream disabled", err);
+		kref_put_mutex(&lz4_global_data->ref, lz4_global_destroy,
+			       &lz4_global_lock);
+		return 0;
+	}
+
+	params->zstrm_mgmt = true;
 	return 0;
 }
 
+static void lz4_ctx_destroy(struct lz4_ctx *zctx)
+{
+	vfree(zctx->mem);
+	kfree(zctx->dstrm);
+	kfree(zctx->cstrm);
+}
+
 static void lz4_destroy(struct zcomp_ctx *ctx)
 {
 	struct lz4_ctx *zctx = ctx->context;
@@ -31,12 +343,36 @@ static void lz4_destroy(struct zcomp_ctx *ctx)
 	if (!zctx)
 		return;
 
-	vfree(zctx->mem);
-	kfree(zctx->dstrm);
-	kfree(zctx->cstrm);
+	lz4_ctx_destroy(zctx);
 	kfree(zctx);
 }
 
+static int lz4_ctx_init(struct zcomp_params *params, struct lz4_ctx *zctx)
+{
+	void *mem __free(kvfree) = NULL;
+	LZ4_streamDecode_t *dstrm __free(kfree) = NULL;
+	LZ4_stream_t *cstrm __free(kfree) = NULL;
+
+	if (params->dict_sz == 0) {
+		mem = vmalloc(LZ4_MEM_COMPRESS);
+		if (!mem)
+			return -ENOMEM;
+	} else {
+		dstrm = kzalloc_obj(*zctx->dstrm);
+		if (!dstrm)
+			return -ENOMEM;
+
+		cstrm = kzalloc_obj(*zctx->cstrm);
+		if (!cstrm)
+			return -ENOMEM;
+	}
+
+	zctx->mem = no_free_ptr(mem);
+	zctx->dstrm = no_free_ptr(dstrm);
+	zctx->cstrm = no_free_ptr(cstrm);
+	return 0;
+}
+
 static int lz4_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
 {
 	struct lz4_ctx *zctx;
@@ -46,31 +382,17 @@ static int lz4_create(struct zcomp_params *params, struct zcomp_ctx *ctx)
 		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;
+	if (lz4_ctx_init(params, zctx)) {
+		lz4_destroy(ctx);
+		return -ENOMEM;
 	}
 
 	return 0;
-
-error:
-	lz4_destroy(ctx);
-	return -ENOMEM;
 }
 
-static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
-			struct zcomp_req *req)
+static int __lz4_compress(struct zcomp_params *params, struct lz4_ctx *zctx,
+			  struct zcomp_req *req)
 {
-	struct lz4_ctx *zctx = ctx->context;
 	int ret;
 
 	if (!zctx->cstrm) {
@@ -92,6 +414,47 @@ static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
 	return 0;
 }
 
+static int lz4_compress_managed(struct zcomp_params *params,
+				struct lz4_ctx *zctx, struct zcomp_req *req,
+				struct zcomp_strm *zstrm)
+{
+	struct lz4_stream *mngt_strm =
+		container_of(zstrm_to_managed(zstrm), struct lz4_stream, zstrm);
+
+	scoped_guard(mutex, &lz4_global_data->working_lock)
+	{
+		int cnt;
+		int idx = lz4_global_data->working_submit_idx;
+		struct lz4_req lz4req = {
+			.strm = mngt_strm,
+			.params = params,
+			.ctx = zctx,
+			.zreq = req
+		};
+
+		/* ctx->src is mapped by kmap_local_map() */
+		BUILD_BUG_ON(IS_ENABLED(CONFIG_HIGHMEM));
+		cnt = kfifo_put(&lz4_global_data->working_queue[idx], lz4req);
+		if (cnt == 0)
+			return -EBUSY;
+	}
+	complete(&lz4_global_data->new_task_ready);
+	wait_for_completion(&mngt_strm->completion);
+	return mngt_strm->result;
+}
+
+static int lz4_compress(struct zcomp_params *params, struct zcomp_ctx *ctx,
+			struct zcomp_req *req)
+{
+	struct lz4_ctx *zctx = ctx->context;
+	struct zcomp_strm *zstrm = container_of(ctx, struct zcomp_strm, ctx);
+
+	if (!zstrm->zcomp_managed)
+		return __lz4_compress(params, zctx, req);
+
+	return lz4_compress_managed(params, zctx, req, zstrm);
+}
+
 static int lz4_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
 			  struct zcomp_req *req)
 {
@@ -116,6 +479,61 @@ static int lz4_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx,
 	return 0;
 }
 
+static struct managed_zstrm *lz4_get_stream(struct zcomp_params *params)
+{
+	struct lz4_stream *lz4_strm;
+	struct lz4_drv *drv_data = params->drv_data;
+	struct lz4_ctx *ctx;
+
+	if (!params->zstrm_mgmt)
+		return NULL;
+
+	scoped_guard(mutex, &lz4_global_data->stream_lock)
+	{
+		lz4_strm = list_first_entry_or_null(
+			&lz4_global_data->stream_head, struct lz4_stream, node);
+		if (!lz4_strm)
+			return NULL;
+
+		list_del_init(&lz4_strm->node);
+	}
+
+	scoped_guard(mutex, &drv_data->lock)
+		if (!kfifo_get(&drv_data->ctxqueue, &ctx))
+			ctx = NULL;
+	if (!ctx) {
+		guard(mutex)(&lz4_global_data->stream_lock);
+		list_add(&lz4_strm->node, &lz4_global_data->stream_head);
+		return NULL;
+	}
+	reinit_completion(&lz4_strm->completion);
+	lz4_strm->zstrm.strm.ctx.context = ctx;
+
+	return &lz4_strm->zstrm;
+}
+
+static void lz4_put_stream(struct zcomp_params *params,
+			   struct managed_zstrm *zstrm)
+{
+	struct lz4_stream *lz4_strm;
+	struct lz4_ctx *ctx;
+	struct lz4_drv *drv_data = params->drv_data;
+
+	if (!zstrm)
+		return;
+	if (WARN_ON(!params->zstrm_mgmt))
+		return;
+
+	lz4_strm = container_of(zstrm, struct lz4_stream, zstrm);
+	ctx = zstrm->strm.ctx.context;
+	lz4_strm->zstrm.strm.ctx.context = NULL;
+
+	scoped_guard(mutex, &lz4_global_data->stream_lock)
+		list_add(&lz4_strm->node, &lz4_global_data->stream_head);
+	scoped_guard(mutex, &drv_data->lock)
+		kfifo_put(&drv_data->ctxqueue, ctx);
+}
+
 const struct zcomp_ops backend_lz4 = {
 	.compress	= lz4_compress,
 	.decompress	= lz4_decompress,
@@ -123,5 +541,7 @@ const struct zcomp_ops backend_lz4 = {
 	.destroy_ctx	= lz4_destroy,
 	.setup_params	= lz4_setup_params,
 	.release_params	= lz4_release_params,
+	.get_stream	= lz4_get_stream,
+	.put_stream	= lz4_put_stream,
 	.name		= "lz4",
 };

-- 
2.51.0



  parent reply	other threads:[~2026-03-09 12:23 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-09 12:23 [PATCH RFC v2 0/5] zram: Allow zcomps to manage their own streams Jihan LIN via B4 Relay
2026-03-09 12:23 ` [PATCH RFC v2 1/5] zram: Rename zcomp_strm_{init, free}() Jihan LIN via B4 Relay
2026-03-09 12:23 ` [PATCH RFC v2 2/5] zram: Separate the lock from zcomp_strm Jihan LIN via B4 Relay
2026-03-09 12:23 ` [PATCH RFC v2 3/5] zram: Introduce zcomp-managed streams Jihan LIN via B4 Relay
2026-03-10  1:05   ` Sergey Senozhatsky
2026-03-10 13:31     ` Jihan LIN
2026-03-11  8:58       ` Sergey Senozhatsky
2026-03-09 12:23 ` [PATCH RFC v2 4/5] zram: Use zcomp-managed streams for async write requests Jihan LIN via B4 Relay
2026-03-09 12:23 ` Jihan LIN via B4 Relay [this message]
2026-03-11  8:51 ` [PATCH RFC v2 0/5] zram: Allow zcomps to manage their own streams Sergey Senozhatsky
2026-03-13 14:42   ` Jihan LIN

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260309-b4_zcomp_stream-v2-5-7148622326eb@gmail.com \
    --to=devnull+linjh22s.gmail.com@kernel.org \
    --cc=axboe@kernel.dk \
    --cc=linjh22s@gmail.com \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=minchan@kernel.org \
    --cc=senozhatsky@chromium.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox