* [PATCH RFC 0/2] RFC: erofs: memory-backed mount for non-page-aligned ranges
@ 2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
0 siblings, 0 replies; 8+ messages in thread
From: Alberto Ruiz @ 2026-06-18 16:13 UTC (permalink / raw)
To: Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu, Sandeep Dhavale, Hongbo Li,
Chunhai Guo
Cc: linux-kernel, linux-erofs, Javier Martinez Canillas, Brian Masney,
Eric Curtin, Alberto Ruiz
This series adds a memory-backed ("memback") mode to EROFS that
allows mounting an EROFS image directly from a kernel memory region
without going through the block layer.
I am sending this as an RFC to get EROFS maintainer feedback on
this approach before building the full initramfs series on top of
it. The memback mode may also be useful for other use cases beyond
initramfs where EROFS images need to be served directly from
memory, though it may need further hardening for more general use
cases.
Previous review feedback on the initramfs patches raised concerns
about the block layer dependency — this series addresses that by
providing a direct memory-backed path. It also adds support for
non-page-aligned memory ranges, which is the common case for
initramfs images packed at arbitrary cpio boundaries. Supporting
non-page-aligned ranges is essential for true zero-copy access —
requiring page alignment would force a copy into an aligned buffer
before the data can be used, defeating the purpose.
The full initramfs integration (init/ changes) that wires up the
EROFS initrd detection and layered mount is available for reference
here[0]. It implements support for interleaved cpio and EROFS
layers, it supports cpio CPU microcode update prefix, and uses
overlayfs+tmpfs for write support.
This series was developed with the assistance of Claude Opus 4.6.
All patches carry an Assisted-by tag. I am providing the kunit
testing and I have tested this change against the initramfs series
to boot an EROFS initramfs. I welcome feedback on the approach and
implementation.
Patch 1 adds the core memback implementation.
Patch 2 adds a KUnit test that mounts a compressed EROFS image at a
non-page-aligned offset and verifies decompressed file contents.
[0] https://github.com/aruiz/linux/tree/wip/erofs-initramfs-memback
Signed-off-by: Alberto Ruiz <aruiz@redhat.com>
---
Alberto Ruiz (2):
erofs: add memory-backed mode for non-page-aligned mount
erofs: add KUnit test for memory-backed compressed mount
fs/erofs/Kconfig | 11 ++++
fs/erofs/Makefile | 2 +
fs/erofs/data.c | 33 +++++++++-
fs/erofs/internal.h | 53 +++++++++++----
fs/erofs/memback.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++
fs/erofs/memback_test.c | 151 ++++++++++++++++++++++++++++++++++++++++++
fs/erofs/super.c | 38 +++++++++--
fs/erofs/zdata.c | 16 +++--
8 files changed, 447 insertions(+), 26 deletions(-)
---
base-commit: 6b5a2b7d9bc156e505f09e698d85d6a1547c1206
change-id: 20260617-erofs-memback-0ae2448ba2cc
Best regards,
--
Alberto Ruiz <aruiz@redhat.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH RFC 0/2] RFC: erofs: memory-backed mount for non-page-aligned ranges
@ 2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
0 siblings, 0 replies; 8+ messages in thread
From: Alberto Ruiz via B4 Relay @ 2026-06-18 16:13 UTC (permalink / raw)
To: Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu, Sandeep Dhavale, Hongbo Li,
Chunhai Guo
Cc: linux-kernel, linux-erofs, Javier Martinez Canillas, Brian Masney,
Eric Curtin, Alberto Ruiz
This series adds a memory-backed ("memback") mode to EROFS that
allows mounting an EROFS image directly from a kernel memory region
without going through the block layer.
I am sending this as an RFC to get EROFS maintainer feedback on
this approach before building the full initramfs series on top of
it. The memback mode may also be useful for other use cases beyond
initramfs where EROFS images need to be served directly from
memory, though it may need further hardening for more general use
cases.
Previous review feedback on the initramfs patches raised concerns
about the block layer dependency — this series addresses that by
providing a direct memory-backed path. It also adds support for
non-page-aligned memory ranges, which is the common case for
initramfs images packed at arbitrary cpio boundaries. Supporting
non-page-aligned ranges is essential for true zero-copy access —
requiring page alignment would force a copy into an aligned buffer
before the data can be used, defeating the purpose.
The full initramfs integration (init/ changes) that wires up the
EROFS initrd detection and layered mount is available for reference
here[0]. It implements support for interleaved cpio and EROFS
layers, it supports cpio CPU microcode update prefix, and uses
overlayfs+tmpfs for write support.
This series was developed with the assistance of Claude Opus 4.6.
All patches carry an Assisted-by tag. I am providing the kunit
testing and I have tested this change against the initramfs series
to boot an EROFS initramfs. I welcome feedback on the approach and
implementation.
Patch 1 adds the core memback implementation.
Patch 2 adds a KUnit test that mounts a compressed EROFS image at a
non-page-aligned offset and verifies decompressed file contents.
[0] https://github.com/aruiz/linux/tree/wip/erofs-initramfs-memback
Signed-off-by: Alberto Ruiz <aruiz@redhat.com>
---
Alberto Ruiz (2):
erofs: add memory-backed mode for non-page-aligned mount
erofs: add KUnit test for memory-backed compressed mount
fs/erofs/Kconfig | 11 ++++
fs/erofs/Makefile | 2 +
fs/erofs/data.c | 33 +++++++++-
fs/erofs/internal.h | 53 +++++++++++----
fs/erofs/memback.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++
fs/erofs/memback_test.c | 151 ++++++++++++++++++++++++++++++++++++++++++
fs/erofs/super.c | 38 +++++++++--
fs/erofs/zdata.c | 16 +++--
8 files changed, 447 insertions(+), 26 deletions(-)
---
base-commit: 6b5a2b7d9bc156e505f09e698d85d6a1547c1206
change-id: 20260617-erofs-memback-0ae2448ba2cc
Best regards,
--
Alberto Ruiz <aruiz@redhat.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH RFC 1/2] erofs: add memory-backed mode for non-page-aligned mount
2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
@ 2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
-1 siblings, 0 replies; 8+ messages in thread
From: Alberto Ruiz @ 2026-06-18 16:13 UTC (permalink / raw)
To: Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu, Sandeep Dhavale, Hongbo Li,
Chunhai Guo
Cc: linux-kernel, linux-erofs, Javier Martinez Canillas, Brian Masney,
Eric Curtin, Alberto Ruiz
Add a new "memback" backing mode to EROFS that reads directly from a
contiguous kernel memory region without going through the block layer.
The metadata path (erofs_bread) achieves true zero-copy by using
virt_to_page() + kmap_local_page() to access pages directly,
bypassing the page cache entirely.
The file data path uses new address_space_operations
(erofs_memback_aops) that resolve logical-to-physical offsets via
erofs_map_blocks() and memcpy from the backing memory into file page
cache folios -- one copy, same as the current block device approach
but without any block layer involvement.
For compressed data, erofs_memback_submit_bio() walks bio segments and
copies compressed data from memory, following the same dispatch pattern
used by the existing fileio and fscache modes.
Non-page-aligned memory regions are handled naturally via
offset_in_page() for metadata and byte-granular memcpy for data,
allowing EROFS images at arbitrary offsets regardless of page size.
A kernel-internal API erofs_memback_set_pending(data, size) stores the
memory region parameters in static globals which erofs_fc_get_tree()
consumes on the next mount via get_tree_nodev(), allowing early boot
code to trigger a standard EROFS mount without exposing kernel pointers
as mount option strings.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Alberto Ruiz <aruiz@redhat.com>
---
fs/erofs/Makefile | 1 +
fs/erofs/data.c | 33 +++++++++-
fs/erofs/internal.h | 53 +++++++++++-----
fs/erofs/memback.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++
fs/erofs/super.c | 38 ++++++++++--
fs/erofs/zdata.c | 16 +++--
6 files changed, 284 insertions(+), 26 deletions(-)
diff --git a/fs/erofs/Makefile b/fs/erofs/Makefile
index a80e1762b607..8f3b73835328 100644
--- a/fs/erofs/Makefile
+++ b/fs/erofs/Makefile
@@ -10,4 +10,5 @@ erofs-$(CONFIG_EROFS_FS_ZIP_ZSTD) += decompressor_zstd.o
erofs-$(CONFIG_EROFS_FS_ZIP_ACCEL) += decompressor_crypto.o
erofs-$(CONFIG_EROFS_FS_BACKED_BY_FILE) += fileio.o
erofs-$(CONFIG_EROFS_FS_ONDEMAND) += fscache.o
+erofs-y += memback.o
erofs-$(CONFIG_EROFS_FS_PAGE_CACHE_SHARE) += ishare.o
diff --git a/fs/erofs/data.c b/fs/erofs/data.c
index 44da21c9d777..26fff07df5bd 100644
--- a/fs/erofs/data.c
+++ b/fs/erofs/data.c
@@ -22,7 +22,8 @@ void erofs_put_metabuf(struct erofs_buf *buf)
if (!buf->page)
return;
erofs_unmap_metabuf(buf);
- folio_put(page_folio(buf->page));
+ if (!buf->memback)
+ folio_put(page_folio(buf->page));
buf->page = NULL;
}
@@ -45,6 +46,25 @@ void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap)
return ERR_PTR(err);
}
+ if (buf->memback) {
+ void *addr = (char *)buf->mapping + offset;
+ struct page *page;
+
+ if (offset >= buf->memback_size)
+ return ERR_PTR(-EFSCORRUPTED);
+
+ page = virt_to_page(addr);
+ if (buf->page != page) {
+ erofs_unmap_metabuf(buf);
+ buf->page = page;
+ }
+ if (!need_kmap)
+ return NULL;
+ if (!buf->base)
+ buf->base = kmap_local_page(buf->page);
+ return buf->base + offset_in_page(addr);
+ }
+
if (buf->page) {
folio = page_folio(buf->page);
if (folio_file_page(folio, index) != buf->page)
@@ -70,15 +90,24 @@ int erofs_init_metabuf(struct erofs_buf *buf, struct super_block *sb,
struct erofs_sb_info *sbi = EROFS_SB(sb);
buf->file = NULL;
+ buf->memback = false;
if (in_metabox) {
if (unlikely(!sbi->metabox_inode))
return -EFSCORRUPTED;
buf->mapping = sbi->metabox_inode->i_mapping;
return 0;
}
+ if (erofs_is_memback_mode(sbi)) {
+ buf->memback = true;
+ buf->memback_size = sbi->memback_size;
+ buf->off = 0;
+ /* Reuse the mapping pointer to carry the base address */
+ buf->mapping = (struct address_space *)sbi->memback_data;
+ return 0;
+ }
buf->off = sbi->dif0.fsoff;
if (erofs_is_fileio_mode(sbi)) {
- buf->file = sbi->dif0.file; /* some fs like FUSE needs it */
+ buf->file = sbi->dif0.file; /* some fs like FUSE needs it */
buf->mapping = buf->file->f_mapping;
} else if (erofs_is_fscache_mode(sb))
buf->mapping = sbi->dif0.fscache->inode->i_mapping;
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
index 4792490161ec..75014f8b1596 100644
--- a/fs/erofs/internal.h
+++ b/fs/erofs/internal.h
@@ -103,7 +103,10 @@ struct erofs_xattr_prefix_item {
struct erofs_sb_info {
struct erofs_device_info dif0;
- struct erofs_mount_opts opt; /* options */
+ struct erofs_mount_opts opt; /* options */
+
+ void *memback_data;
+ unsigned long memback_size;
#ifdef CONFIG_EROFS_FS_ZIP
/* list for all registered superblocks, mainly for shrinker */
struct list_head list;
@@ -176,11 +179,16 @@ struct erofs_sb_info {
#define EROFS_MOUNT_DAX_ALWAYS 0x00000040
#define EROFS_MOUNT_DAX_NEVER 0x00000080
#define EROFS_MOUNT_DIRECT_IO 0x00000100
-#define EROFS_MOUNT_INODE_SHARE 0x00000200
+#define EROFS_MOUNT_INODE_SHARE 0x00000200
+
+#define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option)
+#define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option)
+#define test_opt(opt, option) ((opt)->mount_opt & EROFS_MOUNT_##option)
-#define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option)
-#define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option)
-#define test_opt(opt, option) ((opt)->mount_opt & EROFS_MOUNT_##option)
+static inline bool erofs_is_memback_mode(struct erofs_sb_info *sbi)
+{
+ return sbi->memback_data != NULL;
+}
static inline bool erofs_is_fileio_mode(struct erofs_sb_info *sbi)
{
@@ -192,7 +200,8 @@ extern struct file_system_type erofs_anon_fs_type;
static inline bool erofs_is_fscache_mode(struct super_block *sb)
{
return IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) &&
- !erofs_is_fileio_mode(EROFS_SB(sb)) && !sb->s_bdev;
+ !erofs_is_memback_mode(EROFS_SB(sb)) &&
+ !erofs_is_fileio_mode(EROFS_SB(sb)) && !sb->s_bdev;
}
enum {
@@ -205,13 +214,15 @@ struct erofs_buf {
struct address_space *mapping;
struct file *file;
u64 off;
+ unsigned long memback_size;
struct page *page;
void *base;
+ bool memback;
};
-#define __EROFS_BUF_INITIALIZER ((struct erofs_buf){ .page = NULL })
+#define __EROFS_BUF_INITIALIZER ((struct erofs_buf){ .page = NULL })
-#define erofs_blknr(sb, pos) ((erofs_blk_t)((pos) >> (sb)->s_blocksize_bits))
-#define erofs_blkoff(sb, pos) ((pos) & ((sb)->s_blocksize - 1))
+#define erofs_blknr(sb, pos) ((erofs_blk_t)((pos) >> (sb)->s_blocksize_bits))
+#define erofs_blkoff(sb, pos) ((pos) & ((sb)->s_blocksize - 1))
#define erofs_pos(sb, blk) ((erofs_off_t)(blk) << (sb)->s_blocksize_bits)
#define erofs_iblks(i) (round_up((i)->i_size, i_blocksize(i)) >> (i)->i_blkbits)
@@ -414,6 +425,7 @@ extern const struct super_operations erofs_sops;
extern const struct address_space_operations erofs_aops;
extern const struct address_space_operations erofs_fileio_aops;
+extern const struct address_space_operations erofs_memback_aops;
extern const struct address_space_operations z_erofs_aops;
extern const struct address_space_operations erofs_fscache_access_aops;
@@ -476,11 +488,14 @@ erofs_get_aops(struct inode *realinode, bool no_fscache)
if (erofs_inode_is_data_compressed(EROFS_I(realinode)->datalayout)) {
if (!IS_ENABLED(CONFIG_EROFS_FS_ZIP))
return ERR_PTR(-EOPNOTSUPP);
- DO_ONCE_LITE_IF(realinode->i_blkbits != PAGE_SHIFT,
- erofs_info, realinode->i_sb,
- "EXPERIMENTAL EROFS subpage compressed block support in use. Use at your own risk!");
+ DO_ONCE_LITE_IF(
+ realinode->i_blkbits != PAGE_SHIFT, erofs_info,
+ realinode->i_sb,
+ "EXPERIMENTAL EROFS subpage compressed block support in use. Use at your own risk!");
return &z_erofs_aops;
}
+ if (erofs_is_memback_mode(EROFS_SB(realinode->i_sb)))
+ return &erofs_memback_aops;
if (IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && !no_fscache &&
erofs_is_fscache_mode(realinode->i_sb))
return &erofs_fscache_access_aops;
@@ -542,10 +557,20 @@ int z_erofs_parse_cfgs(struct super_block *sb, struct erofs_super_block *dsb);
struct bio *erofs_fileio_bio_alloc(struct erofs_map_dev *mdev);
void erofs_fileio_submit_bio(struct bio *bio);
#else
-static inline struct bio *erofs_fileio_bio_alloc(struct erofs_map_dev *mdev) { return NULL; }
-static inline void erofs_fileio_submit_bio(struct bio *bio) {}
+static inline struct bio *erofs_fileio_bio_alloc(struct erofs_map_dev *mdev)
+{
+ return NULL;
+}
+static inline void erofs_fileio_submit_bio(struct bio *bio)
+{
+}
#endif
+struct bio *erofs_memback_bio_alloc(struct erofs_map_dev *mdev);
+void erofs_memback_submit_bio(struct bio *bio);
+
+void __init erofs_memback_set_pending(void *data, unsigned long size);
+
#ifdef CONFIG_EROFS_FS_ONDEMAND
int erofs_fscache_register_fs(struct super_block *sb);
void erofs_fscache_unregister_fs(struct super_block *sb);
diff --git a/fs/erofs/memback.c b/fs/erofs/memback.c
new file mode 100644
index 000000000000..3e965f35eabe
--- /dev/null
+++ b/fs/erofs/memback.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Memory-backed EROFS support.
+ *
+ * Serves EROFS data directly from a contiguous kernel memory region
+ * (e.g. an initrd) without going through the block layer.
+ */
+#include "internal.h"
+#include <trace/events/erofs.h>
+
+struct erofs_memback_rq {
+ struct bio_vec bvecs[16];
+ struct bio bio;
+ struct erofs_sb_info *sbi;
+};
+
+static void erofs_memback_rq_submit(struct erofs_memback_rq *rq)
+{
+ struct erofs_sb_info *sbi;
+ struct bio_vec bv;
+ struct bvec_iter iter;
+ loff_t pos;
+
+ if (!rq)
+ return;
+
+ sbi = rq->sbi;
+ pos = rq->bio.bi_iter.bi_sector << SECTOR_SHIFT;
+
+ bio_for_each_segment(bv, &rq->bio, iter) {
+ void *dst = bvec_kmap_local(&bv);
+ unsigned long avail = sbi->memback_size - pos;
+ unsigned long len = min_t(unsigned long, bv.bv_len, avail);
+
+ if (pos < sbi->memback_size && len)
+ memcpy(dst, (char *)sbi->memback_data + pos, len);
+ if (len < bv.bv_len)
+ memset(dst + len, 0, bv.bv_len - len);
+ kunmap_local(dst);
+ pos += bv.bv_len;
+ }
+ bio_endio(&rq->bio);
+ bio_uninit(&rq->bio);
+ kfree(rq);
+}
+
+static struct erofs_memback_rq *
+erofs_memback_rq_alloc(struct erofs_map_dev *mdev)
+{
+ struct erofs_memback_rq *rq =
+ kzalloc(sizeof(*rq), GFP_KERNEL | __GFP_NOFAIL);
+
+ bio_init(&rq->bio, NULL, rq->bvecs, ARRAY_SIZE(rq->bvecs), REQ_OP_READ);
+ rq->sbi = EROFS_SB(mdev->m_sb);
+ return rq;
+}
+
+struct bio *erofs_memback_bio_alloc(struct erofs_map_dev *mdev)
+{
+ return &erofs_memback_rq_alloc(mdev)->bio;
+}
+
+void erofs_memback_submit_bio(struct bio *bio)
+{
+ erofs_memback_rq_submit(
+ container_of(bio, struct erofs_memback_rq, bio));
+}
+
+struct erofs_memback_io {
+ struct erofs_map_blocks map;
+ struct erofs_map_dev dev;
+ struct erofs_memback_rq *rq;
+};
+
+static int erofs_memback_scan_folio(struct erofs_memback_io *io,
+ struct inode *inode, struct folio *folio)
+{
+ struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb);
+ struct erofs_map_blocks *map = &io->map;
+ unsigned int cur = 0, end = folio_size(folio), len, attached = 0;
+ loff_t pos = folio_pos(folio), ofs;
+ int err = 0;
+
+ erofs_onlinefolio_init(folio);
+ while (cur < end) {
+ if (!in_range(pos + cur, map->m_la, map->m_llen)) {
+ map->m_la = pos + cur;
+ map->m_llen = end - cur;
+ err = erofs_map_blocks(inode, map);
+ if (err)
+ break;
+ }
+
+ ofs = pos + cur - map->m_la;
+ len = min_t(loff_t, map->m_llen - ofs, end - cur);
+ if (map->m_flags & EROFS_MAP_META) {
+ struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
+ void *src;
+
+ src = erofs_read_metabuf(&buf, inode->i_sb,
+ map->m_pa + ofs,
+ erofs_inode_in_metabox(inode));
+ if (IS_ERR(src)) {
+ err = PTR_ERR(src);
+ break;
+ }
+ memcpy_to_folio(folio, cur, src, len);
+ erofs_put_metabuf(&buf);
+ } else if (!(map->m_flags & EROFS_MAP_MAPPED)) {
+ folio_zero_segment(folio, cur, cur + len);
+ attached = 0;
+ } else {
+ loff_t pa = map->m_pa + ofs;
+
+ if (pa + len > sbi->memback_size) {
+ err = -EFSCORRUPTED;
+ break;
+ }
+ memcpy_to_folio(folio, cur,
+ (char *)sbi->memback_data + pa, len);
+ attached = 1;
+ }
+ cur += len;
+ }
+ erofs_onlinefolio_end(folio, err, false);
+ return err;
+}
+
+static int erofs_memback_read_folio(struct file *file, struct folio *folio)
+{
+ bool need_iput;
+ struct inode *realinode =
+ erofs_real_inode(folio_inode(folio), &need_iput);
+ struct erofs_memback_io io = {};
+ int err;
+
+ trace_erofs_read_folio(realinode, folio, true);
+ err = erofs_memback_scan_folio(&io, realinode, folio);
+ if (need_iput)
+ iput(realinode);
+ return err;
+}
+
+static void erofs_memback_readahead(struct readahead_control *rac)
+{
+ bool need_iput;
+ struct inode *realinode =
+ erofs_real_inode(rac->mapping->host, &need_iput);
+ struct erofs_memback_io io = {};
+ struct folio *folio;
+ int err;
+
+ trace_erofs_readahead(realinode, readahead_index(rac),
+ readahead_count(rac), true);
+ while ((folio = readahead_folio(rac))) {
+ err = erofs_memback_scan_folio(&io, realinode, folio);
+ if (err && err != -EINTR)
+ erofs_err(realinode->i_sb,
+ "readahead error at folio %lu @ nid %llu",
+ folio->index, EROFS_I(realinode)->nid);
+ }
+ if (need_iput)
+ iput(realinode);
+}
+
+const struct address_space_operations erofs_memback_aops = {
+ .read_folio = erofs_memback_read_folio,
+ .readahead = erofs_memback_readahead,
+};
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index 802add6652fd..724389d79aac 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -625,9 +625,8 @@ static void erofs_set_sysfs_name(struct super_block *sb)
sbi->fsid);
else if (sbi->fsid)
super_set_sysfs_name_generic(sb, "%s", sbi->fsid);
- else if (erofs_is_fileio_mode(sbi))
- super_set_sysfs_name_generic(sb, "%s",
- bdi_dev_name(sb->s_bdi));
+ else if (erofs_is_memback_mode(sbi) || erofs_is_fileio_mode(sbi))
+ super_set_sysfs_name_generic(sb, "%s", bdi_dev_name(sb->s_bdi));
else
super_set_sysfs_name_id(sb);
}
@@ -708,7 +707,7 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
return -EINVAL;
}
- if (erofs_is_fileio_mode(sbi)) {
+ if (erofs_is_memback_mode(sbi) || erofs_is_fileio_mode(sbi)) {
sb->s_blocksize = 1 << sbi->blkszbits;
sb->s_blocksize_bits = sbi->blkszbits;
} else if (!sb_set_blocksize(sb, 1 << sbi->blkszbits)) {
@@ -791,11 +790,39 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
return 0;
}
+/*
+ * Pending memback parameters, consumed during erofs_fc_get_tree().
+ * Only used by single-threaded __init code, no synchronization needed.
+ */
+static void *erofs_memback_pending_data;
+static unsigned long erofs_memback_pending_size;
+
+void __init erofs_memback_set_pending(void *data, unsigned long size)
+{
+ erofs_memback_pending_data = data;
+ erofs_memback_pending_size = size;
+}
+
+static void erofs_memback_consume_pending(struct erofs_sb_info *sbi)
+{
+ sbi->memback_data = erofs_memback_pending_data;
+ sbi->memback_size = erofs_memback_pending_size;
+ erofs_memback_pending_data = NULL;
+ erofs_memback_pending_size = 0;
+}
+
static int erofs_fc_get_tree(struct fs_context *fc)
{
struct erofs_sb_info *sbi = fc->s_fs_info;
int ret;
+ if (erofs_memback_pending_data) {
+ erofs_memback_consume_pending(sbi);
+ return get_tree_nodev(fc, erofs_fc_fill_super);
+ }
+ if (erofs_is_memback_mode(sbi))
+ return get_tree_nodev(fc, erofs_fc_fill_super);
+
if (IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && sbi->fsid)
return get_tree_nodev(fc, erofs_fc_fill_super);
@@ -928,7 +955,8 @@ static void erofs_kill_sb(struct super_block *sb)
{
struct erofs_sb_info *sbi = EROFS_SB(sb);
- if ((IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && sbi->fsid) ||
+ if (erofs_is_memback_mode(sbi) ||
+ (IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && sbi->fsid) ||
sbi->dif0.file)
kill_anon_super(sb);
else
diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
index c6240dccbb0f..af4ddb492660 100644
--- a/fs/erofs/zdata.c
+++ b/fs/erofs/zdata.c
@@ -1709,10 +1709,12 @@ static void z_erofs_submit_queue(struct z_erofs_frontend *f,
sb->s_blocksize);
do {
bvec.bv_page = NULL;
- if (bio && (cur != last_pa ||
- bio->bi_bdev != mdev.m_bdev)) {
+ if (bio &&
+ (cur != last_pa || bio->bi_bdev != mdev.m_bdev)) {
drain_io:
- if (erofs_is_fileio_mode(EROFS_SB(sb)))
+ if (erofs_is_memback_mode(EROFS_SB(sb)))
+ erofs_memback_submit_bio(bio);
+ else if (erofs_is_fileio_mode(EROFS_SB(sb)))
erofs_fileio_submit_bio(bio);
else if (erofs_is_fscache_mode(sb))
erofs_fscache_submit_bio(bio);
@@ -1742,7 +1744,9 @@ static void z_erofs_submit_queue(struct z_erofs_frontend *f,
}
if (!bio) {
- if (erofs_is_fileio_mode(EROFS_SB(sb)))
+ if (erofs_is_memback_mode(EROFS_SB(sb)))
+ bio = erofs_memback_bio_alloc(&mdev);
+ else if (erofs_is_fileio_mode(EROFS_SB(sb)))
bio = erofs_fileio_bio_alloc(&mdev);
else if (erofs_is_fscache_mode(sb))
bio = erofs_fscache_bio_alloc(&mdev);
@@ -1772,7 +1776,9 @@ static void z_erofs_submit_queue(struct z_erofs_frontend *f,
} while (next != Z_EROFS_PCLUSTER_TAIL);
if (bio) {
- if (erofs_is_fileio_mode(EROFS_SB(sb)))
+ if (erofs_is_memback_mode(EROFS_SB(sb)))
+ erofs_memback_submit_bio(bio);
+ else if (erofs_is_fileio_mode(EROFS_SB(sb)))
erofs_fileio_submit_bio(bio);
else if (erofs_is_fscache_mode(sb))
erofs_fscache_submit_bio(bio);
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH RFC 1/2] erofs: add memory-backed mode for non-page-aligned mount
@ 2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
0 siblings, 0 replies; 8+ messages in thread
From: Alberto Ruiz via B4 Relay @ 2026-06-18 16:13 UTC (permalink / raw)
To: Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu, Sandeep Dhavale, Hongbo Li,
Chunhai Guo
Cc: linux-kernel, linux-erofs, Javier Martinez Canillas, Brian Masney,
Eric Curtin, Alberto Ruiz
From: Alberto Ruiz <aruiz@redhat.com>
Add a new "memback" backing mode to EROFS that reads directly from a
contiguous kernel memory region without going through the block layer.
The metadata path (erofs_bread) achieves true zero-copy by using
virt_to_page() + kmap_local_page() to access pages directly,
bypassing the page cache entirely.
The file data path uses new address_space_operations
(erofs_memback_aops) that resolve logical-to-physical offsets via
erofs_map_blocks() and memcpy from the backing memory into file page
cache folios -- one copy, same as the current block device approach
but without any block layer involvement.
For compressed data, erofs_memback_submit_bio() walks bio segments and
copies compressed data from memory, following the same dispatch pattern
used by the existing fileio and fscache modes.
Non-page-aligned memory regions are handled naturally via
offset_in_page() for metadata and byte-granular memcpy for data,
allowing EROFS images at arbitrary offsets regardless of page size.
A kernel-internal API erofs_memback_set_pending(data, size) stores the
memory region parameters in static globals which erofs_fc_get_tree()
consumes on the next mount via get_tree_nodev(), allowing early boot
code to trigger a standard EROFS mount without exposing kernel pointers
as mount option strings.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Alberto Ruiz <aruiz@redhat.com>
---
fs/erofs/Makefile | 1 +
fs/erofs/data.c | 33 +++++++++-
fs/erofs/internal.h | 53 +++++++++++-----
fs/erofs/memback.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++
fs/erofs/super.c | 38 ++++++++++--
fs/erofs/zdata.c | 16 +++--
6 files changed, 284 insertions(+), 26 deletions(-)
diff --git a/fs/erofs/Makefile b/fs/erofs/Makefile
index a80e1762b607..8f3b73835328 100644
--- a/fs/erofs/Makefile
+++ b/fs/erofs/Makefile
@@ -10,4 +10,5 @@ erofs-$(CONFIG_EROFS_FS_ZIP_ZSTD) += decompressor_zstd.o
erofs-$(CONFIG_EROFS_FS_ZIP_ACCEL) += decompressor_crypto.o
erofs-$(CONFIG_EROFS_FS_BACKED_BY_FILE) += fileio.o
erofs-$(CONFIG_EROFS_FS_ONDEMAND) += fscache.o
+erofs-y += memback.o
erofs-$(CONFIG_EROFS_FS_PAGE_CACHE_SHARE) += ishare.o
diff --git a/fs/erofs/data.c b/fs/erofs/data.c
index 44da21c9d777..26fff07df5bd 100644
--- a/fs/erofs/data.c
+++ b/fs/erofs/data.c
@@ -22,7 +22,8 @@ void erofs_put_metabuf(struct erofs_buf *buf)
if (!buf->page)
return;
erofs_unmap_metabuf(buf);
- folio_put(page_folio(buf->page));
+ if (!buf->memback)
+ folio_put(page_folio(buf->page));
buf->page = NULL;
}
@@ -45,6 +46,25 @@ void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap)
return ERR_PTR(err);
}
+ if (buf->memback) {
+ void *addr = (char *)buf->mapping + offset;
+ struct page *page;
+
+ if (offset >= buf->memback_size)
+ return ERR_PTR(-EFSCORRUPTED);
+
+ page = virt_to_page(addr);
+ if (buf->page != page) {
+ erofs_unmap_metabuf(buf);
+ buf->page = page;
+ }
+ if (!need_kmap)
+ return NULL;
+ if (!buf->base)
+ buf->base = kmap_local_page(buf->page);
+ return buf->base + offset_in_page(addr);
+ }
+
if (buf->page) {
folio = page_folio(buf->page);
if (folio_file_page(folio, index) != buf->page)
@@ -70,15 +90,24 @@ int erofs_init_metabuf(struct erofs_buf *buf, struct super_block *sb,
struct erofs_sb_info *sbi = EROFS_SB(sb);
buf->file = NULL;
+ buf->memback = false;
if (in_metabox) {
if (unlikely(!sbi->metabox_inode))
return -EFSCORRUPTED;
buf->mapping = sbi->metabox_inode->i_mapping;
return 0;
}
+ if (erofs_is_memback_mode(sbi)) {
+ buf->memback = true;
+ buf->memback_size = sbi->memback_size;
+ buf->off = 0;
+ /* Reuse the mapping pointer to carry the base address */
+ buf->mapping = (struct address_space *)sbi->memback_data;
+ return 0;
+ }
buf->off = sbi->dif0.fsoff;
if (erofs_is_fileio_mode(sbi)) {
- buf->file = sbi->dif0.file; /* some fs like FUSE needs it */
+ buf->file = sbi->dif0.file; /* some fs like FUSE needs it */
buf->mapping = buf->file->f_mapping;
} else if (erofs_is_fscache_mode(sb))
buf->mapping = sbi->dif0.fscache->inode->i_mapping;
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
index 4792490161ec..75014f8b1596 100644
--- a/fs/erofs/internal.h
+++ b/fs/erofs/internal.h
@@ -103,7 +103,10 @@ struct erofs_xattr_prefix_item {
struct erofs_sb_info {
struct erofs_device_info dif0;
- struct erofs_mount_opts opt; /* options */
+ struct erofs_mount_opts opt; /* options */
+
+ void *memback_data;
+ unsigned long memback_size;
#ifdef CONFIG_EROFS_FS_ZIP
/* list for all registered superblocks, mainly for shrinker */
struct list_head list;
@@ -176,11 +179,16 @@ struct erofs_sb_info {
#define EROFS_MOUNT_DAX_ALWAYS 0x00000040
#define EROFS_MOUNT_DAX_NEVER 0x00000080
#define EROFS_MOUNT_DIRECT_IO 0x00000100
-#define EROFS_MOUNT_INODE_SHARE 0x00000200
+#define EROFS_MOUNT_INODE_SHARE 0x00000200
+
+#define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option)
+#define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option)
+#define test_opt(opt, option) ((opt)->mount_opt & EROFS_MOUNT_##option)
-#define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option)
-#define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option)
-#define test_opt(opt, option) ((opt)->mount_opt & EROFS_MOUNT_##option)
+static inline bool erofs_is_memback_mode(struct erofs_sb_info *sbi)
+{
+ return sbi->memback_data != NULL;
+}
static inline bool erofs_is_fileio_mode(struct erofs_sb_info *sbi)
{
@@ -192,7 +200,8 @@ extern struct file_system_type erofs_anon_fs_type;
static inline bool erofs_is_fscache_mode(struct super_block *sb)
{
return IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) &&
- !erofs_is_fileio_mode(EROFS_SB(sb)) && !sb->s_bdev;
+ !erofs_is_memback_mode(EROFS_SB(sb)) &&
+ !erofs_is_fileio_mode(EROFS_SB(sb)) && !sb->s_bdev;
}
enum {
@@ -205,13 +214,15 @@ struct erofs_buf {
struct address_space *mapping;
struct file *file;
u64 off;
+ unsigned long memback_size;
struct page *page;
void *base;
+ bool memback;
};
-#define __EROFS_BUF_INITIALIZER ((struct erofs_buf){ .page = NULL })
+#define __EROFS_BUF_INITIALIZER ((struct erofs_buf){ .page = NULL })
-#define erofs_blknr(sb, pos) ((erofs_blk_t)((pos) >> (sb)->s_blocksize_bits))
-#define erofs_blkoff(sb, pos) ((pos) & ((sb)->s_blocksize - 1))
+#define erofs_blknr(sb, pos) ((erofs_blk_t)((pos) >> (sb)->s_blocksize_bits))
+#define erofs_blkoff(sb, pos) ((pos) & ((sb)->s_blocksize - 1))
#define erofs_pos(sb, blk) ((erofs_off_t)(blk) << (sb)->s_blocksize_bits)
#define erofs_iblks(i) (round_up((i)->i_size, i_blocksize(i)) >> (i)->i_blkbits)
@@ -414,6 +425,7 @@ extern const struct super_operations erofs_sops;
extern const struct address_space_operations erofs_aops;
extern const struct address_space_operations erofs_fileio_aops;
+extern const struct address_space_operations erofs_memback_aops;
extern const struct address_space_operations z_erofs_aops;
extern const struct address_space_operations erofs_fscache_access_aops;
@@ -476,11 +488,14 @@ erofs_get_aops(struct inode *realinode, bool no_fscache)
if (erofs_inode_is_data_compressed(EROFS_I(realinode)->datalayout)) {
if (!IS_ENABLED(CONFIG_EROFS_FS_ZIP))
return ERR_PTR(-EOPNOTSUPP);
- DO_ONCE_LITE_IF(realinode->i_blkbits != PAGE_SHIFT,
- erofs_info, realinode->i_sb,
- "EXPERIMENTAL EROFS subpage compressed block support in use. Use at your own risk!");
+ DO_ONCE_LITE_IF(
+ realinode->i_blkbits != PAGE_SHIFT, erofs_info,
+ realinode->i_sb,
+ "EXPERIMENTAL EROFS subpage compressed block support in use. Use at your own risk!");
return &z_erofs_aops;
}
+ if (erofs_is_memback_mode(EROFS_SB(realinode->i_sb)))
+ return &erofs_memback_aops;
if (IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && !no_fscache &&
erofs_is_fscache_mode(realinode->i_sb))
return &erofs_fscache_access_aops;
@@ -542,10 +557,20 @@ int z_erofs_parse_cfgs(struct super_block *sb, struct erofs_super_block *dsb);
struct bio *erofs_fileio_bio_alloc(struct erofs_map_dev *mdev);
void erofs_fileio_submit_bio(struct bio *bio);
#else
-static inline struct bio *erofs_fileio_bio_alloc(struct erofs_map_dev *mdev) { return NULL; }
-static inline void erofs_fileio_submit_bio(struct bio *bio) {}
+static inline struct bio *erofs_fileio_bio_alloc(struct erofs_map_dev *mdev)
+{
+ return NULL;
+}
+static inline void erofs_fileio_submit_bio(struct bio *bio)
+{
+}
#endif
+struct bio *erofs_memback_bio_alloc(struct erofs_map_dev *mdev);
+void erofs_memback_submit_bio(struct bio *bio);
+
+void __init erofs_memback_set_pending(void *data, unsigned long size);
+
#ifdef CONFIG_EROFS_FS_ONDEMAND
int erofs_fscache_register_fs(struct super_block *sb);
void erofs_fscache_unregister_fs(struct super_block *sb);
diff --git a/fs/erofs/memback.c b/fs/erofs/memback.c
new file mode 100644
index 000000000000..3e965f35eabe
--- /dev/null
+++ b/fs/erofs/memback.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Memory-backed EROFS support.
+ *
+ * Serves EROFS data directly from a contiguous kernel memory region
+ * (e.g. an initrd) without going through the block layer.
+ */
+#include "internal.h"
+#include <trace/events/erofs.h>
+
+struct erofs_memback_rq {
+ struct bio_vec bvecs[16];
+ struct bio bio;
+ struct erofs_sb_info *sbi;
+};
+
+static void erofs_memback_rq_submit(struct erofs_memback_rq *rq)
+{
+ struct erofs_sb_info *sbi;
+ struct bio_vec bv;
+ struct bvec_iter iter;
+ loff_t pos;
+
+ if (!rq)
+ return;
+
+ sbi = rq->sbi;
+ pos = rq->bio.bi_iter.bi_sector << SECTOR_SHIFT;
+
+ bio_for_each_segment(bv, &rq->bio, iter) {
+ void *dst = bvec_kmap_local(&bv);
+ unsigned long avail = sbi->memback_size - pos;
+ unsigned long len = min_t(unsigned long, bv.bv_len, avail);
+
+ if (pos < sbi->memback_size && len)
+ memcpy(dst, (char *)sbi->memback_data + pos, len);
+ if (len < bv.bv_len)
+ memset(dst + len, 0, bv.bv_len - len);
+ kunmap_local(dst);
+ pos += bv.bv_len;
+ }
+ bio_endio(&rq->bio);
+ bio_uninit(&rq->bio);
+ kfree(rq);
+}
+
+static struct erofs_memback_rq *
+erofs_memback_rq_alloc(struct erofs_map_dev *mdev)
+{
+ struct erofs_memback_rq *rq =
+ kzalloc(sizeof(*rq), GFP_KERNEL | __GFP_NOFAIL);
+
+ bio_init(&rq->bio, NULL, rq->bvecs, ARRAY_SIZE(rq->bvecs), REQ_OP_READ);
+ rq->sbi = EROFS_SB(mdev->m_sb);
+ return rq;
+}
+
+struct bio *erofs_memback_bio_alloc(struct erofs_map_dev *mdev)
+{
+ return &erofs_memback_rq_alloc(mdev)->bio;
+}
+
+void erofs_memback_submit_bio(struct bio *bio)
+{
+ erofs_memback_rq_submit(
+ container_of(bio, struct erofs_memback_rq, bio));
+}
+
+struct erofs_memback_io {
+ struct erofs_map_blocks map;
+ struct erofs_map_dev dev;
+ struct erofs_memback_rq *rq;
+};
+
+static int erofs_memback_scan_folio(struct erofs_memback_io *io,
+ struct inode *inode, struct folio *folio)
+{
+ struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb);
+ struct erofs_map_blocks *map = &io->map;
+ unsigned int cur = 0, end = folio_size(folio), len, attached = 0;
+ loff_t pos = folio_pos(folio), ofs;
+ int err = 0;
+
+ erofs_onlinefolio_init(folio);
+ while (cur < end) {
+ if (!in_range(pos + cur, map->m_la, map->m_llen)) {
+ map->m_la = pos + cur;
+ map->m_llen = end - cur;
+ err = erofs_map_blocks(inode, map);
+ if (err)
+ break;
+ }
+
+ ofs = pos + cur - map->m_la;
+ len = min_t(loff_t, map->m_llen - ofs, end - cur);
+ if (map->m_flags & EROFS_MAP_META) {
+ struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
+ void *src;
+
+ src = erofs_read_metabuf(&buf, inode->i_sb,
+ map->m_pa + ofs,
+ erofs_inode_in_metabox(inode));
+ if (IS_ERR(src)) {
+ err = PTR_ERR(src);
+ break;
+ }
+ memcpy_to_folio(folio, cur, src, len);
+ erofs_put_metabuf(&buf);
+ } else if (!(map->m_flags & EROFS_MAP_MAPPED)) {
+ folio_zero_segment(folio, cur, cur + len);
+ attached = 0;
+ } else {
+ loff_t pa = map->m_pa + ofs;
+
+ if (pa + len > sbi->memback_size) {
+ err = -EFSCORRUPTED;
+ break;
+ }
+ memcpy_to_folio(folio, cur,
+ (char *)sbi->memback_data + pa, len);
+ attached = 1;
+ }
+ cur += len;
+ }
+ erofs_onlinefolio_end(folio, err, false);
+ return err;
+}
+
+static int erofs_memback_read_folio(struct file *file, struct folio *folio)
+{
+ bool need_iput;
+ struct inode *realinode =
+ erofs_real_inode(folio_inode(folio), &need_iput);
+ struct erofs_memback_io io = {};
+ int err;
+
+ trace_erofs_read_folio(realinode, folio, true);
+ err = erofs_memback_scan_folio(&io, realinode, folio);
+ if (need_iput)
+ iput(realinode);
+ return err;
+}
+
+static void erofs_memback_readahead(struct readahead_control *rac)
+{
+ bool need_iput;
+ struct inode *realinode =
+ erofs_real_inode(rac->mapping->host, &need_iput);
+ struct erofs_memback_io io = {};
+ struct folio *folio;
+ int err;
+
+ trace_erofs_readahead(realinode, readahead_index(rac),
+ readahead_count(rac), true);
+ while ((folio = readahead_folio(rac))) {
+ err = erofs_memback_scan_folio(&io, realinode, folio);
+ if (err && err != -EINTR)
+ erofs_err(realinode->i_sb,
+ "readahead error at folio %lu @ nid %llu",
+ folio->index, EROFS_I(realinode)->nid);
+ }
+ if (need_iput)
+ iput(realinode);
+}
+
+const struct address_space_operations erofs_memback_aops = {
+ .read_folio = erofs_memback_read_folio,
+ .readahead = erofs_memback_readahead,
+};
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index 802add6652fd..724389d79aac 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -625,9 +625,8 @@ static void erofs_set_sysfs_name(struct super_block *sb)
sbi->fsid);
else if (sbi->fsid)
super_set_sysfs_name_generic(sb, "%s", sbi->fsid);
- else if (erofs_is_fileio_mode(sbi))
- super_set_sysfs_name_generic(sb, "%s",
- bdi_dev_name(sb->s_bdi));
+ else if (erofs_is_memback_mode(sbi) || erofs_is_fileio_mode(sbi))
+ super_set_sysfs_name_generic(sb, "%s", bdi_dev_name(sb->s_bdi));
else
super_set_sysfs_name_id(sb);
}
@@ -708,7 +707,7 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
return -EINVAL;
}
- if (erofs_is_fileio_mode(sbi)) {
+ if (erofs_is_memback_mode(sbi) || erofs_is_fileio_mode(sbi)) {
sb->s_blocksize = 1 << sbi->blkszbits;
sb->s_blocksize_bits = sbi->blkszbits;
} else if (!sb_set_blocksize(sb, 1 << sbi->blkszbits)) {
@@ -791,11 +790,39 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
return 0;
}
+/*
+ * Pending memback parameters, consumed during erofs_fc_get_tree().
+ * Only used by single-threaded __init code, no synchronization needed.
+ */
+static void *erofs_memback_pending_data;
+static unsigned long erofs_memback_pending_size;
+
+void __init erofs_memback_set_pending(void *data, unsigned long size)
+{
+ erofs_memback_pending_data = data;
+ erofs_memback_pending_size = size;
+}
+
+static void erofs_memback_consume_pending(struct erofs_sb_info *sbi)
+{
+ sbi->memback_data = erofs_memback_pending_data;
+ sbi->memback_size = erofs_memback_pending_size;
+ erofs_memback_pending_data = NULL;
+ erofs_memback_pending_size = 0;
+}
+
static int erofs_fc_get_tree(struct fs_context *fc)
{
struct erofs_sb_info *sbi = fc->s_fs_info;
int ret;
+ if (erofs_memback_pending_data) {
+ erofs_memback_consume_pending(sbi);
+ return get_tree_nodev(fc, erofs_fc_fill_super);
+ }
+ if (erofs_is_memback_mode(sbi))
+ return get_tree_nodev(fc, erofs_fc_fill_super);
+
if (IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && sbi->fsid)
return get_tree_nodev(fc, erofs_fc_fill_super);
@@ -928,7 +955,8 @@ static void erofs_kill_sb(struct super_block *sb)
{
struct erofs_sb_info *sbi = EROFS_SB(sb);
- if ((IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && sbi->fsid) ||
+ if (erofs_is_memback_mode(sbi) ||
+ (IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && sbi->fsid) ||
sbi->dif0.file)
kill_anon_super(sb);
else
diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
index c6240dccbb0f..af4ddb492660 100644
--- a/fs/erofs/zdata.c
+++ b/fs/erofs/zdata.c
@@ -1709,10 +1709,12 @@ static void z_erofs_submit_queue(struct z_erofs_frontend *f,
sb->s_blocksize);
do {
bvec.bv_page = NULL;
- if (bio && (cur != last_pa ||
- bio->bi_bdev != mdev.m_bdev)) {
+ if (bio &&
+ (cur != last_pa || bio->bi_bdev != mdev.m_bdev)) {
drain_io:
- if (erofs_is_fileio_mode(EROFS_SB(sb)))
+ if (erofs_is_memback_mode(EROFS_SB(sb)))
+ erofs_memback_submit_bio(bio);
+ else if (erofs_is_fileio_mode(EROFS_SB(sb)))
erofs_fileio_submit_bio(bio);
else if (erofs_is_fscache_mode(sb))
erofs_fscache_submit_bio(bio);
@@ -1742,7 +1744,9 @@ static void z_erofs_submit_queue(struct z_erofs_frontend *f,
}
if (!bio) {
- if (erofs_is_fileio_mode(EROFS_SB(sb)))
+ if (erofs_is_memback_mode(EROFS_SB(sb)))
+ bio = erofs_memback_bio_alloc(&mdev);
+ else if (erofs_is_fileio_mode(EROFS_SB(sb)))
bio = erofs_fileio_bio_alloc(&mdev);
else if (erofs_is_fscache_mode(sb))
bio = erofs_fscache_bio_alloc(&mdev);
@@ -1772,7 +1776,9 @@ static void z_erofs_submit_queue(struct z_erofs_frontend *f,
} while (next != Z_EROFS_PCLUSTER_TAIL);
if (bio) {
- if (erofs_is_fileio_mode(EROFS_SB(sb)))
+ if (erofs_is_memback_mode(EROFS_SB(sb)))
+ erofs_memback_submit_bio(bio);
+ else if (erofs_is_fileio_mode(EROFS_SB(sb)))
erofs_fileio_submit_bio(bio);
else if (erofs_is_fscache_mode(sb))
erofs_fscache_submit_bio(bio);
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH RFC 2/2] erofs: add KUnit test for memory-backed compressed mount
2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
@ 2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
-1 siblings, 0 replies; 8+ messages in thread
From: Alberto Ruiz @ 2026-06-18 16:13 UTC (permalink / raw)
To: Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu, Sandeep Dhavale, Hongbo Li,
Chunhai Guo
Cc: linux-kernel, linux-erofs, Javier Martinez Canillas, Brian Masney,
Eric Curtin, Alberto Ruiz
Add a KUnit test that exercises the EROFS memback mount path with a
compressed (LZ4) image. The test embeds a minimal 8 KiB EROFS image
containing a single file with known content ("The quick brown fox. "
repeated 250 times), mounts it via erofs_memback_set_pending(), reads
back the decompressed file data, and verifies it byte-for-byte.
Since erofs_memback_set_pending() is __init, the test uses
kunit_test_init_section_suites() so it runs during kernel init.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Alberto Ruiz <aruiz@redhat.com>
---
fs/erofs/Kconfig | 11 ++++
fs/erofs/Makefile | 1 +
fs/erofs/memback_test.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 163 insertions(+)
diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig
index 97c48ebe8458..adf26e67924c 100644
--- a/fs/erofs/Kconfig
+++ b/fs/erofs/Kconfig
@@ -213,3 +213,14 @@ config EROFS_FS_PAGE_CACHE_SHARE
content fingerprints on the same machine.
If unsure, say N.
+
+config EROFS_FS_MEMBACK_KUNIT_TEST
+ bool "Test EROFS memory-backed mount" if !KUNIT_ALL_TESTS
+ depends on EROFS_FS=y && EROFS_FS_ZIP && KUNIT=y
+ default KUNIT_ALL_TESTS
+ help
+ KUnit test for the EROFS memory-backed mount path. Embeds a small
+ LZ4-compressed EROFS image and verifies that it can be mounted and
+ read via the memback interface during kernel init.
+
+ Say N unless you are running KUnit tests.
diff --git a/fs/erofs/Makefile b/fs/erofs/Makefile
index 8f3b73835328..b98c95e96e85 100644
--- a/fs/erofs/Makefile
+++ b/fs/erofs/Makefile
@@ -12,3 +12,4 @@ erofs-$(CONFIG_EROFS_FS_BACKED_BY_FILE) += fileio.o
erofs-$(CONFIG_EROFS_FS_ONDEMAND) += fscache.o
erofs-y += memback.o
erofs-$(CONFIG_EROFS_FS_PAGE_CACHE_SHARE) += ishare.o
+obj-$(CONFIG_EROFS_FS_MEMBACK_KUNIT_TEST) += memback_test.o
diff --git a/fs/erofs/memback_test.c b/fs/erofs/memback_test.c
new file mode 100644
index 000000000000..9661b76cfa0f
--- /dev/null
+++ b/fs/erofs/memback_test.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for EROFS memory-backed mount (memback).
+ *
+ * Embeds a minimal LZ4-compressed EROFS image and verifies that the memback
+ * path can mount it and serve decompressed file data correctly.
+ */
+#include <kunit/test.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/init_syscalls.h>
+#include <linux/namei.h>
+#include <linux/slab.h>
+#include <uapi/linux/mount.h>
+#include "internal.h"
+
+#define EROFS_MEMBACK_TEST_DIR "/erofs_memback_test"
+#define EROFS_MEMBACK_TEST_FILE EROFS_MEMBACK_TEST_DIR "/testfile"
+
+/*
+ * "The quick brown fox. " repeated 250 times = 5250 bytes, LZ4-compressed
+ * into a 2-block (4096 x 2) EROFS image by:
+ * mkfs.erofs -zlz4 -b4096 -x-1 image.img testdir/
+ *
+ * The full image is 8192 bytes but only 384 bytes are non-zero. We store
+ * just the two non-zero regions (superblock+metadata at offset 1024 and
+ * compressed data at offset 8140) and reconstruct the image at runtime.
+ *
+ * The image is placed at a non-page-aligned offset (3 × 512 = 1536 bytes)
+ * within the allocation to exercise the non-page-aligned memback path.
+ */
+#define EROFS_TEST_IMAGE_SIZE 8192
+#define EROFS_TEST_IMAGE_OFFSET 1536
+
+static const unsigned char erofs_test_metadata[] __initconst = {
+ 0xe2, 0xe1, 0xf5, 0xe0, 0xee, 0x93, 0xd1, 0x4a, 0x03, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x24, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x5d, 0x77, 0x31, 0x6a, 0x00, 0x00, 0x00, 0x00, 0xac, 0xb1, 0x04, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2c, 0x51, 0x16, 0x54, 0xcc, 0x6a, 0x43, 0xdd, 0x96, 0xc4, 0x3e, 0x45,
+ 0x16, 0x53, 0x0e, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0xed, 0x41, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00,
+ 0xe8, 0x03, 0x00, 0x00, 0x50, 0x77, 0x31, 0x6a, 0x00, 0x00, 0x00, 0x00,
+ 0x7c, 0x8d, 0x20, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x02, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x02, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x01, 0x00,
+ 0x2e, 0x2e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x66, 0x69, 0x6c, 0x65, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00,
+ 0x82, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00,
+ 0x50, 0x77, 0x31, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x3a, 0x37, 0x14,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x82, 0x04,
+};
+
+static const unsigned char erofs_test_cdata[] __initconst = {
+ 0xff, 0x06, 0x54, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b,
+ 0x20, 0x62, 0x72, 0x6f, 0x77, 0x6e, 0x20, 0x66, 0x6f, 0x78, 0x2e,
+ 0x20, 0x15, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x69, 0x50, 0x66, 0x6f, 0x78, 0x2e, 0x20,
+};
+
+#define EXPECTED_PATTERN "The quick brown fox. "
+#define EXPECTED_PATTERN_LEN 21
+#define EXPECTED_REPEATS 250
+#define EXPECTED_FILE_SIZE (EXPECTED_PATTERN_LEN * EXPECTED_REPEATS)
+
+static void __init erofs_memback_test_mount_compressed(struct kunit *test)
+{
+ void *buf;
+ struct file *file;
+ char *readbuf;
+ ssize_t nread;
+ int ret, i;
+
+ buf = kzalloc(EROFS_TEST_IMAGE_OFFSET + EROFS_TEST_IMAGE_SIZE,
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+ memcpy(buf + EROFS_TEST_IMAGE_OFFSET + 1024, erofs_test_metadata,
+ sizeof(erofs_test_metadata));
+ memcpy(buf + EROFS_TEST_IMAGE_OFFSET + 8140, erofs_test_cdata,
+ sizeof(erofs_test_cdata));
+
+ erofs_memback_set_pending(buf + EROFS_TEST_IMAGE_OFFSET,
+ EROFS_TEST_IMAGE_SIZE);
+
+ ret = init_mkdir(EROFS_MEMBACK_TEST_DIR, 0700);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = init_mount("none", EROFS_MEMBACK_TEST_DIR, "erofs", MS_RDONLY,
+ NULL);
+ if (ret) {
+ init_rmdir(EROFS_MEMBACK_TEST_DIR);
+ kfree(buf);
+ KUNIT_FAIL(test, "init_mount failed: %d", ret);
+ return;
+ }
+
+ file = filp_open(EROFS_MEMBACK_TEST_FILE, O_RDONLY, 0);
+ if (IS_ERR(file)) {
+ init_umount(EROFS_MEMBACK_TEST_DIR, 0);
+ init_rmdir(EROFS_MEMBACK_TEST_DIR);
+ kfree(buf);
+ KUNIT_FAIL(test, "filp_open failed: %ld", PTR_ERR(file));
+ return;
+ }
+
+ KUNIT_EXPECT_EQ(test, (int)i_size_read(file_inode(file)),
+ EXPECTED_FILE_SIZE);
+
+ readbuf = kzalloc(EXPECTED_FILE_SIZE, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readbuf);
+
+ nread = kernel_read(file, readbuf, EXPECTED_FILE_SIZE, &file->f_pos);
+ KUNIT_EXPECT_EQ(test, (int)nread, EXPECTED_FILE_SIZE);
+
+ for (i = 0; i < EXPECTED_REPEATS && nread == EXPECTED_FILE_SIZE; i++)
+ KUNIT_EXPECT_MEMEQ(test, readbuf + i * EXPECTED_PATTERN_LEN,
+ EXPECTED_PATTERN, EXPECTED_PATTERN_LEN);
+
+ kfree(readbuf);
+ fput(file);
+ init_umount(EROFS_MEMBACK_TEST_DIR, 0);
+ init_rmdir(EROFS_MEMBACK_TEST_DIR);
+ kfree(buf);
+}
+
+static struct kunit_case erofs_memback_test_cases[] __initdata = {
+ KUNIT_CASE(erofs_memback_test_mount_compressed),
+ {},
+};
+
+static struct kunit_suite __refdata erofs_memback_test_suite = {
+ .name = "erofs_memback",
+ .test_cases = erofs_memback_test_cases,
+};
+kunit_test_init_section_suites(&erofs_memback_test_suite);
+
+MODULE_DESCRIPTION("EROFS memback KUnit test suite");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH RFC 2/2] erofs: add KUnit test for memory-backed compressed mount
@ 2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
0 siblings, 0 replies; 8+ messages in thread
From: Alberto Ruiz via B4 Relay @ 2026-06-18 16:13 UTC (permalink / raw)
To: Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu, Sandeep Dhavale, Hongbo Li,
Chunhai Guo
Cc: linux-kernel, linux-erofs, Javier Martinez Canillas, Brian Masney,
Eric Curtin, Alberto Ruiz
From: Alberto Ruiz <aruiz@redhat.com>
Add a KUnit test that exercises the EROFS memback mount path with a
compressed (LZ4) image. The test embeds a minimal 8 KiB EROFS image
containing a single file with known content ("The quick brown fox. "
repeated 250 times), mounts it via erofs_memback_set_pending(), reads
back the decompressed file data, and verifies it byte-for-byte.
Since erofs_memback_set_pending() is __init, the test uses
kunit_test_init_section_suites() so it runs during kernel init.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Alberto Ruiz <aruiz@redhat.com>
---
fs/erofs/Kconfig | 11 ++++
fs/erofs/Makefile | 1 +
fs/erofs/memback_test.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 163 insertions(+)
diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig
index 97c48ebe8458..adf26e67924c 100644
--- a/fs/erofs/Kconfig
+++ b/fs/erofs/Kconfig
@@ -213,3 +213,14 @@ config EROFS_FS_PAGE_CACHE_SHARE
content fingerprints on the same machine.
If unsure, say N.
+
+config EROFS_FS_MEMBACK_KUNIT_TEST
+ bool "Test EROFS memory-backed mount" if !KUNIT_ALL_TESTS
+ depends on EROFS_FS=y && EROFS_FS_ZIP && KUNIT=y
+ default KUNIT_ALL_TESTS
+ help
+ KUnit test for the EROFS memory-backed mount path. Embeds a small
+ LZ4-compressed EROFS image and verifies that it can be mounted and
+ read via the memback interface during kernel init.
+
+ Say N unless you are running KUnit tests.
diff --git a/fs/erofs/Makefile b/fs/erofs/Makefile
index 8f3b73835328..b98c95e96e85 100644
--- a/fs/erofs/Makefile
+++ b/fs/erofs/Makefile
@@ -12,3 +12,4 @@ erofs-$(CONFIG_EROFS_FS_BACKED_BY_FILE) += fileio.o
erofs-$(CONFIG_EROFS_FS_ONDEMAND) += fscache.o
erofs-y += memback.o
erofs-$(CONFIG_EROFS_FS_PAGE_CACHE_SHARE) += ishare.o
+obj-$(CONFIG_EROFS_FS_MEMBACK_KUNIT_TEST) += memback_test.o
diff --git a/fs/erofs/memback_test.c b/fs/erofs/memback_test.c
new file mode 100644
index 000000000000..9661b76cfa0f
--- /dev/null
+++ b/fs/erofs/memback_test.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for EROFS memory-backed mount (memback).
+ *
+ * Embeds a minimal LZ4-compressed EROFS image and verifies that the memback
+ * path can mount it and serve decompressed file data correctly.
+ */
+#include <kunit/test.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/init_syscalls.h>
+#include <linux/namei.h>
+#include <linux/slab.h>
+#include <uapi/linux/mount.h>
+#include "internal.h"
+
+#define EROFS_MEMBACK_TEST_DIR "/erofs_memback_test"
+#define EROFS_MEMBACK_TEST_FILE EROFS_MEMBACK_TEST_DIR "/testfile"
+
+/*
+ * "The quick brown fox. " repeated 250 times = 5250 bytes, LZ4-compressed
+ * into a 2-block (4096 x 2) EROFS image by:
+ * mkfs.erofs -zlz4 -b4096 -x-1 image.img testdir/
+ *
+ * The full image is 8192 bytes but only 384 bytes are non-zero. We store
+ * just the two non-zero regions (superblock+metadata at offset 1024 and
+ * compressed data at offset 8140) and reconstruct the image at runtime.
+ *
+ * The image is placed at a non-page-aligned offset (3 × 512 = 1536 bytes)
+ * within the allocation to exercise the non-page-aligned memback path.
+ */
+#define EROFS_TEST_IMAGE_SIZE 8192
+#define EROFS_TEST_IMAGE_OFFSET 1536
+
+static const unsigned char erofs_test_metadata[] __initconst = {
+ 0xe2, 0xe1, 0xf5, 0xe0, 0xee, 0x93, 0xd1, 0x4a, 0x03, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x24, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x5d, 0x77, 0x31, 0x6a, 0x00, 0x00, 0x00, 0x00, 0xac, 0xb1, 0x04, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2c, 0x51, 0x16, 0x54, 0xcc, 0x6a, 0x43, 0xdd, 0x96, 0xc4, 0x3e, 0x45,
+ 0x16, 0x53, 0x0e, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0xed, 0x41, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00,
+ 0xe8, 0x03, 0x00, 0x00, 0x50, 0x77, 0x31, 0x6a, 0x00, 0x00, 0x00, 0x00,
+ 0x7c, 0x8d, 0x20, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x02, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x02, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x01, 0x00,
+ 0x2e, 0x2e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x66, 0x69, 0x6c, 0x65, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00,
+ 0x82, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00,
+ 0x50, 0x77, 0x31, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x3a, 0x37, 0x14,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x82, 0x04,
+};
+
+static const unsigned char erofs_test_cdata[] __initconst = {
+ 0xff, 0x06, 0x54, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b,
+ 0x20, 0x62, 0x72, 0x6f, 0x77, 0x6e, 0x20, 0x66, 0x6f, 0x78, 0x2e,
+ 0x20, 0x15, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x69, 0x50, 0x66, 0x6f, 0x78, 0x2e, 0x20,
+};
+
+#define EXPECTED_PATTERN "The quick brown fox. "
+#define EXPECTED_PATTERN_LEN 21
+#define EXPECTED_REPEATS 250
+#define EXPECTED_FILE_SIZE (EXPECTED_PATTERN_LEN * EXPECTED_REPEATS)
+
+static void __init erofs_memback_test_mount_compressed(struct kunit *test)
+{
+ void *buf;
+ struct file *file;
+ char *readbuf;
+ ssize_t nread;
+ int ret, i;
+
+ buf = kzalloc(EROFS_TEST_IMAGE_OFFSET + EROFS_TEST_IMAGE_SIZE,
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+ memcpy(buf + EROFS_TEST_IMAGE_OFFSET + 1024, erofs_test_metadata,
+ sizeof(erofs_test_metadata));
+ memcpy(buf + EROFS_TEST_IMAGE_OFFSET + 8140, erofs_test_cdata,
+ sizeof(erofs_test_cdata));
+
+ erofs_memback_set_pending(buf + EROFS_TEST_IMAGE_OFFSET,
+ EROFS_TEST_IMAGE_SIZE);
+
+ ret = init_mkdir(EROFS_MEMBACK_TEST_DIR, 0700);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = init_mount("none", EROFS_MEMBACK_TEST_DIR, "erofs", MS_RDONLY,
+ NULL);
+ if (ret) {
+ init_rmdir(EROFS_MEMBACK_TEST_DIR);
+ kfree(buf);
+ KUNIT_FAIL(test, "init_mount failed: %d", ret);
+ return;
+ }
+
+ file = filp_open(EROFS_MEMBACK_TEST_FILE, O_RDONLY, 0);
+ if (IS_ERR(file)) {
+ init_umount(EROFS_MEMBACK_TEST_DIR, 0);
+ init_rmdir(EROFS_MEMBACK_TEST_DIR);
+ kfree(buf);
+ KUNIT_FAIL(test, "filp_open failed: %ld", PTR_ERR(file));
+ return;
+ }
+
+ KUNIT_EXPECT_EQ(test, (int)i_size_read(file_inode(file)),
+ EXPECTED_FILE_SIZE);
+
+ readbuf = kzalloc(EXPECTED_FILE_SIZE, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readbuf);
+
+ nread = kernel_read(file, readbuf, EXPECTED_FILE_SIZE, &file->f_pos);
+ KUNIT_EXPECT_EQ(test, (int)nread, EXPECTED_FILE_SIZE);
+
+ for (i = 0; i < EXPECTED_REPEATS && nread == EXPECTED_FILE_SIZE; i++)
+ KUNIT_EXPECT_MEMEQ(test, readbuf + i * EXPECTED_PATTERN_LEN,
+ EXPECTED_PATTERN, EXPECTED_PATTERN_LEN);
+
+ kfree(readbuf);
+ fput(file);
+ init_umount(EROFS_MEMBACK_TEST_DIR, 0);
+ init_rmdir(EROFS_MEMBACK_TEST_DIR);
+ kfree(buf);
+}
+
+static struct kunit_case erofs_memback_test_cases[] __initdata = {
+ KUNIT_CASE(erofs_memback_test_mount_compressed),
+ {},
+};
+
+static struct kunit_suite __refdata erofs_memback_test_suite = {
+ .name = "erofs_memback",
+ .test_cases = erofs_memback_test_cases,
+};
+kunit_test_init_section_suites(&erofs_memback_test_suite);
+
+MODULE_DESCRIPTION("EROFS memback KUnit test suite");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH RFC 0/2] RFC: erofs: memory-backed mount for non-page-aligned ranges
2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
` (2 preceding siblings ...)
(?)
@ 2026-06-19 0:32 ` Gao Xiang
-1 siblings, 0 replies; 8+ messages in thread
From: Gao Xiang @ 2026-06-19 0:32 UTC (permalink / raw)
To: aruiz, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu, Sandeep Dhavale,
Hongbo Li, Chunhai Guo
Cc: linux-kernel, linux-erofs, Javier Martinez Canillas, Brian Masney,
Eric Curtin
Hi Alberto,
On 2026/6/19 00:13, Alberto Ruiz via B4 Relay wrote:
> This series adds a memory-backed ("memback") mode to EROFS that
> allows mounting an EROFS image directly from a kernel memory region
> without going through the block layer.
>
> I am sending this as an RFC to get EROFS maintainer feedback on
> this approach before building the full initramfs series on top of
> it. The memback mode may also be useful for other use cases beyond
First, thanks for your interest and efforts. On my side I'm glad
if EROFS can be used for the initramfs use case (for many years!).
> initramfs where EROFS images need to be served directly from
> memory, though it may need further hardening for more general use
> cases.
>
> Previous review feedback on the initramfs patches raised concerns
> about the block layer dependency — this series addresses that by
Could you have a pointer where "concerns about the block layer
dependency" comes from? I don't notice that on the mailing list.
> providing a direct memory-backed path. It also adds support for
But I think EROFS subsystem can not decide itself if we could
land this functionality or not, in any case we should have a
discussion with VFS/MM folks first to know if the rough
direction is acceptable or not. And we also need to know
what's the concerns before we draft a version.
There may have some barriers on my side to land this feature:
- erofs_memback_set_pending() can be used to set an arbitary
kernel virt address. I don't think it's a good direction
to be honest, for example, previously cramfs gets some
arbitary address using mtd_point() and implement XIP feature
but I think it's quite outdated.
Yes, I've seen erofs_memback_set_pending() can only be used
for specific cases since it has __init annotation, but it
also limits _the other potential use cases_.
- currently modern memory backends (memory devices like PMEM
and CXL) all use Linux DAX infrastructure, which means we
shouldn't bypass the DAX infrastructure to operate arbitary
memory address directly.
That is my own opinion at least, but if MM/FS folks think EROFS
can work on arbitary kernel virt address instead, I'm fine, but
I think it's highly impossible.
So at least on this part, I wonder if we should enhance ramdax
(drivers/nvdimm/ramdax.c) to support initramfs use cases and
make EROFS to use ramdax + DAX infrastrure to mount initramfs
instead.
> non-page-aligned memory ranges, which is the common case for
> initramfs images packed at arbitrary cpio boundaries. Supporting
That is another issue I would like to discuss:
If EROFS filesystem data is page aligned, we can just use the
initramfs memory directly and so that no page cache is needed.
And in that cases, no double cache anymore (see "ramfs and
ramdisk" section in https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt).
> non-page-aligned ranges is essential for true zero-copy access —
I guess that is not true: I've looked the code, basically the
code uses the page cache (double caching), and the unaligned
data will be copied into page cache (aligned data) first.
Since mmap use cases need page aligned buffers, and in practice
of the linux kernel, it will be page cache or DAX. So in order
to do that, unaligned initramfs needs do double caching and an
extra copy at least to fulfill the requirement.
> requiring page alignment would force a copy into an aligned buffer
> before the data can be used, defeating the purpose.
same as above.
Anyway, I wonder if we could avoid non-page-aligned initramfs so
that ramdax can be enhanced to be used directly.
>
> The full initramfs integration (init/ changes) that wires up the
> EROFS initrd detection and layered mount is available for reference
> here[0]. It implements support for interleaved cpio and EROFS
> layers, it supports cpio CPU microcode update prefix, and uses
> overlayfs+tmpfs for write support.
If that's why EROFS is unaligned, and we just add some padding
between cpio and erofs image (I think the padding is small than
4k)? or can cpio CPU microcode update prefix placed after the
erofs image?
>
> This series was developed with the assistance of Claude Opus 4.6.
> All patches carry an Assisted-by tag. I am providing the kunit
> testing and I have tested this change against the initramfs series
> to boot an EROFS initramfs. I welcome feedback on the approach and
> implementation.
Yes, the generated code can work but IMHO it doesn't mean it's well
suitable for upstream...
It's just my own humble opinion, but I expect VFS/MM folks could
have similar thoughts. However, I'm also interested if EROFS can
be used for initramfs, as long as we have a proper way to implement
that.
Thanks,
Gao Xiang
>
> Patch 1 adds the core memback implementation.
> Patch 2 adds a KUnit test that mounts a compressed EROFS image at a
> non-page-aligned offset and verifies decompressed file contents.
>
> [0] https://github.com/aruiz/linux/tree/wip/erofs-initramfs-memback
>
> Signed-off-by: Alberto Ruiz <aruiz@redhat.com>
> ---
> Alberto Ruiz (2):
> erofs: add memory-backed mode for non-page-aligned mount
> erofs: add KUnit test for memory-backed compressed mount
>
> fs/erofs/Kconfig | 11 ++++
> fs/erofs/Makefile | 2 +
> fs/erofs/data.c | 33 +++++++++-
> fs/erofs/internal.h | 53 +++++++++++----
> fs/erofs/memback.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++
> fs/erofs/memback_test.c | 151 ++++++++++++++++++++++++++++++++++++++++++
> fs/erofs/super.c | 38 +++++++++--
> fs/erofs/zdata.c | 16 +++--
> 8 files changed, 447 insertions(+), 26 deletions(-)
> ---
> base-commit: 6b5a2b7d9bc156e505f09e698d85d6a1547c1206
> change-id: 20260617-erofs-memback-0ae2448ba2cc
>
> Best regards,
> --
> Alberto Ruiz <aruiz@redhat.com>
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH RFC 2/2] erofs: add KUnit test for memory-backed compressed mount
2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
(?)
@ 2026-06-22 18:52 ` kernel test robot
-1 siblings, 0 replies; 8+ messages in thread
From: kernel test robot @ 2026-06-22 18:52 UTC (permalink / raw)
To: Alberto Ruiz via B4 Relay; +Cc: oe-kbuild-all
Hi Alberto,
[This is a private test report for your RFC patch.]
kernel test robot noticed the following build warnings:
[auto build test WARNING on 6b5a2b7d9bc156e505f09e698d85d6a1547c1206]
url: https://github.com/intel-lab-lkp/linux/commits/Alberto-Ruiz-via-B4-Relay/erofs-add-memory-backed-mode-for-non-page-aligned-mount/20260622-162856
base: 6b5a2b7d9bc156e505f09e698d85d6a1547c1206
patch link: https://lore.kernel.org/r/20260618-erofs-memback-v1-2-5aa7006a241a%40redhat.com
patch subject: [PATCH RFC 2/2] erofs: add KUnit test for memory-backed compressed mount
config: x86_64-rhel-9.4-ltp (https://download.01.org/0day-ci/archive/20260622/202606222038.4MTbEObD-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260622/202606222038.4MTbEObD-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202606222038.4MTbEObD-lkp@intel.com/
All warnings (new ones prefixed by >>):
fs/erofs/memback.c: In function 'erofs_memback_scan_folio':
>> fs/erofs/memback.c:80:61: warning: variable 'attached' set but not used [-Wunused-but-set-variable]
80 | unsigned int cur = 0, end = folio_size(folio), len, attached = 0;
| ^~~~~~~~
vim +/attached +80 fs/erofs/memback.c
74
75 static int erofs_memback_scan_folio(struct erofs_memback_io *io,
76 struct inode *inode, struct folio *folio)
77 {
78 struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb);
79 struct erofs_map_blocks *map = &io->map;
> 80 unsigned int cur = 0, end = folio_size(folio), len, attached = 0;
81 loff_t pos = folio_pos(folio), ofs;
82 int err = 0;
83
84 erofs_onlinefolio_init(folio);
85 while (cur < end) {
86 if (!in_range(pos + cur, map->m_la, map->m_llen)) {
87 map->m_la = pos + cur;
88 map->m_llen = end - cur;
89 err = erofs_map_blocks(inode, map);
90 if (err)
91 break;
92 }
93
94 ofs = pos + cur - map->m_la;
95 len = min_t(loff_t, map->m_llen - ofs, end - cur);
96 if (map->m_flags & EROFS_MAP_META) {
97 struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
98 void *src;
99
100 src = erofs_read_metabuf(&buf, inode->i_sb,
101 map->m_pa + ofs,
102 erofs_inode_in_metabox(inode));
103 if (IS_ERR(src)) {
104 err = PTR_ERR(src);
105 break;
106 }
107 memcpy_to_folio(folio, cur, src, len);
108 erofs_put_metabuf(&buf);
109 } else if (!(map->m_flags & EROFS_MAP_MAPPED)) {
110 folio_zero_segment(folio, cur, cur + len);
111 attached = 0;
112 } else {
113 loff_t pa = map->m_pa + ofs;
114
115 if (pa + len > sbi->memback_size) {
116 err = -EFSCORRUPTED;
117 break;
118 }
119 memcpy_to_folio(folio, cur,
120 (char *)sbi->memback_data + pa, len);
121 attached = 1;
122 }
123 cur += len;
124 }
125 erofs_onlinefolio_end(folio, err, false);
126 return err;
127 }
128
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-06-22 18:52 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-18 16:13 [PATCH RFC 0/2] RFC: erofs: memory-backed mount for non-page-aligned ranges Alberto Ruiz
2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
2026-06-18 16:13 ` [PATCH RFC 1/2] erofs: add memory-backed mode for non-page-aligned mount Alberto Ruiz
2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
2026-06-18 16:13 ` [PATCH RFC 2/2] erofs: add KUnit test for memory-backed compressed mount Alberto Ruiz
2026-06-18 16:13 ` Alberto Ruiz via B4 Relay
2026-06-22 18:52 ` kernel test robot
2026-06-19 0:32 ` [PATCH RFC 0/2] RFC: erofs: memory-backed mount for non-page-aligned ranges Gao Xiang
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.