| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * KUnit test of ext4 multiblocks allocation. |
| */ |
| |
| #include <kunit/test.h> |
| #include <kunit/static_stub.h> |
| #include <linux/random.h> |
| |
| #include "ext4.h" |
| |
| struct mbt_grp_ctx { |
| struct buffer_head bitmap_bh; |
| /* desc and gd_bh are just the place holders for now */ |
| struct ext4_group_desc desc; |
| struct buffer_head gd_bh; |
| }; |
| |
| struct mbt_ctx { |
| struct mbt_grp_ctx *grp_ctx; |
| }; |
| |
| struct mbt_ext4_super_block { |
| struct ext4_super_block es; |
| struct ext4_sb_info sbi; |
| struct mbt_ctx 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 struct inode *mbt_alloc_inode(struct super_block *sb) |
| { |
| struct ext4_inode_info *ei; |
| |
| ei = kmalloc(sizeof(struct ext4_inode_info), GFP_KERNEL); |
| if (!ei) |
| return NULL; |
| |
| INIT_LIST_HEAD(&ei->i_orphan); |
| init_rwsem(&ei->xattr_sem); |
| init_rwsem(&ei->i_data_sem); |
| inode_init_once(&ei->vfs_inode); |
| ext4_fc_init_inode(&ei->vfs_inode); |
| |
| return &ei->vfs_inode; |
| } |
| |
| static void mbt_free_inode(struct inode *inode) |
| { |
| kfree(EXT4_I(inode)); |
| } |
| |
| static const struct super_operations mbt_sops = { |
| .alloc_inode = mbt_alloc_inode, |
| .free_inode = mbt_free_inode, |
| }; |
| |
| 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 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; |
| |
| sb = sget(&mbt_fs_type, NULL, mbt_set, 0, NULL); |
| if (IS_ERR(sb)) |
| goto out; |
| |
| 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); |
| return NULL; |
| } |
| |
| static void mbt_ext4_free_super_block(struct super_block *sb) |
| { |
| struct mbt_ext4_super_block *fsb = MBT_SB(sb); |
| struct ext4_sb_info *sbi = EXT4_SB(sb); |
| |
| kfree(sbi->s_blockgroup_lock); |
| deactivate_super(sb); |
| kfree(fsb); |
| } |
| |
| struct mbt_ext4_block_layout { |
| unsigned char blocksize_bits; |
| unsigned int cluster_bits; |
| uint32_t blocks_per_group; |
| ext4_group_t group_count; |
| uint16_t desc_size; |
| }; |
| |
| static void mbt_init_sb_layout(struct super_block *sb, |
| struct mbt_ext4_block_layout *layout) |
| { |
| struct ext4_sb_info *sbi = EXT4_SB(sb); |
| struct ext4_super_block *es = sbi->s_es; |
| |
| sb->s_blocksize = 1UL << layout->blocksize_bits; |
| sb->s_blocksize_bits = layout->blocksize_bits; |
| |
| sbi->s_groups_count = layout->group_count; |
| sbi->s_blocks_per_group = layout->blocks_per_group; |
| sbi->s_cluster_bits = layout->cluster_bits; |
| sbi->s_cluster_ratio = 1U << layout->cluster_bits; |
| 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 * |
| layout->group_count); |
| } |
| |
| 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; |
| } |
| |
| static void mbt_grp_ctx_release(struct mbt_grp_ctx *grp_ctx) |
| { |
| kfree(grp_ctx->bitmap_bh.b_data); |
| grp_ctx->bitmap_bh.b_data = NULL; |
| } |
| |
| static void mbt_ctx_mark_used(struct super_block *sb, ext4_group_t group, |
| unsigned int start, unsigned int len) |
| { |
| struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, 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) |
| { |
| struct mbt_ctx *ctx = MBT_CTX(sb); |
| ext4_group_t i, ngroups = ext4_get_groups_count(sb); |
| |
| ctx->grp_ctx = kcalloc(ngroups, sizeof(struct mbt_grp_ctx), |
| GFP_KERNEL); |
| if (ctx->grp_ctx == NULL) |
| return -ENOMEM; |
| |
| for (i = 0; i < ngroups; i++) |
| if (mbt_grp_ctx_init(sb, &ctx->grp_ctx[i])) |
| goto out; |
| |
| /* |
| * first data block(first cluster in first group) is used by |
| * metadata, mark it used to avoid to alloc data block at first |
| * 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: |
| while (i-- > 0) |
| mbt_grp_ctx_release(&ctx->grp_ctx[i]); |
| kfree(ctx->grp_ctx); |
| return -ENOMEM; |
| } |
| |
| static void mbt_ctx_release(struct super_block *sb) |
| { |
| struct mbt_ctx *ctx = MBT_CTX(sb); |
| ext4_group_t i, ngroups = ext4_get_groups_count(sb); |
| |
| for (i = 0; i < ngroups; i++) |
| mbt_grp_ctx_release(&ctx->grp_ctx[i]); |
| kfree(ctx->grp_ctx); |
| } |
| |
| static struct buffer_head * |
| ext4_read_block_bitmap_nowait_stub(struct super_block *sb, ext4_group_t block_group, |
| bool ignore_locked) |
| { |
| struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, block_group); |
| |
| /* paired with brelse from caller of ext4_read_block_bitmap_nowait */ |
| get_bh(&grp_ctx->bitmap_bh); |
| return &grp_ctx->bitmap_bh; |
| } |
| |
| 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; |
| } |
| |
| static struct ext4_group_desc * |
| ext4_get_group_desc_stub(struct super_block *sb, ext4_group_t block_group, |
| struct buffer_head **bh) |
| { |
| struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, block_group); |
| |
| if (bh != NULL) |
| *bh = &grp_ctx->gd_bh; |
| |
| return &grp_ctx->desc; |
| } |
| |
| static int |
| ext4_mb_mark_context_stub(handle_t *handle, struct super_block *sb, bool state, |
| ext4_group_t group, ext4_grpblk_t blkoff, |
| ext4_grpblk_t len, int flags, |
| ext4_grpblk_t *ret_changed) |
| { |
| struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group); |
| struct buffer_head *bitmap_bh = &grp_ctx->bitmap_bh; |
| |
| if (state) |
| mb_set_bits(bitmap_bh->b_data, blkoff, len); |
| else |
| mb_clear_bits(bitmap_bh->b_data, blkoff, len); |
| |
| return 0; |
| } |
| |
| #define TEST_GOAL_GROUP 1 |
| static int mbt_kunit_init(struct kunit *test) |
| { |
| struct mbt_ext4_block_layout *layout = |
| (struct mbt_ext4_block_layout *)(test->param_value); |
| struct super_block *sb; |
| int ret; |
| |
| sb = mbt_ext4_alloc_super_block(); |
| if (sb == NULL) |
| return -ENOMEM; |
| |
| mbt_init_sb_layout(sb, layout); |
| |
| ret = mbt_ctx_init(sb); |
| if (ret != 0) { |
| mbt_ext4_free_super_block(sb); |
| return ret; |
| } |
| |
| test->priv = sb; |
| kunit_activate_static_stub(test, |
| ext4_read_block_bitmap_nowait, |
| ext4_read_block_bitmap_nowait_stub); |
| kunit_activate_static_stub(test, |
| ext4_wait_block_bitmap, |
| ext4_wait_block_bitmap_stub); |
| kunit_activate_static_stub(test, |
| ext4_get_group_desc, |
| ext4_get_group_desc_stub); |
| 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; |
| } |
| |
| 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); |
| } |
| |
| static void test_new_blocks_simple(struct kunit *test) |
| { |
| struct super_block *sb = (struct super_block *)test->priv; |
| 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); |
| |
| 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); |
| found = ext4_mb_new_blocks_simple(&ar, &err); |
| KUNIT_ASSERT_EQ_MSG(test, ar.goal, found, |
| "failed to alloc block at goal, expected %llu found %llu", |
| ar.goal, found); |
| |
| /* get block after goal in goal group */ |
| ar.goal = ext4_group_first_block_no(sb, goal_group); |
| found = ext4_mb_new_blocks_simple(&ar, &err); |
| KUNIT_ASSERT_EQ_MSG(test, ar.goal + EXT4_C2B(sbi, 1), found, |
| "failed to alloc block after goal in goal group, expected %llu found %llu", |
| ar.goal + 1, found); |
| |
| /* get block after goal group */ |
| mbt_ctx_mark_used(sb, goal_group, 0, EXT4_CLUSTERS_PER_GROUP(sb)); |
| ar.goal = ext4_group_first_block_no(sb, goal_group); |
| found = ext4_mb_new_blocks_simple(&ar, &err); |
| KUNIT_ASSERT_EQ_MSG(test, |
| ext4_group_first_block_no(sb, goal_group + 1), found, |
| "failed to alloc block after goal group, expected %llu found %llu", |
| ext4_group_first_block_no(sb, goal_group + 1), found); |
| |
| /* get block before goal group */ |
| for (i = goal_group; i < ext4_get_groups_count(sb); i++) |
| mbt_ctx_mark_used(sb, i, 0, EXT4_CLUSTERS_PER_GROUP(sb)); |
| ar.goal = ext4_group_first_block_no(sb, goal_group); |
| found = ext4_mb_new_blocks_simple(&ar, &err); |
| KUNIT_ASSERT_EQ_MSG(test, |
| ext4_group_first_block_no(sb, 0) + EXT4_C2B(sbi, 1), found, |
| "failed to alloc block before goal group, expected %llu found %llu", |
| ext4_group_first_block_no(sb, 0 + EXT4_C2B(sbi, 1)), found); |
| |
| /* no block available, fail to allocate block */ |
| for (i = 0; i < ext4_get_groups_count(sb); i++) |
| mbt_ctx_mark_used(sb, i, 0, EXT4_CLUSTERS_PER_GROUP(sb)); |
| ar.goal = ext4_group_first_block_no(sb, goal_group); |
| found = ext4_mb_new_blocks_simple(&ar, &err); |
| KUNIT_ASSERT_NE_MSG(test, err, 0, |
| "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); |
| } |
| |
| #define COUNT_FOR_ESTIMATE 100000 |
| static void test_mb_mark_used_cost(struct kunit *test) |
| { |
| struct ext4_buddy e4b; |
| struct super_block *sb = (struct super_block *)test->priv; |
| struct ext4_free_extent ex; |
| int ret; |
| struct test_range ranges[TEST_RANGE_COUNT]; |
| int i, j; |
| unsigned long start, end, all = 0; |
| |
| /* buddy cache assumes that each page contains at least one block */ |
| if (sb->s_blocksize > PAGE_SIZE) |
| kunit_skip(test, "blocksize exceeds pagesize"); |
| |
| ret = ext4_mb_load_buddy(sb, TEST_GOAL_GROUP, &e4b); |
| KUNIT_ASSERT_EQ(test, ret, 0); |
| |
| ex.fe_group = TEST_GOAL_GROUP; |
| for (j = 0; j < COUNT_FOR_ESTIMATE; j++) { |
| mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT); |
| start = jiffies; |
| for (i = 0; i < TEST_RANGE_COUNT; i++) { |
| if (ranges[i].len == 0) |
| continue; |
| |
| ex.fe_start = ranges[i].start; |
| ex.fe_len = ranges[i].len; |
| ext4_lock_group(sb, TEST_GOAL_GROUP); |
| mb_mark_used(&e4b, &ex); |
| ext4_unlock_group(sb, TEST_GOAL_GROUP); |
| } |
| end = jiffies; |
| all += (end - start); |
| |
| for (i = 0; i < TEST_RANGE_COUNT; i++) { |
| if (ranges[i].len == 0) |
| continue; |
| |
| ext4_lock_group(sb, TEST_GOAL_GROUP); |
| mb_free_blocks(NULL, &e4b, ranges[i].start, |
| ranges[i].len); |
| ext4_unlock_group(sb, TEST_GOAL_GROUP); |
| } |
| } |
| |
| kunit_info(test, "costed jiffies %lu\n", all); |
| ext4_mb_unload_buddy(&e4b); |
| } |
| |
| static const struct mbt_ext4_block_layout mbt_test_layouts[] = { |
| { |
| .blocksize_bits = 10, |
| .cluster_bits = 3, |
| .blocks_per_group = 8192, |
| .group_count = 4, |
| .desc_size = 64, |
| }, |
| { |
| .blocksize_bits = 12, |
| .cluster_bits = 3, |
| .blocks_per_group = 8192, |
| .group_count = 4, |
| .desc_size = 64, |
| }, |
| { |
| .blocksize_bits = 16, |
| .cluster_bits = 3, |
| .blocks_per_group = 8192, |
| .group_count = 4, |
| .desc_size = 64, |
| }, |
| }; |
| |
| static void mbt_show_layout(const struct mbt_ext4_block_layout *layout, |
| char *desc) |
| { |
| snprintf(desc, KUNIT_PARAM_DESC_SIZE, "block_bits=%d cluster_bits=%d " |
| "blocks_per_group=%d group_count=%d desc_size=%d\n", |
| layout->blocksize_bits, layout->cluster_bits, |
| layout->blocks_per_group, layout->group_count, |
| layout->desc_size); |
| } |
| 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), |
| KUNIT_CASE_PARAM_ATTR(test_mb_mark_used_cost, mbt_layouts_gen_params, |
| { .speed = KUNIT_SPEED_SLOW }), |
| {} |
| }; |
| |
| static struct kunit_suite mbt_test_suite = { |
| .name = "ext4_mballoc_test", |
| .init = mbt_kunit_init, |
| .exit = mbt_kunit_exit, |
| .test_cases = mbt_test_cases, |
| }; |
| |
| kunit_test_suites(&mbt_test_suite); |
| |
| MODULE_LICENSE("GPL"); |