fs: ext4: cache extent data

When a file contains extents, U-Boot currently reads extent-related data
for each block in the file, even if that data is located in the same
block each time. This significantly slows down loading of files that use
extents. Implement a very dumb cache to prevent repeatedly reading the
same block. Files with extents now load as fast as files without.

Note: There are many cases where read_allocated_block() is called. This
patch only addresses one of those places; all others still read redundant
data in any case they did before. This is a minimal patch to fix the
load command; other cases aren't fixed.

Signed-off-by: Stephen Warren <swarren@nvidia.com>
diff --git a/fs/ext4/ext4_common.c b/fs/ext4/ext4_common.c
index 67e2471..29308e3 100644
--- a/fs/ext4/ext4_common.c
+++ b/fs/ext4/ext4_common.c
@@ -510,7 +510,8 @@
 
 restart_read:
 	/* read the block no allocated to a file */
-	first_block_no_of_root = read_allocated_block(g_parent_inode, blk_idx);
+	first_block_no_of_root = read_allocated_block(g_parent_inode, blk_idx,
+						      NULL);
 	if (first_block_no_of_root <= 0)
 		goto fail;
 
@@ -646,7 +647,7 @@
 
 	/* get the block no allocated to a file */
 	for (blk_idx = 0; blk_idx < directory_blocks; blk_idx++) {
-		blknr = read_allocated_block(parent_inode, blk_idx);
+		blknr = read_allocated_block(parent_inode, blk_idx, NULL);
 		if (blknr <= 0)
 			goto fail;
 
@@ -943,7 +944,7 @@
 
 	/* read the block no allocated to a file */
 	for (blk_idx = 0; blk_idx < directory_blocks; blk_idx++) {
-		blknr = read_allocated_block(g_parent_inode, blk_idx);
+		blknr = read_allocated_block(g_parent_inode, blk_idx, NULL);
 		if (blknr <= 0)
 			break;
 		inodeno = unlink_filename(filename, blknr);
@@ -1522,7 +1523,7 @@
 #endif
 
 static struct ext4_extent_header *ext4fs_get_extent_block
-	(struct ext2_data *data, char *buf,
+	(struct ext2_data *data, struct ext_block_cache *cache,
 		struct ext4_extent_header *ext_block,
 		uint32_t fileblock, int log2_blksz)
 {
@@ -1551,12 +1552,10 @@
 
 		block = le16_to_cpu(index[i].ei_leaf_hi);
 		block = (block << 32) + le32_to_cpu(index[i].ei_leaf_lo);
-
-		if (ext4fs_devread((lbaint_t)block << log2_blksz, 0, blksz,
-				   buf))
-			ext_block = (struct ext4_extent_header *)buf;
-		else
+		block <<= log2_blksz;
+		if (!ext_cache_read(cache, (lbaint_t)block, blksz))
 			return NULL;
+		ext_block = (struct ext4_extent_header *)cache->buf;
 	}
 }
 
@@ -1613,7 +1612,8 @@
 	return 1;
 }
 
-long int read_allocated_block(struct ext2_inode *inode, int fileblock)
+long int read_allocated_block(struct ext2_inode *inode, int fileblock,
+			      struct ext_block_cache *cache)
 {
 	long int blknr;
 	int blksz;
@@ -1630,20 +1630,26 @@
 
 	if (le32_to_cpu(inode->flags) & EXT4_EXTENTS_FL) {
 		long int startblock, endblock;
-		char *buf = zalloc(blksz);
-		if (!buf)
-			return -ENOMEM;
+		struct ext_block_cache *c, cd;
 		struct ext4_extent_header *ext_block;
 		struct ext4_extent *extent;
 		int i;
+
+		if (cache) {
+			c = cache;
+		} else {
+			c = &cd;
+			ext_cache_init(c);
+		}
 		ext_block =
-			ext4fs_get_extent_block(ext4fs_root, buf,
+			ext4fs_get_extent_block(ext4fs_root, c,
 						(struct ext4_extent_header *)
 						inode->b.blocks.dir_blocks,
 						fileblock, log2_blksz);
 		if (!ext_block) {
 			printf("invalid extent block\n");
-			free(buf);
+			if (!cache)
+				ext_cache_fini(c);
 			return -EINVAL;
 		}
 
@@ -1655,19 +1661,22 @@
 
 			if (startblock > fileblock) {
 				/* Sparse file */
-				free(buf);
+				if (!cache)
+					ext_cache_fini(c);
 				return 0;
 
 			} else if (fileblock < endblock) {
 				start = le16_to_cpu(extent[i].ee_start_hi);
 				start = (start << 32) +
 					le32_to_cpu(extent[i].ee_start_lo);
-				free(buf);
+				if (!cache)
+					ext_cache_fini(c);
 				return (fileblock - startblock) + start;
 			}
 		}
 
-		free(buf);
+		if (!cache)
+			ext_cache_fini(c);
 		return 0;
 	}
 
diff --git a/fs/ext4/ext4_journal.c b/fs/ext4/ext4_journal.c
index 148593d..6adbab9 100644
--- a/fs/ext4/ext4_journal.c
+++ b/fs/ext4/ext4_journal.c
@@ -347,7 +347,7 @@
 	ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO,
 			  (struct ext2_inode *)&inode_journal);
 	blknr = read_allocated_block((struct ext2_inode *)
-				     &inode_journal, i);
+				     &inode_journal, i, NULL);
 	ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0, fs->blksz,
 		       temp_buff);
 	p_jdb = (char *)temp_buff;
@@ -372,7 +372,7 @@
 				be32_to_cpu(jdb->h_sequence)) == 0)
 				continue;
 		}
-		blknr = read_allocated_block(&inode_journal, i);
+		blknr = read_allocated_block(&inode_journal, i, NULL);
 		ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0,
 			       fs->blksz, metadata_buff);
 		put_ext4((uint64_t)((uint64_t)be32_to_cpu(tag->block) * (uint64_t)fs->blksz),
@@ -419,7 +419,8 @@
 	}
 
 	ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, &inode_journal);
-	blknr = read_allocated_block(&inode_journal, EXT2_JOURNAL_SUPERBLOCK);
+	blknr = read_allocated_block(&inode_journal, EXT2_JOURNAL_SUPERBLOCK,
+				     NULL);
 	ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0, fs->blksz,
 		       temp_buff);
 	jsb = (struct journal_superblock_t *) temp_buff;
@@ -443,7 +444,7 @@
 
 	i = be32_to_cpu(jsb->s_first);
 	while (1) {
-		blknr = read_allocated_block(&inode_journal, i);
+		blknr = read_allocated_block(&inode_journal, i, NULL);
 		memset(temp_buff1, '\0', fs->blksz);
 		ext4fs_devread((lbaint_t)blknr * fs->sect_perblk,
 			       0, fs->blksz, temp_buff1);
@@ -537,7 +538,7 @@
 		ext4_read_superblock((char *)fs->sb);
 
 		blknr = read_allocated_block(&inode_journal,
-					 EXT2_JOURNAL_SUPERBLOCK);
+					 EXT2_JOURNAL_SUPERBLOCK, NULL);
 		put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz),
 			 (struct journal_superblock_t *)temp_buff,
 			 (uint32_t) fs->blksz);
@@ -566,7 +567,7 @@
 
 	ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, &inode_journal);
 	jsb_blknr = read_allocated_block(&inode_journal,
-					 EXT2_JOURNAL_SUPERBLOCK);
+					 EXT2_JOURNAL_SUPERBLOCK, NULL);
 	ext4fs_devread((lbaint_t)jsb_blknr * fs->sect_perblk, 0, fs->blksz,
 		       temp_buff);
 	jsb = (struct journal_superblock_t *) temp_buff;
@@ -618,7 +619,7 @@
 	ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO,
 			  &inode_journal);
 	jsb_blknr = read_allocated_block(&inode_journal,
-					 EXT2_JOURNAL_SUPERBLOCK);
+					 EXT2_JOURNAL_SUPERBLOCK, NULL);
 	ext4fs_devread((lbaint_t)jsb_blknr * fs->sect_perblk, 0, fs->blksz,
 		       temp_buff);
 	jsb = (struct journal_superblock_t *) temp_buff;
@@ -645,16 +646,17 @@
 	long int blknr;
 	int i;
 	ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, &inode_journal);
-	blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++);
+	blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++, NULL);
 	update_descriptor_block(blknr);
 	for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) {
 		if (journal_ptr[i]->blknr == -1)
 			break;
-		blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++);
+		blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++,
+					     NULL);
 		put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz),
 			 journal_ptr[i]->buf, fs->blksz);
 	}
-	blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++);
+	blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++, NULL);
 	update_commit_block(blknr);
 	printf("update journal finished\n");
 }
diff --git a/fs/ext4/ext4_write.c b/fs/ext4/ext4_write.c
index 4eb77c3..95ffa3d 100644
--- a/fs/ext4/ext4_write.c
+++ b/fs/ext4/ext4_write.c
@@ -479,7 +479,7 @@
 
 	/* release data blocks */
 	for (i = 0; i < no_blocks; i++) {
-		blknr = read_allocated_block(&inode, i);
+		blknr = read_allocated_block(&inode, i, NULL);
 		if (blknr == 0)
 			continue;
 		if (blknr < 0)
@@ -695,7 +695,7 @@
 		ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO,
 				  &inode_journal);
 		blknr = read_allocated_block(&inode_journal,
-					EXT2_JOURNAL_SUPERBLOCK);
+					EXT2_JOURNAL_SUPERBLOCK, NULL);
 		ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0, fs->blksz,
 			       temp_buff);
 		jsb = (struct journal_superblock_t *)temp_buff;
@@ -776,7 +776,7 @@
 		long int blknr;
 		int blockend = fs->blksz;
 		int skipfirst = 0;
-		blknr = read_allocated_block(file_inode, i);
+		blknr = read_allocated_block(file_inode, i, NULL);
 		if (blknr <= 0)
 			return -1;
 
diff --git a/fs/ext4/ext4fs.c b/fs/ext4/ext4fs.c
index 2a28031..26db677 100644
--- a/fs/ext4/ext4fs.c
+++ b/fs/ext4/ext4fs.c
@@ -62,6 +62,9 @@
 	lbaint_t delayed_next = 0;
 	char *delayed_buf = NULL;
 	short status;
+	struct ext_block_cache cache;
+
+	ext_cache_init(&cache);
 
 	if (blocksize <= 0)
 		return -1;
@@ -77,9 +80,11 @@
 		int blockoff = pos - (blocksize * i);
 		int blockend = blocksize;
 		int skipfirst = 0;
-		blknr = read_allocated_block(&(node->inode), i);
-		if (blknr < 0)
+		blknr = read_allocated_block(&node->inode, i, &cache);
+		if (blknr < 0) {
+			ext_cache_fini(&cache);
 			return -1;
+		}
 
 		blknr = blknr << log2_fs_blocksize;
 
@@ -109,8 +114,10 @@
 							delayed_skipfirst,
 							delayed_extent,
 							delayed_buf);
-					if (status == 0)
+					if (status == 0) {
+						ext_cache_fini(&cache);
 						return -1;
+					}
 					previous_block_number = blknr;
 					delayed_start = blknr;
 					delayed_extent = blockend;
@@ -136,8 +143,10 @@
 							delayed_skipfirst,
 							delayed_extent,
 							delayed_buf);
-				if (status == 0)
+				if (status == 0) {
+					ext_cache_fini(&cache);
 					return -1;
+				}
 				previous_block_number = -1;
 			}
 			/* Zero no more than `len' bytes. */
@@ -153,12 +162,15 @@
 		status = ext4fs_devread(delayed_start,
 					delayed_skipfirst, delayed_extent,
 					delayed_buf);
-		if (status == 0)
+		if (status == 0) {
+			ext_cache_fini(&cache);
 			return -1;
+		}
 		previous_block_number = -1;
 	}
 
 	*actread  = len;
+	ext_cache_fini(&cache);
 	return 0;
 }
 
@@ -252,3 +264,32 @@
 	return -ENOSYS;
 #endif
 }
+
+void ext_cache_init(struct ext_block_cache *cache)
+{
+	memset(cache, 0, sizeof(*cache));
+}
+
+void ext_cache_fini(struct ext_block_cache *cache)
+{
+	free(cache->buf);
+	ext_cache_init(cache);
+}
+
+int ext_cache_read(struct ext_block_cache *cache, lbaint_t block, int size)
+{
+	/* This could be more lenient, but this is simple and enough for now */
+	if (cache->buf && cache->block == block && cache->size == size)
+		return 1;
+	ext_cache_fini(cache);
+	cache->buf = malloc(size);
+	if (!cache->buf)
+		return 0;
+	if (!ext4fs_devread(block, 0, size, cache->buf)) {
+		free(cache->buf);
+		return 0;
+	}
+	cache->block = block;
+	cache->size = size;
+	return 1;
+}