Merge tag 'exfat-for-6.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkin...
[linux-2.6-microblaze.git] / fs / ext4 / mballoc-test.c
index f94901f..044ca52 100644 (file)
@@ -5,6 +5,7 @@
 
 #include <kunit/test.h>
 #include <kunit/static_stub.h>
+#include <linux/random.h>
 
 #include "ext4.h"
 
@@ -20,41 +21,135 @@ struct mbt_ctx {
 };
 
 struct mbt_ext4_super_block {
-       struct super_block sb;
+       struct ext4_super_block es;
+       struct ext4_sb_info sbi;
        struct mbt_ctx mbt_ctx;
 };
 
-#define MBT_CTX(_sb) (&(container_of((_sb), struct mbt_ext4_super_block, sb)->mbt_ctx))
+#define MBT_SB(_sb) (container_of((_sb)->s_fs_info, struct mbt_ext4_super_block, sbi))
+#define MBT_CTX(_sb) (&MBT_SB(_sb)->mbt_ctx)
 #define MBT_GRP_CTX(_sb, _group) (&MBT_CTX(_sb)->grp_ctx[_group])
 
+static const struct super_operations mbt_sops = {
+};
+
+static void mbt_kill_sb(struct super_block *sb)
+{
+       generic_shutdown_super(sb);
+}
+
+static struct file_system_type mbt_fs_type = {
+       .name                   = "mballoc test",
+       .kill_sb                = mbt_kill_sb,
+};
+
+static int mbt_mb_init(struct super_block *sb)
+{
+       ext4_fsblk_t block;
+       int ret;
+
+       /* needed by ext4_mb_init->bdev_nonrot(sb->s_bdev) */
+       sb->s_bdev = kzalloc(sizeof(*sb->s_bdev), GFP_KERNEL);
+       if (sb->s_bdev == NULL)
+               return -ENOMEM;
+
+       sb->s_bdev->bd_queue = kzalloc(sizeof(struct request_queue), GFP_KERNEL);
+       if (sb->s_bdev->bd_queue == NULL) {
+               kfree(sb->s_bdev);
+               return -ENOMEM;
+       }
+
+       /*
+        * needed by ext4_mb_init->ext4_mb_init_backend-> sbi->s_buddy_cache =
+        * new_inode(sb);
+        */
+       INIT_LIST_HEAD(&sb->s_inodes);
+       sb->s_op = &mbt_sops;
+
+       ret = ext4_mb_init(sb);
+       if (ret != 0)
+               goto err_out;
+
+       block = ext4_count_free_clusters(sb);
+       ret = percpu_counter_init(&EXT4_SB(sb)->s_freeclusters_counter, block,
+                                 GFP_KERNEL);
+       if (ret != 0)
+               goto err_mb_release;
+
+       ret = percpu_counter_init(&EXT4_SB(sb)->s_dirtyclusters_counter, 0,
+                                 GFP_KERNEL);
+       if (ret != 0)
+               goto err_freeclusters;
+
+       return 0;
+
+err_freeclusters:
+       percpu_counter_destroy(&EXT4_SB(sb)->s_freeclusters_counter);
+err_mb_release:
+       ext4_mb_release(sb);
+err_out:
+       kfree(sb->s_bdev->bd_queue);
+       kfree(sb->s_bdev);
+       return ret;
+}
+
+static void mbt_mb_release(struct super_block *sb)
+{
+       percpu_counter_destroy(&EXT4_SB(sb)->s_dirtyclusters_counter);
+       percpu_counter_destroy(&EXT4_SB(sb)->s_freeclusters_counter);
+       ext4_mb_release(sb);
+       kfree(sb->s_bdev->bd_queue);
+       kfree(sb->s_bdev);
+}
+
+static int mbt_set(struct super_block *sb, void *data)
+{
+       return 0;
+}
+
 static struct super_block *mbt_ext4_alloc_super_block(void)
 {
-       struct ext4_super_block *es = kzalloc(sizeof(*es), GFP_KERNEL);
-       struct ext4_sb_info *sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
-       struct mbt_ext4_super_block *fsb = kzalloc(sizeof(*fsb), GFP_KERNEL);
+       struct mbt_ext4_super_block *fsb;
+       struct super_block *sb;
+       struct ext4_sb_info *sbi;
+
+       fsb = kzalloc(sizeof(*fsb), GFP_KERNEL);
+       if (fsb == NULL)
+               return NULL;
 
-       if (fsb == NULL || sbi == NULL || es == NULL)
+       sb = sget(&mbt_fs_type, NULL, mbt_set, 0, NULL);
+       if (IS_ERR(sb))
                goto out;
 
-       sbi->s_es = es;
-       fsb->sb.s_fs_info = sbi;
-       return &fsb->sb;
+       sbi = &fsb->sbi;
+
+       sbi->s_blockgroup_lock =
+               kzalloc(sizeof(struct blockgroup_lock), GFP_KERNEL);
+       if (!sbi->s_blockgroup_lock)
+               goto out_deactivate;
+
+       bgl_lock_init(sbi->s_blockgroup_lock);
+
+       sbi->s_es = &fsb->es;
+       sb->s_fs_info = sbi;
+
+       up_write(&sb->s_umount);
+       return sb;
 
+out_deactivate:
+       deactivate_locked_super(sb);
 out:
        kfree(fsb);
-       kfree(sbi);
-       kfree(es);
        return NULL;
 }
 
 static void mbt_ext4_free_super_block(struct super_block *sb)
 {
-       struct mbt_ext4_super_block *fsb =
-               container_of(sb, struct mbt_ext4_super_block, sb);
+       struct mbt_ext4_super_block *fsb = MBT_SB(sb);
        struct ext4_sb_info *sbi = EXT4_SB(sb);
 
-       kfree(sbi->s_es);
-       kfree(sbi);
+       kfree(sbi->s_blockgroup_lock);
+       deactivate_super(sb);
        kfree(fsb);
 }
 
@@ -82,6 +177,9 @@ static void mbt_init_sb_layout(struct super_block *sb,
        sbi->s_clusters_per_group = layout->blocks_per_group >>
                                    layout->cluster_bits;
        sbi->s_desc_size = layout->desc_size;
+       sbi->s_desc_per_block_bits =
+               sb->s_blocksize_bits - (fls(layout->desc_size) - 1);
+       sbi->s_desc_per_block = 1 << sbi->s_desc_per_block_bits;
 
        es->s_first_data_block = cpu_to_le32(0);
        es->s_blocks_count_lo = cpu_to_le32(layout->blocks_per_group *
@@ -91,9 +189,13 @@ static void mbt_init_sb_layout(struct super_block *sb,
 static int mbt_grp_ctx_init(struct super_block *sb,
                            struct mbt_grp_ctx *grp_ctx)
 {
+       ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
+
        grp_ctx->bitmap_bh.b_data = kzalloc(EXT4_BLOCK_SIZE(sb), GFP_KERNEL);
        if (grp_ctx->bitmap_bh.b_data == NULL)
                return -ENOMEM;
+       mb_set_bits(grp_ctx->bitmap_bh.b_data, max, sb->s_blocksize * 8 - max);
+       ext4_free_group_clusters_set(sb, &grp_ctx->desc, max);
 
        return 0;
 }
@@ -112,6 +214,13 @@ static void mbt_ctx_mark_used(struct super_block *sb, ext4_group_t group,
        mb_set_bits(grp_ctx->bitmap_bh.b_data, start, len);
 }
 
+static void *mbt_ctx_bitmap(struct super_block *sb, ext4_group_t group)
+{
+       struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group);
+
+       return grp_ctx->bitmap_bh.b_data;
+}
+
 /* called after mbt_init_sb_layout */
 static int mbt_ctx_init(struct super_block *sb)
 {
@@ -133,6 +242,8 @@ static int mbt_ctx_init(struct super_block *sb)
         * block which will fail ext4_sb_block_valid check.
         */
        mb_set_bits(ctx->grp_ctx[0].bitmap_bh.b_data, 0, 1);
+       ext4_free_group_clusters_set(sb, &ctx->grp_ctx[0].desc,
+                                    EXT4_CLUSTERS_PER_GROUP(sb) - 1);
 
        return 0;
 out:
@@ -167,6 +278,13 @@ static int ext4_wait_block_bitmap_stub(struct super_block *sb,
                                       ext4_group_t block_group,
                                       struct buffer_head *bh)
 {
+       /*
+        * real ext4_wait_block_bitmap will set these flags and
+        * functions like ext4_mb_init_cache will verify the flags.
+        */
+       set_buffer_uptodate(bh);
+       set_bitmap_uptodate(bh);
+       set_buffer_verified(bh);
        return 0;
 }
 
@@ -232,6 +350,14 @@ static int mbt_kunit_init(struct kunit *test)
        kunit_activate_static_stub(test,
                                   ext4_mb_mark_context,
                                   ext4_mb_mark_context_stub);
+
+       /* stub function will be called in mbt_mb_init->ext4_mb_init */
+       if (mbt_mb_init(sb) != 0) {
+               mbt_ctx_release(sb);
+               mbt_ext4_free_super_block(sb);
+               return -ENOMEM;
+       }
+
        return 0;
 }
 
@@ -239,6 +365,7 @@ static void mbt_kunit_exit(struct kunit *test)
 {
        struct super_block *sb = (struct super_block *)test->priv;
 
+       mbt_mb_release(sb);
        mbt_ctx_release(sb);
        mbt_ext4_free_super_block(sb);
 }
@@ -246,14 +373,19 @@ static void mbt_kunit_exit(struct kunit *test)
 static void test_new_blocks_simple(struct kunit *test)
 {
        struct super_block *sb = (struct super_block *)test->priv;
-       struct inode inode = { .i_sb = sb, };
+       struct inode *inode;
        struct ext4_allocation_request ar;
        ext4_group_t i, goal_group = TEST_GOAL_GROUP;
        int err = 0;
        ext4_fsblk_t found;
        struct ext4_sb_info *sbi = EXT4_SB(sb);
 
-       ar.inode = &inode;
+       inode = kunit_kzalloc(test, sizeof(*inode), GFP_KERNEL);
+       if (!inode)
+               return;
+
+       inode->i_sb = sb;
+       ar.inode = inode;
 
        /* get block at goal */
        ar.goal = ext4_group_first_block_no(sb, goal_group);
@@ -297,6 +429,436 @@ static void test_new_blocks_simple(struct kunit *test)
                "unexpectedly get block when no block is available");
 }
 
+#define TEST_RANGE_COUNT 8
+
+struct test_range {
+       ext4_grpblk_t start;
+       ext4_grpblk_t len;
+};
+
+static void
+mbt_generate_test_ranges(struct super_block *sb, struct test_range *ranges,
+                        int count)
+{
+       ext4_grpblk_t start, len, max;
+       int i;
+
+       max = EXT4_CLUSTERS_PER_GROUP(sb) / count;
+       for (i = 0; i < count; i++) {
+               start = get_random_u32() % max;
+               len = get_random_u32() % max;
+               len = min(len, max - start);
+
+               ranges[i].start = start + i * max;
+               ranges[i].len = len;
+       }
+}
+
+static void
+validate_free_blocks_simple(struct kunit *test, struct super_block *sb,
+                           ext4_group_t goal_group, ext4_grpblk_t start,
+                           ext4_grpblk_t len)
+{
+       void *bitmap;
+       ext4_grpblk_t bit, max = EXT4_CLUSTERS_PER_GROUP(sb);
+       ext4_group_t i;
+
+       for (i = 0; i < ext4_get_groups_count(sb); i++) {
+               if (i == goal_group)
+                       continue;
+
+               bitmap = mbt_ctx_bitmap(sb, i);
+               bit = mb_find_next_zero_bit(bitmap, max, 0);
+               KUNIT_ASSERT_EQ_MSG(test, bit, max,
+                                   "free block on unexpected group %d", i);
+       }
+
+       bitmap = mbt_ctx_bitmap(sb, goal_group);
+       bit = mb_find_next_zero_bit(bitmap, max, 0);
+       KUNIT_ASSERT_EQ(test, bit, start);
+
+       bit = mb_find_next_bit(bitmap, max, bit + 1);
+       KUNIT_ASSERT_EQ(test, bit, start + len);
+}
+
+static void
+test_free_blocks_simple_range(struct kunit *test, ext4_group_t goal_group,
+                             ext4_grpblk_t start, ext4_grpblk_t len)
+{
+       struct super_block *sb = (struct super_block *)test->priv;
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+       struct inode *inode;
+       ext4_fsblk_t block;
+
+       inode = kunit_kzalloc(test, sizeof(*inode), GFP_KERNEL);
+       if (!inode)
+               return;
+       inode->i_sb = sb;
+
+       if (len == 0)
+               return;
+
+       block = ext4_group_first_block_no(sb, goal_group) +
+               EXT4_C2B(sbi, start);
+       ext4_free_blocks_simple(inode, block, len);
+       validate_free_blocks_simple(test, sb, goal_group, start, len);
+       mbt_ctx_mark_used(sb, goal_group, 0, EXT4_CLUSTERS_PER_GROUP(sb));
+}
+
+static void test_free_blocks_simple(struct kunit *test)
+{
+       struct super_block *sb = (struct super_block *)test->priv;
+       ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
+       ext4_group_t i;
+       struct test_range ranges[TEST_RANGE_COUNT];
+
+       for (i = 0; i < ext4_get_groups_count(sb); i++)
+               mbt_ctx_mark_used(sb, i, 0, max);
+
+       mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
+       for (i = 0; i < TEST_RANGE_COUNT; i++)
+               test_free_blocks_simple_range(test, TEST_GOAL_GROUP,
+                       ranges[i].start, ranges[i].len);
+}
+
+static void
+test_mark_diskspace_used_range(struct kunit *test,
+                              struct ext4_allocation_context *ac,
+                              ext4_grpblk_t start,
+                              ext4_grpblk_t len)
+{
+       struct super_block *sb = (struct super_block *)test->priv;
+       int ret;
+       void *bitmap;
+       ext4_grpblk_t i, max;
+
+       /* ext4_mb_mark_diskspace_used will BUG if len is 0 */
+       if (len == 0)
+               return;
+
+       ac->ac_b_ex.fe_group = TEST_GOAL_GROUP;
+       ac->ac_b_ex.fe_start = start;
+       ac->ac_b_ex.fe_len = len;
+
+       bitmap = mbt_ctx_bitmap(sb, TEST_GOAL_GROUP);
+       memset(bitmap, 0, sb->s_blocksize);
+       ret = ext4_mb_mark_diskspace_used(ac, NULL, 0);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       max = EXT4_CLUSTERS_PER_GROUP(sb);
+       i = mb_find_next_bit(bitmap, max, 0);
+       KUNIT_ASSERT_EQ(test, i, start);
+       i = mb_find_next_zero_bit(bitmap, max, i + 1);
+       KUNIT_ASSERT_EQ(test, i, start + len);
+       i = mb_find_next_bit(bitmap, max, i + 1);
+       KUNIT_ASSERT_EQ(test, max, i);
+}
+
+static void test_mark_diskspace_used(struct kunit *test)
+{
+       struct super_block *sb = (struct super_block *)test->priv;
+       struct inode *inode;
+       struct ext4_allocation_context ac;
+       struct test_range ranges[TEST_RANGE_COUNT];
+       int i;
+
+       mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
+
+       inode = kunit_kzalloc(test, sizeof(*inode), GFP_KERNEL);
+       if (!inode)
+               return;
+       inode->i_sb = sb;
+
+       ac.ac_status = AC_STATUS_FOUND;
+       ac.ac_sb = sb;
+       ac.ac_inode = inode;
+       for (i = 0; i < TEST_RANGE_COUNT; i++)
+               test_mark_diskspace_used_range(test, &ac, ranges[i].start,
+                                              ranges[i].len);
+}
+
+static void mbt_generate_buddy(struct super_block *sb, void *buddy,
+                              void *bitmap, struct ext4_group_info *grp)
+{
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+       uint32_t order, off;
+       void *bb, *bb_h;
+       int max;
+
+       memset(buddy, 0xff, sb->s_blocksize);
+       memset(grp, 0, offsetof(struct ext4_group_info,
+                                bb_counters[MB_NUM_ORDERS(sb)]));
+
+       bb = bitmap;
+       max = EXT4_CLUSTERS_PER_GROUP(sb);
+       bb_h = buddy + sbi->s_mb_offsets[1];
+
+       off = mb_find_next_zero_bit(bb, max, 0);
+       grp->bb_first_free = off;
+       while (off < max) {
+               grp->bb_counters[0]++;
+               grp->bb_free++;
+
+               if (!(off & 1) && !mb_test_bit(off + 1, bb)) {
+                       grp->bb_free++;
+                       grp->bb_counters[0]--;
+                       mb_clear_bit(off >> 1, bb_h);
+                       grp->bb_counters[1]++;
+                       grp->bb_largest_free_order = 1;
+                       off++;
+               }
+
+               off = mb_find_next_zero_bit(bb, max, off + 1);
+       }
+
+       for (order = 1; order < MB_NUM_ORDERS(sb) - 1; order++) {
+               bb = buddy + sbi->s_mb_offsets[order];
+               bb_h = buddy + sbi->s_mb_offsets[order + 1];
+               max = max >> 1;
+               off = mb_find_next_zero_bit(bb, max, 0);
+
+               while (off < max) {
+                       if (!(off & 1) && !mb_test_bit(off + 1, bb)) {
+                               mb_set_bits(bb, off, 2);
+                               grp->bb_counters[order] -= 2;
+                               mb_clear_bit(off >> 1, bb_h);
+                               grp->bb_counters[order + 1]++;
+                               grp->bb_largest_free_order = order + 1;
+                               off++;
+                       }
+
+                       off = mb_find_next_zero_bit(bb, max, off + 1);
+               }
+       }
+
+       max = EXT4_CLUSTERS_PER_GROUP(sb);
+       off = mb_find_next_zero_bit(bitmap, max, 0);
+       while (off < max) {
+               grp->bb_fragments++;
+
+               off = mb_find_next_bit(bitmap, max, off + 1);
+               if (off + 1 >= max)
+                       break;
+
+               off = mb_find_next_zero_bit(bitmap, max, off + 1);
+       }
+}
+
+static void
+mbt_validate_group_info(struct kunit *test, struct ext4_group_info *grp1,
+                       struct ext4_group_info *grp2)
+{
+       struct super_block *sb = (struct super_block *)test->priv;
+       int i;
+
+       KUNIT_ASSERT_EQ(test, grp1->bb_first_free,
+                       grp2->bb_first_free);
+       KUNIT_ASSERT_EQ(test, grp1->bb_fragments,
+                       grp2->bb_fragments);
+       KUNIT_ASSERT_EQ(test, grp1->bb_free, grp2->bb_free);
+       KUNIT_ASSERT_EQ(test, grp1->bb_largest_free_order,
+                       grp2->bb_largest_free_order);
+
+       for (i = 1; i < MB_NUM_ORDERS(sb); i++) {
+               KUNIT_ASSERT_EQ_MSG(test, grp1->bb_counters[i],
+                                   grp2->bb_counters[i],
+                                   "bb_counters[%d] diffs, expected %d, generated %d",
+                                   i, grp1->bb_counters[i],
+                                   grp2->bb_counters[i]);
+       }
+}
+
+static void
+do_test_generate_buddy(struct kunit *test, struct super_block *sb, void *bitmap,
+                          void *mbt_buddy, struct ext4_group_info *mbt_grp,
+                          void *ext4_buddy, struct ext4_group_info *ext4_grp)
+{
+       int i;
+
+       mbt_generate_buddy(sb, mbt_buddy, bitmap, mbt_grp);
+
+       for (i = 0; i < MB_NUM_ORDERS(sb); i++)
+               ext4_grp->bb_counters[i] = 0;
+       /* needed by validation in ext4_mb_generate_buddy */
+       ext4_grp->bb_free = mbt_grp->bb_free;
+       memset(ext4_buddy, 0xff, sb->s_blocksize);
+       ext4_mb_generate_buddy(sb, ext4_buddy, bitmap, TEST_GOAL_GROUP,
+                              ext4_grp);
+
+       KUNIT_ASSERT_EQ(test, memcmp(mbt_buddy, ext4_buddy, sb->s_blocksize),
+                       0);
+       mbt_validate_group_info(test, mbt_grp, ext4_grp);
+}
+
+static void test_mb_generate_buddy(struct kunit *test)
+{
+       struct super_block *sb = (struct super_block *)test->priv;
+       void *bitmap, *expected_bb, *generate_bb;
+       struct ext4_group_info *expected_grp, *generate_grp;
+       struct test_range ranges[TEST_RANGE_COUNT];
+       int i;
+
+       bitmap = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bitmap);
+       expected_bb = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, expected_bb);
+       generate_bb = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, generate_bb);
+       expected_grp = kunit_kzalloc(test, offsetof(struct ext4_group_info,
+                               bb_counters[MB_NUM_ORDERS(sb)]), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, expected_grp);
+       generate_grp = ext4_get_group_info(sb, TEST_GOAL_GROUP);
+       KUNIT_ASSERT_NOT_NULL(test, generate_grp);
+
+       mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
+       for (i = 0; i < TEST_RANGE_COUNT; i++) {
+               mb_set_bits(bitmap, ranges[i].start, ranges[i].len);
+               do_test_generate_buddy(test, sb, bitmap, expected_bb,
+                                      expected_grp, generate_bb, generate_grp);
+       }
+}
+
+static void
+test_mb_mark_used_range(struct kunit *test, struct ext4_buddy *e4b,
+                       ext4_grpblk_t start, ext4_grpblk_t len, void *bitmap,
+                       void *buddy, struct ext4_group_info *grp)
+{
+       struct super_block *sb = (struct super_block *)test->priv;
+       struct ext4_free_extent ex;
+       int i;
+
+       /* mb_mark_used only accepts non-zero len */
+       if (len == 0)
+               return;
+
+       ex.fe_start = start;
+       ex.fe_len = len;
+       ex.fe_group = TEST_GOAL_GROUP;
+
+       ext4_lock_group(sb, TEST_GOAL_GROUP);
+       mb_mark_used(e4b, &ex);
+       ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+       mb_set_bits(bitmap, start, len);
+       /* bypass bb_free validatoin in ext4_mb_generate_buddy */
+       grp->bb_free -= len;
+       memset(buddy, 0xff, sb->s_blocksize);
+       for (i = 0; i < MB_NUM_ORDERS(sb); i++)
+               grp->bb_counters[i] = 0;
+       ext4_mb_generate_buddy(sb, buddy, bitmap, 0, grp);
+
+       KUNIT_ASSERT_EQ(test, memcmp(buddy, e4b->bd_buddy, sb->s_blocksize),
+                       0);
+       mbt_validate_group_info(test, grp, e4b->bd_info);
+}
+
+static void test_mb_mark_used(struct kunit *test)
+{
+       struct ext4_buddy e4b;
+       struct super_block *sb = (struct super_block *)test->priv;
+       void *bitmap, *buddy;
+       struct ext4_group_info *grp;
+       int ret;
+       struct test_range ranges[TEST_RANGE_COUNT];
+       int i;
+
+       /* buddy cache assumes that each page contains at least one block */
+       if (sb->s_blocksize > PAGE_SIZE)
+               kunit_skip(test, "blocksize exceeds pagesize");
+
+       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 = kunit_kzalloc(test, offsetof(struct ext4_group_info,
+                               bb_counters[MB_NUM_ORDERS(sb)]), GFP_KERNEL);
+
+       ret = ext4_mb_load_buddy(sb, TEST_GOAL_GROUP, &e4b);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       grp->bb_free = EXT4_CLUSTERS_PER_GROUP(sb);
+       mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
+       for (i = 0; i < TEST_RANGE_COUNT; i++)
+               test_mb_mark_used_range(test, &e4b, ranges[i].start,
+                                       ranges[i].len, bitmap, buddy, grp);
+
+       ext4_mb_unload_buddy(&e4b);
+}
+
+static void
+test_mb_free_blocks_range(struct kunit *test, struct ext4_buddy *e4b,
+                         ext4_grpblk_t start, ext4_grpblk_t len, void *bitmap,
+                         void *buddy, struct ext4_group_info *grp)
+{
+       struct super_block *sb = (struct super_block *)test->priv;
+       int i;
+
+       /* mb_free_blocks will WARN if len is 0 */
+       if (len == 0)
+               return;
+
+       ext4_lock_group(sb, e4b->bd_group);
+       mb_free_blocks(NULL, e4b, start, len);
+       ext4_unlock_group(sb, e4b->bd_group);
+
+       mb_clear_bits(bitmap, start, len);
+       /* bypass bb_free validatoin in ext4_mb_generate_buddy */
+       grp->bb_free += len;
+       memset(buddy, 0xff, sb->s_blocksize);
+       for (i = 0; i < MB_NUM_ORDERS(sb); i++)
+               grp->bb_counters[i] = 0;
+       ext4_mb_generate_buddy(sb, buddy, bitmap, 0, grp);
+
+       KUNIT_ASSERT_EQ(test, memcmp(buddy, e4b->bd_buddy, sb->s_blocksize),
+                       0);
+       mbt_validate_group_info(test, grp, e4b->bd_info);
+
+}
+
+static void test_mb_free_blocks(struct kunit *test)
+{
+       struct ext4_buddy e4b;
+       struct super_block *sb = (struct super_block *)test->priv;
+       void *bitmap, *buddy;
+       struct ext4_group_info *grp;
+       struct ext4_free_extent ex;
+       int ret;
+       int i;
+       struct test_range ranges[TEST_RANGE_COUNT];
+
+       /* buddy cache assumes that each page contains at least one block */
+       if (sb->s_blocksize > PAGE_SIZE)
+               kunit_skip(test, "blocksize exceeds pagesize");
+
+       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 = kunit_kzalloc(test, offsetof(struct ext4_group_info,
+                               bb_counters[MB_NUM_ORDERS(sb)]), GFP_KERNEL);
+
+       ret = ext4_mb_load_buddy(sb, TEST_GOAL_GROUP, &e4b);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ex.fe_start = 0;
+       ex.fe_len = EXT4_CLUSTERS_PER_GROUP(sb);
+       ex.fe_group = TEST_GOAL_GROUP;
+
+       ext4_lock_group(sb, TEST_GOAL_GROUP);
+       mb_mark_used(&e4b, &ex);
+       ext4_unlock_group(sb, TEST_GOAL_GROUP);
+
+       grp->bb_free = 0;
+       memset(bitmap, 0xff, sb->s_blocksize);
+
+       mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
+       for (i = 0; i < TEST_RANGE_COUNT; i++)
+               test_mb_free_blocks_range(test, &e4b, ranges[i].start,
+                                         ranges[i].len, bitmap, buddy, grp);
+
+       ext4_mb_unload_buddy(&e4b);
+}
+
 static const struct mbt_ext4_block_layout mbt_test_layouts[] = {
        {
                .blocksize_bits = 10,
@@ -334,6 +896,11 @@ KUNIT_ARRAY_PARAM(mbt_layouts, mbt_test_layouts, mbt_show_layout);
 
 static struct kunit_case mbt_test_cases[] = {
        KUNIT_CASE_PARAM(test_new_blocks_simple, mbt_layouts_gen_params),
+       KUNIT_CASE_PARAM(test_free_blocks_simple, mbt_layouts_gen_params),
+       KUNIT_CASE_PARAM(test_mb_generate_buddy, mbt_layouts_gen_params),
+       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),
        {}
 };