[Concept,07/15] ext4l: Implement buffer write I/O and allocation

Message ID 20251230234134.906477-8-sjg@u-boot.org
State New
Headers
Series ext4l: Infrastructure and fixes for write support (part K) |

Commit Message

Simon Glass Dec. 30, 2025, 11:41 p.m. UTC
  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(-)
  

Patch

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;
 }
 
 /**