[PATCH 00/15] ext4l: Infrastructure and fixes for write support (part K)
From: Simon Glass <simon.glass@canonical.com> This series adds infrastructure and bug fixes needed for ext4l write support. It includes: - kmem_cache implementation controlled by CONFIG_LIB_KMEM_CACHE - Bit operation functions imported from Linux (find_bit, fns) - Little-endian bit operations for ext4 bitmaps - Buffer I/O infrastructure for write operations - Folio and buffer head fixes for U-Boot's malloc'd buffers - Inode handling fixes (i_mode, i_blocks, iput eviction) - Journal cleanup detection in bh_cache_clear() - Various bug fixes for clang warnings and multi-word bit operations Simon Glass (15): lib: Add CONFIG_LIB_KMEM_CACHE for full kmem_cache support ext4l: Add journal_head detection in bh_cache_clear bitops: linux: Add fns() to find N'th set bit lib: linux: Add find_bit from Linux ext4l: Implement little-endian bit operations ext4l: Adjust folio offset and mapping operations ext4l: Implement buffer write I/O and allocation ext4l: Fix inode_init_owner to set i_mode ext4l: Add ext4_commit_super() declaration ext4l: Fix dquot functions to update i_blocks ext4l: Return boot-relative time from ktime_get_real_seconds() ext4l: Implement iput() to evict deleted inodes ext4l: Use percpu_counter initialized field ext4l: Fix bit operations for bits beyond first word ext4l: Fix cmpxchg macro warning with clang fs/ext4l/ext4_uboot.h | 95 +++++++++------- fs/ext4l/stub.c | 47 +++++++- fs/ext4l/support.c | 246 ++++++++++++++++++++++++++++++++++++++--- include/linux/bitops.h | 13 +++ include/linux/find.h | 176 +++++++++++++++++++++++++++++ include/linux/slab.h | 27 +++-- lib/Kconfig | 8 ++ lib/Makefile | 2 + lib/find_bit.c | 142 ++++++++++++++++++++++++ lib/kmem_cache.c | 20 ++++ 10 files changed, 704 insertions(+), 72 deletions(-) create mode 100644 include/linux/find.h create mode 100644 lib/find_bit.c create mode 100644 lib/kmem_cache.c -- 2.43.0 base-commit: 78fd0874707fd755f243f8aa6252a8e19be2b21b branch: extk
From: Simon Glass <simon.glass@canonical.com> Add a Kconfig option to control whether full kmem_cache_free() and kmem_cache_destroy() implementations are provided in lib/linux_compat.c Most boards do not need these functions, so they can use simple inline stubs in slab.h. Subsystems like ext4 that require proper cache management can select CONFIG_LIB_KMEM_CACHE. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/slab.h | 27 ++++++++++++++++----------- lib/Kconfig | 8 ++++++++ lib/Makefile | 1 + lib/kmem_cache.c | 20 ++++++++++++++++++++ 4 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 lib/kmem_cache.c diff --git a/include/linux/slab.h b/include/linux/slab.h index 2b374641534..caaaa3d9e16 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -84,24 +84,19 @@ static inline void *krealloc(const void *p, size_t new_size, gfp_t flags) void *kmemdup(const void *src, size_t len, gfp_t gfp); -/* kmem_cache stubs */ +/* kmem_cache implementation / stubs */ struct kmem_cache { int sz; }; struct kmem_cache *get_mem(int element_sz); -#define kmem_cache_create(a, sz, c, d, e) ({ (void)(a); (void)(e); get_mem(sz); }) +#define kmem_cache_create(a, sz, c, d, e) get_mem(sz) void *kmem_cache_alloc(struct kmem_cache *obj, gfp_t flag); -static inline void *kmem_cache_zalloc(struct kmem_cache *obj, gfp_t flags) -{ - void *ret = kmem_cache_alloc(obj, flags); - - if (ret) - memset(ret, 0, obj->sz); - return ret; -} - +#if CONFIG_IS_ENABLED(LIB_KMEM_CACHE) +void kmem_cache_free(struct kmem_cache *cachep, void *obj); +void kmem_cache_destroy(struct kmem_cache *cachep); +#else static inline void kmem_cache_free(struct kmem_cache *cachep, void *obj) { free(obj); @@ -111,5 +106,15 @@ static inline void kmem_cache_destroy(struct kmem_cache *cachep) { free(cachep); } +#endif + +static inline void *kmem_cache_zalloc(struct kmem_cache *obj, gfp_t flags) +{ + void *ret = kmem_cache_alloc(obj, flags); + + if (ret) + memset(ret, 0, obj->sz); + return ret; +} #endif /* _LINUX_SLAB_H */ diff --git a/lib/Kconfig b/lib/Kconfig index 9c7eb27c392..5ddf8125766 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -396,6 +396,14 @@ config TPL_TINY_MEMSET config RBTREE bool +config LIB_KMEM_CACHE + bool "Enable full kmem_cache implementation" + help + Provide a proper kmem_cache implementation in lib/linux_compat.c + that tracks allocated objects. This is needed by subsystems like + ext4 that require cache management. When disabled, simple inline + stubs are used instead. + config BITREVERSE bool "Bit reverse library from Linux" diff --git a/lib/Makefile b/lib/Makefile index 15a43b2cc5e..01b967d46b7 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -135,6 +135,7 @@ obj-$(CONFIG_$(PHASE_)OF_LIBFDT) += fdtdec.o fdtdec_common.o fdt_print.o obj-y += hang.o obj-y += linux_compat.o obj-y += linux_string.o +obj-$(CONFIG_$(PHASE_)LIB_KMEM_CACHE) += kmem_cache.o obj-$(CONFIG_$(PHASE_)LMB) += lmb.o obj-y += membuf.o obj-$(CONFIG_REGEX) += slre.o diff --git a/lib/kmem_cache.c b/lib/kmem_cache.c new file mode 100644 index 00000000000..bff9329aa53 --- /dev/null +++ b/lib/kmem_cache.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * kmem_cache implementation for U-Boot + * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#include <malloc.h> +#include <linux/slab.h> + +void kmem_cache_free(struct kmem_cache *cachep, void *obj) +{ + free(obj); +} + +void kmem_cache_destroy(struct kmem_cache *cachep) +{ + free(cachep); +} -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> When debugging journal-cleanup issues, stale journal_head attachments on buffer_heads can cause crashes on subsequent mounts. Add detection logic in bh_cache_clear() to warn when a buffer_head still has a journal_head attached. This indicates the journal was not properly destroyed before unmount. Clear the JBD flag and pointer to prevent issues with subsequent mounts. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/support.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/fs/ext4l/support.c b/fs/ext4l/support.c index a046654ca54..127a3920c96 100644 --- a/fs/ext4l/support.c +++ b/fs/ext4l/support.c @@ -222,6 +222,29 @@ static void bh_cache_insert(struct buffer_head *bh) * * Called on unmount to free all cached buffers. */ +/** + * bh_clear_stale_jbd() - Clear stale journal_head from buffer_head + * @bh: buffer_head to check + * + * Check if the buffer still has journal_head attached. This should not happen + * if the journal was properly destroyed, but warn if it does to help debugging. + * Clear the JBD flag and b_private to prevent issues with subsequent mounts. + */ +static void bh_clear_stale_jbd(struct buffer_head *bh) +{ + if (buffer_jbd(bh)) { + log_err("bh %p block %llu still has JBD (b_private %p)\n", + bh, (unsigned long long)bh->b_blocknr, bh->b_private); + /* + * Clear the JBD flag and b_private to prevent issues. + * The journal_head itself will be freed when the + * journal_head cache is destroyed. + */ + clear_buffer_jbd(bh); + bh->b_private = NULL; + } +} + void bh_cache_clear(void) { int i; @@ -231,9 +254,12 @@ void bh_cache_clear(void) for (entry = bh_cache[i]; entry; entry = next) { next = entry->next; if (entry->bh) { + struct buffer_head *bh = entry->bh; + + bh_clear_stale_jbd(bh); /* Release the cache's reference */ - if (atomic_dec_and_test(&entry->bh->b_count)) - free_buffer_head(entry->bh); + if (atomic_dec_and_test(&bh->b_count)) + free_buffer_head(bh); } free(entry); } -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add the fns() function from Linux to find the N'th set bit in a word. This is needed by lib/find_bit.c which uses it in the FIND_NTH_BIT macro. Taken from Linux v6.19 Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/bitops.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/linux/bitops.h b/include/linux/bitops.h index f826d7f3b34..86f7ee492b4 100644 --- a/include/linux/bitops.h +++ b/include/linux/bitops.h @@ -194,6 +194,19 @@ static inline unsigned long __ffs64(u64 word) return __ffs((unsigned long)word); } +/** + * fns - find N'th set bit in a word + * @word: The word to search + * @n: Bit to find + */ +static inline unsigned int fns(unsigned long word, unsigned int n) +{ + while (word && n--) + word &= word - 1; + + return word ? __ffs(word) : BITS_PER_LONG; +} + /** * __set_bit - Set a bit in memory * @nr: the bit to set -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add lib/find_bit.c and include/linux/find.h from Linux v6.19, trimmed to include only the functions needed for ext4l: find_first_bit(), find_first_zero_bit(), find_next_bit(), find_next_zero_bit() and find_last_bit() The following items are removed from the Linux originals: - find.h: _and_bit, _andnot_bit, _or_bit, _nth_bit variants, wrap functions, clump8 functions, big-endian support, most for_each_... macros - find_bit.c: Corresponding implementations, random.h include Add wrapper functions matching sandbox's asm/bitops.h declarations (int return type, void* addr) that call the _find_* implementations. Build find_bit.o only for sandbox for now. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/find.h | 176 +++++++++++++++++++++++++++++++++++++++++++ lib/Makefile | 1 + lib/find_bit.c | 142 ++++++++++++++++++++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100644 include/linux/find.h create mode 100644 lib/find_bit.c diff --git a/include/linux/find.h b/include/linux/find.h new file mode 100644 index 00000000000..117d9015c57 --- /dev/null +++ b/include/linux/find.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_FIND_H_ +#define __LINUX_FIND_H_ + +#include <linux/bitops.h> + +unsigned long _find_next_bit(const unsigned long *addr1, unsigned long nbits, + unsigned long start); +unsigned long _find_next_zero_bit(const unsigned long *addr, unsigned long nbits, + unsigned long start); +extern unsigned long _find_first_bit(const unsigned long *addr, unsigned long size); +extern unsigned long _find_first_zero_bit(const unsigned long *addr, unsigned long size); +extern unsigned long _find_last_bit(const unsigned long *addr, unsigned long size); + +#ifndef find_next_bit +/** + * find_next_bit - find the next set bit in a memory region + * @addr: The address to base the search on + * @size: The bitmap size in bits + * @offset: The bitnumber to start searching at + * + * Returns the bit number for the next set bit + * If no bits are set, returns @size. + */ +static __always_inline +unsigned long find_next_bit(const unsigned long *addr, unsigned long size, + unsigned long offset) +{ + if (small_const_nbits(size)) { + unsigned long val; + + if (unlikely(offset >= size)) + return size; + + val = *addr & GENMASK(size - 1, offset); + return val ? __ffs(val) : size; + } + + return _find_next_bit(addr, size, offset); +} +#endif + +#ifndef find_next_zero_bit +/** + * find_next_zero_bit - find the next cleared bit in a memory region + * @addr: The address to base the search on + * @size: The bitmap size in bits + * @offset: The bitnumber to start searching at + * + * Returns the bit number of the next zero bit + * If no bits are zero, returns @size. + */ +static __always_inline +unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size, + unsigned long offset) +{ + if (small_const_nbits(size)) { + unsigned long val; + + if (unlikely(offset >= size)) + return size; + + val = *addr | ~GENMASK(size - 1, offset); + return val == ~0UL ? size : ffz(val); + } + + return _find_next_zero_bit(addr, size, offset); +} +#endif + +#ifndef find_first_bit +/** + * find_first_bit - find the first set bit in a memory region + * @addr: The address to start the search at + * @size: The maximum number of bits to search + * + * Returns the bit number of the first set bit. + * If no bits are set, returns @size. + */ +static __always_inline +unsigned long find_first_bit(const unsigned long *addr, unsigned long size) +{ + if (small_const_nbits(size)) { + unsigned long val = *addr & GENMASK(size - 1, 0); + + return val ? __ffs(val) : size; + } + + return _find_first_bit(addr, size); +} +#endif + +#ifndef find_first_zero_bit +/** + * find_first_zero_bit - find the first cleared bit in a memory region + * @addr: The address to start the search at + * @size: The maximum number of bits to search + * + * Returns the bit number of the first cleared bit. + * If no bits are zero, returns @size. + */ +static __always_inline +unsigned long find_first_zero_bit(const unsigned long *addr, unsigned long size) +{ + if (small_const_nbits(size)) { + unsigned long val = *addr | ~GENMASK(size - 1, 0); + + return val == ~0UL ? size : ffz(val); + } + + return _find_first_zero_bit(addr, size); +} +#endif + +#ifndef find_last_bit +/** + * find_last_bit - find the last set bit in a memory region + * @addr: The address to start the search at + * @size: The number of bits to search + * + * Returns the bit number of the last set bit, or size. + */ +static __always_inline +unsigned long find_last_bit(const unsigned long *addr, unsigned long size) +{ + if (small_const_nbits(size)) { + unsigned long val = *addr & GENMASK(size - 1, 0); + + return val ? __fls(val) : size; + } + + return _find_last_bit(addr, size); +} +#endif + +#if defined(__LITTLE_ENDIAN) + +static __always_inline +unsigned long find_next_zero_bit_le(const void *addr, unsigned long size, unsigned long offset) +{ + return find_next_zero_bit(addr, size, offset); +} + +static __always_inline +unsigned long find_next_bit_le(const void *addr, unsigned long size, unsigned long offset) +{ + return find_next_bit(addr, size, offset); +} + +static __always_inline +unsigned long find_first_zero_bit_le(const void *addr, unsigned long size) +{ + return find_first_zero_bit(addr, size); +} + +#else +#error "Please fix <asm/byteorder.h>" +#endif + +#define for_each_set_bit(bit, addr, size) \ + for ((bit) = 0; (bit) = find_next_bit((addr), (size), (bit)), (bit) < (size); (bit)++) + +/* same as for_each_set_bit() but use bit as value to start with */ +#define for_each_set_bit_from(bit, addr, size) \ + for (; (bit) = find_next_bit((addr), (size), (bit)), (bit) < (size); (bit)++) + +#define for_each_clear_bit(bit, addr, size) \ + for ((bit) = 0; \ + (bit) = find_next_zero_bit((addr), (size), (bit)), (bit) < (size); \ + (bit)++) + +/* same as for_each_clear_bit() but use bit as value to start with */ +#define for_each_clear_bit_from(bit, addr, size) \ + for (; (bit) = find_next_zero_bit((addr), (size), (bit)), (bit) < (size); (bit)++) + +#endif /*__LINUX_FIND_H_ */ diff --git a/lib/Makefile b/lib/Makefile index 01b967d46b7..3890a827d1c 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_PHYSMEM) += physmem.o obj-y += rc4.o obj-$(CONFIG_RBTREE) += rbtree.o obj-$(CONFIG_BITREVERSE) += bitrev.o +obj-$(CONFIG_SANDBOX) += find_bit.o obj-y += list_sort.o endif diff --git a/lib/find_bit.c b/lib/find_bit.c new file mode 100644 index 00000000000..762c5448d84 --- /dev/null +++ b/lib/find_bit.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* bit search implementation + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * Copyright (C) 2008 IBM Corporation + * 'find_last_bit' is written by Rusty Russell <rusty@rustcorp.com.au> + * (Inspired by David Howell's find_next_bit implementation) + * + * Rewritten by Yury Norov <yury.norov@gmail.com> to decrease + * size and improve performance, 2015. + */ + +#include <asm/byteorder.h> +#include <linux/bitops.h> +#include <linux/bitmap.h> +#include <linux/export.h> + +/* + * Common helper for find_bit() function family + * @FETCH: The expression that fetches and pre-processes each word of bitmap(s) + * @MUNGE: The expression that post-processes a word containing found bit (may be empty) + * @size: The bitmap size in bits + */ +#define FIND_FIRST_BIT(FETCH, MUNGE, size) \ +({ \ + unsigned long idx, val, sz = (size); \ + \ + for (idx = 0; idx * BITS_PER_LONG < sz; idx++) { \ + val = (FETCH); \ + if (val) { \ + sz = min(idx * BITS_PER_LONG + __ffs(MUNGE(val)), sz); \ + break; \ + } \ + } \ + \ + sz; \ +}) + +/* + * Common helper for find_next_bit() function family + * @FETCH: The expression that fetches and pre-processes each word of bitmap(s) + * @MUNGE: The expression that post-processes a word containing found bit (may be empty) + * @size: The bitmap size in bits + * @start: The bitnumber to start searching at + */ +#define FIND_NEXT_BIT(FETCH, MUNGE, size, start) \ +({ \ + unsigned long mask, idx, tmp, sz = (size), __start = (start); \ + \ + if (unlikely(__start >= sz)) \ + goto out; \ + \ + mask = MUNGE(BITMAP_FIRST_WORD_MASK(__start)); \ + idx = __start / BITS_PER_LONG; \ + \ + for (tmp = (FETCH) & mask; !tmp; tmp = (FETCH)) { \ + if ((idx + 1) * BITS_PER_LONG >= sz) \ + goto out; \ + idx++; \ + } \ + \ + sz = min(idx * BITS_PER_LONG + __ffs(MUNGE(tmp)), sz); \ +out: \ + sz; \ +}) + +#ifndef find_first_bit +/* + * Find the first set bit in a memory region. + */ +unsigned long _find_first_bit(const unsigned long *addr, unsigned long size) +{ + return FIND_FIRST_BIT(addr[idx], /* nop */, size); +} +EXPORT_SYMBOL(_find_first_bit); +#endif + +#ifndef find_first_zero_bit +/* + * Find the first cleared bit in a memory region. + */ +unsigned long _find_first_zero_bit(const unsigned long *addr, unsigned long size) +{ + return FIND_FIRST_BIT(~addr[idx], /* nop */, size); +} +EXPORT_SYMBOL(_find_first_zero_bit); +#endif + +#ifndef find_next_bit +unsigned long _find_next_bit(const unsigned long *addr, unsigned long nbits, unsigned long start) +{ + return FIND_NEXT_BIT(addr[idx], /* nop */, nbits, start); +} +EXPORT_SYMBOL(_find_next_bit); +#endif + +#ifndef find_next_zero_bit +unsigned long _find_next_zero_bit(const unsigned long *addr, unsigned long nbits, + unsigned long start) +{ + return FIND_NEXT_BIT(~addr[idx], /* nop */, nbits, start); +} +EXPORT_SYMBOL(_find_next_zero_bit); +#endif + +#ifndef find_last_bit +unsigned long _find_last_bit(const unsigned long *addr, unsigned long size) +{ + if (size) { + unsigned long val = BITMAP_LAST_WORD_MASK(size); + unsigned long idx = (size-1) / BITS_PER_LONG; + + do { + val &= addr[idx]; + if (val) + return idx * BITS_PER_LONG + __fls(val); + + val = ~0ul; + } while (idx--); + } + return size; +} +EXPORT_SYMBOL(_find_last_bit); +#endif + +/* + * Wrapper functions matching sandbox's asm/bitops.h declarations. + * These provide the expected function signatures for sandbox platform. + */ +int find_first_zero_bit(void *addr, unsigned int size) +{ + return _find_first_zero_bit(addr, size); +} +EXPORT_SYMBOL(find_first_zero_bit); + +int find_next_zero_bit(void *addr, int size, int offset) +{ + return _find_next_zero_bit(addr, size, offset); +} +EXPORT_SYMBOL(find_next_zero_bit); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The ext4 block allocator uses little-endian bit operations on block bitmaps. Implement these operations by wrapping the existing set/test/clear_bit() functions. Add find_next_zero_bit() to search for free blocks in bitmaps. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 39bade68654..4ce98eeb7ed 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -349,13 +349,15 @@ struct buffer_head *sb_getblk(struct super_block *sb, sector_t block); * We implement them in interface.c for sandbox. */ -/* Little-endian bit operations */ -#define __set_bit_le(nr, addr) ((void)(nr), (void)(addr)) -#define test_bit_le(nr, addr) ({ (void)(nr); (void)(addr); 0; }) +/* Little-endian bit operations - use arch-provided find_next_zero_bit */ #define find_next_zero_bit_le(addr, size, offset) \ - ({ (void)(addr); (void)(size); (offset); }) -#define __test_and_clear_bit_le(nr, addr) ({ (void)(nr); (void)(addr); 0; }) -#define __test_and_set_bit_le(nr, addr) ({ (void)(nr); (void)(addr); 0; }) + find_next_zero_bit((void *)addr, size, offset) +#define __set_bit_le(nr, addr) set_bit(nr, addr) +#define test_bit_le(nr, addr) test_bit(nr, addr) +#define __test_and_clear_bit_le(nr, addr) \ + ({ int __old = test_bit(nr, addr); clear_bit(nr, addr); __old; }) +#define __test_and_set_bit_le(nr, addr) \ + ({ int __old = test_bit(nr, addr); set_bit(nr, addr); __old; }) /* KUNIT stub */ #define KUNIT_STATIC_STUB_REDIRECT(...) do { } while (0) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The existing folios macros assume page-aligned memory, but U-Boot uses malloc'd buffers for simplicity. Update the macros accordinging: - offset_in_folio(): Calculate the offset from the folio's data pointer - bh_offset(): Calculate the actual offset within the folio - folio_set_bh(): Actually set b_folio and b_data - kmap_local_folio(): Return a pointer to folio data + offset Implement __filemap_get_folio(), folio_put() and folio_get() for folio-lifecycle management. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 33 ++++++++++-------- fs/ext4l/support.c | 80 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 14 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 4ce98eeb7ed..d7053c11d31 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -1247,16 +1247,21 @@ struct folio_batch { /* folio operations - stubs */ #define folio_mark_dirty(f) do { (void)(f); } while (0) -#define offset_in_folio(f, p) ({ (void)(f); (unsigned int)((unsigned long)(p) & (PAGE_SIZE - 1)); }) +/* + * offset_in_folio - calculate offset of pointer within folio's data + * In Linux this uses page alignment, but in U-Boot we use the folio's + * actual data pointer since our buffers are malloc'd. + */ +#define offset_in_folio(f, p) ((f) ? (unsigned int)((uintptr_t)(p) - (uintptr_t)(f)->data) : 0U) #define folio_buffers(f) ({ (void)(f); (struct buffer_head *)NULL; }) #define virt_to_folio(p) ({ (void)(p); (struct folio *)NULL; }) -#define folio_set_bh(bh, f, off) do { (void)(bh); (void)(f); (void)(off); } while (0) +#define folio_set_bh(bh, f, off) do { if ((bh) && (f)) { (bh)->b_folio = (f); (bh)->b_data = (char *)(f)->data + (off); } } while (0) #define memcpy_from_folio(dst, f, off, len) do { (void)(dst); (void)(f); (void)(off); (void)(len); } while (0) #define folio_test_uptodate(f) ({ (void)(f); 1; }) #define folio_pos(f) ({ (void)(f); 0LL; }) #define folio_size(f) ({ (void)(f); PAGE_SIZE; }) #define folio_unlock(f) do { (void)(f); } while (0) -#define folio_put(f) do { (void)(f); } while (0) +/* folio_put and folio_get are implemented in support.c */ #define folio_lock(f) do { (void)(f); } while (0) #define folio_batch_init(fb) do { (fb)->nr = 0; } while (0) #define filemap_get_folios(m, i, e, fb) ({ (void)(m); (void)(i); (void)(e); (void)(fb); 0U; }) @@ -1357,7 +1362,7 @@ static inline int generic_error_remove_folio(struct address_space *mapping, #define FGP_WRITEBEGIN (FGP_LOCK | FGP_WRITE | FGP_CREAT | FGP_STABLE) /* kmap/kunmap stubs for inline.c */ -#define kmap_local_folio(folio, off) ({ (void)(folio); (void)(off); (void *)NULL; }) +#define kmap_local_folio(folio, off) ((folio) ? (char *)(folio)->data + (off) : NULL) #define kunmap_local(addr) do { (void)(addr); } while (0) /* Folio zeroing stubs for inline.c */ @@ -1367,13 +1372,12 @@ static inline int generic_error_remove_folio(struct address_space *mapping, /* mapping_gfp_mask stub */ #define mapping_gfp_mask(m) ({ (void)(m); GFP_KERNEL; }) -/* __filemap_get_folio stub */ -static inline struct folio *__filemap_get_folio(struct address_space *mapping, - pgoff_t index, unsigned int fgp_flags, - gfp_t gfp) -{ - return NULL; -} +/* Folio operations - implemented in support.c */ +struct folio *__filemap_get_folio(struct address_space *mapping, + pgoff_t index, unsigned int fgp_flags, + gfp_t gfp); +void folio_put(struct folio *folio); +void folio_get(struct folio *folio); /* projid_t - project ID type */ typedef unsigned int projid_t; @@ -1545,7 +1549,9 @@ static inline char *d_path(const struct path *path, char *buf, int buflen) /* Buffer operations - additional */ #define getblk_unmovable(bdev, block, size) sb_getblk(bdev->bd_super, block) #define create_empty_buffers(f, s, flags) ({ (void)(f); (void)(s); (void)(flags); (struct buffer_head *)NULL; }) -#define bh_offset(bh) (0UL) +/* bh_offset returns offset of b_data within the folio */ +#define bh_offset(bh) ((bh)->b_folio ? \ + (unsigned long)((char *)(bh)->b_data - (char *)(bh)->b_folio->data) : 0UL) #define block_invalidate_folio(f, o, l) do { } while (0) #define block_write_end(pos, len, copied, folio) ({ (void)(pos); (void)(len); (void)(folio); (copied); }) #define block_dirty_folio(m, f) ({ (void)(m); (void)(f); false; }) @@ -2542,8 +2548,7 @@ static inline unsigned long ext4_find_next_bit_le(const void *addr, /* WARN_RATELIMIT - just evaluate condition, no warning in U-Boot */ #define WARN_RATELIMIT(condition, ...) (condition) -/* folio_get - increment folio refcount (no-op in U-Boot) */ -#define folio_get(f) do { (void)(f); } while (0) +/* folio_get - now implemented in support.c */ /* array_index_nospec - bounds checking without speculation (no-op in U-Boot) */ #define array_index_nospec(index, size) (index) diff --git a/fs/ext4l/support.c b/fs/ext4l/support.c index 127a3920c96..e5343aab198 100644 --- a/fs/ext4l/support.c +++ b/fs/ext4l/support.c @@ -626,3 +626,83 @@ int bh_read(struct buffer_head *bh, int flags) submit_bh(REQ_OP_READ | flags, bh); return buffer_uptodate(bh) ? 0 : -EIO; } + +/** + * __filemap_get_folio() - Get or create a folio for a mapping + * @mapping: The address_space to search + * @index: The page index + * @fgp_flags: Flags (FGP_CREAT to create if not found) + * @gfp: Memory allocation flags + * Return: Folio pointer or ERR_PTR on error + */ +struct folio *__filemap_get_folio(struct address_space *mapping, + pgoff_t index, unsigned int fgp_flags, + gfp_t gfp) +{ + struct folio *folio; + int i; + + /* Search for existing folio in cache */ + if (mapping) { + for (i = 0; i < mapping->folio_cache_count; i++) { + folio = mapping->folio_cache[i]; + if (folio && folio->index == index) { + /* Found existing folio, bump refcount */ + folio->_refcount++; + return folio; + } + } + } + + /* If not creating, return error */ + if (!(fgp_flags & FGP_CREAT)) + return ERR_PTR(-ENOENT); + + /* Create new folio */ + folio = kzalloc(sizeof(struct folio), gfp); + if (!folio) + return ERR_PTR(-ENOMEM); + + folio->data = kzalloc(PAGE_SIZE, gfp); + if (!folio->data) { + kfree(folio); + return ERR_PTR(-ENOMEM); + } + + folio->index = index; + folio->mapping = mapping; + folio->_refcount = 1; + + /* Add to cache if there's room */ + if (mapping && mapping->folio_cache_count < FOLIO_CACHE_MAX) { + mapping->folio_cache[mapping->folio_cache_count++] = folio; + /* Extra ref for cache */ + folio->_refcount++; + } + + return folio; +} + +/** + * folio_put() - Release a reference to a folio + * @folio: The folio to release + */ +void folio_put(struct folio *folio) +{ + if (!folio) + return; + if (--folio->_refcount > 0) + return; + kfree(folio->data); + kfree(folio); +} + +/** + * folio_get() - Acquire a reference to a folio + * @folio: The folio to reference + */ +void folio_get(struct folio *folio) +{ + if (folio) + folio->_refcount++; +} -- 2.43.0
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
From: Simon Glass <simon.glass@canonical.com> Fix inode_init_owner() to properly set i_mode, which is needed for ext4_create() to work correctly. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index f4831a9b5c2..35db84588d6 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -410,7 +410,7 @@ extern struct inode *new_inode(struct super_block *sb); #define i_uid_write(inode, uid) do { } while (0) #define i_gid_write(inode, gid) do { } while (0) #define inode_fsuid_set(inode, idmap) do { } while (0) -#define inode_init_owner(idmap, i, dir, mode) do { } while (0) +#define inode_init_owner(idmap, i, dir, mode) do { (i)->i_mode = (mode); } while (0) #define insert_inode_locked(inode) (0) #define unlock_new_inode(inode) do { } while (0) #define clear_nlink(inode) do { } while (0) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add declaration for ext4_commit_super() to the ext4_uboot.h header and update the comment to reflect both superblock initialisation and commit functions. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 35db84588d6..610fe34b556 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -2075,8 +2075,9 @@ struct fs_context { bool silent; }; -/* ext4 superblock initialisation */ +/* ext4 superblock initialisation and commit */ int ext4_fill_super(struct super_block *sb, struct fs_context *fc); +int ext4_commit_super(struct super_block *sb); /* fs_parameter stubs */ struct fs_parameter { -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The dquot_alloc_block(), dquot_free_block() and dquot_alloc_block_nofail() functions are stubs that do nothing. These functions are called by ext4 when allocating and freeing blocks, and they should update the inode's i_blocks field. Fix these functions to properly track block allocation in i_blocks, which is stored in 512-byte units. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 3 ++- fs/ext4l/stub.c | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 610fe34b556..c5eddca3aef 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -375,7 +375,8 @@ struct buffer_head *sb_getblk(struct super_block *sb, sector_t block); /* Quota operations - stubs (only define if quotaops.h not included) */ #ifndef _LINUX_QUOTAOPS_H -#define dquot_alloc_block_nofail(inode, nr) ({ (void)(inode); (void)(nr); 0; }) +#define dquot_alloc_block_nofail(inode, nr) \ + ({ (inode)->i_blocks += (nr) << ((inode)->i_blkbits - 9); 0; }) #define dquot_initialize(inode) ({ (void)(inode); 0; }) #define dquot_free_inode(inode) do { (void)(inode); } while (0) #define dquot_alloc_inode(inode) ({ (void)(inode); 0; }) diff --git a/fs/ext4l/stub.c b/fs/ext4l/stub.c index 2d066be4af3..ce68ec28b20 100644 --- a/fs/ext4l/stub.c +++ b/fs/ext4l/stub.c @@ -669,11 +669,22 @@ void dquot_free_space_nodirty(struct inode *inode, loff_t size) int dquot_alloc_block(struct inode *inode, loff_t nr) { + /* + * Update i_blocks to reflect the allocated blocks. + * i_blocks is in 512-byte units, so convert from fs blocks. + */ + inode->i_blocks += nr << (inode->i_blkbits - 9); + return 0; } void dquot_free_block(struct inode *inode, loff_t nr) { + /* + * Update i_blocks to reflect the freed blocks. + * i_blocks is in 512-byte units, so convert from fs blocks. + */ + inode->i_blocks -= nr << (inode->i_blkbits - 9); } /* -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The ktime_get_real_seconds() stub returns 0, causing deleted inodes to have zero i_dtime. This causes fsck to complain about deleted inodes with zero deletion time. Instead, use the boot-relative time in seconds. While not a real wall-clock timestamp, it provides a non-zero value that satisfies filesystem-consistency checks. Future work can improve on this, perhaps using an on-board RTC. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index c5eddca3aef..5c66e35662d 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -402,8 +402,8 @@ struct buffer_head *sb_getblk(struct super_block *sb, sector_t block); #define sb_find_get_block(sb, block) ((struct buffer_head *)NULL) #define sync_dirty_buffer(bh) submit_bh(REQ_OP_WRITE, bh) -/* Time functions */ -#define ktime_get_real_seconds() (0) +/* Time functions - use boot-relative time for timestamps */ +#define ktime_get_real_seconds() (get_timer(0) / 1000) #define time_before32(a, b) (0) /* Inode operations - iget_locked and new_inode are in interface.c */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Implement iput() to properly handle inode reference counting and eviction. When the reference count drops to zero and the inode has no links (i_nlink == 0), call ext4_evict_inode() to free the inode's data blocks and the inode itself. This is required for unlink operations to properly free filesystem resources. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 4 ++-- fs/ext4l/stub.c | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 5c66e35662d..18ea2b1bcd4 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -491,8 +491,8 @@ int __ext4_xattr_set_credits(struct super_block *sb, struct inode *inode, /* RCU barrier - stub */ #define rcu_barrier() do { } while (0) -/* inode/dentry operations - stubs */ -#define iput(inode) do { } while (0) +/* inode/dentry operations */ +void iput(struct inode *inode); /* current task - from linux/sched.h */ #include <linux/sched.h> diff --git a/fs/ext4l/stub.c b/fs/ext4l/stub.c index ce68ec28b20..2b7618df64c 100644 --- a/fs/ext4l/stub.c +++ b/fs/ext4l/stub.c @@ -587,6 +587,29 @@ struct dentry *d_make_root(struct inode *inode) return de; } +/** + * iput() - Release a reference to an inode + * @inode: Inode to release + * + * Decrements the inode reference count. When the reference count reaches + * zero and the inode has no links, the inode is evicted (freed). + */ +void iput(struct inode *inode) +{ + if (!inode) + return; + + if (atomic_dec_and_test(&inode->i_count)) { + /* Last reference - check if inode should be evicted */ + if (inode->i_nlink == 0 && inode->i_sb && + inode->i_sb->s_op && inode->i_sb->s_op->evict_inode) { + inode->i_sb->s_op->evict_inode(inode); + /* Sync dirty buffers after eviction */ + bh_cache_sync(); + } + } +} + /* percpu init rwsem */ int percpu_init_rwsem(struct percpu_rw_semaphore *sem) { -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Update the percpu_counter_initialized() and percpu_counter_init() macros in ext4_uboot.h to use the new initialized field. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 18ea2b1bcd4..2d0e817be40 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -368,7 +368,7 @@ struct buffer_head *sb_getblk(struct super_block *sb, sector_t block); #define percpu_counter_add(fbc, amount) ((fbc)->count += (amount)) #define percpu_counter_inc(fbc) ((fbc)->count++) #define percpu_counter_dec(fbc) ((fbc)->count--) -#define percpu_counter_initialized(fbc) (1) +#define percpu_counter_initialized(fbc) ((fbc)->initialized) /* Group permission - stub */ #define in_group_p(gid) (0) @@ -1216,7 +1216,8 @@ static inline ktime_t ktime_add_ns(ktime_t kt, s64 ns) #define write_trylock(lock) ({ (void)(lock); 1; }) /* percpu counter init/destroy */ -#define percpu_counter_init(fbc, val, gfp) ({ (fbc)->count = (val); 0; }) +#define percpu_counter_init(fbc, val, gfp) \ + ({ (fbc)->count = (val); (fbc)->initialized = true; 0; }) #define percpu_counter_destroy(fbc) do { } while (0) /* ratelimit macros */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The set_bit(), clear_bit(), and change_bit() functions only modify the first word of a bitmap, regardless of the bit number. For bit numbers
= 64 on 64-bit systems, the shift wraps around and modifies the wrong bit position.
Fix these functions to properly calculate the word offset before modifying the bit. This is needed for block bitmap operations where bit numbers can be in the thousands. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/stub.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/ext4l/stub.c b/fs/ext4l/stub.c index 2b7618df64c..f072cb5713f 100644 --- a/fs/ext4l/stub.c +++ b/fs/ext4l/stub.c @@ -77,26 +77,27 @@ typedef struct journal_s journal_t; /* * Bit operations - sandbox declares these extern but doesn't implement them. + * These work on bitmaps where nr is the absolute bit number. */ void set_bit(int nr, void *addr) { - unsigned long *p = (unsigned long *)addr; + unsigned long *p = (unsigned long *)addr + (nr / BITS_PER_LONG); - *p |= (1UL << nr); + *p |= (1UL << (nr % BITS_PER_LONG)); } void clear_bit(int nr, void *addr) { - unsigned long *p = (unsigned long *)addr; + unsigned long *p = (unsigned long *)addr + (nr / BITS_PER_LONG); - *p &= ~(1UL << nr); + *p &= ~(1UL << (nr % BITS_PER_LONG)); } void change_bit(int nr, void *addr) { - unsigned long *p = (unsigned long *)addr; + unsigned long *p = (unsigned long *)addr + (nr / BITS_PER_LONG); - *p ^= (1UL << nr); + *p ^= (1UL << (nr % BITS_PER_LONG)); } /* -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Rename local variables in cmpxchg macro to avoid shadowing when used inside try_cmpxchg, which also declares __old. Clang complains about "variable '__old' is uninitialised when used within its own initialisation" due to the nested macro expansion. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 2d0e817be40..087d8394ab6 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -101,12 +101,12 @@ struct timespec64 { /* cmpxchg - compare and exchange, single-threaded version */ #define cmpxchg(ptr, old, new) ({ \ - typeof(*(ptr)) __old = (old); \ - typeof(*(ptr)) __new = (new); \ - typeof(*(ptr)) __ret = *(ptr); \ - if (__ret == __old) \ - *(ptr) = __new; \ - __ret; \ + typeof(*(ptr)) __cmpxchg_old = (old); \ + typeof(*(ptr)) __cmpxchg_new = (new); \ + typeof(*(ptr)) __cmpxchg_ret = *(ptr); \ + if (__cmpxchg_ret == __cmpxchg_old) \ + *(ptr) = __cmpxchg_new; \ + __cmpxchg_ret; \ }) /* Reference count type */ -- 2.43.0
participants (1)
-
Simon Glass