From: "Theodore Ts'o" <tytso@mit.edu>
To: Ext4 Developers List <linux-ext4@vger.kernel.org>
Cc: "Theodore Ts'o" <tytso@mit.edu>
Subject: [PATCH 06/27] ext4: expand KUnit tests for fs/ext4/mballoc.c
Date: Wed, 11 Nov 2026 23:06:17 -0500 [thread overview]
Message-ID: <20261112040638.1576836-7-tytso@mit.edu> (raw)
In-Reply-To: <20261112040638.1576836-1-tytso@mit.edu>
Expand the ext4_mballoc_test KUnit test suite in fs/ext4/mballoc-test.c
from 7 subtests (21 test cases) to 32 subtests (96 parameterized test
cases across 1K, 4K, and 64K block sizes) to comprehensively exercise
the multi-block allocator in fs/ext4/mballoc.c:
- Low-level bitmap helpers (mb_set_bit, mb_clear_bit, mb_test_bit,
mb_find_next_zero_bit, mb_find_next_bit, mb_clear_bits, mb_set_bits,
and mb_test_and_clear_bits across clean, partially clear, and fully
clear ranges).
- Buddy bitmap lookup and extent discovery (mb_find_buddy,
mb_find_order_for_block, mb_find_extent for free, used, and merged
power-of-two ranges, and mb_regenerate_buddy).
- Corrupted block bitmap detection in ext4_mb_generate_buddy (setting
EXT4_GROUP_INFO_BBITMAP_CORRUPT when bitmap free count mismatches
grp->bb_free) and double-free / EXT4_STATE_FC_REPLAY handling in
mb_free_blocks.
- Group fragment size order calculation (mb_avg_fragment_size_order),
per-order XArray insertion/migration/removal
(mb_update_avg_fragment_size and mb_set_largest_free_order), and
largest free order tracking.
- Group eligibility filtering (ext4_mb_good_group_nolock and
ext4_mb_good_group) across CR_POWER2_ALIGNED, CR_GOAL_LEN_FAST,
CR_BEST_AVAIL_LEN, CR_GOAL_LEN_SLOW, and CR_ANY_FREE, including
BBITMAP_CORRUPT, 0 free blocks, uninitialized bitmaps, and flex_bg
metadata allocation rules.
- Goal-based allocation (ext4_mb_find_by_goal) covering power-of-two
buddies, non-power-of-two extents, EXT4_MB_HINT_MERGE tail merging,
stripe-aligned requests, and occupied goals.
- Buddy and bitmap group scanning (ext4_mb_simple_scan_group,
ext4_mb_scan_aligned, ext4_mb_complex_scan_group,
ext4_mb_measure_extent, and ext4_mb_try_best_found) including
s_mb_max_to_scan limits and EXT4_MB_HINT_FIRST.
- Regular allocator orchestration (ext4_mb_regular_allocator) for
EXT4_MB_HINT_GOAL_ONLY, stream allocation (EXT4_MB_STREAM_ALLOC) with
s_mb_last_groups updates, and MB_OPTIMIZE_SCAN with CR_BEST_AVAIL_LEN
goal length trimming.
- Block bitmap prefetching (ext4_mb_prefetch and ext4_mb_prefetch_fini)
and allocation context initialization and request normalization
(ext4_mb_initialize_context and ext4_mb_normalize_request across
metadata/no-normalize, small/large/huge files, stripe alignment, and
existing preallocation overlap avoidance).
- Inode and locality group preallocation lifecycles (ext4_mb_pa_alloc,
ext4_mb_pa_put_free, ext4_mb_new_preallocation,
ext4_mb_use_preallocated, ext4_mb_generate_from_pa,
ext4_mb_discard_group_preallocations, ext4_discard_preallocations,
ext4_mb_discard_preallocations, and locality group list trimming past
PREALLOC_TB_SIZE).
- Top-level allocation and deallocation entry points
(ext4_mb_new_blocks including PA satisfaction and ENOSPC retry after
discarding group PAs, ext4_mb_mark_context, ext4_mb_mark_bb,
ext4_free_blocks with EXT4_FREE_BLOCKS_NO_QUOT_UPDATE /
VALIDATED / METADATA and cross-group extents, mb_free_metadata
merging and splitting with ext4_process_freed_data,
ext4_group_add_blocks, ext4_trim_fs with a static stub for
ext4_issue_discard, and ext4_mballoc_query_range).
Export the tested functions with EXPORT_SYMBOL_FOR_EXT4_TEST and add a
KUNIT_STATIC_STUB_REDIRECT hook to ext4_issue_discard.
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
fs/ext4/mballoc-test.c | 1855 +++++++++++++++++++++++++++++++++++++++-
fs/ext4/mballoc.c | 201 ++++-
fs/ext4/mballoc.h | 49 ++
3 files changed, 2089 insertions(+), 16 deletions(-)
diff --git a/fs/ext4/mballoc-test.c b/fs/ext4/mballoc-test.c
index d31780075c21..402f4efdf25a 100644
--- a/fs/ext4/mballoc-test.c
+++ b/fs/ext4/mballoc-test.c
@@ -79,6 +79,7 @@ static int mbt_mb_init(struct super_block *sb)
sb->s_bdev = kzalloc_obj(*sb->s_bdev);
if (sb->s_bdev == NULL)
return -ENOMEM;
+ bdev_set_flag(sb->s_bdev, BD_READ_ONLY);
sb->s_bdev->bd_queue = kzalloc_obj(struct request_queue);
if (sb->s_bdev->bd_queue == NULL) {
@@ -162,10 +163,15 @@ static struct super_block *mbt_ext4_alloc_super_block(void)
goto out_deactivate;
bgl_lock_init(sbi->s_blockgroup_lock);
+ spin_lock_init(&sbi->s_error_lock);
+ ratelimit_state_init(&sbi->s_err_ratelimit_state, 5 * HZ, 10);
+ ratelimit_state_init(&sbi->s_warning_ratelimit_state, 5 * HZ, 10);
+ ratelimit_state_init(&sbi->s_msg_ratelimit_state, 5 * HZ, 10);
sbi->s_es = &fsb->es;
sbi->s_sb = sb;
sb->s_fs_info = sbi;
+ set_opt(sb, ERRORS_CONT);
up_write(&sb->s_umount);
return sb;
@@ -341,15 +347,72 @@ ext4_mb_mark_context_stub(handle_t *handle, struct super_block *sb, bool state,
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group);
struct buffer_head *bitmap_bh = &grp_ctx->bitmap_bh;
+ struct ext4_group_desc *gdp = &grp_ctx->desc;
+ ext4_grpblk_t i, already = 0, changed = len;
+ __u32 free;
- if (state)
+ for (i = 0; i < len; i++) {
+ if (mb_test_bit_test(blkoff + i, bitmap_bh->b_data) == state)
+ already++;
+ }
+ if (flags & 0x0001 /* EXT4_MB_BITMAP_MARKED_CHECK */)
+ changed = len - already;
+
+ free = ext4_free_group_clusters(sb, gdp);
+ if (state) {
mb_set_bits(bitmap_bh->b_data, blkoff, len);
- else
+ if (free >= changed)
+ ext4_free_group_clusters_set(sb, gdp, free - changed);
+ } else {
mb_clear_bits_test(bitmap_bh->b_data, blkoff, len);
+ ext4_free_group_clusters_set(sb, gdp, free + changed);
+ }
+
+ if (ret_changed)
+ *ret_changed = changed;
return 0;
}
+static int
+ext4_issue_discard_stub(struct super_block *sb, ext4_group_t block_group,
+ ext4_grpblk_t cluster, int count)
+{
+ return 0;
+}
+
+static struct inode *mbt_alloc_test_inode(struct kunit *test,
+ struct super_block *sb,
+ umode_t mode)
+{
+ struct ext4_inode_info *ei;
+ struct inode *inode;
+
+ ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ei);
+
+ INIT_LIST_HEAD(&ei->i_orphan);
+ init_rwsem(&ei->xattr_sem);
+ init_rwsem(&ei->i_data_sem);
+ ei->i_prealloc_node = RB_ROOT;
+ atomic_set(&ei->i_prealloc_active, 0);
+ rwlock_init(&ei->i_prealloc_lock);
+ spin_lock_init(&ei->i_block_reservation_lock);
+ spin_lock_init(&ei->i_raw_lock);
+
+ inode = &ei->vfs_inode;
+ inode_init_once(inode);
+ ext4_fc_init_inode(inode);
+ inode->i_sb = sb;
+ inode->i_ino = 12;
+ inode->i_mode = mode;
+ inode->i_flags |= S_NOQUOTA;
+ atomic_set(&inode->i_writecount, 1);
+ ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS);
+
+ return inode;
+}
+
#define TEST_GOAL_GROUP 1
static int mbt_kunit_init(struct kunit *test)
{
@@ -382,6 +445,9 @@ static int mbt_kunit_init(struct kunit *test)
kunit_activate_static_stub(test,
ext4_mb_mark_context,
ext4_mb_mark_context_stub);
+ kunit_activate_static_stub(test,
+ ext4_issue_discard,
+ ext4_issue_discard_stub);
/* stub function will be called in mbt_mb_init->ext4_mb_init */
if (mbt_mb_init(sb) != 0) {
@@ -402,7 +468,10 @@ static void mbt_kunit_exit(struct kunit *test)
if (!sb)
return;
+ if (sb->s_blocksize <= PAGE_SIZE)
+ ext4_mb_discard_preallocations_test(sb, 0);
mbt_mb_release(sb);
+ rcu_barrier();
mbt_ctx_release(sb);
mbt_ext4_free_super_block(sb);
}
@@ -417,11 +486,7 @@ static void test_new_blocks_simple(struct kunit *test)
ext4_fsblk_t found;
struct ext4_sb_info *sbi = EXT4_SB(sb);
- inode = kunit_kzalloc(test, sizeof(*inode), GFP_KERNEL);
- if (!inode)
- return;
-
- inode->i_sb = sb;
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
ar.inode = inode;
/* get block at goal */
@@ -527,10 +592,7 @@ test_free_blocks_simple_range(struct kunit *test, ext4_group_t goal_group,
struct inode *inode;
ext4_fsblk_t block;
- inode = kunit_kzalloc(test, sizeof(*inode), GFP_KERNEL);
- if (!inode)
- return;
- inode->i_sb = sb;
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
if (len == 0)
return;
@@ -601,10 +663,7 @@ static void test_mark_diskspace_used(struct kunit *test)
mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
- inode = kunit_kzalloc(test, sizeof(*inode), GFP_KERNEL);
- if (!inode)
- return;
- inode->i_sb = sb;
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
ac.ac_status = AC_STATUS_FOUND;
ac.ac_sb = sb;
@@ -949,6 +1008,1747 @@ static void test_mb_mark_used_cost(struct kunit *test)
ext4_mb_unload_buddy_test(&e4b);
}
+static void test_mb_bit_ops(struct kunit *test)
+{
+ u8 *buf;
+ void *unaligned;
+ int bit, i;
+
+ buf = kunit_kzalloc(test, 128, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+
+ /*
+ * Use an unaligned pointer (offset by 3 bytes) to exercise
+ * mb_correct_addr_and_bit() on 64-bit/32-bit architectures.
+ */
+ unaligned = buf + 3;
+
+ mb_set_bit_test(0, unaligned);
+ mb_set_bit_test(7, unaligned);
+ mb_set_bit_test(64, unaligned);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(0, unaligned), 0);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(1, unaligned), 0);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(7, unaligned), 0);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(64, unaligned), 0);
+
+ mb_clear_bit_test(7, unaligned);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(7, unaligned), 0);
+
+ bit = mb_find_next_bit_test(unaligned, 128, 1);
+ KUNIT_EXPECT_EQ(test, bit, 64);
+
+ /* Clamping when no set bit exists before max */
+ bit = mb_find_next_bit_test(unaligned, 60, 1);
+ KUNIT_EXPECT_EQ(test, bit, 60);
+
+ /* Zero-length mb_set_bits / mb_clear_bits should be a no-op */
+ memset(buf, 0, 128);
+ mb_set_bits(unaligned, 10, 0);
+ KUNIT_EXPECT_EQ(test, mb_find_next_bit_test(unaligned, 256, 0), 256);
+
+ /* Multi-word unaligned range spanning head bits, 32-bit words, and tail */
+ mb_set_bits(unaligned, 5, 75);
+ KUNIT_EXPECT_EQ(test, mb_find_next_bit_test(unaligned, 256, 0), 5);
+ KUNIT_EXPECT_EQ(test, mb_find_next_zero_bit_test(unaligned, 256, 5), 80);
+ /* Clamping when no zero bit exists before max */
+ KUNIT_EXPECT_EQ(test, mb_find_next_zero_bit_test(unaligned, 50, 5), 50);
+
+ /* Clear a sub-range across 32-bit boundaries */
+ mb_clear_bits_test(unaligned, 13, 50);
+ for (i = 0; i < 100; i++) {
+ int expected = (i >= 5 && i < 13) || (i >= 63 && i < 80);
+
+ KUNIT_EXPECT_EQ(test, !!mb_test_bit_test(i, unaligned), expected);
+ }
+}
+
+static void test_mb_test_and_clear_bits(struct kunit *test)
+{
+ u8 *buf;
+ void *bm;
+ int ret, i;
+
+ buf = kunit_kzalloc(test, 128, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+ bm = buf + 3;
+
+ /* Case 1: All bits in range [5, 85) are set -> returns -1 and clears all */
+ memset(buf, 0xff, 128);
+ ret = mb_test_and_clear_bits_test(bm, 5, 80);
+ KUNIT_EXPECT_EQ(test, ret, -1);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(4, bm), 0);
+ for (i = 5; i < 85; i++)
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(i, bm), 0);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(85, bm), 0);
+
+ /* Case 2: Zero bit in the unaligned head (bit 7) */
+ memset(buf, 0xff, 128);
+ mb_clear_bit_test(7, bm);
+ ret = mb_test_and_clear_bits_test(bm, 5, 80);
+ KUNIT_EXPECT_EQ(test, ret, 7);
+ for (i = 5; i < 85; i++)
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(i, bm), 0);
+
+ /* Case 3: Zero bit inside the 32-bit word fast-path region (bit 45) */
+ memset(buf, 0xff, 128);
+ mb_clear_bit_test(45, bm);
+ ret = mb_test_and_clear_bits_test(bm, 5, 80);
+ KUNIT_EXPECT_EQ(test, ret, 45);
+ for (i = 5; i < 85; i++)
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(i, bm), 0);
+}
+
+static void test_mb_find_buddy_and_order(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_buddy e4b;
+ struct ext4_free_extent ex;
+ void *bb;
+ int max = 0, ret;
+ int ord0, ord1, ord2, ord3;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ ret = ext4_mb_load_buddy_test(sb, TEST_GOAL_GROUP, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /* Order 0 returns bitmap and bit count */
+ bb = mb_find_buddy_test(&e4b, 0, &max);
+ KUNIT_EXPECT_PTR_EQ(test, bb, e4b.bd_bitmap);
+ KUNIT_EXPECT_EQ(test, max, (int)(sb->s_blocksize << 3));
+
+ /* Valid higher order returns buddy offset */
+ bb = mb_find_buddy_test(&e4b, 1, &max);
+ KUNIT_EXPECT_PTR_EQ(test, bb, e4b.bd_buddy + sbi->s_mb_offsets[1]);
+ KUNIT_EXPECT_EQ(test, max, sbi->s_mb_maxs[1]);
+
+ /* Out-of-bounds order (> bd_blkbits + 1) returns NULL and max = 0 */
+ bb = mb_find_buddy_test(&e4b, MB_NUM_ORDERS(sb), &max);
+ KUNIT_EXPECT_NULL(test, bb);
+ KUNIT_EXPECT_EQ(test, max, 0);
+
+ /*
+ * Mark cluster 0 used in group 1: this splits the group so that:
+ * - cluster 1 is free at order 0
+ * - clusters [2, 4) are free at order 1
+ * - clusters [4, 8) are free at order 2
+ * - clusters [8, 16) are free at order 3
+ */
+ ex.fe_group = TEST_GOAL_GROUP;
+ ex.fe_start = 0;
+ ex.fe_len = 1;
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ mb_mark_used_test(&e4b, &ex);
+ ord0 = mb_find_order_for_block_test(&e4b, 1);
+ ord1 = mb_find_order_for_block_test(&e4b, 2);
+ ord2 = mb_find_order_for_block_test(&e4b, 4);
+ ord3 = mb_find_order_for_block_test(&e4b, 8);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+ KUNIT_EXPECT_EQ(test, ord0, 0);
+ KUNIT_EXPECT_EQ(test, ord1, 1);
+ KUNIT_EXPECT_EQ(test, ord2, 2);
+ KUNIT_EXPECT_EQ(test, ord3, 3);
+
+ ext4_mb_unload_buddy_test(&e4b);
+}
+
+static void test_mb_find_extent(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_buddy e4b;
+ struct ext4_free_extent ex, found_used, found_small, found_coalesced, found_unaligned;
+ int ret, len_used, len_small, len_coalesced, len_unaligned;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ ret = ext4_mb_load_buddy_test(sb, TEST_GOAL_GROUP, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /*
+ * Leave only [10, 26) (16 clusters) free:
+ * [10, 12) order 1 (2) + [12, 16) order 2 (4) +
+ * [16, 24) order 3 (8) + [24, 26) order 1 (2).
+ */
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ ex.fe_group = TEST_GOAL_GROUP;
+ ex.fe_start = 0;
+ ex.fe_len = 10;
+ mb_mark_used_test(&e4b, &ex);
+
+ ex.fe_start = 26;
+ ex.fe_len = EXT4_CLUSTERS_PER_GROUP(sb) - 26;
+ mb_mark_used_test(&e4b, &ex);
+
+ len_used = mb_find_extent_test(&e4b, 5, 8, &found_used);
+ len_small = mb_find_extent_test(&e4b, 10, 1, &found_small);
+ len_coalesced = mb_find_extent_test(&e4b, 10, 20, &found_coalesced);
+ len_unaligned = mb_find_extent_test(&e4b, 11, 20, &found_unaligned);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+ /* Used block returns 0 */
+ KUNIT_EXPECT_EQ(test, len_used, 0);
+ KUNIT_EXPECT_EQ(test, found_used.fe_len, 0);
+
+ /* Needed = 1 at block 10 returns the order-1 buddy [10, 12) */
+ KUNIT_EXPECT_EQ(test, len_small, 2);
+ KUNIT_EXPECT_EQ(test, found_small.fe_start, 10);
+ KUNIT_EXPECT_EQ(test, found_small.fe_len, 2);
+
+ /* Needed = 20 at block 10 coalesces across orders 1, 2, 3, 1 -> 16 clusters */
+ KUNIT_EXPECT_EQ(test, len_coalesced, 16);
+ KUNIT_EXPECT_EQ(test, found_coalesced.fe_start, 10);
+ KUNIT_EXPECT_EQ(test, found_coalesced.fe_len, 16);
+
+ /* Unaligned start at block 11 coalesces [11, 26) -> 15 clusters */
+ KUNIT_EXPECT_EQ(test, len_unaligned, 15);
+ KUNIT_EXPECT_EQ(test, found_unaligned.fe_start, 11);
+ KUNIT_EXPECT_EQ(test, found_unaligned.fe_len, 15);
+
+ ext4_mb_unload_buddy_test(&e4b);
+}
+
+static void test_mb_generate_buddy_corrupt(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_group_info *grp;
+ void *bitmap, *buddy;
+ ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
+ bool is_corrupt;
+ ext4_grpblk_t corrected_free;
+ int i;
+
+ bitmap = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bitmap);
+ buddy = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buddy);
+
+ grp = ext4_get_group_info(sb, TEST_GOAL_GROUP);
+ KUNIT_ASSERT_NOT_NULL(test, grp);
+
+ /* Mark 10 clusters used in bitmap, so actual free is max - 10 */
+ mb_set_bits(bitmap, max, sb->s_blocksize * 8 - max);
+ mb_set_bits(bitmap, 0, 10);
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ for (i = 0; i < MB_NUM_ORDERS(sb); i++)
+ grp->bb_counters[i] = 0;
+ /* Intentionally set wrong bb_free = max (mismatch with max - 10) */
+ grp->bb_free = max;
+ memset(buddy, 0xff, sb->s_blocksize);
+ ext4_mb_generate_buddy_test(sb, buddy, bitmap, TEST_GOAL_GROUP, grp);
+ is_corrupt = EXT4_MB_GRP_BBITMAP_CORRUPT(grp);
+ corrected_free = grp->bb_free;
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+ KUNIT_EXPECT_TRUE(test, is_corrupt);
+ KUNIT_EXPECT_EQ(test, corrected_free, max - 10);
+}
+
+static void test_mb_free_blocks_double_free_and_fc_replay(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_buddy e4b;
+ struct ext4_free_extent ex;
+ bool corrupt_normal, corrupt_fc;
+ ext4_grpblk_t free_after_fc;
+ int ret;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ /* Part 1: Normal mode double-free marks group bitmap corrupt */
+ ret = ext4_mb_load_buddy_test(sb, TEST_GOAL_GROUP, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ /* Clusters [10, 14) are already free; freeing them again is a double free */
+ mb_free_blocks_test(NULL, &e4b, 10, 4);
+ corrupt_normal = EXT4_MB_GRP_BBITMAP_CORRUPT(e4b.bd_info);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_unload_buddy_test(&e4b);
+
+ KUNIT_EXPECT_TRUE(test, corrupt_normal);
+
+ /*
+ * Part 2: In EXT4_FC_REPLAY mode, freeing an already-freed range
+ * triggers mb_regenerate_buddy() instead of marking the group corrupt.
+ */
+ ret = ext4_mb_load_buddy_test(sb, 2, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ext4_lock_group(sb, 2);
+ ex.fe_group = 2;
+ ex.fe_start = 10;
+ ex.fe_len = 10;
+ mb_mark_used_test(&e4b, &ex);
+ /* Free sub-range [12, 16) first so [12, 16) is already free */
+ mb_free_blocks_test(NULL, &e4b, 12, 4);
+
+ /* Re-freeing [12, 16) in EXT4_FC_REPLAY mode regenerates buddy cleanly */
+ sbi->s_mount_state |= EXT4_FC_REPLAY;
+ mb_free_blocks_test(NULL, &e4b, 12, 4);
+ sbi->s_mount_state &= ~EXT4_FC_REPLAY;
+
+ corrupt_fc = EXT4_MB_GRP_BBITMAP_CORRUPT(e4b.bd_info);
+ free_after_fc = e4b.bd_info->bb_free;
+ ext4_unlock_group(sb, 2);
+ ext4_mb_unload_buddy_test(&e4b);
+
+ KUNIT_EXPECT_FALSE(test, corrupt_fc);
+ KUNIT_EXPECT_EQ(test, free_after_fc, EXT4_CLUSTERS_PER_GROUP(sb) - 6);
+}
+
+static void test_mb_xarray_orders_and_frag_size(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_group_info *grp;
+ int ord_1, ord_4, ord_7, ord_8;
+ int old_lfo, new_lfo, new_afso;
+ void *xa_lfo_entry, *xa_afso_entry, *xa_old_entry;
+ int i;
+
+ set_opt2(sb, MB_OPTIMIZE_SCAN);
+
+ /* mb_avg_fragment_size_order computes clamp(fls(len) - 2, 0, MB_NUM_ORDERS - 1) */
+ ord_1 = mb_avg_fragment_size_order_test(sb, 1);
+ ord_4 = mb_avg_fragment_size_order_test(sb, 4);
+ ord_7 = mb_avg_fragment_size_order_test(sb, 7);
+ ord_8 = mb_avg_fragment_size_order_test(sb, 8);
+ KUNIT_EXPECT_EQ(test, ord_1, 0);
+ KUNIT_EXPECT_EQ(test, ord_4, 1);
+ KUNIT_EXPECT_EQ(test, ord_7, 1);
+ KUNIT_EXPECT_EQ(test, ord_8, 2);
+
+ grp = ext4_get_group_info(sb, TEST_GOAL_GROUP);
+ KUNIT_ASSERT_NOT_NULL(test, grp);
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ old_lfo = grp->bb_largest_free_order;
+ for (i = 0; i < MB_NUM_ORDERS(sb); i++)
+ grp->bb_counters[i] = 0;
+ grp->bb_counters[3] = 2;
+ grp->bb_free = 16;
+ grp->bb_fragments = 2;
+
+ mb_set_largest_free_order_test(sb, grp);
+ mb_update_avg_fragment_size_test(sb, grp);
+
+ new_lfo = grp->bb_largest_free_order;
+ new_afso = grp->bb_avg_fragment_size_order;
+ xa_lfo_entry = xa_load(&sbi->s_mb_largest_free_orders[3], TEST_GOAL_GROUP);
+ xa_afso_entry = xa_load(&sbi->s_mb_avg_fragment_size[2], TEST_GOAL_GROUP);
+
+ /* Now mark group completely full and verify XArray entries are erased */
+ grp->bb_counters[3] = 0;
+ grp->bb_free = 0;
+ grp->bb_fragments = 0;
+ mb_set_largest_free_order_test(sb, grp);
+ mb_update_avg_fragment_size_test(sb, grp);
+ xa_old_entry = xa_load(&sbi->s_mb_largest_free_orders[3], TEST_GOAL_GROUP);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+ KUNIT_EXPECT_EQ(test, old_lfo, -1);
+ KUNIT_EXPECT_EQ(test, new_lfo, 3);
+ KUNIT_EXPECT_EQ(test, new_afso, 2);
+ KUNIT_EXPECT_PTR_EQ(test, xa_lfo_entry, grp);
+ KUNIT_EXPECT_PTR_EQ(test, xa_afso_entry, grp);
+ KUNIT_EXPECT_NULL(test, xa_old_entry);
+}
+
+static void test_mb_good_group(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_buddy e4b;
+ struct ext4_allocation_context ac = {};
+ struct ext4_group_info *grp1, *grp2;
+ int ret;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ /* Initialize group 1 buddy cache so bb_fragments and orders are populated */
+ ret = ext4_mb_load_buddy_test(sb, TEST_GOAL_GROUP, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ext4_mb_unload_buddy_test(&e4b);
+
+ grp1 = ext4_get_group_info(sb, TEST_GOAL_GROUP);
+ KUNIT_ASSERT_NOT_NULL(test, grp1);
+
+ ac.ac_sb = sb;
+ ac.ac_2order = 3;
+ ac.ac_g_ex.fe_len = 8;
+ ac.ac_flags = EXT4_MB_HINT_DATA;
+
+ /* Group 1 has all clusters free in 1 fragment -> eligible at all CRs */
+ KUNIT_EXPECT_TRUE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_POWER2_ALIGNED));
+ KUNIT_EXPECT_TRUE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_GOAL_LEN_FAST));
+ KUNIT_EXPECT_TRUE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_BEST_AVAIL_LEN));
+ KUNIT_EXPECT_TRUE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_GOAL_LEN_SLOW));
+ KUNIT_EXPECT_TRUE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_ANY_FREE));
+
+ /*
+ * Simulate high fragmentation: free = 16, fragments = 8 (avg = 2 < 8),
+ * largest_free_order = 1 (< ac_2order = 3).
+ * CR_POWER2_ALIGNED, CR_GOAL_LEN_FAST, CR_BEST_AVAIL_LEN must reject,
+ * while CR_GOAL_LEN_SLOW (free 16 >= 8) and CR_ANY_FREE accept.
+ */
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ grp1->bb_free = 16;
+ grp1->bb_fragments = 8;
+ grp1->bb_largest_free_order = 1;
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+ KUNIT_EXPECT_FALSE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_POWER2_ALIGNED));
+ KUNIT_EXPECT_FALSE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_GOAL_LEN_FAST));
+ KUNIT_EXPECT_FALSE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_BEST_AVAIL_LEN));
+ KUNIT_EXPECT_TRUE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_GOAL_LEN_SLOW));
+ KUNIT_EXPECT_TRUE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_ANY_FREE));
+
+ /* When free = 4 < fe_len = 8, CR_GOAL_LEN_SLOW rejects, CR_ANY_FREE accepts */
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ grp1->bb_free = 4;
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+ KUNIT_EXPECT_FALSE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_GOAL_LEN_SLOW));
+ KUNIT_EXPECT_TRUE(test, ext4_mb_good_group_test(&ac, TEST_GOAL_GROUP,
+ CR_ANY_FREE));
+
+ /* Flex_bg first group avoidance at CR_POWER2_ALIGNED for data */
+ ret = ext4_mb_load_buddy_test(sb, 0, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ext4_mb_unload_buddy_test(&e4b);
+ sbi->s_log_groups_per_flex = 2; /* flex_size = 4 >= EXT4_FLEX_SIZE_DIR_ALLOC_SCHEME */
+ KUNIT_EXPECT_FALSE(test, ext4_mb_good_group_test(&ac, 0, CR_POWER2_ALIGNED));
+ sbi->s_log_groups_per_flex = 0;
+
+ /*
+ * Test ext4_mb_good_group_nolock on uninitialized group 2:
+ * At inexpensive CR_POWER2_ALIGNED, it skips initializing group 2 and returns 0.
+ * At expensive CR_GOAL_LEN_SLOW (with STRICT_CHECK), it initializes group 2
+ * and returns 1.
+ */
+ grp2 = ext4_get_group_info(sb, 2);
+ KUNIT_ASSERT_NOT_NULL(test, grp2);
+ KUNIT_EXPECT_TRUE(test, EXT4_MB_GRP_NEED_INIT(grp2));
+
+ ac.ac_criteria = CR_POWER2_ALIGNED;
+ ret = ext4_mb_good_group_nolock_test(&ac, 2, CR_POWER2_ALIGNED);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_TRUE(test, EXT4_MB_GRP_NEED_INIT(grp2));
+
+ ac.ac_flags |= EXT4_MB_STRICT_CHECK;
+ ac.ac_criteria = CR_GOAL_LEN_SLOW;
+ ret = ext4_mb_good_group_nolock_test(&ac, 2, CR_GOAL_LEN_SLOW);
+ KUNIT_EXPECT_EQ(test, ret, 1);
+ KUNIT_EXPECT_FALSE(test, EXT4_MB_GRP_NEED_INIT(grp2));
+}
+
+static void test_mb_find_by_goal(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_buddy e4b;
+ struct ext4_allocation_context ac = {};
+ struct inode *inode;
+ int ret;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+
+ /* Mark [24, 32) used in group 1 so [20, 24) has only 4 free clusters */
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 24, 8);
+ ext4_get_group_info(sb, TEST_GOAL_GROUP)->bb_free -= 8;
+
+ /* 1. Exact goal hit at [10, 14) */
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_flags = EXT4_MB_HINT_TRY_GOAL;
+ ac.ac_g_ex.fe_group = TEST_GOAL_GROUP;
+ ac.ac_g_ex.fe_start = 10;
+ ac.ac_g_ex.fe_len = 4;
+ ac.ac_o_ex = ac.ac_g_ex;
+
+ ret = ext4_mb_find_by_goal_test(&ac, &e4b);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_group, TEST_GOAL_GROUP);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_start, 10);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 4);
+
+ /* 2. Partial goal at [20, 28) without HINT_MERGE fails to match */
+ memset(&ac, 0, sizeof(ac));
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_flags = EXT4_MB_HINT_TRY_GOAL;
+ ac.ac_g_ex.fe_group = TEST_GOAL_GROUP;
+ ac.ac_g_ex.fe_start = 20;
+ ac.ac_g_ex.fe_len = 8;
+ ac.ac_o_ex = ac.ac_g_ex;
+
+ ret = ext4_mb_find_by_goal_test(&ac, &e4b);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_CONTINUE);
+
+ /* 3. Partial goal at [20, 28) WITH EXT4_MB_HINT_MERGE allocates [20, 24) */
+ memset(&ac, 0, sizeof(ac));
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_flags = EXT4_MB_HINT_TRY_GOAL | EXT4_MB_HINT_MERGE;
+ ac.ac_g_ex.fe_group = TEST_GOAL_GROUP;
+ ac.ac_g_ex.fe_start = 20;
+ ac.ac_g_ex.fe_len = 8;
+ ac.ac_o_ex = ac.ac_g_ex;
+
+ ret = ext4_mb_find_by_goal_test(&ac, &e4b);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_start, 20);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 4);
+
+ /*
+ * 4. Stripe-aligned check: s_stripe = 8 clusters; unaligned start 36
+ * rejected, aligned 40 accepted.
+ */
+ sbi->s_stripe = EXT4_C2B(sbi, 8);
+ memset(&ac, 0, sizeof(ac));
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_flags = EXT4_MB_HINT_TRY_GOAL;
+ ac.ac_g_ex.fe_group = TEST_GOAL_GROUP;
+ ac.ac_g_ex.fe_start = 36;
+ ac.ac_g_ex.fe_len = 8;
+ ac.ac_o_ex = ac.ac_g_ex;
+
+ ret = ext4_mb_find_by_goal_test(&ac, &e4b);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_CONTINUE);
+
+ ac.ac_g_ex.fe_start = 40;
+ ac.ac_o_ex = ac.ac_g_ex;
+ ret = ext4_mb_find_by_goal_test(&ac, &e4b);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_start, 40);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 8);
+ sbi->s_stripe = 0;
+}
+
+static void test_mb_simple_and_aligned_scan(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_buddy e4b;
+ struct ext4_allocation_context ac = {};
+ struct inode *inode;
+ int ret;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+
+ /*
+ * Mark cluster 0 used in group 1 so:
+ * - order 3 buddy [8, 16) is the first order-3 buddy
+ * - first 16-cluster stripe [0, 16) is not completely free, so
+ * stripe-aligned scan must skip to cluster 16.
+ */
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 0, 1);
+
+ ret = ext4_mb_load_buddy_test(sb, TEST_GOAL_GROUP, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /* 1. ext4_mb_simple_scan_group with ac_2order = 3 (8 clusters) */
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_criteria = CR_POWER2_ALIGNED;
+ ac.ac_2order = 3;
+ ac.ac_g_ex.fe_len = 8;
+ ac.ac_o_ex.fe_len = 8;
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_simple_scan_group_test(&ac, &e4b);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_release_context_test(&ac);
+
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_group, TEST_GOAL_GROUP);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_start, 8);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 8);
+
+ /* 2. ext4_mb_scan_aligned with stripe = 16 clusters */
+ sbi->s_stripe = EXT4_C2B(sbi, 16);
+ memset(&ac, 0, sizeof(ac));
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_criteria = CR_GOAL_LEN_FAST;
+ ac.ac_g_ex.fe_len = 16;
+ ac.ac_o_ex.fe_len = 16;
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_scan_aligned_test(&ac, &e4b);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_release_context_test(&ac);
+
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_group, TEST_GOAL_GROUP);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_start, 16);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 16);
+
+ sbi->s_stripe = 0;
+ ext4_mb_unload_buddy_test(&e4b);
+}
+
+static void test_mb_complex_scan_and_measure(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_buddy e4b;
+ struct ext4_allocation_context ac = {};
+ struct inode *inode;
+ ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
+ int ret;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+
+ /*
+ * Create 3 power-of-2 aligned free extents in TEST_GOAL_GROUP:
+ * - [0, 4) len 4 (order 2 buddy)
+ * - [16, 32) len 16 (order 4 buddy)
+ * - [40, 48) len 8 (order 3 buddy)
+ * Mark everything else used (total free = 28 clusters).
+ */
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 4, 12);
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 32, 8);
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 48, max - 48);
+ ext4_get_group_info(sb, TEST_GOAL_GROUP)->bb_free = 28;
+
+ ret = ext4_mb_load_buddy_test(sb, TEST_GOAL_GROUP, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /*
+ * Case 1: Goal len = 5 at CR_GOAL_LEN_SLOW.
+ * Scans [0, 4) (len 4 < 5), [16, 32) (order 4 buddy -> len 16 > 5),
+ * and [40, 48) (order 3 buddy -> len 8 > 5, smaller than 16 so preferred!),
+ * then allocates 5 clusters from [40, 48) at finish_group.
+ */
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_criteria = CR_GOAL_LEN_SLOW;
+ ac.ac_g_ex.fe_len = 5;
+ ac.ac_o_ex.fe_len = 5;
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_complex_scan_group_test(&ac, &e4b);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_release_context_test(&ac);
+
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_start, 40);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 5);
+
+ /*
+ * Case 2: Goal len = 32 (larger than any free extent) with s_mb_max_to_scan = 1.
+ * Scanning [0, 4) and [16, 32) increments ac_found to 2 > s_mb_max_to_scan,
+ * triggering AC_STATUS_BREAK with best smaller extent [16, 32) (len 16)
+ * recorded in ac_b_ex. Calling ext4_mb_try_best_found() then claims [16, 32).
+ */
+ sbi->s_mb_max_to_scan = 1;
+ memset(&ac, 0, sizeof(ac));
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_criteria = CR_ANY_FREE;
+ ac.ac_g_ex.fe_len = 32;
+ ac.ac_o_ex.fe_len = 32;
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_complex_scan_group_test(&ac, &e4b);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_BREAK);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_start, 16);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 16);
+
+ ext4_mb_try_best_found_test(&ac, &e4b);
+ ext4_mb_release_context_test(&ac);
+
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_start, 16);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 16);
+
+ /*
+ * Case 3: EXT4_MB_HINT_FIRST immediately takes the first free extent [0, 4).
+ */
+ sbi->s_mb_max_to_scan = 200;
+ memset(&ac, 0, sizeof(ac));
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_criteria = CR_ANY_FREE;
+ ac.ac_flags = EXT4_MB_HINT_FIRST;
+ ac.ac_g_ex.fe_len = 4;
+ ac.ac_o_ex.fe_len = 4;
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_complex_scan_group_test(&ac, &e4b);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+ ext4_mb_release_context_test(&ac);
+
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_start, 0);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 4);
+
+ ext4_mb_unload_buddy_test(&e4b);
+}
+
+static void test_mb_regular_allocator(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_allocation_context ac = {};
+ struct inode *inode;
+ int ret, hash;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+
+ /*
+ * 1. EXT4_MB_HINT_GOAL_ONLY when goal block (group 0, cluster 0) is already
+ * marked used: must stop immediately without scanning other blocks.
+ */
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_flags = EXT4_MB_HINT_GOAL_ONLY;
+ ac.ac_g_ex.fe_group = 0;
+ ac.ac_g_ex.fe_start = 0;
+ ac.ac_g_ex.fe_len = 1;
+ ac.ac_o_ex = ac.ac_g_ex;
+
+ ret = ext4_mb_regular_allocator_test(&ac);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_CONTINUE);
+
+ /*
+ * 2. Stream allocation (EXT4_MB_STREAM_ALLOC) with group 0 full:
+ * allocates in group 1 and updates s_mb_last_groups[hash] to 1.
+ */
+ mbt_ctx_mark_used(sb, 0, 0, EXT4_CLUSTERS_PER_GROUP(sb));
+ ext4_free_group_clusters_set(sb, &MBT_GRP_CTX(sb, 0)->desc, 0);
+ ext4_get_group_info(sb, 0)->bb_free = 0;
+
+ memset(&ac, 0, sizeof(ac));
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_flags = EXT4_MB_STREAM_ALLOC | EXT4_MB_HINT_DATA;
+ ac.ac_g_ex.fe_len = 8;
+ ac.ac_orig_goal_len = 8;
+ ac.ac_o_ex.fe_len = 8;
+
+ ret = ext4_mb_regular_allocator_test(&ac);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_group, 1);
+
+ hash = (unsigned int)inode->i_ino % sbi->s_mb_nr_global_goals;
+ KUNIT_EXPECT_EQ(test, READ_ONCE(sbi->s_mb_last_groups[hash]), 1);
+
+ /*
+ * 3. MB_OPTIMIZE_SCAN with CR_BEST_AVAIL_LEN goal length trimming:
+ * Make group 2 the only group with free blocks, fragmented into two
+ * 8-cluster chunks [0, 8) and [16, 24) so largest_free_order = 3
+ * and avg_fragment_size = 8.
+ * Note: group 2 may have already been prefetched during step 2, so
+ * mark its blocks used via mb_mark_used_test on the loaded buddy.
+ */
+ set_opt2(sb, MB_OPTIMIZE_SCAN);
+ mbt_ctx_mark_used(sb, 1, 0, EXT4_CLUSTERS_PER_GROUP(sb));
+ ext4_free_group_clusters_set(sb, &MBT_GRP_CTX(sb, 1)->desc, 0);
+ ext4_get_group_info(sb, 1)->bb_free = 0;
+ ext4_get_group_info(sb, 1)->bb_fragments = 0;
+ mb_set_largest_free_order_test(sb, ext4_get_group_info(sb, 1));
+ mb_update_avg_fragment_size_test(sb, ext4_get_group_info(sb, 1));
+
+ mbt_ctx_mark_used(sb, 3, 0, EXT4_CLUSTERS_PER_GROUP(sb));
+ ext4_free_group_clusters_set(sb, &MBT_GRP_CTX(sb, 3)->desc, 0);
+ ext4_get_group_info(sb, 3)->bb_free = 0;
+ ext4_get_group_info(sb, 3)->bb_fragments = 0;
+ mb_set_largest_free_order_test(sb, ext4_get_group_info(sb, 3));
+ mb_update_avg_fragment_size_test(sb, ext4_get_group_info(sb, 3));
+
+ {
+ struct ext4_buddy e4b;
+ struct ext4_free_extent ex;
+
+ ret = ext4_mb_load_buddy_test(sb, 2, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ext4_lock_group(sb, 2);
+ ex.fe_group = 2;
+ ex.fe_start = 8;
+ ex.fe_len = 8;
+ mb_mark_used_test(&e4b, &ex);
+ ex.fe_start = 24;
+ ex.fe_len = EXT4_CLUSTERS_PER_GROUP(sb) - 24;
+ mb_mark_used_test(&e4b, &ex);
+ ext4_unlock_group(sb, 2);
+ ext4_mb_unload_buddy_test(&e4b);
+ }
+
+ memset(&ac, 0, sizeof(ac));
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_status = AC_STATUS_CONTINUE;
+ ac.ac_flags = EXT4_MB_HINT_DATA;
+ ac.ac_g_ex.fe_group = 0;
+ ac.ac_g_ex.fe_len = 16;
+ ac.ac_orig_goal_len = 16;
+ ac.ac_o_ex.fe_len = 8;
+
+ ret = ext4_mb_regular_allocator_test(&ac);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, ac.ac_status, AC_STATUS_FOUND);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_group, 2);
+ KUNIT_EXPECT_EQ(test, ac.ac_b_ex.fe_len, 8);
+}
+
+static void test_mb_prefetch(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_group_info *grp0, *grp1, *grp2;
+ ext4_group_t next_grp;
+ int ios = 0;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ grp0 = ext4_get_group_info(sb, 0);
+ grp1 = ext4_get_group_info(sb, 1);
+ grp2 = ext4_get_group_info(sb, 2);
+ KUNIT_ASSERT_NOT_NULL(test, grp0);
+ KUNIT_ASSERT_NOT_NULL(test, grp1);
+ KUNIT_ASSERT_NOT_NULL(test, grp2);
+
+ KUNIT_EXPECT_TRUE(test, EXT4_MB_GRP_NEED_INIT(grp0));
+ KUNIT_EXPECT_TRUE(test, EXT4_MB_GRP_NEED_INIT(grp1));
+ KUNIT_EXPECT_TRUE(test, EXT4_MB_GRP_NEED_INIT(grp2));
+
+ next_grp = ext4_mb_prefetch(sb, 0, 2, &ios);
+ KUNIT_EXPECT_EQ(test, next_grp, 2);
+ /* First prefetch of uninitialized buffers increments ios count */
+ KUNIT_EXPECT_EQ(test, ios, 2);
+
+ /* Repeated prefetch on the same groups does not re-read bitmaps */
+ ios = 0;
+ ext4_mb_prefetch(sb, 0, 2, &ios);
+ KUNIT_EXPECT_EQ(test, ios, 0);
+
+ /* ext4_mb_prefetch_fini initializes the prefetched groups [0, 2) */
+ ext4_mb_prefetch_fini(sb, next_grp, 2);
+ KUNIT_EXPECT_FALSE(test, EXT4_MB_GRP_NEED_INIT(grp0));
+ KUNIT_EXPECT_FALSE(test, EXT4_MB_GRP_NEED_INIT(grp1));
+ KUNIT_EXPECT_TRUE(test, EXT4_MB_GRP_NEED_INIT(grp2));
+}
+
+static void test_mb_initialize_context(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_allocation_context ac = {};
+ struct ext4_allocation_request ar = {};
+ struct inode *inode;
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+
+ /* 1. Small open file (<= s_mb_stream_request) uses locality group alloc */
+ i_size_write(inode, sb->s_blocksize);
+ ar.inode = inode;
+ ar.logical = 0;
+ ar.len = 1;
+ ar.goal = ext4_group_first_block_no(sb, TEST_GOAL_GROUP);
+ ar.flags = EXT4_MB_HINT_DATA;
+
+ ext4_mb_initialize_context_test(&ac, &ar);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_NE(test, ac.ac_flags & EXT4_MB_HINT_GROUP_ALLOC, 0);
+ KUNIT_EXPECT_NOT_NULL(test, ac.ac_lg);
+ KUNIT_EXPECT_EQ(test, ac.ac_g_ex.fe_group, TEST_GOAL_GROUP);
+
+ /* 2. Large open file (> s_mb_stream_request) uses stream alloc */
+ memset(&ac, 0, sizeof(ac));
+ i_size_write(inode, (loff_t)(sbi->s_mb_stream_request + 64) << sb->s_blocksize_bits);
+ ext4_mb_initialize_context_test(&ac, &ar);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_NE(test, ac.ac_flags & EXT4_MB_STREAM_ALLOC, 0);
+ KUNIT_EXPECT_NULL(test, ac.ac_lg);
+
+ /*
+ * 3. Closed file (i_writecount == 0) with size == isize and
+ * size > s_mb_stream_request sets EXT4_MB_HINT_NOPREALLOC.
+ */
+ memset(&ac, 0, sizeof(ac));
+ atomic_set(&inode->i_writecount, 0);
+ ar.logical = EXT4_LBLK_CMASK(sbi, sbi->s_mb_stream_request + EXT4_C2B(sbi, 2));
+ ar.len = 1;
+ i_size_write(inode, (loff_t)(ar.logical + EXT4_C2B(sbi, 1)) << sb->s_blocksize_bits);
+ /* Also test out-of-range ar.goal fallback to group 0 */
+ ar.goal = ext4_blocks_count(sbi->s_es) + 100;
+ ext4_mb_initialize_context_test(&ac, &ar);
+ ext4_mb_release_context_test(&ac);
+ KUNIT_EXPECT_NE(test, ac.ac_flags & EXT4_MB_HINT_NOPREALLOC, 0);
+ KUNIT_EXPECT_EQ(test, ac.ac_o_ex.fe_group, 0);
+ KUNIT_EXPECT_EQ(test, ac.ac_o_ex.fe_start, 0);
+}
+
+static void test_mb_normalize_request(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_allocation_context ac = {};
+ struct ext4_allocation_request ar = {};
+ struct inode *inode;
+ struct ext4_inode_info *ei;
+ struct ext4_prealloc_space left_pa = {}, right_pa = {};
+ int bsbits = sb->s_blocksize_bits;
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+ ei = EXT4_I(inode);
+
+ /* 1. Metadata request (no EXT4_MB_HINT_DATA) is not normalized */
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_o_ex.fe_logical = 0;
+ ac.ac_o_ex.fe_len = 1;
+ ac.ac_g_ex = ac.ac_o_ex;
+ ext4_mb_normalize_request_test(&ac, &ar);
+ KUNIT_EXPECT_EQ(test, ac.ac_g_ex.fe_len, 1);
+
+ /* 2. Locality group request normalizes fe_len to s_mb_group_prealloc */
+ memset(&ac, 0, sizeof(ac));
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_flags = EXT4_MB_HINT_DATA | EXT4_MB_HINT_GROUP_ALLOC;
+ ac.ac_lg = raw_cpu_ptr(sbi->s_locality_groups);
+ ac.ac_o_ex.fe_len = 1;
+ ac.ac_g_ex = ac.ac_o_ex;
+ ext4_mb_normalize_request_test(&ac, &ar);
+ KUNIT_EXPECT_EQ(test, ac.ac_g_ex.fe_len, sbi->s_mb_group_prealloc);
+
+ /* 3. Small file (<= 16KB) normalizes up to max(SZ_16K, cluster_size) */
+ if ((SZ_16K >> bsbits) >= sbi->s_cluster_ratio) {
+ memset(&ac, 0, sizeof(ac));
+ i_size_write(inode, 4096);
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_flags = EXT4_MB_HINT_DATA;
+ ac.ac_o_ex.fe_logical = 0;
+ ac.ac_o_ex.fe_len = 1;
+ ac.ac_g_ex = ac.ac_o_ex;
+ ext4_mb_normalize_request_test(&ac, &ar);
+ KUNIT_EXPECT_EQ(test, ac.ac_g_ex.fe_len,
+ EXT4_NUM_B2C(sbi, SZ_16K >> bsbits));
+ }
+
+ /* 4. 200KB file normalizes up to next power of two (256KB) */
+ if ((SZ_256K >> bsbits) >= sbi->s_cluster_ratio) {
+ memset(&ac, 0, sizeof(ac));
+ i_size_write(inode, 200 * 1024);
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_flags = EXT4_MB_HINT_DATA;
+ ac.ac_o_ex.fe_logical = 0;
+ ac.ac_o_ex.fe_len = 1;
+ ac.ac_g_ex = ac.ac_o_ex;
+ ext4_mb_normalize_request_test(&ac, &ar);
+ KUNIT_EXPECT_EQ(test, ac.ac_g_ex.fe_len,
+ EXT4_NUM_B2C(sbi, SZ_256K >> bsbits));
+ }
+
+ /*
+ * 5. Trimming against left/right allocated neighbors (ar.pleft / ar.pright)
+ * and against existing inode preallocations (ext4_mb_pa_adjust_overlap).
+ */
+ if (((loff_t)EXT4_C2B(sbi, 16) << bsbits) <= SZ_1M) {
+ memset(&ac, 0, sizeof(ac));
+ memset(&ar, 0, sizeof(ar));
+ i_size_write(inode, (loff_t)EXT4_C2B(sbi, 16) << bsbits);
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_flags = EXT4_MB_HINT_DATA;
+ ac.ac_o_ex.fe_logical = EXT4_C2B(sbi, 4);
+ ac.ac_o_ex.fe_len = 1;
+ ac.ac_g_ex = ac.ac_o_ex;
+
+ /* Left neighbor ends at cluster 2 (logical block EXT4_C2B(sbi, 2) - 1) */
+ ar.lleft = EXT4_C2B(sbi, 2) - 1;
+ ar.pleft = ext4_group_first_block_no(sb, TEST_GOAL_GROUP) + ar.lleft;
+ /* Right neighbor starts at cluster 10 */
+ ar.lright = EXT4_C2B(sbi, 10);
+ ar.pright = ext4_group_first_block_no(sb, TEST_GOAL_GROUP) + ar.lright;
+
+ /*
+ * Also insert a left PA covering clusters [2, 3) and a right PA covering
+ * clusters [8, 10) into ei->i_prealloc_node so ext4_mb_pa_adjust_overlap
+ * trims the range further to clusters [3, 8) (5 clusters).
+ */
+ spin_lock_init(&left_pa.pa_lock);
+ left_pa.pa_lstart = EXT4_C2B(sbi, 2);
+ left_pa.pa_len = 1;
+ left_pa.pa_free = 1;
+ left_pa.pa_deleted = 0;
+
+ spin_lock_init(&right_pa.pa_lock);
+ right_pa.pa_lstart = EXT4_C2B(sbi, 8);
+ right_pa.pa_len = 2;
+ right_pa.pa_free = 2;
+ right_pa.pa_deleted = 0;
+
+ rb_link_node(&left_pa.pa_node.inode_node, NULL,
+ &ei->i_prealloc_node.rb_node);
+ rb_insert_color(&left_pa.pa_node.inode_node, &ei->i_prealloc_node);
+ rb_link_node(&right_pa.pa_node.inode_node,
+ &left_pa.pa_node.inode_node,
+ &left_pa.pa_node.inode_node.rb_right);
+ rb_insert_color(&right_pa.pa_node.inode_node, &ei->i_prealloc_node);
+
+ ext4_mb_normalize_request_test(&ac, &ar);
+ ei->i_prealloc_node = RB_ROOT;
+
+ KUNIT_EXPECT_EQ(test, ac.ac_g_ex.fe_logical, EXT4_C2B(sbi, 3));
+ KUNIT_EXPECT_EQ(test, ac.ac_g_ex.fe_len, 5);
+ }
+}
+
+static void test_mb_inode_pa_lifecycle(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct inode *inode;
+ struct ext4_inode_info *ei;
+ struct ext4_allocation_request ar = {};
+ struct ext4_group_info *grp;
+ ext4_group_t pa_grp;
+ ext4_grpblk_t pa_off, free_before;
+ ext4_fsblk_t blk1, blk2;
+ void *pa_bitmap;
+ int err = 0;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+ ei = EXT4_I(inode);
+ /* Large file size (> s_mb_stream_request) so inode PA is used */
+ i_size_write(inode, (loff_t)(sbi->s_mb_stream_request + 64) << sb->s_blocksize_bits);
+
+ ar.inode = inode;
+ ar.logical = 0;
+ ar.len = 1;
+ ar.goal = ext4_group_first_block_no(sb, TEST_GOAL_GROUP);
+ ar.flags = EXT4_MB_HINT_DATA;
+
+ blk1 = ext4_mb_new_blocks(NULL, &ar, &err);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_NE(test, blk1, 0ULL);
+ KUNIT_EXPECT_EQ(test, ar.len, 1);
+ KUNIT_EXPECT_EQ(test, atomic_read(&ei->i_prealloc_active), 1);
+ KUNIT_EXPECT_FALSE(test, RB_EMPTY_ROOT(&ei->i_prealloc_node));
+
+ ext4_get_group_no_and_offset(sb, blk1, &pa_grp, &pa_off);
+ grp = ext4_get_group_info(sb, pa_grp);
+ free_before = ext4_free_group_clusters(sb, &MBT_GRP_CTX(sb, pa_grp)->desc) + 1;
+
+ /* Verify ext4_mb_generate_from_pa marks the PA's clusters in a bitmap */
+ pa_bitmap = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pa_bitmap);
+ ext4_lock_group(sb, pa_grp);
+ ext4_mb_generate_from_pa_test(sb, pa_bitmap, pa_grp);
+ ext4_unlock_group(sb, pa_grp);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(pa_off, pa_bitmap), 0);
+
+ /* Second sequential allocation is served directly from the inode PA */
+ ar.logical = EXT4_C2B(sbi, 1);
+ ar.len = 1;
+ ar.goal = blk1 + EXT4_C2B(sbi, 1);
+ blk2 = ext4_mb_new_blocks(NULL, &ar, &err);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, blk2, blk1 + EXT4_C2B(sbi, 1));
+
+ /* Discard inode preallocations and verify cleanup */
+ ext4_discard_preallocations(inode);
+ KUNIT_EXPECT_EQ(test, atomic_read(&ei->i_prealloc_active), 0);
+ KUNIT_EXPECT_TRUE(test, RB_EMPTY_ROOT(&ei->i_prealloc_node));
+ /* Only the 2 allocated clusters should remain used in the group */
+ KUNIT_EXPECT_EQ(test, grp->bb_free, free_before - 2);
+}
+
+static void test_mb_group_pa_lifecycle(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct inode *inode1, *inode2;
+ struct ext4_allocation_request ar = {};
+ struct ext4_group_info *grp;
+ ext4_fsblk_t blk1, blk2;
+ int err = 0, discarded;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode1 = mbt_alloc_test_inode(test, sb, S_IFREG);
+ inode2 = mbt_alloc_test_inode(test, sb, S_IFREG);
+ inode2->i_ino = 13;
+
+ /* Small files (<= s_mb_stream_request) use locality group PA (MB_GROUP_PA) */
+ i_size_write(inode1, sb->s_blocksize);
+ i_size_write(inode2, sb->s_blocksize);
+
+ ar.inode = inode1;
+ ar.logical = 0;
+ ar.len = 1;
+ ar.goal = ext4_group_first_block_no(sb, TEST_GOAL_GROUP);
+ ar.flags = EXT4_MB_HINT_DATA;
+
+ blk1 = ext4_mb_new_blocks(NULL, &ar, &err);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, blk1, ext4_group_first_block_no(sb, TEST_GOAL_GROUP));
+
+ /* Second small file allocation reuses the locality group PA */
+ ar.inode = inode2;
+ ar.logical = 0;
+ ar.len = 1;
+ ar.goal = ext4_group_first_block_no(sb, TEST_GOAL_GROUP);
+ blk2 = ext4_mb_new_blocks(NULL, &ar, &err);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, blk2, blk1 + EXT4_C2B(sbi, 1));
+
+ /* Discard all group preallocations and verify free count is restored */
+ discarded = ext4_mb_discard_preallocations_test(sb, 0);
+ rcu_barrier();
+ KUNIT_EXPECT_EQ(test, discarded, (int)sbi->s_mb_group_prealloc - 2);
+
+ grp = ext4_get_group_info(sb, TEST_GOAL_GROUP);
+ KUNIT_EXPECT_EQ(test, grp->bb_free, EXT4_CLUSTERS_PER_GROUP(sb) - 2);
+}
+
+static void test_mb_lg_list_trim(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_locality_group *lg;
+ struct inode *inode;
+ struct ext4_buddy e4b;
+ struct ext4_prealloc_space *pa;
+ int i, ret, count = 0;
+ int order = fls(15) - 1; /* pa_free = 15 -> order 3 */
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+ sbi->s_mb_group_prealloc = 16;
+
+ ret = ext4_mb_load_buddy_test(sb, TEST_GOAL_GROUP, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /*
+ * Create 9 locality-group PAs in bucket order 3 (each of length 16 with
+ * 1 cluster used -> pa_free = 15). When the 9th PA is added via
+ * ext4_mb_release_context -> ext4_mb_add_n_trim, lg_prealloc_count > 8
+ * triggers ext4_mb_discard_lg_preallocations() to trim the list to 5!
+ */
+ for (i = 0; i < 9; i++) {
+ struct ext4_allocation_context ac = {};
+ struct ext4_free_extent ex = {
+ .fe_group = TEST_GOAL_GROUP,
+ .fe_start = i * 32,
+ .fe_len = 16,
+ };
+
+ ac.ac_sb = sb;
+ ac.ac_inode = inode;
+ ac.ac_flags = EXT4_MB_HINT_DATA | EXT4_MB_HINT_GROUP_ALLOC;
+ ac.ac_lg = raw_cpu_ptr(sbi->s_locality_groups);
+ mutex_lock(&ac.ac_lg->lg_mutex);
+
+ ret = ext4_mb_pa_alloc_test(&ac);
+ if (ret) {
+ mutex_unlock(&ac.ac_lg->lg_mutex);
+ ext4_mb_unload_buddy_test(&e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ }
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ mb_mark_used_test(&e4b, &ex);
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+ ac.ac_status = AC_STATUS_FOUND;
+ ac.ac_o_ex.fe_len = 1;
+ ac.ac_b_ex = ex;
+ ac.ac_f_ex = ex;
+ ext4_mb_new_preallocation_test(&ac);
+ /* Release context updates pa_free to 15 and runs ext4_mb_add_n_trim */
+ ext4_mb_release_context_test(&ac);
+ }
+
+ ext4_mb_unload_buddy_test(&e4b);
+
+ lg = raw_cpu_ptr(sbi->s_locality_groups);
+ spin_lock(&lg->lg_prealloc_lock);
+ list_for_each_entry(pa, &lg->lg_prealloc_list[order], pa_node.lg_list)
+ count++;
+ spin_unlock(&lg->lg_prealloc_lock);
+
+ KUNIT_EXPECT_EQ(test, count, 5);
+}
+
+static void test_mb_new_blocks_and_enospc_retry(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct inode *inode1, *inode2;
+ struct ext4_allocation_request ar = {};
+ ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
+ ext4_fsblk_t blk;
+ int err = 0;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode1 = mbt_alloc_test_inode(test, sb, S_IFREG);
+ inode2 = mbt_alloc_test_inode(test, sb, S_IFREG);
+ inode2->i_ino = 14;
+ i_size_write(inode1, sb->s_blocksize);
+
+ /*
+ * Leave only 16 free clusters in group 1 and mark groups 0, 2, 3 full.
+ * Set s_mb_group_prealloc = 16 so allocating 1 cluster for inode1
+ * preallocates the remaining 15 clusters into a group PA (leaving
+ * 0 free clusters in buddy for all groups).
+ */
+ mbt_ctx_mark_used(sb, 0, 0, max);
+ ext4_free_group_clusters_set(sb, &MBT_GRP_CTX(sb, 0)->desc, 0);
+ ext4_get_group_info(sb, 0)->bb_free = 0;
+
+ mbt_ctx_mark_used(sb, 1, 16, max - 16);
+ ext4_free_group_clusters_set(sb, &MBT_GRP_CTX(sb, 1)->desc, 16);
+ ext4_get_group_info(sb, 1)->bb_free = 16;
+
+ mbt_ctx_mark_used(sb, 2, 0, max);
+ ext4_free_group_clusters_set(sb, &MBT_GRP_CTX(sb, 2)->desc, 0);
+ ext4_get_group_info(sb, 2)->bb_free = 0;
+
+ mbt_ctx_mark_used(sb, 3, 0, max);
+ ext4_free_group_clusters_set(sb, &MBT_GRP_CTX(sb, 3)->desc, 0);
+ ext4_get_group_info(sb, 3)->bb_free = 0;
+
+ percpu_counter_set(&sbi->s_freeclusters_counter, 16);
+ sbi->s_mb_group_prealloc = 16;
+
+ ar.inode = inode1;
+ ar.logical = 0;
+ ar.len = 1;
+ ar.goal = ext4_group_first_block_no(sb, 1);
+ ar.flags = EXT4_MB_HINT_DATA;
+
+ blk = ext4_mb_new_blocks(NULL, &ar, &err);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, ext4_get_group_info(sb, 1)->bb_free, 0);
+
+ /*
+ * Now request 4 metadata clusters (no EXT4_MB_HINT_DATA so it cannot
+ * use the PA directly). First allocator pass finds 0 free clusters,
+ * then ext4_mb_discard_preallocations_should_retry() discards the PA
+ * (recovering 15 clusters) and retries, succeeding!
+ */
+ memset(&ar, 0, sizeof(ar));
+ ar.inode = inode2;
+ ar.len = 4;
+ ar.goal = ext4_group_first_block_no(sb, 1);
+ ar.flags = 0;
+
+ blk = ext4_mb_new_blocks(NULL, &ar, &err);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_NE(test, blk, 0ULL);
+ KUNIT_EXPECT_EQ(test, ar.len, 4);
+
+ /*
+ * The 4-cluster power-of-2 allocation claimed [4, 8), leaving two
+ * fragments [1, 4) (3 clusters) and [8, 16) (8 clusters).
+ * Requesting 11 clusters allocates the largest fragment (8 clusters),
+ * then requesting 3 clusters allocates [1, 4), and the next request
+ * returns -ENOSPC.
+ */
+ ar.len = 11;
+ blk = ext4_mb_new_blocks(NULL, &ar, &err);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, ar.len, 8);
+
+ ar.len = 3;
+ blk = ext4_mb_new_blocks(NULL, &ar, &err);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, ar.len, 3);
+
+ ar.len = 1;
+ blk = ext4_mb_new_blocks(NULL, &ar, &err);
+ KUNIT_EXPECT_EQ(test, err, -ENOSPC);
+ KUNIT_EXPECT_EQ(test, blk, 0ULL);
+}
+
+static int
+ext4_handle_dirty_metadata_Updated_stub(const char *where, unsigned int line,
+ handle_t *handle, struct inode *inode,
+ struct buffer_head *bh)
+{
+ return 0;
+}
+
+static void test_mb_mark_context_and_mark_bb(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_group_desc *gdp1, *gdp2;
+ ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
+ ext4_grpblk_t changed = 0;
+ ext4_fsblk_t boundary_blk;
+ __u32 free_before;
+ int ret;
+
+ /* Test the real ext4_mb_mark_context implementation */
+ kunit_deactivate_static_stub(test, ext4_mb_mark_context);
+ kunit_activate_static_stub(test, __ext4_handle_dirty_metadata,
+ ext4_handle_dirty_metadata_Updated_stub);
+
+ gdp1 = ext4_get_group_desc(sb, 1, NULL);
+ gdp2 = ext4_get_group_desc(sb, 2, NULL);
+ KUNIT_ASSERT_NOT_NULL(test, gdp1);
+ KUNIT_ASSERT_NOT_NULL(test, gdp2);
+ free_before = ext4_free_group_clusters(sb, gdp1);
+
+ /* 1. Mark [10, 20) used with EXT4_MB_BITMAP_MARKED_CHECK (0x1) */
+ ret = ext4_mb_mark_context(NULL, sb, true, 1, 10, 10, 0x0001, &changed);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, changed, 10);
+ KUNIT_EXPECT_EQ(test, ext4_free_group_clusters(sb, gdp1), free_before - 10);
+
+ /* 2. Mark overlapping [15, 25) used -> only 5 new clusters changed */
+ ret = ext4_mb_mark_context(NULL, sb, true, 1, 15, 10, 0x0001, &changed);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, changed, 5);
+ KUNIT_EXPECT_EQ(test, ext4_free_group_clusters(sb, gdp1), free_before - 15);
+
+ /* 3. Clear [10, 25) -> 15 clusters freed */
+ ret = ext4_mb_mark_context(NULL, sb, false, 1, 10, 15, 0x0001, &changed);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, changed, 15);
+ KUNIT_EXPECT_EQ(test, ext4_free_group_clusters(sb, gdp1), free_before);
+
+ /*
+ * 4. Test ext4_mb_mark_bb across the boundary between group 1 and group 2:
+ * 2 clusters at the end of group 1 + 3 clusters at the start of group 2.
+ */
+ kunit_activate_static_stub(test, ext4_mb_mark_context,
+ ext4_mb_mark_context_stub);
+ boundary_blk = ext4_group_first_block_no(sb, 2) - EXT4_C2B(sbi, 2);
+ ext4_mb_mark_bb(sb, boundary_blk, EXT4_C2B(sbi, 5), true);
+
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(max - 2, mbt_ctx_bitmap(sb, 1)), 0);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(max - 1, mbt_ctx_bitmap(sb, 1)), 0);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(0, mbt_ctx_bitmap(sb, 2)), 0);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(2, mbt_ctx_bitmap(sb, 2)), 0);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(3, mbt_ctx_bitmap(sb, 2)), 0);
+
+ ext4_mb_mark_bb(sb, boundary_blk, EXT4_C2B(sbi, 5), false);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(max - 2, mbt_ctx_bitmap(sb, 1)), 0);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(0, mbt_ctx_bitmap(sb, 2)), 0);
+}
+
+static void test_ext4_free_blocks_flags_and_cross_group(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct inode *inode;
+ struct ext4_buddy e4b;
+ ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
+ ext4_fsblk_t grp1_first = ext4_group_first_block_no(sb, 1);
+ ext4_fsblk_t grp2_first = ext4_group_first_block_no(sb, 2);
+ s64 free_before;
+ int ret;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+
+ /* Mark clusters [10, 20) and [max - 2, max) in group 1, and [0, 3) in group 2 used */
+ mbt_ctx_mark_used(sb, 1, 10, 10);
+ mbt_ctx_mark_used(sb, 1, max - 2, 2);
+ mbt_ctx_mark_used(sb, 2, 0, 3);
+ ext4_get_group_info(sb, 1)->bb_free -= 12;
+ ext4_get_group_info(sb, 2)->bb_free -= 3;
+
+ ret = ext4_mb_load_buddy_test(sb, 1, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ext4_mb_unload_buddy_test(&e4b);
+ ret = ext4_mb_load_buddy_test(sb, 2, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ext4_mb_unload_buddy_test(&e4b);
+
+ /*
+ * 1. EXT4_FREE_BLOCKS_NOFREE_FIRST_CLUSTER | EXT4_FREE_BLOCKS_NOFREE_LAST_CLUSTER:
+ * Freeing from cluster 10 + 2 blocks through cluster 12 + 3 blocks
+ * skips partial clusters 10 and 12 and frees only full cluster 11!
+ */
+ ext4_free_blocks(NULL, inode, NULL,
+ grp1_first + EXT4_C2B(sbi, 10) + 2,
+ EXT4_C2B(sbi, 2) - 2 + 3,
+ EXT4_FREE_BLOCKS_NOFREE_FIRST_CLUSTER |
+ EXT4_FREE_BLOCKS_NOFREE_LAST_CLUSTER |
+ EXT4_FREE_BLOCKS_NO_QUOT_UPDATE);
+
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(10, mbt_ctx_bitmap(sb, 1)), 0);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(11, mbt_ctx_bitmap(sb, 1)), 0);
+ KUNIT_EXPECT_NE(test, mb_test_bit_test(12, mbt_ctx_bitmap(sb, 1)), 0);
+
+ /*
+ * 2. EXT4_FREE_BLOCKS_RERESERVE_CLUSTER: frees cluster 12 in bitmap & buddy
+ * without incrementing s_freeclusters_counter.
+ */
+ free_before = percpu_counter_sum(&sbi->s_freeclusters_counter);
+ ext4_free_blocks(NULL, inode, NULL,
+ grp1_first + EXT4_C2B(sbi, 12),
+ EXT4_C2B(sbi, 1),
+ EXT4_FREE_BLOCKS_RERESERVE_CLUSTER |
+ EXT4_FREE_BLOCKS_NO_QUOT_UPDATE);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(12, mbt_ctx_bitmap(sb, 1)), 0);
+ KUNIT_EXPECT_EQ(test, percpu_counter_sum(&sbi->s_freeclusters_counter),
+ free_before);
+
+ /*
+ * 3. Cross-group boundary free: 2 clusters at end of group 1 + 3 clusters
+ * at start of group 2.
+ */
+ ext4_free_blocks(NULL, inode, NULL,
+ grp2_first - EXT4_C2B(sbi, 2),
+ EXT4_C2B(sbi, 5),
+ EXT4_FREE_BLOCKS_NO_QUOT_UPDATE);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(max - 2, mbt_ctx_bitmap(sb, 1)), 0);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(max - 1, mbt_ctx_bitmap(sb, 1)), 0);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(0, mbt_ctx_bitmap(sb, 2)), 0);
+ KUNIT_EXPECT_EQ(test, mb_test_bit_test(2, mbt_ctx_bitmap(sb, 2)), 0);
+}
+
+static void test_mb_free_metadata_and_process_freed_data(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct inode *inode;
+ struct ext4_buddy e4b;
+ struct ext4_group_info *grp;
+ struct ext4_free_data *entry;
+ transaction_t trans = { .t_tid = 42 };
+ handle_t handle = { .h_transaction = &trans };
+ ext4_fsblk_t grp1_first = ext4_group_first_block_no(sb, TEST_GOAL_GROUP);
+ ext4_grpblk_t merged_start = 0, merged_count = 0;
+ int node_count = 0, ret;
+ struct rb_node *n;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+
+ /* Mark clusters [20, 28) used in group 1 and load buddy */
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 20, 8);
+ grp = ext4_get_group_info(sb, TEST_GOAL_GROUP);
+ grp->bb_free -= 8;
+
+ ret = ext4_mb_load_buddy_test(sb, TEST_GOAL_GROUP, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ext4_mb_unload_buddy_test(&e4b);
+
+ /*
+ * Free [20, 22), then [25, 28), then the middle [22, 25) as metadata
+ * with a valid journal handle (tid = 42).
+ * This exercises rbtree insertion and both prev and next extent merging!
+ */
+ ext4_free_blocks(&handle, inode, NULL,
+ grp1_first + EXT4_C2B(sbi, 20), EXT4_C2B(sbi, 2),
+ EXT4_FREE_BLOCKS_METADATA | EXT4_FREE_BLOCKS_NO_QUOT_UPDATE);
+ ext4_free_blocks(&handle, inode, NULL,
+ grp1_first + EXT4_C2B(sbi, 25), EXT4_C2B(sbi, 3),
+ EXT4_FREE_BLOCKS_METADATA | EXT4_FREE_BLOCKS_NO_QUOT_UPDATE);
+ ext4_free_blocks(&handle, inode, NULL,
+ grp1_first + EXT4_C2B(sbi, 22), EXT4_C2B(sbi, 3),
+ EXT4_FREE_BLOCKS_METADATA | EXT4_FREE_BLOCKS_NO_QUOT_UPDATE);
+
+ KUNIT_EXPECT_EQ(test, atomic_read(&sbi->s_mb_free_pending), 8);
+ /* Blocks are not yet freed in buddy until transaction commit */
+ KUNIT_EXPECT_EQ(test, grp->bb_free, EXT4_CLUSTERS_PER_GROUP(sb) - 8);
+
+ ext4_lock_group(sb, TEST_GOAL_GROUP);
+ for (n = rb_first(&grp->bb_free_root); n; n = rb_next(n)) {
+ entry = rb_entry(n, struct ext4_free_data, efd_node);
+ merged_start = entry->efd_start_cluster;
+ merged_count = entry->efd_count;
+ node_count++;
+ }
+ ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+ /* All 3 freed extents for tid 42 must have merged into a single node [20, 28) */
+ KUNIT_EXPECT_EQ(test, node_count, 1);
+ KUNIT_EXPECT_EQ(test, merged_start, 20);
+ KUNIT_EXPECT_EQ(test, merged_count, 8);
+
+ /* Commit tid 42 -> ext4_process_freed_data releases [20, 28) into buddy */
+ ext4_process_freed_data(sb, 42);
+ KUNIT_EXPECT_EQ(test, atomic_read(&sbi->s_mb_free_pending), 0);
+ KUNIT_EXPECT_TRUE(test, RB_EMPTY_ROOT(&grp->bb_free_root));
+ KUNIT_EXPECT_EQ(test, grp->bb_free, EXT4_CLUSTERS_PER_GROUP(sb));
+}
+
+static void test_ext4_group_add_blocks(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct ext4_buddy e4b;
+ struct ext4_group_info *grp;
+ ext4_fsblk_t grp1_first = ext4_group_first_block_no(sb, TEST_GOAL_GROUP);
+ s64 free_before;
+ int ret;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ /* Mark [50, 60) used in group 1 and initialize buddy */
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 50, 10);
+ grp = ext4_get_group_info(sb, TEST_GOAL_GROUP);
+ grp->bb_free -= 10;
+
+ ret = ext4_mb_load_buddy_test(sb, TEST_GOAL_GROUP, &e4b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ext4_mb_unload_buddy_test(&e4b);
+
+ free_before = percpu_counter_sum(&sbi->s_freeclusters_counter);
+ ret = ext4_group_add_blocks(NULL, sb,
+ grp1_first + EXT4_C2B(sbi, 50),
+ EXT4_C2B(sbi, 10));
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, grp->bb_free, EXT4_CLUSTERS_PER_GROUP(sb));
+ KUNIT_EXPECT_EQ(test, percpu_counter_sum(&sbi->s_freeclusters_counter),
+ free_before + 10);
+
+ /* Cross-group boundary addition must fail with -EINVAL */
+ ret = ext4_group_add_blocks(NULL, sb,
+ grp1_first + EXT4_C2B(sbi, EXT4_CLUSTERS_PER_GROUP(sb) - 2),
+ EXT4_C2B(sbi, 5));
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ /* System zone block (block 0) must fail with -EINVAL */
+ ret = ext4_group_add_blocks(NULL, sb, 0, EXT4_C2B(sbi, 1));
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+static int mbt_discard_calls;
+static int mbt_discard_clusters;
+
+static int
+ext4_issue_discard_count_stub(struct super_block *sb, ext4_group_t block_group,
+ ext4_grpblk_t cluster, int count)
+{
+ mbt_discard_calls++;
+ mbt_discard_clusters += count;
+ return 0;
+}
+
+static void test_ext4_trim_fs(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct fstrim_range range = {};
+ struct ext4_group_info *grp1;
+ struct inode *inode;
+ ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
+ int ret;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ inode = mbt_alloc_test_inode(test, sb, S_IFREG);
+ kunit_activate_static_stub(test, ext4_issue_discard,
+ ext4_issue_discard_count_stub);
+
+ /*
+ * Mark groups 0, 2, 3 completely used.
+ * In group 1, leave only [10, 14) (4 clusters) and [30, 50) (20 clusters) free.
+ */
+ mbt_ctx_mark_used(sb, 0, 0, max);
+ ext4_get_group_info(sb, 0)->bb_free = 0;
+ mbt_ctx_mark_used(sb, 2, 0, max);
+ ext4_get_group_info(sb, 2)->bb_free = 0;
+ mbt_ctx_mark_used(sb, 3, 0, max);
+ ext4_get_group_info(sb, 3)->bb_free = 0;
+
+ mbt_ctx_mark_used(sb, 1, 0, 10);
+ mbt_ctx_mark_used(sb, 1, 14, 16);
+ mbt_ctx_mark_used(sb, 1, 50, max - 50);
+ grp1 = ext4_get_group_info(sb, 1);
+ grp1->bb_free = 24;
+
+ /* Invalid range checks */
+ range.start = 0;
+ range.len = sb->s_blocksize - 1;
+ range.minlen = 0;
+ KUNIT_EXPECT_EQ(test, ext4_trim_fs(sb, &range), -EINVAL);
+
+ /* Trim with minlen = 10 clusters: skips [10, 14) and trims only [30, 50) (20 clusters) */
+ mbt_discard_calls = 0;
+ mbt_discard_clusters = 0;
+ range.start = 0;
+ range.len = (u64)ext4_blocks_count(sbi->s_es) << sb->s_blocksize_bits;
+ range.minlen = (u64)EXT4_C2B(sbi, 10) << sb->s_blocksize_bits;
+
+ ret = ext4_trim_fs(sb, &range);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, mbt_discard_calls, 1);
+ KUNIT_EXPECT_EQ(test, mbt_discard_clusters, 20);
+ KUNIT_EXPECT_EQ(test, range.len,
+ (u64)EXT4_C2B(sbi, 20) << sb->s_blocksize_bits);
+ KUNIT_EXPECT_TRUE(test, EXT4_MB_GRP_WAS_TRIMMED(grp1));
+
+ /* Second trim with same minlen is skipped because WAS_TRIMMED is set */
+ range.len = (u64)ext4_blocks_count(sbi->s_es) << sb->s_blocksize_bits;
+ ret = ext4_trim_fs(sb, &range);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, mbt_discard_calls, 1);
+ KUNIT_EXPECT_EQ(test, range.len, 0ULL);
+
+ /* Freeing a block clears WAS_TRIMMED, allowing subsequent trim */
+ ext4_free_blocks(NULL, inode, NULL,
+ ext4_group_first_block_no(sb, 1) + EXT4_C2B(sbi, 50),
+ EXT4_C2B(sbi, 1), EXT4_FREE_BLOCKS_NO_QUOT_UPDATE);
+ KUNIT_EXPECT_FALSE(test, EXT4_MB_GRP_WAS_TRIMMED(grp1));
+}
+
+struct mbt_query_ctx {
+ int meta_calls;
+ ext4_grpblk_t meta_start;
+ ext4_grpblk_t meta_len;
+ int free_calls;
+ ext4_grpblk_t free_starts[4];
+ ext4_grpblk_t free_lens[4];
+ int stop_after;
+};
+
+static int mbt_query_meta_cb(struct super_block *sb, ext4_group_t agno,
+ ext4_grpblk_t start, ext4_grpblk_t len, void *priv)
+{
+ struct mbt_query_ctx *q = priv;
+
+ q->meta_calls++;
+ q->meta_start = start;
+ q->meta_len = len;
+ return 0;
+}
+
+static int mbt_query_free_cb(struct super_block *sb, ext4_group_t agno,
+ ext4_grpblk_t start, ext4_grpblk_t len, void *priv)
+{
+ struct mbt_query_ctx *q = priv;
+
+ if (q->free_calls < 4) {
+ q->free_starts[q->free_calls] = start;
+ q->free_lens[q->free_calls] = len;
+ }
+ q->free_calls++;
+ if (q->stop_after && q->free_calls >= q->stop_after)
+ return -ECANCELED;
+ return 0;
+}
+
+static void test_ext4_mballoc_query_range(struct kunit *test)
+{
+ struct super_block *sb = (struct super_block *)test->priv;
+ struct mbt_query_ctx q = {};
+ ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
+ int ret;
+
+ if (sb->s_blocksize > PAGE_SIZE)
+ kunit_skip(test, "blocksize exceeds pagesize");
+
+ /*
+ * In group 1:
+ * [0, 10) used (before bb_first_free = 10 -> reported to meta_formatter)
+ * [10, 15) free (len 5)
+ * [15, 25) used
+ * [25, 32) free (len 7)
+ * [32, max) used
+ */
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 0, 10);
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 15, 10);
+ mbt_ctx_mark_used(sb, TEST_GOAL_GROUP, 32, max - 32);
+ ext4_get_group_info(sb, TEST_GOAL_GROUP)->bb_free = 12;
+
+ ret = ext4_mballoc_query_range(sb, TEST_GOAL_GROUP, 0, 100,
+ mbt_query_meta_cb, mbt_query_free_cb, &q);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, q.meta_calls, 1);
+ KUNIT_EXPECT_EQ(test, q.meta_start, 0);
+ KUNIT_EXPECT_EQ(test, q.meta_len, 10);
+ KUNIT_EXPECT_EQ(test, q.free_calls, 2);
+ KUNIT_EXPECT_EQ(test, q.free_starts[0], 10);
+ KUNIT_EXPECT_EQ(test, q.free_lens[0], 5);
+ KUNIT_EXPECT_EQ(test, q.free_starts[1], 25);
+ KUNIT_EXPECT_EQ(test, q.free_lens[1], 7);
+
+ /* Early abort from formatter callback propagates error code */
+ memset(&q, 0, sizeof(q));
+ q.stop_after = 1;
+ ret = ext4_mballoc_query_range(sb, TEST_GOAL_GROUP, 0, 100,
+ NULL, mbt_query_free_cb, &q);
+ KUNIT_EXPECT_EQ(test, ret, -ECANCELED);
+ KUNIT_EXPECT_EQ(test, q.free_calls, 1);
+}
+
static const struct mbt_ext4_block_layout mbt_test_layouts[] = {
{
.blocksize_bits = 10,
@@ -991,6 +2791,31 @@ static struct kunit_case mbt_test_cases[] = {
KUNIT_CASE_PARAM(test_mb_mark_used, mbt_layouts_gen_params),
KUNIT_CASE_PARAM(test_mb_free_blocks, mbt_layouts_gen_params),
KUNIT_CASE_PARAM(test_mark_diskspace_used, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_bit_ops, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_test_and_clear_bits, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_find_buddy_and_order, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_find_extent, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_generate_buddy_corrupt, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_free_blocks_double_free_and_fc_replay, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_xarray_orders_and_frag_size, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_good_group, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_find_by_goal, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_simple_and_aligned_scan, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_complex_scan_and_measure, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_regular_allocator, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_prefetch, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_initialize_context, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_normalize_request, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_inode_pa_lifecycle, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_group_pa_lifecycle, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_lg_list_trim, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_new_blocks_and_enospc_retry, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_mark_context_and_mark_bb, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_ext4_free_blocks_flags_and_cross_group, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_mb_free_metadata_and_process_freed_data, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_ext4_group_add_blocks, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_ext4_trim_fs, mbt_layouts_gen_params),
+ KUNIT_CASE_PARAM(test_ext4_mballoc_query_range, mbt_layouts_gen_params),
KUNIT_CASE_PARAM_ATTR(test_mb_mark_used_cost, mbt_layouts_gen_params,
{ .speed = KUNIT_SPEED_SLOW }),
{}
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index 0f73fb826dbf..46ba07e017a3 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -3957,11 +3957,14 @@ void ext4_mb_release(struct super_block *sb)
kfree(sbi->s_mb_last_groups);
}
-static inline int ext4_issue_discard(struct super_block *sb,
+int ext4_issue_discard(struct super_block *sb,
ext4_group_t block_group, ext4_grpblk_t cluster, int count)
{
ext4_fsblk_t discard_block;
+ KUNIT_STATIC_STUB_REDIRECT(ext4_issue_discard, sb, block_group,
+ cluster, count);
+
discard_block = (EXT4_C2B(EXT4_SB(sb), cluster) +
ext4_group_first_block_no(sb, block_group));
count = EXT4_C2B(EXT4_SB(sb), count);
@@ -7275,12 +7278,199 @@ void ext4_free_blocks_simple_test(struct inode *inode, ext4_fsblk_t block,
}
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_free_blocks_simple_test);
+void mb_set_bit_test(int bit, void *addr)
+{
+ mb_set_bit(bit, addr);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(mb_set_bit_test);
+
+int mb_test_and_clear_bits_test(void *bm, int cur, int len)
+{
+ return mb_test_and_clear_bits(bm, cur, len);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(mb_test_and_clear_bits_test);
+
+void *mb_find_buddy_test(struct ext4_buddy *e4b, int order, int *max)
+{
+ return mb_find_buddy(e4b, order, max);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(mb_find_buddy_test);
+
+int mb_find_order_for_block_test(struct ext4_buddy *e4b, int block)
+{
+ return mb_find_order_for_block(e4b, block);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(mb_find_order_for_block_test);
+
+int mb_find_extent_test(struct ext4_buddy *e4b, int block, int needed,
+ struct ext4_free_extent *ex)
+{
+ return mb_find_extent(e4b, block, needed, ex);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(mb_find_extent_test);
+
+void mb_regenerate_buddy_test(struct ext4_buddy *e4b)
+{
+ mb_regenerate_buddy(e4b);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(mb_regenerate_buddy_test);
+
+int mb_avg_fragment_size_order_test(struct super_block *sb, ext4_grpblk_t len)
+{
+ return mb_avg_fragment_size_order(sb, len);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(mb_avg_fragment_size_order_test);
+
+void mb_update_avg_fragment_size_test(struct super_block *sb,
+ struct ext4_group_info *grp)
+{
+ mb_update_avg_fragment_size(sb, grp);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(mb_update_avg_fragment_size_test);
+
+void mb_set_largest_free_order_test(struct super_block *sb,
+ struct ext4_group_info *grp)
+{
+ mb_set_largest_free_order(sb, grp);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(mb_set_largest_free_order_test);
+
+bool ext4_mb_good_group_test(struct ext4_allocation_context *ac,
+ ext4_group_t group, enum criteria cr)
+{
+ return ext4_mb_good_group(ac, group, cr);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_good_group_test);
+
+int ext4_mb_good_group_nolock_test(struct ext4_allocation_context *ac,
+ ext4_group_t group, enum criteria cr)
+{
+ return ext4_mb_good_group_nolock(ac, group, cr);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_good_group_nolock_test);
+
+int ext4_mb_find_by_goal_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b)
+{
+ return ext4_mb_find_by_goal(ac, e4b);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_find_by_goal_test);
+
+void ext4_mb_simple_scan_group_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b)
+{
+ ext4_mb_simple_scan_group(ac, e4b);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_simple_scan_group_test);
+
+void ext4_mb_scan_aligned_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b)
+{
+ ext4_mb_scan_aligned(ac, e4b);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_scan_aligned_test);
+
+void ext4_mb_complex_scan_group_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b)
+{
+ ext4_mb_complex_scan_group(ac, e4b);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_complex_scan_group_test);
+
+void ext4_mb_measure_extent_test(struct ext4_allocation_context *ac,
+ struct ext4_free_extent *ex,
+ struct ext4_buddy *e4b)
+{
+ ext4_mb_measure_extent(ac, ex, e4b);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_measure_extent_test);
+
+void ext4_mb_try_best_found_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b)
+{
+ ext4_mb_try_best_found(ac, e4b);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_try_best_found_test);
+
+int ext4_mb_regular_allocator_test(struct ext4_allocation_context *ac)
+{
+ return ext4_mb_regular_allocator(ac);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_regular_allocator_test);
+
+void ext4_mb_initialize_context_test(struct ext4_allocation_context *ac,
+ struct ext4_allocation_request *ar)
+{
+ ext4_mb_initialize_context(ac, ar);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_initialize_context_test);
+
+void ext4_mb_normalize_request_test(struct ext4_allocation_context *ac,
+ struct ext4_allocation_request *ar)
+{
+ ext4_mb_normalize_request(ac, ar);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_normalize_request_test);
+
+bool ext4_mb_use_preallocated_test(struct ext4_allocation_context *ac)
+{
+ return ext4_mb_use_preallocated(ac);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_use_preallocated_test);
+
+void ext4_mb_new_preallocation_test(struct ext4_allocation_context *ac)
+{
+ ext4_mb_new_preallocation(ac);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_new_preallocation_test);
+
+void ext4_mb_release_context_test(struct ext4_allocation_context *ac)
+{
+ ext4_mb_release_context(ac);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_release_context_test);
+
+int ext4_mb_pa_alloc_test(struct ext4_allocation_context *ac)
+{
+ return ext4_mb_pa_alloc(ac);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_pa_alloc_test);
+
+void ext4_mb_pa_put_free_test(struct ext4_allocation_context *ac)
+{
+ ext4_mb_pa_put_free(ac);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_pa_put_free_test);
+
+void ext4_mb_generate_from_pa_test(struct super_block *sb, void *bitmap,
+ ext4_group_t group)
+{
+ ext4_mb_generate_from_pa(sb, bitmap, group);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_generate_from_pa_test);
+
+int ext4_mb_discard_group_preallocations_test(struct super_block *sb,
+ ext4_group_t group, int *busy)
+{
+ return ext4_mb_discard_group_preallocations(sb, group, busy);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_discard_group_preallocations_test);
+
+int ext4_mb_discard_preallocations_test(struct super_block *sb, int needed)
+{
+ return ext4_mb_discard_preallocations(sb, needed);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_discard_preallocations_test);
+
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_wait_block_bitmap);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_init);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_get_group_desc);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_count_free_clusters);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_get_group_info);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_free_group_clusters);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_free_group_clusters_set);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_get_group_no_and_offset);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_get_group_number);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_release);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_read_block_bitmap_nowait);
EXPORT_SYMBOL_FOR_EXT4_TEST(mb_set_bits);
@@ -7288,4 +7478,13 @@ EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_fc_init_inode);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_mark_context);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_new_blocks);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_free_blocks);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_prefetch);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_prefetch_fini);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_discard_preallocations);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_mark_bb);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_process_freed_data);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_group_add_blocks);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_trim_fs);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mballoc_query_range);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_issue_discard);
#endif
diff --git a/fs/ext4/mballoc.h b/fs/ext4/mballoc.h
index 39333ce72cbd..17751ed6c9f5 100644
--- a/fs/ext4/mballoc.h
+++ b/fs/ext4/mballoc.h
@@ -275,6 +275,8 @@ extern int ext4_mb_mark_context(handle_t *handle,
ext4_group_t group, ext4_grpblk_t blkoff,
ext4_grpblk_t len, int flags,
ext4_grpblk_t *ret_changed);
+extern int ext4_issue_discard(struct super_block *sb,
+ ext4_group_t block_group, ext4_grpblk_t cluster, int count);
#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
extern void mb_clear_bits_test(void *bm, int cur, int len);
extern ext4_fsblk_t
@@ -283,7 +285,54 @@ ext4_mb_new_blocks_simple_test(struct ext4_allocation_request *ar,
extern int mb_find_next_zero_bit_test(void *addr, int max, int start);
extern int mb_find_next_bit_test(void *addr, int max, int start);
extern void mb_clear_bit_test(int bit, void *addr);
+extern void mb_set_bit_test(int bit, void *addr);
extern int mb_test_bit_test(int bit, void *addr);
+extern int mb_test_and_clear_bits_test(void *bm, int cur, int len);
+extern void *mb_find_buddy_test(struct ext4_buddy *e4b, int order, int *max);
+extern int mb_find_order_for_block_test(struct ext4_buddy *e4b, int block);
+extern int mb_find_extent_test(struct ext4_buddy *e4b, int block, int needed,
+ struct ext4_free_extent *ex);
+extern void mb_regenerate_buddy_test(struct ext4_buddy *e4b);
+extern int mb_avg_fragment_size_order_test(struct super_block *sb,
+ ext4_grpblk_t len);
+extern void mb_update_avg_fragment_size_test(struct super_block *sb,
+ struct ext4_group_info *grp);
+extern void mb_set_largest_free_order_test(struct super_block *sb,
+ struct ext4_group_info *grp);
+extern bool ext4_mb_good_group_test(struct ext4_allocation_context *ac,
+ ext4_group_t group, enum criteria cr);
+extern int ext4_mb_good_group_nolock_test(struct ext4_allocation_context *ac,
+ ext4_group_t group, enum criteria cr);
+extern int ext4_mb_find_by_goal_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b);
+extern void ext4_mb_simple_scan_group_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b);
+extern void ext4_mb_scan_aligned_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b);
+extern void ext4_mb_complex_scan_group_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b);
+extern void ext4_mb_measure_extent_test(struct ext4_allocation_context *ac,
+ struct ext4_free_extent *ex,
+ struct ext4_buddy *e4b);
+extern void ext4_mb_try_best_found_test(struct ext4_allocation_context *ac,
+ struct ext4_buddy *e4b);
+extern int ext4_mb_regular_allocator_test(struct ext4_allocation_context *ac);
+extern void ext4_mb_initialize_context_test(struct ext4_allocation_context *ac,
+ struct ext4_allocation_request *ar);
+extern void ext4_mb_normalize_request_test(struct ext4_allocation_context *ac,
+ struct ext4_allocation_request *ar);
+extern bool ext4_mb_use_preallocated_test(struct ext4_allocation_context *ac);
+extern void ext4_mb_new_preallocation_test(struct ext4_allocation_context *ac);
+extern void ext4_mb_release_context_test(struct ext4_allocation_context *ac);
+extern int ext4_mb_pa_alloc_test(struct ext4_allocation_context *ac);
+extern void ext4_mb_pa_put_free_test(struct ext4_allocation_context *ac);
+extern void ext4_mb_generate_from_pa_test(struct super_block *sb, void *bitmap,
+ ext4_group_t group);
+extern int ext4_mb_discard_group_preallocations_test(struct super_block *sb,
+ ext4_group_t group,
+ int *busy);
+extern int ext4_mb_discard_preallocations_test(struct super_block *sb,
+ int needed);
extern int
ext4_mb_mark_diskspace_used_test(struct ext4_allocation_context *ac,
handle_t *handle);
--
2.56.0.542.g3ef89b7020-goog
next prev parent reply other threads:[~2026-11-12 4:07 UTC|newest]
Thread overview: 55+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 4:06 [PATCH 00/27] ext4: add lots of KUnit tests! Theodore Ts'o
2026-11-12 4:06 ` [PATCH 01/27] ext4: add KUinit tests for fs/ext4/extents_status.c Theodore Ts'o
2026-11-12 4:18 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 02/27] ext4: add KUnit tests for fs/ext4/extents.c Theodore Ts'o
2026-11-12 4:28 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 03/27] ext4: add KUnit tests for ext4_ext_split() leaf and index splitting Theodore Ts'o
2026-11-12 4:15 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 04/27] ext4: add KUnit tests for fs/ext4/xattr.c Theodore Ts'o
2026-11-12 4:26 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 05/27] ext4: add KUnit tests for fs/ext4/namei.c Theodore Ts'o
2026-11-12 4:21 ` sashiko-bot
2026-11-12 4:06 ` Theodore Ts'o [this message]
2026-11-12 4:26 ` [PATCH 06/27] ext4: expand KUnit tests for fs/ext4/mballoc.c sashiko-bot
2026-11-12 4:06 ` [PATCH 07/27] ext4: add KUnit tests for fs/ext4/inline.c Theodore Ts'o
2026-11-12 4:29 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 08/27] ext4: zero s_fc_replay_state in ext4_fc_replay_cleanup() Theodore Ts'o
2026-11-12 4:16 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 09/27] ext4: add KUnit tests for fs/ext4/fast_commit.c Theodore Ts'o
2026-11-12 4:28 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 10/27] ext4: expand KUnit tests for fs/ext4/inode.c Theodore Ts'o
2026-11-12 4:36 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 11/27] ext4: add KUnit tests for fs/ext4/ialloc.c Theodore Ts'o
2026-11-12 4:25 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 12/27] ext4: add KUnit tests for fs/ext4/balloc.c Theodore Ts'o
2026-11-12 4:16 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 13/27] ext4: add KUnit tests for fs/ext4/orphan.c Theodore Ts'o
2026-11-12 4:18 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 14/27] ext4: add KUnit tests for fs/ext4/block_validity.c Theodore Ts'o
2026-11-12 4:21 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 15/27] ext4: check multi-block overlap on left traversal in add_system_zone() Theodore Ts'o
2026-11-12 4:10 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 16/27] ext4: enable full feature coverage in fs/ext4/.kunitconfig Theodore Ts'o
2026-11-12 4:24 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 17/27] ext4: add KUnit tests for fs/ext4/super.c Theodore Ts'o
2026-11-12 4:27 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 18/27] ext4: add KUnit tests for fs/ext4/sysfs.c Theodore Ts'o
2026-11-12 4:32 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 19/27] ext4: add KUnit tests for fs/ext4/ioctl.c Theodore Ts'o
2026-11-12 4:35 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 20/27] ext4: add KUnit tests for fs/ext4/resize.c Theodore Ts'o
2026-11-12 4:34 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 21/27] ext4: add KUnit tests for fs/ext4/migrate.c Theodore Ts'o
2026-11-12 4:37 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 22/27] ext4: add KUnit tests for fs/ext4/indirect.c Theodore Ts'o
2026-11-12 4:46 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 23/27] ext4: expand KUnit tests in fs/ext4/extents-test.c for file range operations Theodore Ts'o
2026-11-12 4:34 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 24/27] ext4: add KUnit tests for fs/ext4/fsmap.c Theodore Ts'o
2026-11-12 4:30 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 25/27] ext4: add KUnit tests for fs/ext4/move_extent.c Theodore Ts'o
2026-11-12 4:36 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 26/27] ext4: add KUnit tests for fs/ext4/mmp.c Theodore Ts'o
2026-11-12 4:29 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 27/27] ext4: add KUnit tests for fs/ext4/dir.c Theodore Ts'o
2026-11-12 4:41 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261112040638.1576836-7-tytso@mit.edu \
--to=tytso@mit.edu \
--cc=linux-ext4@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.