1 // SPDX-License-Identifier: MIT
3 * Copyright © 2019 Intel Corporation
7 #include <kunit/test.h>
9 #include <linux/prime_numbers.h>
10 #include <linux/sched/signal.h>
12 #include <drm/drm_buddy.h>
14 #include "../lib/drm_random.h"
16 #define TIMEOUT(name__) \
17 unsigned long name__ = jiffies + MAX_SCHEDULE_TIMEOUT
19 static unsigned int random_seed;
21 static inline u64 get_size(int order, u64 chunk_size)
23 return (1 << order) * chunk_size;
27 static bool __timeout(unsigned long timeout, const char *fmt, ...)
31 if (!signal_pending(current)) {
33 if (time_before(jiffies, timeout))
46 static void __dump_block(struct kunit *test, struct drm_buddy *mm,
47 struct drm_buddy_block *block, bool buddy)
49 kunit_err(test, "block info: header=%llx, state=%u, order=%d, offset=%llx size=%llx root=%d buddy=%d\n",
50 block->header, drm_buddy_block_state(block),
51 drm_buddy_block_order(block), drm_buddy_block_offset(block),
52 drm_buddy_block_size(mm, block), !block->parent, buddy);
55 static void dump_block(struct kunit *test, struct drm_buddy *mm,
56 struct drm_buddy_block *block)
58 struct drm_buddy_block *buddy;
60 __dump_block(test, mm, block, false);
62 buddy = drm_get_buddy(block);
64 __dump_block(test, mm, buddy, true);
67 static int check_block(struct kunit *test, struct drm_buddy *mm,
68 struct drm_buddy_block *block)
70 struct drm_buddy_block *buddy;
71 unsigned int block_state;
76 block_state = drm_buddy_block_state(block);
78 if (block_state != DRM_BUDDY_ALLOCATED &&
79 block_state != DRM_BUDDY_FREE && block_state != DRM_BUDDY_SPLIT) {
80 kunit_err(test, "block state mismatch\n");
84 block_size = drm_buddy_block_size(mm, block);
85 offset = drm_buddy_block_offset(block);
87 if (block_size < mm->chunk_size) {
88 kunit_err(test, "block size smaller than min size\n");
92 /* We can't use is_power_of_2() for a u64 on 32-bit systems. */
93 if (block_size & (block_size - 1)) {
94 kunit_err(test, "block size not power of two\n");
98 if (!IS_ALIGNED(block_size, mm->chunk_size)) {
99 kunit_err(test, "block size not aligned to min size\n");
103 if (!IS_ALIGNED(offset, mm->chunk_size)) {
104 kunit_err(test, "block offset not aligned to min size\n");
108 if (!IS_ALIGNED(offset, block_size)) {
109 kunit_err(test, "block offset not aligned to block size\n");
113 buddy = drm_get_buddy(block);
115 if (!buddy && block->parent) {
116 kunit_err(test, "buddy has gone fishing\n");
121 if (drm_buddy_block_offset(buddy) != (offset ^ block_size)) {
122 kunit_err(test, "buddy has wrong offset\n");
126 if (drm_buddy_block_size(mm, buddy) != block_size) {
127 kunit_err(test, "buddy size mismatch\n");
131 if (drm_buddy_block_state(buddy) == block_state &&
132 block_state == DRM_BUDDY_FREE) {
133 kunit_err(test, "block and its buddy are free\n");
141 static int check_blocks(struct kunit *test, struct drm_buddy *mm,
142 struct list_head *blocks, u64 expected_size, bool is_contiguous)
144 struct drm_buddy_block *block;
145 struct drm_buddy_block *prev;
153 list_for_each_entry(block, blocks, link) {
154 err = check_block(test, mm, block);
156 if (!drm_buddy_block_is_allocated(block)) {
157 kunit_err(test, "block not allocated\n");
161 if (is_contiguous && prev) {
166 prev_offset = drm_buddy_block_offset(prev);
167 prev_block_size = drm_buddy_block_size(mm, prev);
168 offset = drm_buddy_block_offset(block);
170 if (offset != (prev_offset + prev_block_size)) {
171 kunit_err(test, "block offset mismatch\n");
179 total += drm_buddy_block_size(mm, block);
184 if (total != expected_size) {
185 kunit_err(test, "size mismatch, expected=%llx, found=%llx\n",
186 expected_size, total);
193 kunit_err(test, "prev block, dump:\n");
194 dump_block(test, mm, prev);
197 kunit_err(test, "bad block, dump:\n");
198 dump_block(test, mm, block);
203 static int check_mm(struct kunit *test, struct drm_buddy *mm)
205 struct drm_buddy_block *root;
206 struct drm_buddy_block *prev;
212 kunit_err(test, "n_roots is zero\n");
216 if (mm->n_roots != hweight64(mm->size)) {
217 kunit_err(test, "n_roots mismatch, n_roots=%u, expected=%lu\n",
218 mm->n_roots, hweight64(mm->size));
226 for (i = 0; i < mm->n_roots; ++i) {
227 struct drm_buddy_block *block;
232 kunit_err(test, "root(%u) is NULL\n", i);
237 err = check_block(test, mm, root);
239 if (!drm_buddy_block_is_free(root)) {
240 kunit_err(test, "root not free\n");
244 order = drm_buddy_block_order(root);
247 if (order != mm->max_order) {
248 kunit_err(test, "max order root missing\n");
258 prev_offset = drm_buddy_block_offset(prev);
259 prev_block_size = drm_buddy_block_size(mm, prev);
260 offset = drm_buddy_block_offset(root);
262 if (offset != (prev_offset + prev_block_size)) {
263 kunit_err(test, "root offset mismatch\n");
268 block = list_first_entry_or_null(&mm->free_list[order],
269 struct drm_buddy_block, link);
271 kunit_err(test, "root mismatch at order=%u\n", order);
279 total += drm_buddy_block_size(mm, root);
283 if (total != mm->size) {
284 kunit_err(test, "expected mm size=%llx, found=%llx\n",
292 kunit_err(test, "prev root(%u), dump:\n", i - 1);
293 dump_block(test, mm, prev);
297 kunit_err(test, "bad root(%u), dump:\n", i);
298 dump_block(test, mm, root);
304 static void mm_config(u64 *size, u64 *chunk_size)
306 DRM_RND_STATE(prng, random_seed);
309 /* Nothing fancy, just try to get an interesting bit pattern */
311 prandom_seed_state(&prng, random_seed);
313 /* Let size be a random number of pages up to 8 GB (2M pages) */
314 s = 1 + drm_prandom_u32_max_state((BIT(33 - 12)) - 1, &prng);
315 /* Let the chunk size be a random power of 2 less than size */
316 ms = BIT(drm_prandom_u32_max_state(ilog2(s), &prng));
317 /* Round size down to the chunk size */
320 /* Convert from pages to bytes */
321 *chunk_size = (u64)ms << 12;
322 *size = (u64)s << 12;
325 static void drm_test_buddy_alloc_pathological(struct kunit *test)
327 u64 mm_size, size, start = 0;
328 struct drm_buddy_block *block;
329 const int max_order = 3;
330 unsigned long flags = 0;
338 * Create a pot-sized mm, then allocate one of each possible
339 * order within. This should leave the mm with exactly one
340 * page left. Free the largest block, then whittle down again.
341 * Eventually we will have a fully 50% fragmented mm.
344 mm_size = PAGE_SIZE << max_order;
345 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, PAGE_SIZE),
346 "buddy_init failed\n");
348 KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
350 for (top = max_order; top; top--) {
351 /* Make room by freeing the largest allocated block */
352 block = list_first_entry_or_null(&blocks, typeof(*block), link);
354 list_del(&block->link);
355 drm_buddy_free_block(&mm, block);
358 for (order = top; order--;) {
359 size = get_size(order, PAGE_SIZE);
360 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start,
363 "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
366 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
367 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
369 list_move_tail(&block->link, &blocks);
372 /* There should be one final page for this sub-allocation */
373 size = get_size(0, PAGE_SIZE);
374 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
375 size, size, &tmp, flags),
376 "buddy_alloc hit -ENOMEM for hole\n");
378 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
379 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
381 list_move_tail(&block->link, &holes);
383 size = get_size(top, PAGE_SIZE);
384 KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
385 size, size, &tmp, flags),
386 "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
390 drm_buddy_free_list(&mm, &holes);
392 /* Nothing larger than blocks of chunk_size now available */
393 for (order = 1; order <= max_order; order++) {
394 size = get_size(order, PAGE_SIZE);
395 KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
396 size, size, &tmp, flags),
397 "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
401 list_splice_tail(&holes, &blocks);
402 drm_buddy_free_list(&mm, &blocks);
406 static void drm_test_buddy_alloc_smoke(struct kunit *test)
408 u64 mm_size, chunk_size, start = 0;
409 unsigned long flags = 0;
414 DRM_RND_STATE(prng, random_seed);
417 mm_config(&mm_size, &chunk_size);
419 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, chunk_size),
420 "buddy_init failed\n");
422 order = drm_random_order(mm.max_order + 1, &prng);
423 KUNIT_ASSERT_TRUE(test, order);
425 for (i = 0; i <= mm.max_order; ++i) {
426 struct drm_buddy_block *block;
427 int max_order = order[i];
428 bool timeout = false;
434 KUNIT_ASSERT_FALSE_MSG(test, check_mm(test, &mm),
435 "pre-mm check failed, abort\n");
442 size = get_size(order, chunk_size);
443 err = drm_buddy_alloc_blocks(&mm, start, mm_size, size, size, &tmp, flags);
445 if (err == -ENOMEM) {
446 KUNIT_FAIL(test, "buddy_alloc hit -ENOMEM with order=%d\n",
454 KUNIT_FAIL(test, "buddy_alloc with order=%d failed\n",
461 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
462 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
464 list_move_tail(&block->link, &blocks);
465 KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_order(block), order,
466 "buddy_alloc order mismatch\n");
468 total += drm_buddy_block_size(&mm, block);
470 if (__timeout(end_time, NULL)) {
474 } while (total < mm.size);
477 err = check_blocks(test, &mm, &blocks, total, false);
479 drm_buddy_free_list(&mm, &blocks);
482 KUNIT_EXPECT_FALSE_MSG(test, check_mm(test, &mm),
483 "post-mm check failed\n");
496 static void drm_test_buddy_alloc_pessimistic(struct kunit *test)
498 u64 mm_size, size, start = 0;
499 struct drm_buddy_block *block, *bn;
500 const unsigned int max_order = 16;
501 unsigned long flags = 0;
508 * Create a pot-sized mm, then allocate one of each possible
509 * order within. This should leave the mm with exactly one
513 mm_size = PAGE_SIZE << max_order;
514 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, PAGE_SIZE),
515 "buddy_init failed\n");
517 KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
519 for (order = 0; order < max_order; order++) {
520 size = get_size(order, PAGE_SIZE);
521 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
522 size, size, &tmp, flags),
523 "buddy_alloc hit -ENOMEM with order=%d\n",
526 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
527 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
529 list_move_tail(&block->link, &blocks);
532 /* And now the last remaining block available */
533 size = get_size(0, PAGE_SIZE);
534 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
535 size, size, &tmp, flags),
536 "buddy_alloc hit -ENOMEM on final alloc\n");
538 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
539 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
541 list_move_tail(&block->link, &blocks);
543 /* Should be completely full! */
544 for (order = max_order; order--;) {
545 size = get_size(order, PAGE_SIZE);
546 KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
547 size, size, &tmp, flags),
548 "buddy_alloc unexpectedly succeeded, it should be full!");
551 block = list_last_entry(&blocks, typeof(*block), link);
552 list_del(&block->link);
553 drm_buddy_free_block(&mm, block);
555 /* As we free in increasing size, we make available larger blocks */
557 list_for_each_entry_safe(block, bn, &blocks, link) {
558 list_del(&block->link);
559 drm_buddy_free_block(&mm, block);
561 size = get_size(order, PAGE_SIZE);
562 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
563 size, size, &tmp, flags),
564 "buddy_alloc hit -ENOMEM with order=%d\n",
567 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
568 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
570 list_del(&block->link);
571 drm_buddy_free_block(&mm, block);
575 /* To confirm, now the whole mm should be available */
576 size = get_size(max_order, PAGE_SIZE);
577 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
578 size, size, &tmp, flags),
579 "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
582 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
583 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
585 list_del(&block->link);
586 drm_buddy_free_block(&mm, block);
587 drm_buddy_free_list(&mm, &blocks);
591 static void drm_test_buddy_alloc_optimistic(struct kunit *test)
593 u64 mm_size, size, start = 0;
594 struct drm_buddy_block *block;
595 unsigned long flags = 0;
596 const int max_order = 16;
603 * Create a mm with one block of each order available, and
604 * try to allocate them all.
607 mm_size = PAGE_SIZE * ((1 << (max_order + 1)) - 1);
609 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, PAGE_SIZE),
610 "buddy_init failed\n");
612 KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
614 for (order = 0; order <= max_order; order++) {
615 size = get_size(order, PAGE_SIZE);
616 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
617 size, size, &tmp, flags),
618 "buddy_alloc hit -ENOMEM with order=%d\n",
621 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
622 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
624 list_move_tail(&block->link, &blocks);
627 /* Should be completely full! */
628 size = get_size(0, PAGE_SIZE);
629 KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
630 size, size, &tmp, flags),
631 "buddy_alloc unexpectedly succeeded, it should be full!");
633 drm_buddy_free_list(&mm, &blocks);
637 static void drm_test_buddy_alloc_range(struct kunit *test)
639 unsigned long flags = DRM_BUDDY_RANGE_ALLOCATION;
640 u64 offset, size, rem, chunk_size, end;
641 unsigned long page_num;
645 mm_config(&size, &chunk_size);
647 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, size, chunk_size),
648 "buddy_init failed");
650 KUNIT_ASSERT_FALSE_MSG(test, check_mm(test, &mm),
651 "pre-mm check failed, abort!");
656 for_each_prime_number_from(page_num, 1, ULONG_MAX - 1) {
657 struct drm_buddy_block *block;
660 size = min(page_num * mm.chunk_size, rem);
663 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, offset, end,
666 "alloc_range with offset=%llx, size=%llx failed\n", offset, size);
668 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
669 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_range has no blocks\n");
671 KUNIT_ASSERT_EQ_MSG(test, drm_buddy_block_offset(block), offset,
672 "alloc_range start offset mismatch, found=%llx, expected=%llx\n",
673 drm_buddy_block_offset(block), offset);
675 KUNIT_ASSERT_FALSE(test, check_blocks(test, &mm, &tmp, size, true));
677 list_splice_tail(&tmp, &blocks);
688 drm_buddy_free_list(&mm, &blocks);
690 KUNIT_EXPECT_FALSE_MSG(test, check_mm(test, &mm), "post-mm check failed\n");
695 static void drm_test_buddy_alloc_limit(struct kunit *test)
697 u64 size = U64_MAX, start = 0;
698 struct drm_buddy_block *block;
699 unsigned long flags = 0;
700 LIST_HEAD(allocated);
703 KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, size, PAGE_SIZE));
705 KUNIT_EXPECT_EQ_MSG(test, mm.max_order, DRM_BUDDY_MAX_ORDER,
706 "mm.max_order(%d) != %d\n", mm.max_order,
707 DRM_BUDDY_MAX_ORDER);
709 size = mm.chunk_size << mm.max_order;
710 KUNIT_EXPECT_FALSE(test, drm_buddy_alloc_blocks(&mm, start, size, size,
711 PAGE_SIZE, &allocated, flags));
713 block = list_first_entry_or_null(&allocated, struct drm_buddy_block, link);
714 KUNIT_EXPECT_TRUE(test, block);
716 KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_order(block), mm.max_order,
717 "block order(%d) != %d\n",
718 drm_buddy_block_order(block), mm.max_order);
720 KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_size(&mm, block),
721 BIT_ULL(mm.max_order) * PAGE_SIZE,
722 "block size(%llu) != %llu\n",
723 drm_buddy_block_size(&mm, block),
724 BIT_ULL(mm.max_order) * PAGE_SIZE);
726 drm_buddy_free_list(&mm, &allocated);
730 static int drm_buddy_suite_init(struct kunit_suite *suite)
733 random_seed = get_random_u32();
735 kunit_info(suite, "Testing DRM buddy manager, with random_seed=0x%x\n", random_seed);
740 static struct kunit_case drm_buddy_tests[] = {
741 KUNIT_CASE(drm_test_buddy_alloc_limit),
742 KUNIT_CASE(drm_test_buddy_alloc_range),
743 KUNIT_CASE(drm_test_buddy_alloc_optimistic),
744 KUNIT_CASE(drm_test_buddy_alloc_pessimistic),
745 KUNIT_CASE(drm_test_buddy_alloc_smoke),
746 KUNIT_CASE(drm_test_buddy_alloc_pathological),
750 static struct kunit_suite drm_buddy_test_suite = {
752 .suite_init = drm_buddy_suite_init,
753 .test_cases = drm_buddy_tests,
756 kunit_test_suite(drm_buddy_test_suite);
758 MODULE_AUTHOR("Intel Corporation");
759 MODULE_LICENSE("GPL");