From patchwork Tue Dec 30 23:41:19 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1130 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767138290; bh=HIYEQ2cLBP1vhUzgYl/G/qfZiEUJGvPUyLyjMkpCFiA=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=UaFuN1Bp2x9hAH9i/xCnzVRov3Jsqnqq0EZQTXPakHA36HXq6VBvd1dap8rrY1JBm 0bsBBV+gcfGvu4Vw0qhpMNSLckzOdqbfv61TYcNv2M1dWtf18gnZ9+y4dSj7kd57K8 6ZkZs+4Wtn9SHUS2QtZGHsFL2aPR0nv6+89/SIjHPTd0C3LgoAy/vGpYndlzX5o08n 2fJU44x8mqJuIiB6/PjaosuhuedwUxFAEW1AIMxZkNdi6rlGm28k0WxILxhQOdUprR Zuc2NRzFGmLCk08b8xSplGpVG1kEVKhFQxltq74ZlzxmcJCWb3OxIajcrXCm5/HOYd qw72ln2hZLtfQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2956A68F71 for ; Tue, 30 Dec 2025 16:44:50 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id ngkTej1oghF0 for ; Tue, 30 Dec 2025 16:44:50 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767138290; bh=HIYEQ2cLBP1vhUzgYl/G/qfZiEUJGvPUyLyjMkpCFiA=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=UaFuN1Bp2x9hAH9i/xCnzVRov3Jsqnqq0EZQTXPakHA36HXq6VBvd1dap8rrY1JBm 0bsBBV+gcfGvu4Vw0qhpMNSLckzOdqbfv61TYcNv2M1dWtf18gnZ9+y4dSj7kd57K8 6ZkZs+4Wtn9SHUS2QtZGHsFL2aPR0nv6+89/SIjHPTd0C3LgoAy/vGpYndlzX5o08n 2fJU44x8mqJuIiB6/PjaosuhuedwUxFAEW1AIMxZkNdi6rlGm28k0WxILxhQOdUprR Zuc2NRzFGmLCk08b8xSplGpVG1kEVKhFQxltq74ZlzxmcJCWb3OxIajcrXCm5/HOYd qw72ln2hZLtfQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1940968C58 for ; Tue, 30 Dec 2025 16:44:50 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767138289; bh=tICiIE0df2adDR2h+CQF5k1HB6CEJLHSEPA38YiIcFk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FLPrWNahaVzCzJ70MMSmwWfaoQ4ARVMN5PrzcCoQhAhDAZTJAxV0ADPRXXvXuF8ky KLkGlmHN2QcjTiuR/FD2jzeq+bsywodsE35nssLX/juXSE3YC8NWGtgEOUKiUErI/F v4G1xIHteXZcBmIzQXxOaDyRr2Uq+8dMEFod72hAlglRL9hVrhTiq2/LSSgU+EHzB1 w8l1zQR1nJJWwsYw961Qske5xHFE8MQXZINB2+6DXangOXR0DU2KPNlyoEtvXYjda0 +zjU9rliQgxbA/QIqLA6UIDMvC3r396GPRqcj02j8sM0feEcj/Es7H7/tXpwapLbCl ab9GNBkLN8fkA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2D02968C06; Tue, 30 Dec 2025 16:44:49 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id bzZe81jUTjtR; Tue, 30 Dec 2025 16:44:49 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767138283; bh=VJMdKqE9M9lt//FnnDlOasmFwFKKdBoj4MKQZp7MaYo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bDkp1q7mWrtOkM50W6tZ4sk/k2HtvBUSKTVudajMkKb6WbrnhIMgn1i7GyNTSAVXW pzA1gAeM07QG+ODgnku0I5OilGvTiY5nague0nuyeZWLvCgjziCZGMXXp1+f/dVdrr cL5ifiashTuTrFk4OX0A7SANsyFsmz8VE4xWT5Z9HaMQaSerws6ygCNmz5isjl73V9 e8tpzKMsOTxX6dTHy+BRVO+ARg1RNzjvpkxQqBpwhHx3m7dhl/qj0AQDDvYKbaWLHh C1tElaUanT8qaZG3YjOuVtNi/GaBqTEImEddww385hv7KIsI51FdHa0EBg6XyQa6Nc 2vk7Sldz4AloA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id E001F68C0F; Tue, 30 Dec 2025 16:44:42 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Tue, 30 Dec 2025 16:41:19 -0700 Message-ID: <20251230234134.906477-8-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20251230234134.906477-1-sjg@u-boot.org> References: <20251230234134.906477-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: FVU7NL3U7ZWKUXOJR3RCC5HTSEVBJWCN X-Message-ID-Hash: FVU7NL3U7ZWKUXOJR3RCC5HTSEVBJWCN X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , Claude X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 07/15] ext4l: Implement buffer write I/O and allocation List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass 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 Signed-off-by: Simon Glass --- 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; } /**