1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * KUnit test of ext4 multiblocks allocation. |
4 | */ |
5 | |
6 | #include <kunit/test.h> |
7 | #include <kunit/static_stub.h> |
8 | |
9 | #include "ext4.h" |
10 | |
11 | struct mbt_grp_ctx { |
12 | struct buffer_head bitmap_bh; |
13 | /* desc and gd_bh are just the place holders for now */ |
14 | struct ext4_group_desc desc; |
15 | struct buffer_head gd_bh; |
16 | }; |
17 | |
18 | struct mbt_ctx { |
19 | struct mbt_grp_ctx *grp_ctx; |
20 | }; |
21 | |
22 | struct mbt_ext4_super_block { |
23 | struct super_block sb; |
24 | struct mbt_ctx mbt_ctx; |
25 | }; |
26 | |
27 | #define MBT_CTX(_sb) (&(container_of((_sb), struct mbt_ext4_super_block, sb)->mbt_ctx)) |
28 | #define MBT_GRP_CTX(_sb, _group) (&MBT_CTX(_sb)->grp_ctx[_group]) |
29 | |
30 | static struct super_block *mbt_ext4_alloc_super_block(void) |
31 | { |
32 | struct ext4_super_block *es = kzalloc(size: sizeof(*es), GFP_KERNEL); |
33 | struct ext4_sb_info *sbi = kzalloc(size: sizeof(*sbi), GFP_KERNEL); |
34 | struct mbt_ext4_super_block *fsb = kzalloc(size: sizeof(*fsb), GFP_KERNEL); |
35 | |
36 | if (fsb == NULL || sbi == NULL || es == NULL) |
37 | goto out; |
38 | |
39 | sbi->s_es = es; |
40 | fsb->sb.s_fs_info = sbi; |
41 | return &fsb->sb; |
42 | |
43 | out: |
44 | kfree(objp: fsb); |
45 | kfree(objp: sbi); |
46 | kfree(objp: es); |
47 | return NULL; |
48 | } |
49 | |
50 | static void mbt_ext4_free_super_block(struct super_block *sb) |
51 | { |
52 | struct mbt_ext4_super_block *fsb = |
53 | container_of(sb, struct mbt_ext4_super_block, sb); |
54 | struct ext4_sb_info *sbi = EXT4_SB(sb); |
55 | |
56 | kfree(objp: sbi->s_es); |
57 | kfree(objp: sbi); |
58 | kfree(objp: fsb); |
59 | } |
60 | |
61 | struct mbt_ext4_block_layout { |
62 | unsigned char blocksize_bits; |
63 | unsigned int cluster_bits; |
64 | uint32_t blocks_per_group; |
65 | ext4_group_t group_count; |
66 | uint16_t desc_size; |
67 | }; |
68 | |
69 | static void mbt_init_sb_layout(struct super_block *sb, |
70 | struct mbt_ext4_block_layout *layout) |
71 | { |
72 | struct ext4_sb_info *sbi = EXT4_SB(sb); |
73 | struct ext4_super_block *es = sbi->s_es; |
74 | |
75 | sb->s_blocksize = 1UL << layout->blocksize_bits; |
76 | sb->s_blocksize_bits = layout->blocksize_bits; |
77 | |
78 | sbi->s_groups_count = layout->group_count; |
79 | sbi->s_blocks_per_group = layout->blocks_per_group; |
80 | sbi->s_cluster_bits = layout->cluster_bits; |
81 | sbi->s_cluster_ratio = 1U << layout->cluster_bits; |
82 | sbi->s_clusters_per_group = layout->blocks_per_group >> |
83 | layout->cluster_bits; |
84 | sbi->s_desc_size = layout->desc_size; |
85 | |
86 | es->s_first_data_block = cpu_to_le32(0); |
87 | es->s_blocks_count_lo = cpu_to_le32(layout->blocks_per_group * |
88 | layout->group_count); |
89 | } |
90 | |
91 | static int mbt_grp_ctx_init(struct super_block *sb, |
92 | struct mbt_grp_ctx *grp_ctx) |
93 | { |
94 | grp_ctx->bitmap_bh.b_data = kzalloc(EXT4_BLOCK_SIZE(sb), GFP_KERNEL); |
95 | if (grp_ctx->bitmap_bh.b_data == NULL) |
96 | return -ENOMEM; |
97 | |
98 | return 0; |
99 | } |
100 | |
101 | static void mbt_grp_ctx_release(struct mbt_grp_ctx *grp_ctx) |
102 | { |
103 | kfree(objp: grp_ctx->bitmap_bh.b_data); |
104 | grp_ctx->bitmap_bh.b_data = NULL; |
105 | } |
106 | |
107 | static void mbt_ctx_mark_used(struct super_block *sb, ext4_group_t group, |
108 | unsigned int start, unsigned int len) |
109 | { |
110 | struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group); |
111 | |
112 | mb_set_bits(bm: grp_ctx->bitmap_bh.b_data, cur: start, len); |
113 | } |
114 | |
115 | /* called after mbt_init_sb_layout */ |
116 | static int mbt_ctx_init(struct super_block *sb) |
117 | { |
118 | struct mbt_ctx *ctx = MBT_CTX(sb); |
119 | ext4_group_t i, ngroups = ext4_get_groups_count(sb); |
120 | |
121 | ctx->grp_ctx = kcalloc(n: ngroups, size: sizeof(struct mbt_grp_ctx), |
122 | GFP_KERNEL); |
123 | if (ctx->grp_ctx == NULL) |
124 | return -ENOMEM; |
125 | |
126 | for (i = 0; i < ngroups; i++) |
127 | if (mbt_grp_ctx_init(sb, grp_ctx: &ctx->grp_ctx[i])) |
128 | goto out; |
129 | |
130 | /* |
131 | * first data block(first cluster in first group) is used by |
132 | * metadata, mark it used to avoid to alloc data block at first |
133 | * block which will fail ext4_sb_block_valid check. |
134 | */ |
135 | mb_set_bits(bm: ctx->grp_ctx[0].bitmap_bh.b_data, cur: 0, len: 1); |
136 | |
137 | return 0; |
138 | out: |
139 | while (i-- > 0) |
140 | mbt_grp_ctx_release(grp_ctx: &ctx->grp_ctx[i]); |
141 | kfree(objp: ctx->grp_ctx); |
142 | return -ENOMEM; |
143 | } |
144 | |
145 | static void mbt_ctx_release(struct super_block *sb) |
146 | { |
147 | struct mbt_ctx *ctx = MBT_CTX(sb); |
148 | ext4_group_t i, ngroups = ext4_get_groups_count(sb); |
149 | |
150 | for (i = 0; i < ngroups; i++) |
151 | mbt_grp_ctx_release(grp_ctx: &ctx->grp_ctx[i]); |
152 | kfree(objp: ctx->grp_ctx); |
153 | } |
154 | |
155 | static struct buffer_head * |
156 | ext4_read_block_bitmap_nowait_stub(struct super_block *sb, ext4_group_t block_group, |
157 | bool ignore_locked) |
158 | { |
159 | struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, block_group); |
160 | |
161 | /* paired with brelse from caller of ext4_read_block_bitmap_nowait */ |
162 | get_bh(bh: &grp_ctx->bitmap_bh); |
163 | return &grp_ctx->bitmap_bh; |
164 | } |
165 | |
166 | static int ext4_wait_block_bitmap_stub(struct super_block *sb, |
167 | ext4_group_t block_group, |
168 | struct buffer_head *bh) |
169 | { |
170 | return 0; |
171 | } |
172 | |
173 | static struct ext4_group_desc * |
174 | ext4_get_group_desc_stub(struct super_block *sb, ext4_group_t block_group, |
175 | struct buffer_head **bh) |
176 | { |
177 | struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, block_group); |
178 | |
179 | if (bh != NULL) |
180 | *bh = &grp_ctx->gd_bh; |
181 | |
182 | return &grp_ctx->desc; |
183 | } |
184 | |
185 | static int |
186 | ext4_mb_mark_context_stub(handle_t *handle, struct super_block *sb, bool state, |
187 | ext4_group_t group, ext4_grpblk_t blkoff, |
188 | ext4_grpblk_t len, int flags, |
189 | ext4_grpblk_t *ret_changed) |
190 | { |
191 | struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group); |
192 | struct buffer_head *bitmap_bh = &grp_ctx->bitmap_bh; |
193 | |
194 | if (state) |
195 | mb_set_bits(bm: bitmap_bh->b_data, cur: blkoff, len); |
196 | else |
197 | mb_clear_bits(bitmap_bh->b_data, blkoff, len); |
198 | |
199 | return 0; |
200 | } |
201 | |
202 | #define TEST_GOAL_GROUP 1 |
203 | static int mbt_kunit_init(struct kunit *test) |
204 | { |
205 | struct mbt_ext4_block_layout *layout = |
206 | (struct mbt_ext4_block_layout *)(test->param_value); |
207 | struct super_block *sb; |
208 | int ret; |
209 | |
210 | sb = mbt_ext4_alloc_super_block(); |
211 | if (sb == NULL) |
212 | return -ENOMEM; |
213 | |
214 | mbt_init_sb_layout(sb, layout); |
215 | |
216 | ret = mbt_ctx_init(sb); |
217 | if (ret != 0) { |
218 | mbt_ext4_free_super_block(sb); |
219 | return ret; |
220 | } |
221 | |
222 | test->priv = sb; |
223 | kunit_activate_static_stub(test, |
224 | ext4_read_block_bitmap_nowait, |
225 | ext4_read_block_bitmap_nowait_stub); |
226 | kunit_activate_static_stub(test, |
227 | ext4_wait_block_bitmap, |
228 | ext4_wait_block_bitmap_stub); |
229 | kunit_activate_static_stub(test, |
230 | ext4_get_group_desc, |
231 | ext4_get_group_desc_stub); |
232 | kunit_activate_static_stub(test, |
233 | ext4_mb_mark_context, |
234 | ext4_mb_mark_context_stub); |
235 | return 0; |
236 | } |
237 | |
238 | static void mbt_kunit_exit(struct kunit *test) |
239 | { |
240 | struct super_block *sb = (struct super_block *)test->priv; |
241 | |
242 | mbt_ctx_release(sb); |
243 | mbt_ext4_free_super_block(sb); |
244 | } |
245 | |
246 | static void test_new_blocks_simple(struct kunit *test) |
247 | { |
248 | struct super_block *sb = (struct super_block *)test->priv; |
249 | struct inode inode = { .i_sb = sb, }; |
250 | struct ext4_allocation_request ar; |
251 | ext4_group_t i, goal_group = TEST_GOAL_GROUP; |
252 | int err = 0; |
253 | ext4_fsblk_t found; |
254 | struct ext4_sb_info *sbi = EXT4_SB(sb); |
255 | |
256 | ar.inode = &inode; |
257 | |
258 | /* get block at goal */ |
259 | ar.goal = ext4_group_first_block_no(sb, group_no: goal_group); |
260 | found = ext4_mb_new_blocks_simple(&ar, &err); |
261 | KUNIT_ASSERT_EQ_MSG(test, ar.goal, found, |
262 | "failed to alloc block at goal, expected %llu found %llu" , |
263 | ar.goal, found); |
264 | |
265 | /* get block after goal in goal group */ |
266 | ar.goal = ext4_group_first_block_no(sb, group_no: goal_group); |
267 | found = ext4_mb_new_blocks_simple(&ar, &err); |
268 | KUNIT_ASSERT_EQ_MSG(test, ar.goal + EXT4_C2B(sbi, 1), found, |
269 | "failed to alloc block after goal in goal group, expected %llu found %llu" , |
270 | ar.goal + 1, found); |
271 | |
272 | /* get block after goal group */ |
273 | mbt_ctx_mark_used(sb, group: goal_group, start: 0, EXT4_CLUSTERS_PER_GROUP(sb)); |
274 | ar.goal = ext4_group_first_block_no(sb, group_no: goal_group); |
275 | found = ext4_mb_new_blocks_simple(&ar, &err); |
276 | KUNIT_ASSERT_EQ_MSG(test, |
277 | ext4_group_first_block_no(sb, goal_group + 1), found, |
278 | "failed to alloc block after goal group, expected %llu found %llu" , |
279 | ext4_group_first_block_no(sb, goal_group + 1), found); |
280 | |
281 | /* get block before goal group */ |
282 | for (i = goal_group; i < ext4_get_groups_count(sb); i++) |
283 | mbt_ctx_mark_used(sb, group: i, start: 0, EXT4_CLUSTERS_PER_GROUP(sb)); |
284 | ar.goal = ext4_group_first_block_no(sb, group_no: goal_group); |
285 | found = ext4_mb_new_blocks_simple(&ar, &err); |
286 | KUNIT_ASSERT_EQ_MSG(test, |
287 | ext4_group_first_block_no(sb, 0) + EXT4_C2B(sbi, 1), found, |
288 | "failed to alloc block before goal group, expected %llu found %llu" , |
289 | ext4_group_first_block_no(sb, 0 + EXT4_C2B(sbi, 1)), found); |
290 | |
291 | /* no block available, fail to allocate block */ |
292 | for (i = 0; i < ext4_get_groups_count(sb); i++) |
293 | mbt_ctx_mark_used(sb, group: i, start: 0, EXT4_CLUSTERS_PER_GROUP(sb)); |
294 | ar.goal = ext4_group_first_block_no(sb, group_no: goal_group); |
295 | found = ext4_mb_new_blocks_simple(&ar, &err); |
296 | KUNIT_ASSERT_NE_MSG(test, err, 0, |
297 | "unexpectedly get block when no block is available" ); |
298 | } |
299 | |
300 | static const struct mbt_ext4_block_layout mbt_test_layouts[] = { |
301 | { |
302 | .blocksize_bits = 10, |
303 | .cluster_bits = 3, |
304 | .blocks_per_group = 8192, |
305 | .group_count = 4, |
306 | .desc_size = 64, |
307 | }, |
308 | { |
309 | .blocksize_bits = 12, |
310 | .cluster_bits = 3, |
311 | .blocks_per_group = 8192, |
312 | .group_count = 4, |
313 | .desc_size = 64, |
314 | }, |
315 | { |
316 | .blocksize_bits = 16, |
317 | .cluster_bits = 3, |
318 | .blocks_per_group = 8192, |
319 | .group_count = 4, |
320 | .desc_size = 64, |
321 | }, |
322 | }; |
323 | |
324 | static void mbt_show_layout(const struct mbt_ext4_block_layout *layout, |
325 | char *desc) |
326 | { |
327 | snprintf(buf: desc, KUNIT_PARAM_DESC_SIZE, fmt: "block_bits=%d cluster_bits=%d " |
328 | "blocks_per_group=%d group_count=%d desc_size=%d\n" , |
329 | layout->blocksize_bits, layout->cluster_bits, |
330 | layout->blocks_per_group, layout->group_count, |
331 | layout->desc_size); |
332 | } |
333 | KUNIT_ARRAY_PARAM(mbt_layouts, mbt_test_layouts, mbt_show_layout); |
334 | |
335 | static struct kunit_case mbt_test_cases[] = { |
336 | KUNIT_CASE_PARAM(test_new_blocks_simple, mbt_layouts_gen_params), |
337 | {} |
338 | }; |
339 | |
340 | static struct kunit_suite mbt_test_suite = { |
341 | .name = "ext4_mballoc_test" , |
342 | .init = mbt_kunit_init, |
343 | .exit = mbt_kunit_exit, |
344 | .test_cases = mbt_test_cases, |
345 | }; |
346 | |
347 | kunit_test_suites(&mbt_test_suite); |
348 | |
349 | MODULE_LICENSE("GPL" ); |
350 | |