From: Simon Glass <simon.glass@canonical.com> Add more complete buffer I/O infrastructure so that we can support writing to an ext4 filesystem. - end_buffer_write_sync(): I/O completion callback for sync writes - submit_bh(): Add the write path with b_end_io callback invocation - __getblk(): Allocate journal-descriptor buffers - free_buffer_head(): Don't free shared folios from shadow buffers - __brelse(): Only decrement refcount, don't free - bh_cache_sync(): Sync all dirty buffers to disk - sync_dirty_buffer()/mark_buffer_dirty(): Write buffer immediately The shadow-buffer fix is needed for jbd2 journaling: shadow buffers created by jbd2_journal_write_metadata_buffer() share their folio with the original buffer (as indicated by b_private). With this, the buffer I/O layer is ready for ext4 write support. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 15 +++-- fs/ext4l/support.c | 136 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 133 insertions(+), 18 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index d7053c11d31..f4831a9b5c2 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -328,8 +328,8 @@ extern struct user_namespace init_user_ns; /* Buffer operations - stubs */ #define wait_on_buffer(bh) do { } while (0) #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 mark_buffer_dirty_inode(bh, i) sync_dirty_buffer(bh) +#define mark_buffer_dirty(bh) sync_dirty_buffer(bh) #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); @@ -399,7 +399,7 @@ struct buffer_head *sb_getblk(struct super_block *sb, sector_t block); /* Buffer cache operations */ #define sb_find_get_block(sb, block) ((struct buffer_head *)NULL) -#define sync_dirty_buffer(bh) ({ (void)(bh); 0; }) +#define sync_dirty_buffer(bh) submit_bh(REQ_OP_WRITE, bh) /* Time functions */ #define ktime_get_real_seconds() (0) @@ -2181,8 +2181,8 @@ struct blk_holder_ops { }; static const struct blk_holder_ops fs_holder_ops; -/* end_buffer_write_sync */ -#define end_buffer_write_sync NULL +/* end_buffer_write_sync - implemented in support.c */ +void end_buffer_write_sync(struct buffer_head *bh, int uptodate); /* File system management time flag */ #define FS_MGTIME 0 @@ -2886,7 +2886,9 @@ void free_buffer_head(struct buffer_head *bh); /* ext4l support functions (support.c) */ void ext4l_crc32c_init(void); void bh_cache_clear(void); +int bh_cache_sync(void); int ext4l_read_block(sector_t block, size_t size, void *buffer); +int ext4l_write_block(sector_t block, size_t size, void *buffer); /* ext4l interface functions (interface.c) */ struct blk_desc *ext4l_get_blk_dev(void); @@ -2921,7 +2923,8 @@ struct membuf *ext4l_get_msg_buf(void); /* JBD2 journal.c stubs */ 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; }) +struct buffer_head *__getblk(struct block_device *bdev, sector_t block, + unsigned int size); int bmap(struct inode *inode, sector_t *block); #define trace_jbd2_update_log_tail(j, t, b, f) \ do { (void)(j); (void)(t); (void)(b); (void)(f); } while (0) diff --git a/fs/ext4l/support.c b/fs/ext4l/support.c index e5343aab198..72bf819c3d6 100644 --- a/fs/ext4l/support.c +++ b/fs/ext4l/support.c @@ -267,6 +267,34 @@ void bh_cache_clear(void) } } +/** + * bh_cache_sync() - Sync all dirty buffers to disk + * + * U-Boot doesn't have a journal thread, so we need to manually sync + * all dirty buffers after write operations. + * + * Return: 0 on success, negative on first error + */ +int bh_cache_sync(void) +{ + int i, ret = 0; + struct bh_cache_entry *entry; + + for (i = 0; i < BH_CACHE_SIZE; i++) { + for (entry = bh_cache[i]; entry; entry = entry->next) { + if (entry->bh && buffer_dirty(entry->bh)) { + int err = ext4l_write_block(entry->bh->b_blocknr, + entry->bh->b_size, + entry->bh->b_data); + if (err && !ret) + ret = err; + clear_buffer_dirty(entry->bh); + } + } + } + return ret; +} + /** * alloc_buffer_head() - Allocate a buffer_head structure * @gfp_mask: Allocation flags (ignored in U-Boot) @@ -328,19 +356,25 @@ static struct buffer_head *alloc_buffer_head_with_data(size_t size) * @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. + * jbd2_journal_write_metadata_buffer() share b_data/b_folio with the original + * buffer and should not free them. Shadow buffers are identified by having + * b_private set to point to the original buffer. */ void free_buffer_head(struct buffer_head *bh) { if (!bh) return; + /* + * Shadow buffers (b_private != NULL) share their folio with the + * original buffer. Don't free the shared folio. + */ + if (!bh->b_private && bh->b_folio) + free(bh->b_folio); + /* 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); } @@ -451,6 +485,50 @@ struct buffer_head *sb_getblk(struct super_block *sb, sector_t block) return bh; } +/** + * __getblk() - Get a buffer for a given block device + * @bdev: Block device + * @block: Block number + * @size: Block size + * Return: Buffer head or NULL on error + * + * Similar to sb_getblk but takes a block device instead of superblock. + * Used by the journal to allocate descriptor buffers. + */ +struct buffer_head *__getblk(struct block_device *bdev, sector_t block, + unsigned int size) +{ + struct buffer_head *bh; + + if (!bdev || !size) + return NULL; + + /* Check cache first - must match block number AND size */ + bh = bh_cache_lookup(block, size); + if (bh) + return bh; + + /* Allocate new buffer */ + bh = alloc_buffer_head_with_data(size); + if (!bh) + return NULL; + + bh->b_blocknr = block; + bh->b_bdev = bdev; + bh->b_size = size; + + /* Mark buffer as having a valid disk mapping */ + set_buffer_mapped(bh); + + /* 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 @@ -503,12 +581,17 @@ void brelse(struct buffer_head *bh) } /** - * __brelse() - Release a buffer_head (alternate API) + * __brelse() - Release a buffer_head reference without freeing * @bh: Buffer head to release + * + * Unlike brelse(), this only decrements the reference count without + * freeing the buffer when count reaches zero. Used when caller will + * explicitly free with free_buffer_head() afterward. */ void __brelse(struct buffer_head *bh) { - brelse(bh); + if (bh) + atomic_dec(&bh->b_count); } /** @@ -582,6 +665,23 @@ struct buffer_head *__bread(struct block_device *bdev, sector_t block, return bh; } +/** + * end_buffer_write_sync() - Completion handler for synchronous buffer writes + * @bh: Buffer head that completed I/O + * @uptodate: 1 if I/O succeeded, 0 if failed + * + * This callback is invoked after a buffer write completes. It sets the + * buffer's uptodate state based on the result and unlocks the buffer. + */ +void end_buffer_write_sync(struct buffer_head *bh, int uptodate) +{ + if (uptodate) + set_buffer_uptodate(bh); + else + clear_buffer_uptodate(bh); + unlock_buffer(bh); +} + /** * submit_bh() - Submit a buffer_head for I/O * @op: Operation (REQ_OP_READ, REQ_OP_WRITE, etc.) @@ -590,26 +690,38 @@ struct buffer_head *__bread(struct block_device *bdev, sector_t block, */ int submit_bh(int op, struct buffer_head *bh) { - int ret; + int ret = 0; int op_type = op & REQ_OP_MASK; /* Mask out flags, keep operation type */ + int uptodate; 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 ret; + uptodate = 0; + } else { + set_buffer_uptodate(bh); + uptodate = 1; } - set_buffer_uptodate(bh); } else if (op_type == REQ_OP_WRITE) { ret = ext4l_write_block(bh->b_blocknr, bh->b_size, bh->b_data); if (ret) { clear_buffer_uptodate(bh); - return ret; + set_buffer_write_io_error(bh); + uptodate = 0; + } else { + clear_buffer_write_io_error(bh); + uptodate = 1; } - /* Mark buffer as clean (not dirty) after write */ + } else { + uptodate = 0; } - return 0; + /* Call b_end_io callback if set - U-Boot does sync I/O */ + if (bh->b_end_io) + bh->b_end_io(bh, uptodate); + + return ret; } /** -- 2.43.0