* [PATCH v2 0/7] jffs2: extend write verification to all write paths
@ 2026-08-29 6:16 zhouminqiang
2026-08-29 6:16 ` [PATCH v2 1/7] jffs2: wbuf: clear wbuf on recovery failure paths zhouminqiang
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: zhouminqiang @ 2026-08-29 6:16 UTC (permalink / raw)
To: linux, vz, piotr.wojtaszczyk, maddy, dwmw2, richard
Cc: linux-arm-kernel, linux-kernel, linuxppc-dev, linux-mtd,
chengzhihao1, yangerkun, yi.zhang
When JFFS2 writes data to flash, corruption can occur during the
write transfer (RAM failures, bus errors) or after commit to the
medium (bit flips). To distinguish whether the corruption happened
before or after the data reached the flash, commit a6bc432e296d
("[JFFS2] Add support for write-buffer verification.") introduced
CONFIG_JFFS2_FS_WBUF_VERIFY: reading the data back immediately
after a successful write and comparing it with the in-memory
source buffer provides the missing observation point for that
diagnosis.
However, the current implementation only performs read-back
verification on write-buffer flush paths. NOR flash devices write
directly through jffs2_flash_direct_write() and
jffs2_flash_direct_writev(), with no equivalent check. Data
corruption incidents have been observed on NOR-based devices in
production environments, yet there is no quick diagnostic tool to
isolate whether the corruption occurred during the write transfer
or after commit to the medium. A significant number of deployed
devices rely on JFFS2 on NOR flash, making this gap a practical
concern.
During the investigation of the NOR flash gap, code inspection also
revealed that when the write data length exceeds wbuf_pagesize in
jffs2_flash_writev(), the excess data bypasses the write buffer and
is written directly to flash via mtd_write(), with no verification.
Additionally, jffs2_wbuf_recover() does not clear c->wbuf_len on
recovery failure, which can lead to BUG_ON in jffs2_link_node_ref()
or deadlock in jffs2_flush_wbuf_pad() on a subsequent write.
This series closes these gaps with the following changes:
- Patch 1: fix wbuf recovery failure exit paths to clear
c->wbuf_len, preventing BUG_ON and deadlock
- Patch 2: replace pre-allocated per-superblock wbuf_verify
buffer with on-demand allocation inside jffs2_verify_write()
- Patch 3: add byte-by-byte comparison after memcmp() mismatch
to pinpoint the exact mismatch offset
- Patch 4: add verification in jffs2_flash_writev() for the
mtd_write() path that bypasses the write buffer
- Patch 5: add verification calls in jffs2_flash_direct_write() and
jffs2_flash_direct_writev() for NOR flash devices
- Patch 6: rename CONFIG_JFFS2_FS_WBUF_VERIFY to
CONFIG_JFFS2_FS_WRITE_VERIFY and remove the Kconfig
dependency on CONFIG_JFFS2_FS_WRITEBUFFER
- Patch 7: add module parameter write_verify for runtime
enable/disable of write verification
This series extends the existing write verification mechanism to
cover all write paths. It remains off by default and does not
alter JFFS2's node CRC integrity checks.
Changes in v2:
- Add patch to fix wbuf recovery failure exit paths not clearing
c->wbuf_len, preventing BUG_ON and deadlock on subsequent writes
- Use kmalloc() instead of vmalloc() for verify buffer allocation
- Keep memcmp() on the hotpath and add byte-by-byte comparison
only after a mismatch, rather than replacing memcmp() entirely
- In jffs2_flash_direct_write() and jffs2_flash_direct_writev(),
call mtd_write() before jffs2_sum_add_kvec() to prevent
retlen from being uninitialized if jffs2_sum_add_kvec() causes
an early return
- Update defconfig files to rename CONFIG_JFFS2_FS_WBUF_VERIFY to
CONFIG_JFFS2_FS_WRITE_VERIFY
- Link to v1: https://lore.kernel.org/linux-mtd/20260820105003.2525647-1-zhouminqiang2@huawei.com/T/#t
zhouminqiang (7):
jffs2: wbuf: clear wbuf on recovery failure paths
jffs2: replace per-superblock verify buffer with per-write buffer
jffs2: write verify: add byte-by-byte comparison on mismatch
jffs2: add write verification to direct page writes in flash_writev
jffs2: add write verification to NOR direct write paths
jffs2: rename CONFIG_JFFS2_FS_WBUF_VERIFY to
CONFIG_JFFS2_FS_WRITE_VERIFY
jffs2: add runtime toggle for write verification
arch/arm/configs/keystone_defconfig | 2 +-
arch/arm/configs/lpc32xx_defconfig | 2 +-
arch/arm/configs/pxa3xx_defconfig | 2 +-
arch/arm/configs/pxa_defconfig | 2 +-
arch/powerpc/configs/44x/fsp2_defconfig | 2 +-
fs/jffs2/Kconfig | 31 ++++-
fs/jffs2/jffs2_fs_sb.h | 3 -
fs/jffs2/os-linux.h | 11 ++
fs/jffs2/wbuf.c | 91 +++------------
fs/jffs2/writev.c | 144 +++++++++++++++++++++++-
10 files changed, 198 insertions(+), 92 deletions(-)
--
2.52.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 1/7] jffs2: wbuf: clear wbuf on recovery failure paths
2026-08-29 6:16 [PATCH v2 0/7] jffs2: extend write verification to all write paths zhouminqiang
@ 2026-08-29 6:16 ` zhouminqiang
2026-08-29 6:16 ` [PATCH v2 2/7] jffs2: replace per-superblock verify buffer with per-write buffer zhouminqiang
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: zhouminqiang @ 2026-08-29 6:16 UTC (permalink / raw)
To: linux, vz, piotr.wojtaszczyk, maddy, dwmw2, richard
Cc: linux-arm-kernel, linux-kernel, linuxppc-dev, linux-mtd,
chengzhihao1, yangerkun, yi.zhang
Write verification was introduced by commit a6bc432e296d ("[JFFS2]
Add support for write-buffer verification.") to detect transport or
program-time corruption. When verification fails, jffs2_wbuf_recover()
attempts to recover the data: it first calls jffs2_block_refile()
to mark the old eraseblock's remaining space as REF_OBSOLETE,
then rewrites the old block's data along with the new data still
in the wbuf to a new block. However, when the recovery write
verification also fails, the function returns without clearing
c->wbuf_len, leaving the wbuf still pointing to the refiled old
block, which leads to two kinds of bugs.
Both cases below are illustrated with a filesystem of
erasesize=16KB and wbuf_pagesize=512B.
Case 1: BUG_ON in jffs2_link_node_ref():
User: Two 1KB writes
ref0: [A+0, A+1092) (ri:68B + data:1024B)
ref1: [A+1092, A+2184)
User: append 1KB write
...
jffs2_write_dnode
jffs2_flash_writev
c->wbuf_ofs = A+2048, c->wbuf_len = 512
__jffs2_flush_wbuf
mtd_write -> 0-to-1 bit flip
jffs2_verify_write -> verify failed
jffs2_wbuf_recover
jffs2_block_refile
c->nextblock = NULL
jffs2_link_node_ref -> mark A remaining
ref2: [A+2184, A+16384) space REF_OBSOLETE,
jeb_A->free_size = 0
jffs2_reserve_space_gc
jffs2_do_reserve_space
jffs2_find_nextblock
c->nextblock = B
start = A+1092, end = A+2184
end - start >= c->wbuf_pagesize -> recover data in A
mtd_write
jffs2_verify_write -> verify also failed
return -> c->wbuf_ofs = A+2048
c->wbuf_len = 512
retry
jffs2_flash_writev
if (SECTOR_ADDR(to) != SECTOR_ADDR(c->wbuf_ofs))
-> SECTOR(to) = B
-> SECTOR(c->wbuf_ofs) = A
__jffs2_flush_wbuf(c, PAD_NOACCOUNT) -> flush residual wbuf data
wbuf_jeb = A -> c->wbuf_ofs still points
to refiled old block
mtd_write -> succeeds via NAND AND
jffs2_verify_write -> passed
if (pad)
jffs2_link_node_ref
ref_offset(ref) = c->wbuf_ofs + c->wbuf_len = A+2560
jeb_A->offset = A
c->sector_size = 16384
jeb_A->free_size = 0
ref_offset(ref) != jeb_A->offset +
c->sector_size - jeb_A->free_size -> BUG
Case 2: deadlock in jffs2_flush_wbuf_pad():
User: 1KB write
A_ref0: [A+0, A+1092) (ri:68B + data:1024B)
User: append 1K write
...
jffs2_write_dnode
jffs2_flash_writev
c->wbuf_ofs = A+1024, c->wbuf_len = 512
__jffs2_flush_wbuf
mtd_write -> bit flip
jffs2_verify_write -> verify failed
jffs2_wbuf_recover
jffs2_block_refile
c->nextblock = NULL
jffs2_link_node_ref -> mark A remaining
A_ref1: [A+1092, A+16384) space as REF_OBSOLETE
jffs2_reserve_space_gc
jffs2_do_reserve_space
jffs2_find_nextblock
c->nextblock = B
start = A, end = A+1092
end - start >= c->wbuf_pagesize -> recover data in A
mtd_write
jffs2_verify_write -> verify also failed
jffs2_add_physical_node_ref -> mark written area in
B_ref0: [B+0, B+1536) B as REF_OBSOLETE
return -> c->wbuf_ofs = A+1024
c->wbuf_len = 512
retry
jffs2_flash_writev
down_write(&c->wbuf_sem)
if (SECTOR_ADDR(to) != SECTOR_ADDR(c->wbuf_ofs))
-> SECTOR(to) = B
-> SECTOR(c->wbuf_ofs) = A
__jffs2_flush_wbuf(c, PAD_NOACCOUNT) -> flush residual wbuf data
wbuf_jeb = A -> c->wbuf_ofs still points
to refiled old block
mtd_write -> bit flip
jffs2_verify_write -> verify failed
jffs2_wbuf_recover
jffs2_block_refile
c->nextblock != jeb -> jeb = A, nextblock = B
jffs2_link_node_ref -> append zero-length ref
A_ref2: [A+16384, A+16384) marked REF_OBSOLETE
after the existing one
end = jeb_A->last_node -> inflates to
eraseblock tail
start = A, end = A+16384
jffs2_reserve_space_gc
minsize = end - start = 16384
jffs2_do_reserve_space
jeb = c->nextblock -> points to B
jeb_B->free_size = 16384 - 1536
minsize > jeb_B->free_size -> first recovery's OBSOLETE
ref reduced free_size
jffs2_wbuf_dirty(c)
jffs2_flush_wbuf_pad(c)
down_write(&c->wbuf_sem) -> already held, deadlock
Fix this by setting c->wbuf_len = 0 when jffs2_verify_write fails in
recovery path, and also in the jffs2_reserve_space_gc() and
jffs2_prealloc_raw_node_refs() failure paths, ensuring subsequent
flushes will not attempt writes on refiled blocks.
Signed-off-by: zhouminqiang <zhouminqiang2@huawei.com>
---
fs/jffs2/wbuf.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index 3b7803c75d58..61e3dbd4cd7b 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -390,6 +390,7 @@ static void jffs2_wbuf_recover(struct jffs2_sb_info *c)
if (ret) {
pr_warn("Failed to allocate space for wbuf recovery. Data loss ensues.\n");
kfree(buf);
+ c->wbuf_len = 0;
return;
}
@@ -400,6 +401,7 @@ static void jffs2_wbuf_recover(struct jffs2_sb_info *c)
if (ret) {
pr_warn("Failed to allocate node refs for wbuf recovery. Data loss ensues.\n");
kfree(buf);
+ c->wbuf_len = 0;
return;
}
@@ -431,12 +433,13 @@ static void jffs2_wbuf_recover(struct jffs2_sb_info *c)
if (ret || retlen != towrite || jffs2_verify_write(c, rewrite_buf, ofs)) {
/* Argh. We tried. Really we did. */
- pr_crit("Recovery of wbuf failed due to a second write error\n");
+ pr_crit("Recovery of wbuf failed due to a second write error. Data loss ensues.\n");
kfree(buf);
if (retlen)
jffs2_add_physical_node_ref(c, ofs | REF_OBSOLETE, ref_totlen(c, jeb, first_raw), NULL);
+ c->wbuf_len = 0;
return;
}
pr_notice("Recovery of wbuf succeeded to %08x\n", ofs);
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 2/7] jffs2: replace per-superblock verify buffer with per-write buffer
2026-08-29 6:16 [PATCH v2 0/7] jffs2: extend write verification to all write paths zhouminqiang
2026-08-29 6:16 ` [PATCH v2 1/7] jffs2: wbuf: clear wbuf on recovery failure paths zhouminqiang
@ 2026-08-29 6:16 ` zhouminqiang
2026-08-29 6:16 ` [PATCH v2 3/7] jffs2: write verify: add byte-by-byte comparison on mismatch zhouminqiang
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: zhouminqiang @ 2026-08-29 6:16 UTC (permalink / raw)
To: linux, vz, piotr.wojtaszczyk, maddy, dwmw2, richard
Cc: linux-arm-kernel, linux-kernel, linuxppc-dev, linux-mtd,
chengzhihao1, yangerkun, yi.zhang
Subsequent patches will extend write verification to additional write
paths that may execute concurrently. A shared per-superblock buffer
would require a coarse lock to serialize all verification, hurting
concurrency.
To avoid this contention, remove the wbuf_verify field from
jffs2_sb_info, along with the scattered kmalloc/kfree of wbuf_verify
in jffs2_nand_flash_setup, jffs2_dataflash_setup,
jffs2_nor_wbuf_flash_setup and their corresponding cleanup functions.
Instead, allocate a temporary buffer inside jffs2_verify_write(),
giving each invocation its own buffer and eliminating the shared state.
Signed-off-by: zhouminqiang <zhouminqiang2@huawei.com>
---
fs/jffs2/jffs2_fs_sb.h | 3 ---
fs/jffs2/wbuf.c | 61 ++++++++++++++++--------------------------
2 files changed, 23 insertions(+), 41 deletions(-)
diff --git a/fs/jffs2/jffs2_fs_sb.h b/fs/jffs2/jffs2_fs_sb.h
index 5a7091746f68..8a75870d3fc8 100644
--- a/fs/jffs2/jffs2_fs_sb.h
+++ b/fs/jffs2/jffs2_fs_sb.h
@@ -124,9 +124,6 @@ struct jffs2_sb_info {
uint32_t wbuf_pagesize; /* 0 for NOR and other flashes with no wbuf */
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
- unsigned char *wbuf_verify; /* read-back buffer for verification */
-#endif
#ifdef CONFIG_JFFS2_FS_WRITEBUFFER
unsigned char *wbuf; /* Write-behind buffer for NAND flash */
uint32_t wbuf_ofs;
diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index 61e3dbd4cd7b..f10be57c5543 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -233,19 +233,31 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
int ret;
size_t retlen;
char *eccstr;
+ void *verify_buf;
+
+ verify_buf = kmalloc(c->wbuf_pagesize, GFP_NOFS);
+ if (!verify_buf) {
+ pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
+ __func__);
+ return 0;
+ }
+
+ ret = mtd_read(c->mtd, ofs, c->wbuf_pagesize, &retlen, verify_buf);
- ret = mtd_read(c->mtd, ofs, c->wbuf_pagesize, &retlen, c->wbuf_verify);
if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
pr_warn("%s(): Read back of page at %08x failed: %d\n",
__func__, c->wbuf_ofs, ret);
- return ret;
+ goto out_free;
} else if (retlen != c->wbuf_pagesize) {
pr_warn("%s(): Read back of page at %08x gave short read: %zd not %d\n",
__func__, ofs, retlen, c->wbuf_pagesize);
- return -EIO;
+ ret = -EIO;
+ goto out_free;
+ }
+ if (!memcmp(buf, verify_buf, c->wbuf_pagesize)) {
+ ret = 0;
+ goto out_free;
}
- if (!memcmp(buf, c->wbuf_verify, c->wbuf_pagesize))
- return 0;
if (ret == -EUCLEAN)
eccstr = "corrected";
@@ -261,9 +273,14 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
pr_warn("Read back:\n");
print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
- c->wbuf_verify, c->wbuf_pagesize, 0);
+ verify_buf, c->wbuf_pagesize, 0);
+ kfree(verify_buf);
return -EIO;
+
+out_free:
+ kfree(verify_buf);
+ return ret;
}
#else
#define jffs2_verify_write(c,b,o) (0)
@@ -1217,22 +1234,11 @@ int jffs2_nand_flash_setup(struct jffs2_sb_info *c)
return -ENOMEM;
}
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
- c->wbuf_verify = kmalloc(c->wbuf_pagesize, GFP_KERNEL);
- if (!c->wbuf_verify) {
- kfree(c->oobbuf);
- kfree(c->wbuf);
- return -ENOMEM;
- }
-#endif
return 0;
}
void jffs2_nand_flash_cleanup(struct jffs2_sb_info *c)
{
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
- kfree(c->wbuf_verify);
-#endif
kfree(c->wbuf);
kfree(c->oobbuf);
}
@@ -1272,14 +1278,6 @@ int jffs2_dataflash_setup(struct jffs2_sb_info *c) {
if (!c->wbuf)
return -ENOMEM;
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
- c->wbuf_verify = kmalloc(c->wbuf_pagesize, GFP_KERNEL);
- if (!c->wbuf_verify) {
- kfree(c->wbuf);
- return -ENOMEM;
- }
-#endif
-
pr_info("write-buffering enabled buffer (%d) erasesize (%d)\n",
c->wbuf_pagesize, c->sector_size);
@@ -1287,9 +1285,6 @@ int jffs2_dataflash_setup(struct jffs2_sb_info *c) {
}
void jffs2_dataflash_cleanup(struct jffs2_sb_info *c) {
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
- kfree(c->wbuf_verify);
-#endif
kfree(c->wbuf);
}
@@ -1309,20 +1304,10 @@ int jffs2_nor_wbuf_flash_setup(struct jffs2_sb_info *c) {
if (!c->wbuf)
return -ENOMEM;
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
- c->wbuf_verify = kmalloc(c->wbuf_pagesize, GFP_KERNEL);
- if (!c->wbuf_verify) {
- kfree(c->wbuf);
- return -ENOMEM;
- }
-#endif
return 0;
}
void jffs2_nor_wbuf_flash_cleanup(struct jffs2_sb_info *c) {
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
- kfree(c->wbuf_verify);
-#endif
kfree(c->wbuf);
}
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 3/7] jffs2: write verify: add byte-by-byte comparison on mismatch
2026-08-29 6:16 [PATCH v2 0/7] jffs2: extend write verification to all write paths zhouminqiang
2026-08-29 6:16 ` [PATCH v2 1/7] jffs2: wbuf: clear wbuf on recovery failure paths zhouminqiang
2026-08-29 6:16 ` [PATCH v2 2/7] jffs2: replace per-superblock verify buffer with per-write buffer zhouminqiang
@ 2026-08-29 6:16 ` zhouminqiang
2026-08-29 6:16 ` [PATCH v2 4/7] jffs2: add write verification to direct page writes in flash_writev zhouminqiang
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: zhouminqiang @ 2026-08-29 6:16 UTC (permalink / raw)
To: linux, vz, piotr.wojtaszczyk, maddy, dwmw2, richard
Cc: linux-arm-kernel, linux-kernel, linuxppc-dev, linux-mtd,
chengzhihao1, yangerkun, yi.zhang
jffs2_verify_write() uses memcmp() to compare the write buffer
against data read back from flash. On mismatch, the current code
dumps the entire source and read-back data, which will become
impractical once the verify path is extended to NOR flash where
a single write can span PAGE_SIZE.
Add a byte-by-byte comparison after the memcmp() mismatch path
to locate the exact mismatch offset, reporting the first
differing offset and dumping up to 128 bytes of both the source
and read-back data. memcmp() remains on the hotpath for
successful writes, and the byte-by-byte loop only runs when an
error is actually detected.
Signed-off-by: zhouminqiang <zhouminqiang2@huawei.com>
---
fs/jffs2/wbuf.c | 46 ++++++++++++++++++++++++++++------------------
1 file changed, 28 insertions(+), 18 deletions(-)
diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index f10be57c5543..74e169dedab7 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -231,7 +231,7 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
uint32_t ofs)
{
int ret;
- size_t retlen;
+ size_t retlen, i;
char *eccstr;
void *verify_buf;
@@ -246,10 +246,10 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
pr_warn("%s(): Read back of page at %08x failed: %d\n",
- __func__, c->wbuf_ofs, ret);
+ __func__, ofs, ret);
goto out_free;
} else if (retlen != c->wbuf_pagesize) {
- pr_warn("%s(): Read back of page at %08x gave short read: %zd not %d\n",
+ pr_warn("%s(): Read back of page at %08x gave short read: %zu not %d\n",
__func__, ofs, retlen, c->wbuf_pagesize);
ret = -EIO;
goto out_free;
@@ -259,24 +259,34 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
goto out_free;
}
- if (ret == -EUCLEAN)
- eccstr = "corrected";
- else if (ret == -EBADMSG)
- eccstr = "correction failed";
- else
- eccstr = "OK or unused";
+ for (i = 0; i < c->wbuf_pagesize; i++) {
+ uint8_t c1 = ((uint8_t *)buf)[i];
+ uint8_t c2 = ((uint8_t *)verify_buf)[i];
+ int dump_len;
- pr_warn("Write verify error (ECC %s) at %08x. Wrote:\n",
- eccstr, c->wbuf_ofs);
- print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
- c->wbuf, c->wbuf_pagesize, 0);
+ if (c1 == c2)
+ continue;
- pr_warn("Read back:\n");
- print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
- verify_buf, c->wbuf_pagesize, 0);
+ if (ret == -EUCLEAN)
+ eccstr = "corrected";
+ else if (ret == -EBADMSG)
+ eccstr = "correction failed";
+ else
+ eccstr = "OK or unused";
- kfree(verify_buf);
- return -EIO;
+ dump_len = min_t(int, 128, c->wbuf_pagesize - i);
+ pr_warn("Write verify error (ECC %s) at %08x (+%zu/%d). Wrote:\n",
+ eccstr, ofs, i, c->wbuf_pagesize);
+ print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
+ buf + i, dump_len, 0);
+
+ pr_warn("Read back:\n");
+ print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
+ verify_buf + i, dump_len, 0);
+
+ ret = -EIO;
+ goto out_free;
+ }
out_free:
kfree(verify_buf);
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 4/7] jffs2: add write verification to direct page writes in flash_writev
2026-08-29 6:16 [PATCH v2 0/7] jffs2: extend write verification to all write paths zhouminqiang
` (2 preceding siblings ...)
2026-08-29 6:16 ` [PATCH v2 3/7] jffs2: write verify: add byte-by-byte comparison on mismatch zhouminqiang
@ 2026-08-29 6:16 ` zhouminqiang
2026-08-29 6:16 ` [PATCH v2 5/7] jffs2: add write verification to NOR direct write paths zhouminqiang
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: zhouminqiang @ 2026-08-29 6:16 UTC (permalink / raw)
To: linux, vz, piotr.wojtaszczyk, maddy, dwmw2, richard
Cc: linux-arm-kernel, linux-kernel, linuxppc-dev, linux-mtd,
chengzhihao1, yangerkun, yi.zhang
jffs2_flash_writev() performs writes in three phases: phase 1 fills
the write buffer and flushes it when full, phase 2 writes full pages
directly via mtd_write() bypassing the buffer, and phase 3 fills the
remaining data into the write buffer.
Phases 1 and 3 both invoke __jffs2_flush_wbuf() when the buffer is
full, which includes write-back verification when
CONFIG_JFFS2_FS_WBUF_VERIFY is enabled. However phase 2, which
handles the bulk of the write data, calls mtd_write() directly
without any verification.
Phase 2 direct writes may span multiple wbuf_pagesize pages. Extend
jffs2_verify_write() with a len parameter to specify the data length
to verify, and add the verification call after the phase 2
mtd_write() to cover this gap.
Signed-off-by: zhouminqiang <zhouminqiang2@huawei.com>
---
fs/jffs2/wbuf.c | 35 +++++++++++++++++++++--------------
1 file changed, 21 insertions(+), 14 deletions(-)
diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index 74e169dedab7..81f3538ca258 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -228,38 +228,38 @@ static struct jffs2_raw_node_ref **jffs2_incore_replace_raw(struct jffs2_sb_info
#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
- uint32_t ofs)
+ uint32_t ofs, size_t len)
{
int ret;
size_t retlen, i;
char *eccstr;
void *verify_buf;
- verify_buf = kmalloc(c->wbuf_pagesize, GFP_NOFS);
+ verify_buf = kmalloc(len, GFP_NOFS);
if (!verify_buf) {
pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
__func__);
return 0;
}
- ret = mtd_read(c->mtd, ofs, c->wbuf_pagesize, &retlen, verify_buf);
+ ret = mtd_read(c->mtd, ofs, len, &retlen, verify_buf);
if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
pr_warn("%s(): Read back of page at %08x failed: %d\n",
__func__, ofs, ret);
goto out_free;
- } else if (retlen != c->wbuf_pagesize) {
- pr_warn("%s(): Read back of page at %08x gave short read: %zu not %d\n",
- __func__, ofs, retlen, c->wbuf_pagesize);
+ } else if (retlen != len) {
+ pr_warn("%s(): Read back of page at %08x gave short read: %zu not %zu\n",
+ __func__, ofs, retlen, len);
ret = -EIO;
goto out_free;
}
- if (!memcmp(buf, verify_buf, c->wbuf_pagesize)) {
+ if (!memcmp(buf, verify_buf, len)) {
ret = 0;
goto out_free;
}
- for (i = 0; i < c->wbuf_pagesize; i++) {
+ for (i = 0; i < len; i++) {
uint8_t c1 = ((uint8_t *)buf)[i];
uint8_t c2 = ((uint8_t *)verify_buf)[i];
int dump_len;
@@ -274,9 +274,9 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
else
eccstr = "OK or unused";
- dump_len = min_t(int, 128, c->wbuf_pagesize - i);
- pr_warn("Write verify error (ECC %s) at %08x (+%zu/%d). Wrote:\n",
- eccstr, ofs, i, c->wbuf_pagesize);
+ dump_len = min_t(int, 128, len - i);
+ pr_warn("Write verify error (ECC %s) at %08x (+%zu/%zu). Wrote:\n",
+ eccstr, ofs, i, len);
print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
buf + i, dump_len, 0);
@@ -293,7 +293,7 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
return ret;
}
#else
-#define jffs2_verify_write(c,b,o) (0)
+#define jffs2_verify_write(c,b,o,l) (0)
#endif
/* Recover from failure to write wbuf. Recover the nodes up to the
@@ -458,7 +458,7 @@ static void jffs2_wbuf_recover(struct jffs2_sb_info *c)
ret = mtd_write(c->mtd, ofs, towrite, &retlen,
rewrite_buf);
- if (ret || retlen != towrite || jffs2_verify_write(c, rewrite_buf, ofs)) {
+ if (ret || retlen != towrite || jffs2_verify_write(c, rewrite_buf, ofs, towrite)) {
/* Argh. We tried. Really we did. */
pr_crit("Recovery of wbuf failed due to a second write error. Data loss ensues.\n");
kfree(buf);
@@ -676,7 +676,10 @@ static int __jffs2_flush_wbuf(struct jffs2_sb_info *c, int pad)
retlen, c->wbuf_pagesize);
ret = -EIO;
goto wfail;
- } else if ((ret = jffs2_verify_write(c, c->wbuf, c->wbuf_ofs))) {
+ }
+
+ ret = jffs2_verify_write(c, c->wbuf, c->wbuf_ofs, c->wbuf_pagesize);
+ if (ret) {
wfail:
jffs2_wbuf_recover(c);
@@ -908,6 +911,10 @@ int jffs2_flash_writev(struct jffs2_sb_info *c, const struct kvec *invecs,
if (ret < 0 || wbuf_retlen != PAGE_DIV(vlen))
goto outfile;
+ ret = jffs2_verify_write(c, v, outvec_to, PAGE_DIV(vlen));
+ if (ret)
+ goto outfile;
+
vlen -= wbuf_retlen;
outvec_to += wbuf_retlen;
c->wbuf_ofs = outvec_to;
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 5/7] jffs2: add write verification to NOR direct write paths
2026-08-29 6:16 [PATCH v2 0/7] jffs2: extend write verification to all write paths zhouminqiang
` (3 preceding siblings ...)
2026-08-29 6:16 ` [PATCH v2 4/7] jffs2: add write verification to direct page writes in flash_writev zhouminqiang
@ 2026-08-29 6:16 ` zhouminqiang
2026-08-29 6:16 ` [PATCH v2 6/7] jffs2: rename CONFIG_JFFS2_FS_WBUF_VERIFY to CONFIG_JFFS2_FS_WRITE_VERIFY zhouminqiang
2026-08-29 6:16 ` [PATCH v2 7/7] jffs2: add runtime toggle for write verification zhouminqiang
6 siblings, 0 replies; 8+ messages in thread
From: zhouminqiang @ 2026-08-29 6:16 UTC (permalink / raw)
To: linux, vz, piotr.wojtaszczyk, maddy, dwmw2, richard
Cc: linux-arm-kernel, linux-kernel, linuxppc-dev, linux-mtd,
chengzhihao1, yangerkun, yi.zhang
NOR Flash and other non-writebuffered devices write directly through
jffs2_flash_direct_writev() and jffs2_flash_direct_write() without
any write-back verification. If mtd_write() succeeds but the readable
medium differs from JFFS2's source buffer, a later node CRC failure
cannot distinguish transport/program-time corruption from post-commit
media damage.
Move jffs2_verify_write() from wbuf.c to writev.c so it can be shared
by both writebuffered and direct write paths. Add jffs2_verify_writev()
to iterate over kvec entries and verify each one individually. In both
direct write functions, add mtd_write() return value and retlen checks,
and invoke verification after a successful complete write.
In jffs2_flash_direct_writev(), move the mtd_writev() call before
jffs2_sum_add_kvec() so that *retlen is always set by the MTD layer
first. The original ordering let jffs2_sum_add_kvec() return early on
error without ever touching *retlen, leaving the caller's retlen check
to read an uninitialized value. Keep the same order in
jffs2_flash_direct_write().
Signed-off-by: zhouminqiang <zhouminqiang2@huawei.com>
---
fs/jffs2/os-linux.h | 11 ++++
fs/jffs2/wbuf.c | 70 -------------------------
fs/jffs2/writev.c | 121 +++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 131 insertions(+), 71 deletions(-)
diff --git a/fs/jffs2/os-linux.h b/fs/jffs2/os-linux.h
index 86ab014a349c..e73ef643fd97 100644
--- a/fs/jffs2/os-linux.h
+++ b/fs/jffs2/os-linux.h
@@ -192,6 +192,17 @@ int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
int jffs2_flash_direct_write(struct jffs2_sb_info *c, loff_t ofs, size_t len,
size_t *retlen, const u_char *buf);
+#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
+int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
+ uint32_t ofs, size_t len);
+int jffs2_verify_writev(struct jffs2_sb_info *c,
+ const struct kvec *vecs,
+ unsigned long count, loff_t to);
+#else
+#define jffs2_verify_write(c, b, o, l) (0)
+#define jffs2_verify_writev(c, v, cnt, t) (0)
+#endif
+
#endif /* __JFFS2_OS_LINUX_H__ */
diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index 81f3538ca258..2f4937951a0c 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -226,76 +226,6 @@ static struct jffs2_raw_node_ref **jffs2_incore_replace_raw(struct jffs2_sb_info
return NULL;
}
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
-static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
- uint32_t ofs, size_t len)
-{
- int ret;
- size_t retlen, i;
- char *eccstr;
- void *verify_buf;
-
- verify_buf = kmalloc(len, GFP_NOFS);
- if (!verify_buf) {
- pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
- __func__);
- return 0;
- }
-
- ret = mtd_read(c->mtd, ofs, len, &retlen, verify_buf);
-
- if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
- pr_warn("%s(): Read back of page at %08x failed: %d\n",
- __func__, ofs, ret);
- goto out_free;
- } else if (retlen != len) {
- pr_warn("%s(): Read back of page at %08x gave short read: %zu not %zu\n",
- __func__, ofs, retlen, len);
- ret = -EIO;
- goto out_free;
- }
- if (!memcmp(buf, verify_buf, len)) {
- ret = 0;
- goto out_free;
- }
-
- for (i = 0; i < len; i++) {
- uint8_t c1 = ((uint8_t *)buf)[i];
- uint8_t c2 = ((uint8_t *)verify_buf)[i];
- int dump_len;
-
- if (c1 == c2)
- continue;
-
- if (ret == -EUCLEAN)
- eccstr = "corrected";
- else if (ret == -EBADMSG)
- eccstr = "correction failed";
- else
- eccstr = "OK or unused";
-
- dump_len = min_t(int, 128, len - i);
- pr_warn("Write verify error (ECC %s) at %08x (+%zu/%zu). Wrote:\n",
- eccstr, ofs, i, len);
- print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
- buf + i, dump_len, 0);
-
- pr_warn("Read back:\n");
- print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
- verify_buf + i, dump_len, 0);
-
- ret = -EIO;
- goto out_free;
- }
-
-out_free:
- kfree(verify_buf);
- return ret;
-}
-#else
-#define jffs2_verify_write(c,b,o,l) (0)
-#endif
-
/* Recover from failure to write wbuf. Recover the nodes up to the
* wbuf, not the one which we were starting to try to write. */
diff --git a/fs/jffs2/writev.c b/fs/jffs2/writev.c
index a1bda9dab3f8..2cb8cb030ae3 100644
--- a/fs/jffs2/writev.c
+++ b/fs/jffs2/writev.c
@@ -10,12 +10,121 @@
*/
#include <linux/kernel.h>
+#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include "nodelist.h"
+#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
+int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
+ uint32_t ofs, size_t len)
+{
+ int ret;
+ size_t retlen, i;
+ char *eccstr;
+ void *verify_buf;
+
+ verify_buf = kmalloc(len, GFP_NOFS);
+ if (!verify_buf) {
+ pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
+ __func__);
+ return 0;
+ }
+
+ ret = mtd_read(c->mtd, ofs, len, &retlen, verify_buf);
+
+ if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
+ pr_warn("%s(): Read back of page at %08x failed: %d\n",
+ __func__, ofs, ret);
+ goto out_free;
+ } else if (retlen != len) {
+ pr_warn("%s(): Read back of page at %08x gave short read: %zu not %zu\n",
+ __func__, ofs, retlen, len);
+ ret = -EIO;
+ goto out_free;
+ }
+ if (!memcmp(buf, verify_buf, len)) {
+ ret = 0;
+ goto out_free;
+ }
+
+ for (i = 0; i < len; i++) {
+ uint8_t c1 = ((uint8_t *)buf)[i];
+ uint8_t c2 = ((uint8_t *)verify_buf)[i];
+ int dump_len;
+
+ if (c1 == c2)
+ continue;
+
+ if (ret == -EUCLEAN)
+ eccstr = "corrected";
+ else if (ret == -EBADMSG)
+ eccstr = "correction failed";
+ else
+ eccstr = "OK or unused";
+
+ dump_len = min_t(int, 128, len - i);
+ pr_warn("Write verify error (ECC %s) at %08x (+%zu/%zu). Wrote:\n",
+ eccstr, ofs, i, len);
+ print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
+ buf + i, dump_len, 0);
+
+ pr_warn("Read back:\n");
+ print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
+ verify_buf + i, dump_len, 0);
+
+ ret = -EIO;
+ goto out_free;
+ }
+
+out_free:
+ kfree(verify_buf);
+ return ret;
+}
+
+int jffs2_verify_writev(struct jffs2_sb_info *c,
+ const struct kvec *vecs,
+ unsigned long count, loff_t to)
+{
+ loff_t ofs = to;
+ unsigned long i;
+ int ret;
+
+ for (i = 0; i < count; i++) {
+ if (!vecs[i].iov_len)
+ continue;
+ ret = jffs2_verify_write(c, vecs[i].iov_base, ofs,
+ vecs[i].iov_len);
+ if (ret)
+ return ret;
+ ofs += vecs[i].iov_len;
+ }
+ return 0;
+}
+#endif /* CONFIG_JFFS2_FS_WBUF_VERIFY */
+
int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen)
{
+ int ret;
+
+ ret = mtd_writev(c->mtd, vecs, count, to, retlen);
+
+ if (ret) {
+ pr_warn("%s(): Write failed with %d\n", __func__, ret);
+ } else {
+ size_t totlen = 0;
+ unsigned long i;
+
+ for (i = 0; i < count; i++)
+ totlen += vecs[i].iov_len;
+ if (*retlen != totlen) {
+ pr_warn("%s(): Write was short: %zu instead of %zu\n",
+ __func__, *retlen, totlen);
+ ret = -EIO;
+ } else
+ ret = jffs2_verify_writev(c, vecs, count, to);
+ }
+
if (!jffs2_is_writebuffered(c)) {
if (jffs2_sum_active()) {
int res;
@@ -26,15 +135,25 @@ int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
}
}
- return mtd_writev(c->mtd, vecs, count, to, retlen);
+ return ret;
}
int jffs2_flash_direct_write(struct jffs2_sb_info *c, loff_t ofs, size_t len,
size_t *retlen, const u_char *buf)
{
int ret;
+
ret = mtd_write(c->mtd, ofs, len, retlen, buf);
+ if (ret) {
+ pr_warn("%s(): Write failed with %d\n", __func__, ret);
+ } else if (*retlen != len) {
+ pr_warn("%s(): Write was short: %zu instead of %zu\n",
+ __func__, *retlen, len);
+ ret = -EIO;
+ } else
+ ret = jffs2_verify_write(c, buf, ofs, len);
+
if (jffs2_sum_active()) {
struct kvec vecs[1];
int res;
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 6/7] jffs2: rename CONFIG_JFFS2_FS_WBUF_VERIFY to CONFIG_JFFS2_FS_WRITE_VERIFY
2026-08-29 6:16 [PATCH v2 0/7] jffs2: extend write verification to all write paths zhouminqiang
` (4 preceding siblings ...)
2026-08-29 6:16 ` [PATCH v2 5/7] jffs2: add write verification to NOR direct write paths zhouminqiang
@ 2026-08-29 6:16 ` zhouminqiang
2026-08-29 6:16 ` [PATCH v2 7/7] jffs2: add runtime toggle for write verification zhouminqiang
6 siblings, 0 replies; 8+ messages in thread
From: zhouminqiang @ 2026-08-29 6:16 UTC (permalink / raw)
To: linux, vz, piotr.wojtaszczyk, maddy, dwmw2, richard
Cc: linux-arm-kernel, linux-kernel, linuxppc-dev, linux-mtd,
chengzhihao1, yangerkun, yi.zhang
Write verification now covers all write paths including NOR direct
writes and write-buffer direct page writes, not just the write-buffer
flush path. The Kconfig option also no longer depends on
CONFIG_JFFS2_FS_WRITEBUFFER.
Rename the option from CONFIG_JFFS2_FS_WBUF_VERIFY to
CONFIG_JFFS2_FS_WRITE_VERIFY to reflect its broader scope and
independence from the write-buffer configuration.
Update defconfig files that explicitly enabled the old symbol to use
the new name, so that platforms which previously relied on this
verification continue to have it enabled.
Signed-off-by: zhouminqiang <zhouminqiang2@huawei.com>
---
arch/arm/configs/keystone_defconfig | 2 +-
arch/arm/configs/lpc32xx_defconfig | 2 +-
arch/arm/configs/pxa3xx_defconfig | 2 +-
arch/arm/configs/pxa_defconfig | 2 +-
arch/powerpc/configs/44x/fsp2_defconfig | 2 +-
fs/jffs2/Kconfig | 23 ++++++++++++++++++-----
fs/jffs2/os-linux.h | 2 +-
fs/jffs2/writev.c | 4 ++--
8 files changed, 26 insertions(+), 13 deletions(-)
diff --git a/arch/arm/configs/keystone_defconfig b/arch/arm/configs/keystone_defconfig
index b0cadd878152..d485b8b0b91d 100644
--- a/arch/arm/configs/keystone_defconfig
+++ b/arch/arm/configs/keystone_defconfig
@@ -212,7 +212,7 @@ CONFIG_VFAT_FS=y
CONFIG_NTFS_FS=y
CONFIG_TMPFS=y
CONFIG_JFFS2_FS=y
-CONFIG_JFFS2_FS_WBUF_VERIFY=y
+CONFIG_JFFS2_FS_WRITE_VERIFY=y
CONFIG_UBIFS_FS=y
CONFIG_CRAMFS=y
CONFIG_NFS_FS=y
diff --git a/arch/arm/configs/lpc32xx_defconfig b/arch/arm/configs/lpc32xx_defconfig
index b9e2e603cd95..dd0c6db641be 100644
--- a/arch/arm/configs/lpc32xx_defconfig
+++ b/arch/arm/configs/lpc32xx_defconfig
@@ -164,7 +164,7 @@ CONFIG_MSDOS_FS=y
CONFIG_VFAT_FS=y
CONFIG_TMPFS=y
CONFIG_JFFS2_FS=y
-CONFIG_JFFS2_FS_WBUF_VERIFY=y
+CONFIG_JFFS2_FS_WRITE_VERIFY=y
CONFIG_UBIFS_FS=y
CONFIG_CRAMFS=y
CONFIG_NFS_FS=y
diff --git a/arch/arm/configs/pxa3xx_defconfig b/arch/arm/configs/pxa3xx_defconfig
index fb272e3a2337..f2121269f7c8 100644
--- a/arch/arm/configs/pxa3xx_defconfig
+++ b/arch/arm/configs/pxa3xx_defconfig
@@ -84,7 +84,7 @@ CONFIG_LEDS_TRIGGER_BACKLIGHT=m
CONFIG_LEDS_TRIGGER_GPIO=m
CONFIG_LEDS_TRIGGER_DEFAULT_ON=m
CONFIG_JFFS2_FS=y
-CONFIG_JFFS2_FS_WBUF_VERIFY=y
+CONFIG_JFFS2_FS_WRITE_VERIFY=y
CONFIG_JFFS2_COMPRESSION_OPTIONS=y
CONFIG_JFFS2_LZO=y
CONFIG_JFFS2_RUBIN=y
diff --git a/arch/arm/configs/pxa_defconfig b/arch/arm/configs/pxa_defconfig
index 66cc149c5ca4..e05cbebb0d8b 100644
--- a/arch/arm/configs/pxa_defconfig
+++ b/arch/arm/configs/pxa_defconfig
@@ -596,7 +596,7 @@ CONFIG_TMPFS_POSIX_ACL=y
CONFIG_CONFIGFS_FS=y
CONFIG_JFFS2_FS=m
CONFIG_JFFS2_FS_DEBUG=1
-CONFIG_JFFS2_FS_WBUF_VERIFY=y
+CONFIG_JFFS2_FS_WRITE_VERIFY=y
CONFIG_JFFS2_SUMMARY=y
CONFIG_JFFS2_FS_XATTR=y
CONFIG_JFFS2_COMPRESSION_OPTIONS=y
diff --git a/arch/powerpc/configs/44x/fsp2_defconfig b/arch/powerpc/configs/44x/fsp2_defconfig
index b8b21fa15a07..e626dcb98d34 100644
--- a/arch/powerpc/configs/44x/fsp2_defconfig
+++ b/arch/powerpc/configs/44x/fsp2_defconfig
@@ -97,7 +97,7 @@ CONFIG_EXT4_FS_SECURITY=y
CONFIG_PROC_KCORE=y
CONFIG_TMPFS=y
CONFIG_JFFS2_FS=y
-CONFIG_JFFS2_FS_WBUF_VERIFY=y
+CONFIG_JFFS2_FS_WRITE_VERIFY=y
CONFIG_JFFS2_SUMMARY=y
CONFIG_JFFS2_FS_XATTR=y
CONFIG_CRAMFS=y
diff --git a/fs/jffs2/Kconfig b/fs/jffs2/Kconfig
index 560187d61562..556025a5d438 100644
--- a/fs/jffs2/Kconfig
+++ b/fs/jffs2/Kconfig
@@ -42,13 +42,26 @@ config JFFS2_FS_WRITEBUFFER
- NOR flash with transparent ECC
- DataFlash
-config JFFS2_FS_WBUF_VERIFY
- bool "Verify JFFS2 write-buffer reads"
- depends on JFFS2_FS_WRITEBUFFER
+config JFFS2_FS_WRITE_VERIFY
+ bool "Verify JFFS2 writes"
+ depends on JFFS2_FS
default n
help
- This causes JFFS2 to read back every page written through the
- write-buffer, and check for errors.
+ Read back data immediately after flash writes and compare it
+ with the in-memory image that was written. This covers both
+ write-buffer flushes and direct writes to non-writebuffered
+ devices.
+
+ This may catch corruption introduced after node CRCs are
+ calculated but before/while data is transferred to the flash
+ controller (e.g. RAM or DMA), where mtd_write() may succeed
+ while the medium does not match what JFFS2 intended.
+
+ Without an immediate read-back, a later node CRC failure cannot
+ tell transport/program-time corruption from post-commit media
+ damage.
+
+ If unsure, say 'N'.
config JFFS2_SUMMARY
bool "JFFS2 summary support"
diff --git a/fs/jffs2/os-linux.h b/fs/jffs2/os-linux.h
index e73ef643fd97..65604a6f8148 100644
--- a/fs/jffs2/os-linux.h
+++ b/fs/jffs2/os-linux.h
@@ -192,7 +192,7 @@ int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
int jffs2_flash_direct_write(struct jffs2_sb_info *c, loff_t ofs, size_t len,
size_t *retlen, const u_char *buf);
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
+#ifdef CONFIG_JFFS2_FS_WRITE_VERIFY
int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
uint32_t ofs, size_t len);
int jffs2_verify_writev(struct jffs2_sb_info *c,
diff --git a/fs/jffs2/writev.c b/fs/jffs2/writev.c
index 2cb8cb030ae3..75b4244e93b8 100644
--- a/fs/jffs2/writev.c
+++ b/fs/jffs2/writev.c
@@ -14,7 +14,7 @@
#include <linux/mtd/mtd.h>
#include "nodelist.h"
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
+#ifdef CONFIG_JFFS2_FS_WRITE_VERIFY
int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
uint32_t ofs, size_t len)
{
@@ -100,7 +100,7 @@ int jffs2_verify_writev(struct jffs2_sb_info *c,
}
return 0;
}
-#endif /* CONFIG_JFFS2_FS_WBUF_VERIFY */
+#endif /* CONFIG_JFFS2_FS_WRITE_VERIFY */
int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen)
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 7/7] jffs2: add runtime toggle for write verification
2026-08-29 6:16 [PATCH v2 0/7] jffs2: extend write verification to all write paths zhouminqiang
` (5 preceding siblings ...)
2026-08-29 6:16 ` [PATCH v2 6/7] jffs2: rename CONFIG_JFFS2_FS_WBUF_VERIFY to CONFIG_JFFS2_FS_WRITE_VERIFY zhouminqiang
@ 2026-08-29 6:16 ` zhouminqiang
6 siblings, 0 replies; 8+ messages in thread
From: zhouminqiang @ 2026-08-29 6:16 UTC (permalink / raw)
To: linux, vz, piotr.wojtaszczyk, maddy, dwmw2, richard
Cc: linux-arm-kernel, linux-kernel, linuxppc-dev, linux-mtd,
chengzhihao1, yangerkun, yi.zhang
Write verification is a diagnostic facility that adds read-back
overhead to every write. In production, this overhead is undesirable
unless fault isolation is required.
Add a module parameter jffs2.write_verify (bool, 0644) that defaults
to off when CONFIG_JFFS2_FS_WRITE_VERIFY is enabled. The verification
entry checks READ_ONCE(jffs2_write_verify) and returns immediately
when disabled, avoiding any overhead. The parameter can be toggled at
runtime via /sys/module/jffs2/parameters/write_verify or set at
boot/modprobe time.
Signed-off-by: zhouminqiang <zhouminqiang2@huawei.com>
---
fs/jffs2/Kconfig | 8 ++++++++
fs/jffs2/writev.c | 23 +++++++++++++++++++++++
2 files changed, 31 insertions(+)
diff --git a/fs/jffs2/Kconfig b/fs/jffs2/Kconfig
index 556025a5d438..03dadbd003cd 100644
--- a/fs/jffs2/Kconfig
+++ b/fs/jffs2/Kconfig
@@ -61,6 +61,14 @@ config JFFS2_FS_WRITE_VERIFY
tell transport/program-time corruption from post-commit media
damage.
+ Verification defaults to off when this option is selected and can
+ be enabled at runtime via sysfs:
+
+ /sys/module/jffs2/parameters/write_verify
+
+ Write 0 to disable, 1 to enable. Boot/modprobe parameter
+ jffs2.write_verify=0|1 is also supported.
+
If unsure, say 'N'.
config JFFS2_SUMMARY
diff --git a/fs/jffs2/writev.c b/fs/jffs2/writev.c
index 75b4244e93b8..913183329c54 100644
--- a/fs/jffs2/writev.c
+++ b/fs/jffs2/writev.c
@@ -9,12 +9,32 @@
*
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/kernel.h>
+#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include "nodelist.h"
#ifdef CONFIG_JFFS2_FS_WRITE_VERIFY
+/*
+ * Optional read-back after writes.
+ *
+ * Catch cases where data is corrupted after node CRCs are calculated but
+ * before it is correctly programmed -- e.g. in RAM or during DMA/bus
+ * transfer to the flash controller -- so mtd_write() succeeds while the
+ * medium does not match the in-memory image.
+ *
+ * Runtime toggle: /sys/module/jffs2/parameters/write_verify
+ * (also boot/modprobe: jffs2.write_verify=0|1)
+ */
+static bool jffs2_write_verify;
+module_param_named(write_verify, jffs2_write_verify, bool, 0644);
+MODULE_PARM_DESC(write_verify,
+ "Verify flash writes by reading back (default: N)");
+
+
int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
uint32_t ofs, size_t len)
{
@@ -23,6 +43,9 @@ int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
char *eccstr;
void *verify_buf;
+ if (!READ_ONCE(jffs2_write_verify))
+ return 0;
+
verify_buf = kmalloc(len, GFP_NOFS);
if (!verify_buf) {
pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-29 6:26 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-29 6:16 [PATCH v2 0/7] jffs2: extend write verification to all write paths zhouminqiang
2026-08-29 6:16 ` [PATCH v2 1/7] jffs2: wbuf: clear wbuf on recovery failure paths zhouminqiang
2026-08-29 6:16 ` [PATCH v2 2/7] jffs2: replace per-superblock verify buffer with per-write buffer zhouminqiang
2026-08-29 6:16 ` [PATCH v2 3/7] jffs2: write verify: add byte-by-byte comparison on mismatch zhouminqiang
2026-08-29 6:16 ` [PATCH v2 4/7] jffs2: add write verification to direct page writes in flash_writev zhouminqiang
2026-08-29 6:16 ` [PATCH v2 5/7] jffs2: add write verification to NOR direct write paths zhouminqiang
2026-08-29 6:16 ` [PATCH v2 6/7] jffs2: rename CONFIG_JFFS2_FS_WBUF_VERIFY to CONFIG_JFFS2_FS_WRITE_VERIFY zhouminqiang
2026-08-29 6:16 ` [PATCH v2 7/7] jffs2: add runtime toggle for write verification zhouminqiang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox