[PATCH] fs: jffs2: cache dirent name CRC in the raw node ref

Wagner Popov dos Santos wpopov at gmail.com
Wed Nov 4 11:23:24 PST 2026


jffs2_garbage_collect_deletion_dirent() has to find every obsolete
dirent that carries the same name as the deletion dirent being
collected. To do so it walks the whole raw node list of the parent
inode and, for each obsolete node whose total length matches, reads
the node header back from flash just to compare the name CRC. On NAND
based systems this turns the deletion of a single direntry into a long
sequence of small flash reads, and the cost grows with the number of
obsolete nodes still attached to the directory.

Add the optional CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC, which stores the
dirent name CRC directly in struct jffs2_raw_node_ref. The field is
populated in every path that creates a reference to a dirent node:
jffs2_write_dirent() for newly written names, jffs2_scan_dirent_node()
and jffs2_sum_process_sum_data() during mount, read_direntry() when an
inode is read back, and jffs2_wbuf_recover(), which just carries the
value over to the relocated reference because the node contents do not
change.

The feature costs four extra bytes per raw node reference, so it is
disabled by default and only worth enabling where the memory is less
scarce than the flash bandwidth.

Signed-off-by: Wagner Popov dos Santos <wpopov at gmail.com>
---

On a working partition subject to ongoing writes, new files are created,
existing file contents are modified, and files are deleted. These
operations leave obsolete directory entries that the GC may need to
examine while reclaiming erase blocks. On NAND, this check is performed
by jffs2_garbage_collect_deletion_dirent(), which examines obsolete
dirents to determine whether a deletion dirent can be discarded or is
still needed to delete a matching dirent elsewhere on flash. Caching
the name CRC avoids many small NAND reads during this inspection. The
without this optimization, the function could take several minutes to
return in some cases, causing the corresponding userspace request to
remain blocked for several minutes. The time reduction applies
specifically to this dirent-inspection step, not necessarily to the
total time for all GC activity or partition cleanup. In tests on an
ARM AM3352 platform with NAND flash connected through the GPMC bus, this
step took as little as 2% of its original time in some cases. The
actual improvement depends on the workload and partition state.

diff --git a/fs/jffs2/Kconfig b/fs/jffs2/Kconfig
index 560187d61562..332d005d1731 100644
--- a/fs/jffs2/Kconfig
+++ b/fs/jffs2/Kconfig
@@ -63,6 +63,26 @@ config JFFS2_SUMMARY
 
 	  If unsure, say 'N'.
 
+config JFFS2_FS_CACHE_RAW_NAME_CRC
+	bool "JFFS2 faster GC on cluttered partition"
+	depends on JFFS2_FS
+	default n
+	help
+	  This feature caches a small amount of additional information for
+	  each directory entry, allowing the JFFS2 garbage collector (GC)
+	  to reclaim erase blocks more efficiently and free Flash space for
+	  pending read and write operations.
+
+	  On heavily fragmented or cluttered partitions, this can reduce the
+	  time required for the GC to complete an erase-block reclamation
+	  cycle by a significant margin. The actual benefit depends on the
+	  workload and on how frequently such GC operations occur, so any
+	  improvement may only be noticeable during periods of intensive
+	  Flash activity.
+
+	  This optimization improves GC performance at the cost of a small
+	  amount of additional RAM. If RAM is severely constrained, say 'N'.
+
 config JFFS2_FS_XATTR
 	bool "JFFS2 XATTR support"
 	depends on JFFS2_FS
diff --git a/fs/jffs2/gc.c b/fs/jffs2/gc.c
index 1b833bbffcf5..f4244a46f31a 100644
--- a/fs/jffs2/gc.c
+++ b/fs/jffs2/gc.c
@@ -741,7 +741,19 @@ static int jffs2_garbage_collect_pristine(struct jffs2_sb_info *c,
 			ret = -EIO;
 		goto out_node;
 	}
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+	{
+		struct jffs2_raw_node_ref *new_ref;
+
+		new_ref = jffs2_add_physical_node_ref(c, phys_ofs | REF_PRISTINE, rawlen, ic);
+		/* The node was copied verbatim, so the name CRC is still valid */
+		if (!IS_ERR(new_ref) &&
+		    je16_to_cpu(node->u.nodetype) == JFFS2_NODETYPE_DIRENT)
+			new_ref->name_crc = je32_to_cpu(node->d.name_crc);
+	}
+#else
 	jffs2_add_physical_node_ref(c, phys_ofs | REF_PRISTINE, rawlen, ic);
+#endif
 
 	jffs2_mark_node_obsolete(c, raw);
 	jffs2_dbg(1, "WHEEE! GC REF_PRISTINE node at 0x%08x succeeded\n",
@@ -929,6 +941,13 @@ static int jffs2_garbage_collect_deletion_dirent(struct jffs2_sb_info *c, struct
 			if (!(ref_obsolete(raw)))
 				continue;
 
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+			/* raw->name_crc above zero and differ? It's another file. */
+			if (raw->name_crc && (raw->name_crc != name_crc)) {
+				continue;
+			}
+#endif
+
 			/* Any dirent with the same name is going to have the same length... */
 			if (ref_totlen(c, NULL, raw) != rawlen)
 				continue;
@@ -960,6 +979,14 @@ static int jffs2_garbage_collect_deletion_dirent(struct jffs2_sb_info *c, struct
 			if (je16_to_cpu(rd->nodetype) != JFFS2_NODETYPE_DIRENT)
 				continue;
 
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+			if(!raw->name_crc && je32_to_cpu(rd->name_crc)) {
+				raw->name_crc = je32_to_cpu(rd->name_crc);
+				JFFS2_WARNING("Unspected empty name_crc at %08x\n",
+					ref_offset(raw));
+			}
+#endif
+
 			/* If the name CRC doesn't match, skip */
 			if (je32_to_cpu(rd->name_crc) != name_crc)
 				continue;
diff --git a/fs/jffs2/malloc.c b/fs/jffs2/malloc.c
index 411de8b361b2..6b2f28891ad5 100644
--- a/fs/jffs2/malloc.c
+++ b/fs/jffs2/malloc.c
@@ -175,9 +175,17 @@ static struct jffs2_raw_node_ref *jffs2_alloc_refblock(void)
 		for (i=0; i < REFS_PER_BLOCK; i++) {
 			ret[i].flash_offset = REF_EMPTY_NODE;
 			ret[i].next_in_ino = NULL;
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+			/* Always init name_crc with zero to allow pre-verification
+			 * if the field is being updated in all the proper places. */
+			ret[i].name_crc = 0x00000000;
+#endif
 		}
 		ret[i].flash_offset = REF_LINK_NODE;
 		ret[i].next_in_ino = NULL;
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+		ret[i].name_crc = 0x00000000;
+#endif
 	}
 	return ret;
 }
diff --git a/fs/jffs2/nodelist.h b/fs/jffs2/nodelist.h
index 2e98fa277dab..e21c3d40c7c0 100644
--- a/fs/jffs2/nodelist.h
+++ b/fs/jffs2/nodelist.h
@@ -86,6 +86,11 @@ struct jffs2_raw_node_ref
 		xattr_ref or xattr_datum instead. The common part of those structures
 		has NULL in the first word. See jffs2_raw_ref_to_ic() below */
 	uint32_t flash_offset;
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+	uint32_t name_crc;   /* Used in GC to pre-validation of direntry to avoid flash reading during the
+				process to check if nodes are the same, excepted by the node version number.
+				Zero means 'unknown', in which case the flash still has to be read. */
+#endif
 #undef TEST_TOTLEN
 #ifdef TEST_TOTLEN
 	uint32_t __totlen; /* This may die; use ref_totlen(c, jeb, ) below */
diff --git a/fs/jffs2/readinode.c b/fs/jffs2/readinode.c
index 1caabff9dc91..7615bf5be65a 100644
--- a/fs/jffs2/readinode.c
+++ b/fs/jffs2/readinode.c
@@ -690,6 +690,11 @@ static inline int read_direntry(struct jffs2_sb_info *c, struct jffs2_raw_node_r
 #endif
 	}
 
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+	/* Populate the cache for every dirent, not only partial-name reads */
+	ref->name_crc = je32_to_cpu(rd->name_crc);
+#endif
+
 	fd->nhash = full_name_hash(NULL, fd->name, rd->nsize);
 	fd->next = NULL;
 	fd->name[rd->nsize] = '\0';
diff --git a/fs/jffs2/scan.c b/fs/jffs2/scan.c
index 06e494797724..c063cefd19c3 100644
--- a/fs/jffs2/scan.c
+++ b/fs/jffs2/scan.c
@@ -1104,6 +1104,9 @@ static int jffs2_scan_dirent_node(struct jffs2_sb_info *c, struct jffs2_eraseblo
 
 	fd->raw = jffs2_link_node_ref(c, jeb, ofs | dirent_node_state(rd),
 				      PAD(je32_to_cpu(rd->totlen)), ic);
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+	fd->raw->name_crc = je32_to_cpu(rd->name_crc);
+#endif
 
 	fd->next = NULL;
 	fd->version = je32_to_cpu(rd->version);
diff --git a/fs/jffs2/summary.c b/fs/jffs2/summary.c
index 4521a7723f30..bf4563a08033 100644
--- a/fs/jffs2/summary.c
+++ b/fs/jffs2/summary.c
@@ -472,6 +472,10 @@ static int jffs2_sum_process_sum_data(struct jffs2_sb_info *c, struct jffs2_eras
 
 				fd->raw = sum_link_node_ref(c, jeb,  je32_to_cpu(spd->offset) | REF_UNCHECKED,
 							    PAD(je32_to_cpu(spd->totlen)), ic);
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+				/* Must match the on-flash name_crc, which covers nsize bytes */
+				fd->raw->name_crc = crc32(0, spd->name, spd->nsize);
+#endif
 
 				fd->next = NULL;
 				fd->version = je32_to_cpu(spd->version);
diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index 3b7803c75d58..4d6e2671eaf0 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -526,6 +526,11 @@ static void jffs2_wbuf_recover(struct jffs2_sb_info *c)
 
 		new_ref = jffs2_link_node_ref(c, new_jeb, ofs | ref_flags(raw), rawlen, ic);
 
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+		/* The node contents didn't change, so keep the cached name CRC */
+		new_ref->name_crc = raw->name_crc;
+#endif
+
 		if (adjust_ref) {
 			BUG_ON(*adjust_ref != raw);
 			*adjust_ref = new_ref;
diff --git a/fs/jffs2/write.c b/fs/jffs2/write.c
index cda9a361368e..91f30fb31ea6 100644
--- a/fs/jffs2/write.c
+++ b/fs/jffs2/write.c
@@ -326,6 +326,9 @@ struct jffs2_full_dirent *jffs2_write_dirent(struct jffs2_sb_info *c, struct jff
 		jffs2_free_full_dirent(fd);
 		return ERR_CAST(hold_err);
 	}
+#ifdef CONFIG_JFFS2_FS_CACHE_RAW_NAME_CRC
+	fd->raw->name_crc = crc32(0, name, namelen);
+#endif
 
 	if (retried) {
 		jffs2_dbg_acct_sanity_check(c,NULL);
-- 
2.43.0




More information about the linux-mtd mailing list