@@ -3,7 +3,7 @@
# Makefile for the ext4l filesystem (Linux port)
#
-obj-y := interface.o stub.o
+obj-y := interface.o support.o stub.o
obj-y += balloc.o bitmap.o block_validity.o dir.o ext4_jbd2.o extents.o \
extents_status.o file.o fsmap.o fsync.o hash.o ialloc.o \
@@ -313,9 +313,9 @@ extern struct user_namespace init_user_ns;
#define __bforget(bh) do { } while (0)
#define mark_buffer_dirty_inode(bh, i) do { } while (0)
#define mark_buffer_dirty(bh) do { } while (0)
-#define lock_buffer(bh) do { } while (0)
-#define unlock_buffer(bh) do { } while (0)
-#define sb_getblk(sb, block) ((struct buffer_head *)NULL)
+#define lock_buffer(bh) set_buffer_locked(bh)
+#define unlock_buffer(bh) clear_buffer_locked(bh)
+struct buffer_head *sb_getblk(struct super_block *sb, sector_t block);
#define test_clear_buffer_dirty(bh) ({ (void)(bh); 0; })
#define wait_on_bit_io(addr, bit, mode) do { (void)(addr); (void)(bit); (void)(mode); } while (0)
@@ -1026,7 +1026,7 @@ static inline unsigned long memweight(const void *ptr, size_t bytes)
#define rwsem_is_locked(sem) (1)
/* Buffer operations */
-#define sb_getblk_gfp(sb, blk, gfp) ((struct buffer_head *)NULL)
+#define sb_getblk_gfp(sb, blk, gfp) sb_getblk((sb), (blk))
#define bh_uptodate_or_lock(bh) (1)
/* ext4_read_bh is stubbed in interface.c */
@@ -1870,7 +1870,14 @@ struct file_system_type {
#define FS_ALLOW_IDMAP 32
/* Buffer read sync */
-#define end_buffer_read_sync NULL
+static inline void end_buffer_read_sync(struct buffer_head *bh, int uptodate)
+{
+ if (uptodate)
+ set_buffer_uptodate(bh);
+ else
+ clear_buffer_uptodate(bh);
+ unlock_buffer(bh);
+}
#define REQ_OP_READ 0
/* Superblock flags */
@@ -2377,7 +2384,7 @@ void dquot_free_block(struct inode *inode, loff_t nr);
/* Block device file operations - stubs */
#define set_blocksize(f, size) ({ (void)(f); (void)(size); 0; })
-#define __bread(bdev, block, size) ({ (void)(bdev); (void)(block); (void)(size); (struct buffer_head *)NULL; })
+struct buffer_head *__bread(struct block_device *bdev, sector_t block, unsigned size);
/* Trace stubs for super.c */
#define trace_ext4_sync_fs(sb, wait) do { (void)(sb); (void)(wait); } while (0)
@@ -2823,7 +2830,16 @@ struct wait_bit_entry {
#define filemap_fdatawait_range_keep_errors(m, s, e) \
({ (void)(m); (void)(s); (void)(e); 0; })
#define crc32_be(crc, p, len) crc32(crc, p, len)
-#define free_buffer_head(bh) kfree(bh)
+void free_buffer_head(struct buffer_head *bh);
+
+/* ext4l support functions (support.c) */
+void bh_cache_clear(void);
+int ext4l_read_block(sector_t block, size_t size, void *buffer);
+
+/* ext4l interface functions (interface.c) */
+struct blk_desc *ext4l_get_blk_dev(void);
+struct disk_partition *ext4l_get_partition(void);
+
#define sb_is_blkdev_sb(sb) ({ (void)(sb); 0; })
/* DEFINE_WAIT stub - creates a wait queue entry */
@@ -2850,7 +2866,7 @@ struct wait_bit_entry {
#define trace_jbd2_lock_buffer_stall(...) do { } while (0)
/* JBD2 journal.c stubs */
-#define alloc_buffer_head(gfp) ((struct buffer_head *)kzalloc(sizeof(struct buffer_head), gfp))
+struct buffer_head *alloc_buffer_head(gfp_t gfp_mask);
#define __getblk(bdev, block, size) ({ (void)(bdev); (void)(block); (void)(size); (struct buffer_head *)NULL; })
#define bmap(inode, block) ({ (void)(inode); (void)(block); 0; })
#define trace_jbd2_update_log_tail(j, t, b, f) \
@@ -2897,8 +2913,8 @@ loff_t seq_lseek(struct file *f, loff_t o, int w);
do { (void)(j); (void)(f); } while (0)
/* Block device operations for journal.c */
-#define bh_read(bh, flags) ({ (void)(bh); (void)(flags); 0; })
-#define bh_read_nowait(bh, flags) do { (void)(bh); (void)(flags); } while (0)
+int bh_read(struct buffer_head *bh, int flags);
+#define bh_read_nowait(bh, flags) bh_read(bh, flags)
#define bh_readahead_batch(n, bhs, f) do { (void)(n); (void)(bhs); (void)(f); } while (0)
#define truncate_inode_pages_range(m, s, e) \
do { (void)(m); (void)(s); (void)(e); } while (0)
@@ -5,13 +5,13 @@
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*
- * This provides the minimal interface between U-Boot and the ext4l driver.
+ * This provides the interface between U-Boot's filesystem layer and
+ * the ext4l driver.
*/
#include <blk.h>
#include <part.h>
#include <malloc.h>
-#include <asm/byteorder.h>
#include <linux/errno.h>
#include <linux/jbd2.h>
#include <linux/types.h>
@@ -23,6 +23,58 @@
static struct blk_desc *ext4l_dev_desc;
static struct disk_partition ext4l_part;
+/* Global block device tracking for buffer I/O */
+static struct blk_desc *ext4l_blk_dev;
+static struct disk_partition ext4l_partition;
+static int ext4l_mounted;
+
+/**
+ * ext4l_get_blk_dev() - Get the current block device
+ * Return: Block device descriptor or NULL if not mounted
+ */
+struct blk_desc *ext4l_get_blk_dev(void)
+{
+ if (!ext4l_mounted)
+ return NULL;
+ return ext4l_blk_dev;
+}
+
+/**
+ * ext4l_get_partition() - Get the current partition info
+ * Return: Partition info pointer
+ */
+struct disk_partition *ext4l_get_partition(void)
+{
+ return &ext4l_partition;
+}
+
+/**
+ * ext4l_set_blk_dev() - Set the block device for ext4l operations
+ * @blk_dev: Block device descriptor
+ * @partition: Partition info (can be NULL for whole disk)
+ */
+void ext4l_set_blk_dev(struct blk_desc *blk_dev, struct disk_partition *partition)
+{
+ ext4l_blk_dev = blk_dev;
+ if (partition)
+ memcpy(&ext4l_partition, partition, sizeof(struct disk_partition));
+ else
+ memset(&ext4l_partition, 0, sizeof(struct disk_partition));
+ ext4l_mounted = 1;
+}
+
+/**
+ * ext4l_clear_blk_dev() - Clear block device (unmount)
+ */
+void ext4l_clear_blk_dev(void)
+{
+ /* Clear buffer cache before unmounting */
+ bh_cache_clear();
+
+ ext4l_blk_dev = NULL;
+ ext4l_mounted = 0;
+}
+
int ext4l_probe(struct blk_desc *fs_dev_desc,
struct disk_partition *fs_partition)
{
@@ -37,6 +89,9 @@ int ext4l_probe(struct blk_desc *fs_dev_desc,
if (!fs_dev_desc)
return -EINVAL;
+ /* Set up block device for buffer I/O */
+ ext4l_set_blk_dev(fs_dev_desc, fs_partition);
+
/* Initialise journal subsystem if enabled */
if (IS_ENABLED(CONFIG_EXT4_JOURNAL)) {
ret = jbd2_journal_init_global();
@@ -162,5 +217,6 @@ err_exit_es:
void ext4l_close(void)
{
+ ext4l_clear_blk_dev();
ext4l_dev_desc = NULL;
}
@@ -309,20 +309,14 @@ void *bdev_file_open_by_dev(dev_t dev, int flags, void *holder,
return ERR_PTR(-ENODEV);
}
-struct buffer_head *bdev_getblk(struct block_device *bdev, sector_t block,
- unsigned int size, gfp_t gfp)
-{
- return NULL;
-}
+/* bdev_getblk implemented in interface.c */
int trylock_buffer(struct buffer_head *bh)
{
return 1;
}
-void submit_bh(int op, struct buffer_head *bh)
-{
-}
+/* submit_bh implemented in interface.c */
/* NFS export stubs */
struct dentry *generic_fh_to_parent(struct super_block *sb, struct fid *fid,
@@ -519,6 +513,14 @@ void fs_put_dax(void *dax, void *holder)
/* Block size */
int sb_set_blocksize(struct super_block *sb, int size)
{
+ /* Validate block size */
+ if (size != 1024 && size != 2048 && size != 4096)
+ return 0;
+
+ /* Update superblock fields */
+ sb->s_blocksize = size;
+ sb->s_blocksize_bits = ffs(size) - 1;
+
return size;
}
new file mode 100644
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Internal support functions for ext4l filesystem
+ *
+ * Copyright 2025 Canonical Ltd
+ * Written by Simon Glass <simon.glass@canonical.com>
+ *
+ * This provides internal support functions for the ext4l driver,
+ * including buffer_head I/O and buffer cache.
+ */
+
+#include <blk.h>
+#include <part.h>
+#include <malloc.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+
+#include "ext4_uboot.h"
+#include "ext4.h"
+
+/*
+ * Buffer cache implementation
+ *
+ * Linux's sb_getblk() returns the same buffer_head for the same block number,
+ * allowing flags like BH_Verified, BH_Uptodate, etc. to persist across calls.
+ * This is critical for ext4's bitmap validation which sets buffer_verified()
+ * and expects it to remain set on subsequent lookups.
+ */
+#define BH_CACHE_BITS 8
+#define BH_CACHE_SIZE (1 << BH_CACHE_BITS)
+#define BH_CACHE_MASK (BH_CACHE_SIZE - 1)
+
+struct bh_cache_entry {
+ struct buffer_head *bh;
+ struct bh_cache_entry *next;
+};
+
+static struct bh_cache_entry *bh_cache[BH_CACHE_SIZE];
+
+static inline unsigned int bh_cache_hash(sector_t block)
+{
+ return (unsigned int)(block & BH_CACHE_MASK);
+}
+
+/**
+ * bh_cache_lookup() - Look up a buffer in the cache
+ * @block: Block number to look up
+ * @size: Expected block size
+ * Return: Buffer head if found with matching size, NULL otherwise
+ */
+static struct buffer_head *bh_cache_lookup(sector_t block, size_t size)
+{
+ unsigned int hash = bh_cache_hash(block);
+ struct bh_cache_entry *entry;
+
+ for (entry = bh_cache[hash]; entry; entry = entry->next) {
+ if (entry->bh && entry->bh->b_blocknr == block &&
+ entry->bh->b_size == size) {
+ atomic_inc(&entry->bh->b_count);
+ return entry->bh;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * bh_cache_insert() - Insert a buffer into the cache
+ * @bh: Buffer head to insert
+ */
+static void bh_cache_insert(struct buffer_head *bh)
+{
+ unsigned int hash = bh_cache_hash(bh->b_blocknr);
+ struct bh_cache_entry *entry;
+
+ /* Check if already in cache */
+ for (entry = bh_cache[hash]; entry; entry = entry->next) {
+ if (entry->bh && entry->bh->b_blocknr == bh->b_blocknr)
+ return; /* Already cached */
+ }
+
+ entry = malloc(sizeof(struct bh_cache_entry));
+ if (!entry)
+ return; /* Silently fail - cache is optional */
+
+ entry->bh = bh;
+ entry->next = bh_cache[hash];
+ bh_cache[hash] = entry;
+
+ /* Add a reference to keep the buffer alive in cache */
+ atomic_inc(&bh->b_count);
+}
+
+/**
+ * bh_cache_clear() - Clear the entire buffer cache
+ *
+ * Called on unmount to free all cached buffers.
+ */
+void bh_cache_clear(void)
+{
+ int i;
+ struct bh_cache_entry *entry, *next;
+
+ for (i = 0; i < BH_CACHE_SIZE; i++) {
+ for (entry = bh_cache[i]; entry; entry = next) {
+ next = entry->next;
+ if (entry->bh) {
+ /* Release the cache's reference */
+ if (atomic_dec_and_test(&entry->bh->b_count))
+ free_buffer_head(entry->bh);
+ }
+ free(entry);
+ }
+ bh_cache[i] = NULL;
+ }
+}
+
+/**
+ * alloc_buffer_head() - Allocate a buffer_head structure
+ * @gfp_mask: Allocation flags (ignored in U-Boot)
+ * Return: Pointer to buffer_head or NULL on error
+ */
+struct buffer_head *alloc_buffer_head(gfp_t gfp_mask)
+{
+ struct buffer_head *bh;
+
+ bh = malloc(sizeof(struct buffer_head));
+ if (!bh)
+ return NULL;
+
+ memset(bh, 0, sizeof(struct buffer_head));
+
+ /* Note: b_data will be allocated when needed by read functions */
+ atomic_set(&bh->b_count, 1);
+
+ return bh;
+}
+
+/**
+ * alloc_buffer_head_with_data() - Allocate a buffer_head with data buffer
+ * @size: Size of the data buffer to allocate
+ * Return: Pointer to buffer_head or NULL on error
+ */
+static struct buffer_head *alloc_buffer_head_with_data(size_t size)
+{
+ struct buffer_head *bh;
+
+ bh = malloc(sizeof(struct buffer_head));
+ if (!bh)
+ return NULL;
+
+ memset(bh, 0, sizeof(struct buffer_head));
+
+ bh->b_data = malloc(size);
+ if (!bh->b_data) {
+ free(bh);
+ return NULL;
+ }
+
+ bh->b_size = size;
+ /* Allocate a folio for kmap_local_folio() to work */
+ bh->b_folio = malloc(sizeof(struct folio));
+ if (bh->b_folio) {
+ memset(bh->b_folio, 0, sizeof(struct folio));
+ bh->b_folio->data = bh->b_data;
+ }
+ atomic_set(&bh->b_count, 1);
+ /* Mark that this buffer owns its b_data and should free it */
+ set_bit(BH_OwnsData, &bh->b_state);
+
+ return bh;
+}
+
+/**
+ * free_buffer_head() - Free a buffer_head
+ * @bh: Buffer head to free
+ *
+ * Only free b_data if BH_OwnsData is set. Shadow buffers created by
+ * jbd2_journal_write_metadata_buffer() share b_data with the original
+ * buffer and should not free it.
+ */
+void free_buffer_head(struct buffer_head *bh)
+{
+ if (!bh)
+ return;
+
+ /* Only free b_data if this buffer owns it */
+ if (bh->b_data && test_bit(BH_OwnsData, &bh->b_state))
+ free(bh->b_data);
+ if (bh->b_folio)
+ free(bh->b_folio);
+ free(bh);
+}
+
+/**
+ * ext4l_read_block() - Read a block from the block device
+ * @block: Block number (filesystem block, not sector)
+ * @size: Block size in bytes
+ * @buffer: Destination buffer
+ * Return: 0 on success, negative on error
+ */
+int ext4l_read_block(sector_t block, size_t size, void *buffer)
+{
+ struct blk_desc *blk_dev;
+ struct disk_partition *part;
+ lbaint_t sector;
+ lbaint_t sector_count;
+ unsigned long n;
+
+ blk_dev = ext4l_get_blk_dev();
+ part = ext4l_get_partition();
+ if (!blk_dev)
+ return -EIO;
+
+ /* Convert block to sector */
+ sector = (block * size) / blk_dev->blksz + part->start;
+ sector_count = size / blk_dev->blksz;
+
+ if (sector_count == 0)
+ sector_count = 1;
+
+ n = blk_dread(blk_dev, sector, sector_count, buffer);
+ if (n != sector_count)
+ return -EIO;
+
+ return 0;
+}
+
+/**
+ * sb_getblk() - Get a buffer, using cache if available
+ * @sb: Super block
+ * @block: Block number
+ * Return: Buffer head or NULL on error
+ */
+struct buffer_head *sb_getblk(struct super_block *sb, sector_t block)
+{
+ struct buffer_head *bh;
+
+ if (!sb)
+ return NULL;
+
+ /* Check cache first - must match block number AND size */
+ bh = bh_cache_lookup(block, sb->s_blocksize);
+ if (bh)
+ return bh;
+
+ /* Allocate new buffer */
+ bh = alloc_buffer_head_with_data(sb->s_blocksize);
+ if (!bh)
+ return NULL;
+
+ bh->b_blocknr = block;
+ bh->b_bdev = sb->s_bdev;
+ bh->b_size = sb->s_blocksize;
+
+ /* Don't read - just allocate with zeroed data */
+ memset(bh->b_data, '\0', bh->b_size);
+
+ /* Add to cache */
+ bh_cache_insert(bh);
+
+ return bh;
+}
+
+/**
+ * sb_bread() - Read a block via super_block
+ * @sb: Super block
+ * @block: Block number to read
+ * Return: Buffer head or NULL on error
+ */
+struct buffer_head *sb_bread(struct super_block *sb, sector_t block)
+{
+ struct buffer_head *bh;
+ int ret;
+
+ if (!sb)
+ return NULL;
+
+ bh = sb_getblk(sb, block);
+ if (!bh)
+ return NULL;
+
+ /* If buffer is already up-to-date, return it without re-reading */
+ if (buffer_uptodate(bh))
+ return bh;
+
+ bh->b_blocknr = block;
+ bh->b_bdev = sb->s_bdev;
+ bh->b_size = sb->s_blocksize;
+
+ ret = ext4l_read_block(block, sb->s_blocksize, bh->b_data);
+ if (ret) {
+ brelse(bh);
+ return NULL;
+ }
+
+ /* Mark buffer as up-to-date */
+ set_buffer_uptodate(bh);
+
+ return bh;
+}
+
+/**
+ * brelse() - Release a buffer_head
+ * @bh: Buffer head to release
+ */
+void brelse(struct buffer_head *bh)
+{
+ if (!bh)
+ return;
+
+ if (atomic_dec_and_test(&bh->b_count))
+ free_buffer_head(bh);
+}
+
+/**
+ * __brelse() - Release a buffer_head (alternate API)
+ * @bh: Buffer head to release
+ */
+void __brelse(struct buffer_head *bh)
+{
+ brelse(bh);
+}
+
+/**
+ * bdev_getblk() - Get buffer via block_device
+ * @bdev: Block device
+ * @block: Block number
+ * @size: Block size
+ * @gfp: Allocation flags
+ * Return: Buffer head or NULL
+ */
+struct buffer_head *bdev_getblk(struct block_device *bdev, sector_t block,
+ unsigned size, gfp_t gfp)
+{
+ struct buffer_head *bh;
+
+ /* Check cache first - must match block number AND size */
+ bh = bh_cache_lookup(block, size);
+ if (bh)
+ return bh;
+
+ bh = alloc_buffer_head_with_data(size);
+ if (!bh)
+ return NULL;
+
+ bh->b_blocknr = block;
+ bh->b_bdev = bdev;
+ bh->b_size = size;
+
+ /* Don't read - just allocate with zeroed data */
+ memset(bh->b_data, 0, bh->b_size);
+
+ /* Add to cache */
+ bh_cache_insert(bh);
+
+ return bh;
+}
+
+/**
+ * __bread() - Read a block via block_device
+ * @bdev: Block device
+ * @block: Block number to read
+ * @size: Block size
+ * Return: Buffer head or NULL on error
+ */
+struct buffer_head *__bread(struct block_device *bdev, sector_t block,
+ unsigned size)
+{
+ struct buffer_head *bh;
+ int ret;
+
+ bh = alloc_buffer_head_with_data(size);
+ if (!bh)
+ return NULL;
+
+ bh->b_blocknr = block;
+ bh->b_bdev = bdev;
+ bh->b_size = size;
+
+ ret = ext4l_read_block(block, size, bh->b_data);
+ if (ret) {
+ free_buffer_head(bh);
+ return NULL;
+ }
+
+ /* Mark buffer as up-to-date */
+ set_bit(BH_Uptodate, &bh->b_state);
+
+ return bh;
+}
+
+/**
+ * submit_bh() - Submit a buffer_head for I/O
+ * @op: Operation (REQ_OP_READ, REQ_OP_WRITE, etc.)
+ * @bh: Buffer head to submit
+ */
+void submit_bh(int op, struct buffer_head *bh)
+{
+ int ret;
+ int op_type = op & 0xff; /* Mask out flags, keep operation type */
+
+ if (op_type == REQ_OP_READ) {
+ ret = ext4l_read_block(bh->b_blocknr, bh->b_size, bh->b_data);
+ if (ret) {
+ clear_buffer_uptodate(bh);
+ return;
+ }
+ set_buffer_uptodate(bh);
+ } else if (op_type == REQ_OP_WRITE) {
+ /* Write support not implemented yet */
+ clear_buffer_uptodate(bh);
+ }
+}
+
+/**
+ * bh_read() - Read a buffer_head from disk
+ * @bh: Buffer head to read
+ * @flags: Read flags
+ * Return: 0 on success, negative on error
+ */
+int bh_read(struct buffer_head *bh, int flags)
+{
+ if (!bh || !bh->b_data)
+ return -EINVAL;
+
+ submit_bh(REQ_OP_READ | flags, bh);
+ return buffer_uptodate(bh) ? 0 : -EIO;
+}
@@ -40,6 +40,8 @@ enum bh_state_bits {
BH_PrivateStart,/* not a state bit, but the first bit available
* for private allocation by other entities
*/
+ /* U-Boot specific: marks buffer owns b_data and should free it */
+ BH_OwnsData = BH_PrivateStart,
};
#define MAX_BUF_PER_PAGE (PAGE_SIZE / 512)
@@ -176,8 +178,8 @@ static inline void put_bh(struct buffer_head *bh)
atomic_dec(&bh->b_count);
}
-/* Stubs for U-Boot */
-#define brelse(bh) do { if (bh) put_bh(bh); } while (0)
-#define __brelse(bh) do { put_bh(bh); } while (0)
+/* Buffer release functions - implemented in ext4l/interface.c */
+void brelse(struct buffer_head *bh);
+void __brelse(struct buffer_head *bh);
#endif /* _LINUX_BUFFER_HEAD_H */