1 // SPDX-License-Identifier: MIT
3 * Copyright © 2019 Intel Corporation
4 * Copyright © 2022 Maíra Canal <mairacanal@riseup.net>
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 if (!is_power_of_2(block_size)) {
93 kunit_err(test, "block size not power of two\n");
97 if (!IS_ALIGNED(block_size, mm->chunk_size)) {
98 kunit_err(test, "block size not aligned to min size\n");
102 if (!IS_ALIGNED(offset, mm->chunk_size)) {
103 kunit_err(test, "block offset not aligned to min size\n");
107 if (!IS_ALIGNED(offset, block_size)) {
108 kunit_err(test, "block offset not aligned to block size\n");
112 buddy = drm_get_buddy(block);
114 if (!buddy && block->parent) {
115 kunit_err(test, "buddy has gone fishing\n");
120 if (drm_buddy_block_offset(buddy) != (offset ^ block_size)) {
121 kunit_err(test, "buddy has wrong offset\n");
125 if (drm_buddy_block_size(mm, buddy) != block_size) {
126 kunit_err(test, "buddy size mismatch\n");
130 if (drm_buddy_block_state(buddy) == block_state &&
131 block_state == DRM_BUDDY_FREE) {
132 kunit_err(test, "block and its buddy are free\n");
140 static int check_blocks(struct kunit *test, struct drm_buddy *mm,
141 struct list_head *blocks, u64 expected_size, bool is_contiguous)
143 struct drm_buddy_block *block;
144 struct drm_buddy_block *prev;
152 list_for_each_entry(block, blocks, link) {
153 err = check_block(test, mm, block);
155 if (!drm_buddy_block_is_allocated(block)) {
156 kunit_err(test, "block not allocated\n");
160 if (is_contiguous && prev) {
165 prev_offset = drm_buddy_block_offset(prev);
166 prev_block_size = drm_buddy_block_size(mm, prev);
167 offset = drm_buddy_block_offset(block);
169 if (offset != (prev_offset + prev_block_size)) {
170 kunit_err(test, "block offset mismatch\n");
178 total += drm_buddy_block_size(mm, block);
183 if (total != expected_size) {
184 kunit_err(test, "size mismatch, expected=%llx, found=%llx\n",
185 expected_size, total);
192 kunit_err(test, "prev block, dump:\n");
193 dump_block(test, mm, prev);
196 kunit_err(test, "bad block, dump:\n");
197 dump_block(test, mm, block);
202 static int check_mm(struct kunit *test, struct drm_buddy *mm)
204 struct drm_buddy_block *root;
205 struct drm_buddy_block *prev;
211 kunit_err(test, "n_roots is zero\n");
215 if (mm->n_roots != hweight64(mm->size)) {
216 kunit_err(test, "n_roots mismatch, n_roots=%u, expected=%lu\n",
217 mm->n_roots, hweight64(mm->size));
225 for (i = 0; i < mm->n_roots; ++i) {
226 struct drm_buddy_block *block;
231 kunit_err(test, "root(%u) is NULL\n", i);
236 err = check_block(test, mm, root);
238 if (!drm_buddy_block_is_free(root)) {
239 kunit_err(test, "root not free\n");
243 order = drm_buddy_block_order(root);
246 if (order != mm->max_order) {
247 kunit_err(test, "max order root missing\n");
257 prev_offset = drm_buddy_block_offset(prev);
258 prev_block_size = drm_buddy_block_size(mm, prev);
259 offset = drm_buddy_block_offset(root);
261 if (offset != (prev_offset + prev_block_size)) {
262 kunit_err(test, "root offset mismatch\n");
267 block = list_first_entry_or_null(&mm->free_list[order],
268 struct drm_buddy_block, link);
270 kunit_err(test, "root mismatch at order=%u\n", order);
278 total += drm_buddy_block_size(mm, root);
282 if (total != mm->size) {
283 kunit_err(test, "expected mm size=%llx, found=%llx\n",
291 kunit_err(test, "prev root(%u), dump:\n", i - 1);
292 dump_block(test, mm, prev);
296 kunit_err(test, "bad root(%u), dump:\n", i);
297 dump_block(test, mm, root);
303 static void mm_config(u64 *size, u64 *chunk_size)
305 DRM_RND_STATE(prng, random_seed);
308 /* Nothing fancy, just try to get an interesting bit pattern */
310 prandom_seed_state(&prng, random_seed);
312 /* Let size be a random number of pages up to 8 GB (2M pages) */
313 s = 1 + drm_prandom_u32_max_state((BIT(33 - 12)) - 1, &prng);
314 /* Let the chunk size be a random power of 2 less than size */
315 ms = BIT(drm_prandom_u32_max_state(ilog2(s), &prng));
316 /* Round size down to the chunk size */
319 /* Convert from pages to bytes */
320 *chunk_size = (u64)ms << 12;
321 *size = (u64)s << 12;
324 static void drm_test_buddy_alloc_pathological(struct kunit *test)
326 u64 mm_size, size, start = 0;
327 struct drm_buddy_block *block;
328 const int max_order = 3;
329 unsigned long flags = 0;
337 * Create a pot-sized mm, then allocate one of each possible
338 * order within. This should leave the mm with exactly one
339 * page left. Free the largest block, then whittle down again.
340 * Eventually we will have a fully 50% fragmented mm.
343 mm_size = PAGE_SIZE << max_order;
344 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, PAGE_SIZE),
345 "buddy_init failed\n");
347 KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
349 for (top = max_order; top; top--) {
350 /* Make room by freeing the largest allocated block */
351 block = list_first_entry_or_null(&blocks, typeof(*block), link);
353 list_del(&block->link);
354 drm_buddy_free_block(&mm, block);
357 for (order = top; order--;) {
358 size = get_size(order, PAGE_SIZE);
359 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start,
362 "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
365 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
366 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
368 list_move_tail(&block->link, &blocks);
371 /* There should be one final page for this sub-allocation */
372 size = get_size(0, PAGE_SIZE);
373 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
374 size, size, &tmp, flags),
375 "buddy_alloc hit -ENOMEM for hole\n");
377 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
378 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
380 list_move_tail(&block->link, &holes);
382 size = get_size(top, PAGE_SIZE);
383 KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
384 size, size, &tmp, flags),
385 "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
389 drm_buddy_free_list(&mm, &holes);
391 /* Nothing larger than blocks of chunk_size now available */
392 for (order = 1; order <= max_order; order++) {
393 size = get_size(order, PAGE_SIZE);
394 KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
395 size, size, &tmp, flags),
396 "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
400 list_splice_tail(&holes, &blocks);
401 drm_buddy_free_list(&mm, &blocks);
405 static void drm_test_buddy_alloc_smoke(struct kunit *test)
407 u64 mm_size, chunk_size, start = 0;
408 unsigned long flags = 0;
413 DRM_RND_STATE(prng, random_seed);
416 mm_config(&mm_size, &chunk_size);
418 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, chunk_size),
419 "buddy_init failed\n");
421 order = drm_random_order(mm.max_order + 1, &prng);
422 KUNIT_ASSERT_TRUE(test, order);
424 for (i = 0; i <= mm.max_order; ++i) {
425 struct drm_buddy_block *block;
426 int max_order = order[i];
427 bool timeout = false;
433 KUNIT_ASSERT_FALSE_MSG(test, check_mm(test, &mm),
434 "pre-mm check failed, abort\n");
441 size = get_size(order, chunk_size);
442 err = drm_buddy_alloc_blocks(&mm, start, mm_size, size, size, &tmp, flags);
444 if (err == -ENOMEM) {
445 KUNIT_FAIL(test, "buddy_alloc hit -ENOMEM with order=%d\n",
453 KUNIT_FAIL(test, "buddy_alloc with order=%d failed\n",
460 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
461 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
463 list_move_tail(&block->link, &blocks);
464 KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_order(block), order,
465 "buddy_alloc order mismatch\n");
467 total += drm_buddy_block_size(&mm, block);
469 if (__timeout(end_time, NULL)) {
473 } while (total < mm.size);
476 err = check_blocks(test, &mm, &blocks, total, false);
478 drm_buddy_free_list(&mm, &blocks);
481 KUNIT_EXPECT_FALSE_MSG(test, check_mm(test, &mm),
482 "post-mm check failed\n");
495 static void drm_test_buddy_alloc_pessimistic(struct kunit *test)
497 u64 mm_size, size, start = 0;
498 struct drm_buddy_block *block, *bn;
499 const unsigned int max_order = 16;
500 unsigned long flags = 0;
507 * Create a pot-sized mm, then allocate one of each possible
508 * order within. This should leave the mm with exactly one
512 mm_size = PAGE_SIZE << max_order;
513 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, PAGE_SIZE),
514 "buddy_init failed\n");
516 KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
518 for (order = 0; order < max_order; order++) {
519 size = get_size(order, PAGE_SIZE);
520 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
521 size, size, &tmp, flags),
522 "buddy_alloc hit -ENOMEM with order=%d\n",
525 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
526 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
528 list_move_tail(&block->link, &blocks);
531 /* And now the last remaining block available */
532 size = get_size(0, PAGE_SIZE);
533 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
534 size, size, &tmp, flags),
535 "buddy_alloc hit -ENOMEM on final alloc\n");
537 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
538 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
540 list_move_tail(&block->link, &blocks);
542 /* Should be completely full! */
543 for (order = max_order; order--;) {
544 size = get_size(order, PAGE_SIZE);
545 KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
546 size, size, &tmp, flags),
547 "buddy_alloc unexpectedly succeeded, it should be full!");
550 block = list_last_entry(&blocks, typeof(*block), link);
551 list_del(&block->link);
552 drm_buddy_free_block(&mm, block);
554 /* As we free in increasing size, we make available larger blocks */
556 list_for_each_entry_safe(block, bn, &blocks, link) {
557 list_del(&block->link);
558 drm_buddy_free_block(&mm, block);
560 size = get_size(order, PAGE_SIZE);
561 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
562 size, size, &tmp, flags),
563 "buddy_alloc hit -ENOMEM with order=%d\n",
566 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
567 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
569 list_del(&block->link);
570 drm_buddy_free_block(&mm, block);
574 /* To confirm, now the whole mm should be available */
575 size = get_size(max_order, PAGE_SIZE);
576 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
577 size, size, &tmp, flags),
578 "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
581 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
582 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
584 list_del(&block->link);
585 drm_buddy_free_block(&mm, block);
586 drm_buddy_free_list(&mm, &blocks);
590 static void drm_test_buddy_alloc_optimistic(struct kunit *test)
592 u64 mm_size, size, start = 0;
593 struct drm_buddy_block *block;
594 unsigned long flags = 0;
595 const int max_order = 16;
602 * Create a mm with one block of each order available, and
603 * try to allocate them all.
606 mm_size = PAGE_SIZE * ((1 << (max_order + 1)) - 1);
608 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, PAGE_SIZE),
609 "buddy_init failed\n");
611 KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
613 for (order = 0; order <= max_order; order++) {
614 size = get_size(order, PAGE_SIZE);
615 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
616 size, size, &tmp, flags),
617 "buddy_alloc hit -ENOMEM with order=%d\n",
620 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
621 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
623 list_move_tail(&block->link, &blocks);
626 /* Should be completely full! */
627 size = get_size(0, PAGE_SIZE);
628 KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
629 size, size, &tmp, flags),
630 "buddy_alloc unexpectedly succeeded, it should be full!");
632 drm_buddy_free_list(&mm, &blocks);
636 static void drm_test_buddy_alloc_range(struct kunit *test)
638 unsigned long flags = DRM_BUDDY_RANGE_ALLOCATION;
639 u64 offset, size, rem, chunk_size, end;
640 unsigned long page_num;
644 mm_config(&size, &chunk_size);
646 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, size, chunk_size),
647 "buddy_init failed");
649 KUNIT_ASSERT_FALSE_MSG(test, check_mm(test, &mm),
650 "pre-mm check failed, abort!");
655 for_each_prime_number_from(page_num, 1, ULONG_MAX - 1) {
656 struct drm_buddy_block *block;
659 size = min(page_num * mm.chunk_size, rem);
662 KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, offset, end,
665 "alloc_range with offset=%llx, size=%llx failed\n", offset, size);
667 block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
668 KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_range has no blocks\n");
670 KUNIT_ASSERT_EQ_MSG(test, drm_buddy_block_offset(block), offset,
671 "alloc_range start offset mismatch, found=%llx, expected=%llx\n",
672 drm_buddy_block_offset(block), offset);
674 KUNIT_ASSERT_FALSE(test, check_blocks(test, &mm, &tmp, size, true));
676 list_splice_tail(&tmp, &blocks);
687 drm_buddy_free_list(&mm, &blocks);
689 KUNIT_EXPECT_FALSE_MSG(test, check_mm(test, &mm), "post-mm check failed\n");
694 static void drm_test_buddy_alloc_limit(struct kunit *test)
696 u64 size = U64_MAX, start = 0;
697 struct drm_buddy_block *block;
698 unsigned long flags = 0;
699 LIST_HEAD(allocated);
702 KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, size, PAGE_SIZE));
704 KUNIT_EXPECT_EQ_MSG(test, mm.max_order, DRM_BUDDY_MAX_ORDER,
705 "mm.max_order(%d) != %d\n", mm.max_order,
706 DRM_BUDDY_MAX_ORDER);
708 size = mm.chunk_size << mm.max_order;
709 KUNIT_EXPECT_FALSE(test, drm_buddy_alloc_blocks(&mm, start, size, size,
710 PAGE_SIZE, &allocated, flags));
712 block = list_first_entry_or_null(&allocated, struct drm_buddy_block, link);
713 KUNIT_EXPECT_TRUE(test, block);
715 KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_order(block), mm.max_order,
716 "block order(%d) != %d\n",
717 drm_buddy_block_order(block), mm.max_order);
719 KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_size(&mm, block),
720 BIT_ULL(mm.max_order) * PAGE_SIZE,
721 "block size(%llu) != %llu\n",
722 drm_buddy_block_size(&mm, block),
723 BIT_ULL(mm.max_order) * PAGE_SIZE);
725 drm_buddy_free_list(&mm, &allocated);
729 static int drm_buddy_init_test(struct kunit *test)
732 random_seed = get_random_u32();
737 static struct kunit_case drm_buddy_tests[] = {
738 KUNIT_CASE(drm_test_buddy_alloc_limit),
739 KUNIT_CASE(drm_test_buddy_alloc_range),
740 KUNIT_CASE(drm_test_buddy_alloc_optimistic),
741 KUNIT_CASE(drm_test_buddy_alloc_pessimistic),
742 KUNIT_CASE(drm_test_buddy_alloc_smoke),
743 KUNIT_CASE(drm_test_buddy_alloc_pathological),
747 static struct kunit_suite drm_buddy_test_suite = {
749 .init = drm_buddy_init_test,
750 .test_cases = drm_buddy_tests,
753 kunit_test_suite(drm_buddy_test_suite);
755 MODULE_AUTHOR("Intel Corporation");
756 MODULE_LICENSE("GPL");