14a9671a0SJoel Fernandes // SPDX-License-Identifier: MIT 24a9671a0SJoel Fernandes /* 34a9671a0SJoel Fernandes * Copyright © 2019 Intel Corporation 44a9671a0SJoel Fernandes * Copyright © 2022 Maíra Canal <mairacanal@riseup.net> 54a9671a0SJoel Fernandes */ 64a9671a0SJoel Fernandes 74a9671a0SJoel Fernandes #include <kunit/test.h> 84a9671a0SJoel Fernandes 94a9671a0SJoel Fernandes #include <linux/prime_numbers.h> 104a9671a0SJoel Fernandes #include <linux/sched/signal.h> 114a9671a0SJoel Fernandes #include <linux/sizes.h> 124a9671a0SJoel Fernandes 134a9671a0SJoel Fernandes #include <linux/gpu_buddy.h> 144a9671a0SJoel Fernandes 154a9671a0SJoel Fernandes #include "gpu_random.h" 164a9671a0SJoel Fernandes 174a9671a0SJoel Fernandes static unsigned int random_seed; 184a9671a0SJoel Fernandes 194a9671a0SJoel Fernandes static inline u64 get_size(int order, u64 chunk_size) 204a9671a0SJoel Fernandes { 214a9671a0SJoel Fernandes return (1 << order) * chunk_size; 224a9671a0SJoel Fernandes } 234a9671a0SJoel Fernandes 24ba110db8SJoel Fernandes static void gpu_test_buddy_fragmentation_performance(struct kunit *test) 254a9671a0SJoel Fernandes { 26ba110db8SJoel Fernandes struct gpu_buddy_block *block, *tmp; 274a9671a0SJoel Fernandes int num_blocks, i, ret, count = 0; 284a9671a0SJoel Fernandes LIST_HEAD(allocated_blocks); 294a9671a0SJoel Fernandes unsigned long elapsed_ms; 304a9671a0SJoel Fernandes LIST_HEAD(reverse_list); 314a9671a0SJoel Fernandes LIST_HEAD(test_blocks); 324a9671a0SJoel Fernandes LIST_HEAD(clear_list); 334a9671a0SJoel Fernandes LIST_HEAD(dirty_list); 344a9671a0SJoel Fernandes LIST_HEAD(free_list); 35ba110db8SJoel Fernandes struct gpu_buddy mm; 364a9671a0SJoel Fernandes u64 mm_size = SZ_4G; 374a9671a0SJoel Fernandes ktime_t start, end; 384a9671a0SJoel Fernandes 394a9671a0SJoel Fernandes /* 404a9671a0SJoel Fernandes * Allocation under severe fragmentation 414a9671a0SJoel Fernandes * 424a9671a0SJoel Fernandes * Create severe fragmentation by allocating the entire 4 GiB address space 434a9671a0SJoel Fernandes * as tiny 8 KiB blocks but forcing a 64 KiB alignment. The resulting pattern 444a9671a0SJoel Fernandes * leaves many scattered holes. Split the allocations into two groups and 454a9671a0SJoel Fernandes * return them with different flags to block coalescing, then repeatedly 464a9671a0SJoel Fernandes * allocate and free 64 KiB blocks while timing the loop. This stresses how 474a9671a0SJoel Fernandes * quickly the allocator can satisfy larger, aligned requests from a pool of 484a9671a0SJoel Fernandes * highly fragmented space. 494a9671a0SJoel Fernandes */ 50ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K), 514a9671a0SJoel Fernandes "buddy_init failed\n"); 524a9671a0SJoel Fernandes 534a9671a0SJoel Fernandes num_blocks = mm_size / SZ_64K; 544a9671a0SJoel Fernandes 554a9671a0SJoel Fernandes start = ktime_get(); 564a9671a0SJoel Fernandes /* Allocate with maximum fragmentation - 8K blocks with 64K alignment */ 574a9671a0SJoel Fernandes for (i = 0; i < num_blocks; i++) 58ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, SZ_8K, SZ_64K, 594a9671a0SJoel Fernandes &allocated_blocks, 0), 604a9671a0SJoel Fernandes "buddy_alloc hit an error size=%u\n", SZ_8K); 614a9671a0SJoel Fernandes 624a9671a0SJoel Fernandes list_for_each_entry_safe(block, tmp, &allocated_blocks, link) { 634a9671a0SJoel Fernandes if (count % 4 == 0 || count % 4 == 3) 644a9671a0SJoel Fernandes list_move_tail(&block->link, &clear_list); 654a9671a0SJoel Fernandes else 664a9671a0SJoel Fernandes list_move_tail(&block->link, &dirty_list); 674a9671a0SJoel Fernandes count++; 684a9671a0SJoel Fernandes } 694a9671a0SJoel Fernandes 704a9671a0SJoel Fernandes /* Free with different flags to ensure no coalescing */ 71ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &clear_list, GPU_BUDDY_CLEARED); 72ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &dirty_list, 0); 734a9671a0SJoel Fernandes 744a9671a0SJoel Fernandes for (i = 0; i < num_blocks; i++) 75ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, SZ_64K, SZ_64K, 764a9671a0SJoel Fernandes &test_blocks, 0), 774a9671a0SJoel Fernandes "buddy_alloc hit an error size=%u\n", SZ_64K); 78ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &test_blocks, 0); 794a9671a0SJoel Fernandes 804a9671a0SJoel Fernandes end = ktime_get(); 814a9671a0SJoel Fernandes elapsed_ms = ktime_to_ms(ktime_sub(end, start)); 824a9671a0SJoel Fernandes 834a9671a0SJoel Fernandes kunit_info(test, "Fragmented allocation took %lu ms\n", elapsed_ms); 844a9671a0SJoel Fernandes 85ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 864a9671a0SJoel Fernandes 874a9671a0SJoel Fernandes /* 884a9671a0SJoel Fernandes * Reverse free order under fragmentation 894a9671a0SJoel Fernandes * 904a9671a0SJoel Fernandes * Construct a fragmented 4 GiB space by allocating every 8 KiB block with 914a9671a0SJoel Fernandes * 64 KiB alignment, creating a dense scatter of small regions. Half of the 924a9671a0SJoel Fernandes * blocks are selectively freed to form sparse gaps, while the remaining 934a9671a0SJoel Fernandes * allocations are preserved, reordered in reverse, and released back with 944a9671a0SJoel Fernandes * the cleared flag. This models a pathological reverse-ordered free pattern 954a9671a0SJoel Fernandes * and measures how quickly the allocator can merge and reclaim space when 964a9671a0SJoel Fernandes * deallocation occurs in the opposite order of allocation, exposing the 974a9671a0SJoel Fernandes * cost difference between a linear freelist scan and an ordered tree lookup. 984a9671a0SJoel Fernandes */ 99ba110db8SJoel Fernandes ret = gpu_buddy_init(&mm, mm_size, SZ_4K); 1004a9671a0SJoel Fernandes KUNIT_ASSERT_EQ(test, ret, 0); 1014a9671a0SJoel Fernandes 1024a9671a0SJoel Fernandes start = ktime_get(); 1034a9671a0SJoel Fernandes /* Allocate maximum fragmentation */ 1044a9671a0SJoel Fernandes for (i = 0; i < num_blocks; i++) 105ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, SZ_8K, SZ_64K, 1064a9671a0SJoel Fernandes &allocated_blocks, 0), 1074a9671a0SJoel Fernandes "buddy_alloc hit an error size=%u\n", SZ_8K); 1084a9671a0SJoel Fernandes 1094a9671a0SJoel Fernandes list_for_each_entry_safe(block, tmp, &allocated_blocks, link) { 1104a9671a0SJoel Fernandes if (count % 2 == 0) 1114a9671a0SJoel Fernandes list_move_tail(&block->link, &free_list); 1124a9671a0SJoel Fernandes count++; 1134a9671a0SJoel Fernandes } 114ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &free_list, GPU_BUDDY_CLEARED); 1154a9671a0SJoel Fernandes 1164a9671a0SJoel Fernandes list_for_each_entry_safe_reverse(block, tmp, &allocated_blocks, link) 1174a9671a0SJoel Fernandes list_move(&block->link, &reverse_list); 118ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &reverse_list, GPU_BUDDY_CLEARED); 1194a9671a0SJoel Fernandes 1204a9671a0SJoel Fernandes end = ktime_get(); 1214a9671a0SJoel Fernandes elapsed_ms = ktime_to_ms(ktime_sub(end, start)); 1224a9671a0SJoel Fernandes 1234a9671a0SJoel Fernandes kunit_info(test, "Reverse-ordered free took %lu ms\n", elapsed_ms); 1244a9671a0SJoel Fernandes 125ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 1264a9671a0SJoel Fernandes } 1274a9671a0SJoel Fernandes 128ba110db8SJoel Fernandes static void gpu_test_buddy_alloc_range_bias(struct kunit *test) 1294a9671a0SJoel Fernandes { 1304a9671a0SJoel Fernandes u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem; 131ba110db8SJoel Fernandes GPU_RND_STATE(prng, random_seed); 1324a9671a0SJoel Fernandes unsigned int i, count, *order; 133ba110db8SJoel Fernandes struct gpu_buddy_block *block; 1344a9671a0SJoel Fernandes unsigned long flags; 135ba110db8SJoel Fernandes struct gpu_buddy mm; 1364a9671a0SJoel Fernandes LIST_HEAD(allocated); 1374a9671a0SJoel Fernandes 1384a9671a0SJoel Fernandes bias_size = SZ_1M; 1394a9671a0SJoel Fernandes ps = roundup_pow_of_two(prandom_u32_state(&prng) % bias_size); 1404a9671a0SJoel Fernandes ps = max(SZ_4K, ps); 1414a9671a0SJoel Fernandes mm_size = (SZ_8M-1) & ~(ps-1); /* Multiple roots */ 1424a9671a0SJoel Fernandes 1434a9671a0SJoel Fernandes kunit_info(test, "mm_size=%u, ps=%u\n", mm_size, ps); 1444a9671a0SJoel Fernandes 145ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps), 1464a9671a0SJoel Fernandes "buddy_init failed\n"); 1474a9671a0SJoel Fernandes 1484a9671a0SJoel Fernandes count = mm_size / bias_size; 149ba110db8SJoel Fernandes order = gpu_random_order(count, &prng); 1504a9671a0SJoel Fernandes KUNIT_EXPECT_TRUE(test, order); 1514a9671a0SJoel Fernandes 1524a9671a0SJoel Fernandes /* 1534a9671a0SJoel Fernandes * Idea is to split the address space into uniform bias ranges, and then 1544a9671a0SJoel Fernandes * in some random order allocate within each bias, using various 1554a9671a0SJoel Fernandes * patterns within. This should detect if allocations leak out from a 1564a9671a0SJoel Fernandes * given bias, for example. 1574a9671a0SJoel Fernandes */ 1584a9671a0SJoel Fernandes 1594a9671a0SJoel Fernandes for (i = 0; i < count; i++) { 1604a9671a0SJoel Fernandes LIST_HEAD(tmp); 1614a9671a0SJoel Fernandes u32 size; 1624a9671a0SJoel Fernandes 1634a9671a0SJoel Fernandes bias_start = order[i] * bias_size; 1644a9671a0SJoel Fernandes bias_end = bias_start + bias_size; 1654a9671a0SJoel Fernandes bias_rem = bias_size; 1664a9671a0SJoel Fernandes 1674a9671a0SJoel Fernandes /* internal round_up too big */ 1684a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, 169ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, 1704a9671a0SJoel Fernandes bias_end, bias_size + ps, bias_size, 1714a9671a0SJoel Fernandes &allocated, 172ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 1734a9671a0SJoel Fernandes "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n", 1744a9671a0SJoel Fernandes bias_start, bias_end, bias_size, bias_size); 1754a9671a0SJoel Fernandes 1764a9671a0SJoel Fernandes /* size too big */ 1774a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, 178ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, 1794a9671a0SJoel Fernandes bias_end, bias_size + ps, ps, 1804a9671a0SJoel Fernandes &allocated, 181ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 1824a9671a0SJoel Fernandes "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n", 1834a9671a0SJoel Fernandes bias_start, bias_end, bias_size + ps, ps); 1844a9671a0SJoel Fernandes 1854a9671a0SJoel Fernandes /* bias range too small for size */ 1864a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, 187ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start + ps, 1884a9671a0SJoel Fernandes bias_end, bias_size, ps, 1894a9671a0SJoel Fernandes &allocated, 190ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 1914a9671a0SJoel Fernandes "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n", 1924a9671a0SJoel Fernandes bias_start + ps, bias_end, bias_size, ps); 1934a9671a0SJoel Fernandes 1944a9671a0SJoel Fernandes /* bias misaligned */ 1954a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, 196ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start + ps, 1974a9671a0SJoel Fernandes bias_end - ps, 1984a9671a0SJoel Fernandes bias_size >> 1, bias_size >> 1, 1994a9671a0SJoel Fernandes &allocated, 200ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 2014a9671a0SJoel Fernandes "buddy_alloc h didn't fail with bias(%x-%x), size=%u, ps=%u\n", 2024a9671a0SJoel Fernandes bias_start + ps, bias_end - ps, bias_size >> 1, bias_size >> 1); 2034a9671a0SJoel Fernandes 2044a9671a0SJoel Fernandes /* single big page */ 2054a9671a0SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, 206ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, 2074a9671a0SJoel Fernandes bias_end, bias_size, bias_size, 2084a9671a0SJoel Fernandes &tmp, 209ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 2104a9671a0SJoel Fernandes "buddy_alloc i failed with bias(%x-%x), size=%u, ps=%u\n", 2114a9671a0SJoel Fernandes bias_start, bias_end, bias_size, bias_size); 212ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &tmp, 0); 2134a9671a0SJoel Fernandes 2144a9671a0SJoel Fernandes /* single page with internal round_up */ 2154a9671a0SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, 216ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, 2174a9671a0SJoel Fernandes bias_end, ps, bias_size, 2184a9671a0SJoel Fernandes &tmp, 219ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 2204a9671a0SJoel Fernandes "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n", 2214a9671a0SJoel Fernandes bias_start, bias_end, ps, bias_size); 222ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &tmp, 0); 2234a9671a0SJoel Fernandes 2244a9671a0SJoel Fernandes /* random size within */ 2254a9671a0SJoel Fernandes size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps); 2264a9671a0SJoel Fernandes if (size) 2274a9671a0SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, 228ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, 2294a9671a0SJoel Fernandes bias_end, size, ps, 2304a9671a0SJoel Fernandes &tmp, 231ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 2324a9671a0SJoel Fernandes "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n", 2334a9671a0SJoel Fernandes bias_start, bias_end, size, ps); 2344a9671a0SJoel Fernandes 2354a9671a0SJoel Fernandes bias_rem -= size; 2364a9671a0SJoel Fernandes /* too big for current avail */ 2374a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, 238ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, 2394a9671a0SJoel Fernandes bias_end, bias_rem + ps, ps, 2404a9671a0SJoel Fernandes &allocated, 241ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 2424a9671a0SJoel Fernandes "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n", 2434a9671a0SJoel Fernandes bias_start, bias_end, bias_rem + ps, ps); 2444a9671a0SJoel Fernandes 2454a9671a0SJoel Fernandes if (bias_rem) { 2464a9671a0SJoel Fernandes /* random fill of the remainder */ 2474a9671a0SJoel Fernandes size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps); 2484a9671a0SJoel Fernandes size = max(size, ps); 2494a9671a0SJoel Fernandes 2504a9671a0SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, 251ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, 2524a9671a0SJoel Fernandes bias_end, size, ps, 2534a9671a0SJoel Fernandes &allocated, 254ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 2554a9671a0SJoel Fernandes "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n", 2564a9671a0SJoel Fernandes bias_start, bias_end, size, ps); 2574a9671a0SJoel Fernandes /* 2584a9671a0SJoel Fernandes * Intentionally allow some space to be left 2594a9671a0SJoel Fernandes * unallocated, and ideally not always on the bias 2604a9671a0SJoel Fernandes * boundaries. 2614a9671a0SJoel Fernandes */ 262ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &tmp, 0); 2634a9671a0SJoel Fernandes } else { 2644a9671a0SJoel Fernandes list_splice_tail(&tmp, &allocated); 2654a9671a0SJoel Fernandes } 2664a9671a0SJoel Fernandes } 2674a9671a0SJoel Fernandes 2684a9671a0SJoel Fernandes kfree(order); 269ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, 0); 270ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 2714a9671a0SJoel Fernandes 2724a9671a0SJoel Fernandes /* 2734a9671a0SJoel Fernandes * Something more free-form. Idea is to pick a random starting bias 2744a9671a0SJoel Fernandes * range within the address space and then start filling it up. Also 2754a9671a0SJoel Fernandes * randomly grow the bias range in both directions as we go along. This 2764a9671a0SJoel Fernandes * should give us bias start/end which is not always uniform like above, 2774a9671a0SJoel Fernandes * and in some cases will require the allocator to jump over already 2784a9671a0SJoel Fernandes * allocated nodes in the middle of the address space. 2794a9671a0SJoel Fernandes */ 2804a9671a0SJoel Fernandes 281ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps), 2824a9671a0SJoel Fernandes "buddy_init failed\n"); 2834a9671a0SJoel Fernandes 2844a9671a0SJoel Fernandes bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps); 2854a9671a0SJoel Fernandes bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps); 2864a9671a0SJoel Fernandes bias_end = max(bias_end, bias_start + ps); 2874a9671a0SJoel Fernandes bias_rem = bias_end - bias_start; 2884a9671a0SJoel Fernandes 2894a9671a0SJoel Fernandes do { 2904a9671a0SJoel Fernandes u32 size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps); 2914a9671a0SJoel Fernandes 2924a9671a0SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, 293ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, 2944a9671a0SJoel Fernandes bias_end, size, ps, 2954a9671a0SJoel Fernandes &allocated, 296ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 2974a9671a0SJoel Fernandes "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n", 2984a9671a0SJoel Fernandes bias_start, bias_end, size, ps); 2994a9671a0SJoel Fernandes bias_rem -= size; 3004a9671a0SJoel Fernandes 3014a9671a0SJoel Fernandes /* 3024a9671a0SJoel Fernandes * Try to randomly grow the bias range in both directions, or 3034a9671a0SJoel Fernandes * only one, or perhaps don't grow at all. 3044a9671a0SJoel Fernandes */ 3054a9671a0SJoel Fernandes do { 3064a9671a0SJoel Fernandes u32 old_bias_start = bias_start; 3074a9671a0SJoel Fernandes u32 old_bias_end = bias_end; 3084a9671a0SJoel Fernandes 3094a9671a0SJoel Fernandes if (bias_start) 3104a9671a0SJoel Fernandes bias_start -= round_up(prandom_u32_state(&prng) % bias_start, ps); 3114a9671a0SJoel Fernandes if (bias_end != mm_size) 3124a9671a0SJoel Fernandes bias_end += round_up(prandom_u32_state(&prng) % (mm_size - bias_end), ps); 3134a9671a0SJoel Fernandes 3144a9671a0SJoel Fernandes bias_rem += old_bias_start - bias_start; 3154a9671a0SJoel Fernandes bias_rem += bias_end - old_bias_end; 3164a9671a0SJoel Fernandes } while (!bias_rem && (bias_start || bias_end != mm_size)); 3174a9671a0SJoel Fernandes } while (bias_rem); 3184a9671a0SJoel Fernandes 3194a9671a0SJoel Fernandes KUNIT_ASSERT_EQ(test, bias_start, 0); 3204a9671a0SJoel Fernandes KUNIT_ASSERT_EQ(test, bias_end, mm_size); 3214a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, 322ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, bias_end, 3234a9671a0SJoel Fernandes ps, ps, 3244a9671a0SJoel Fernandes &allocated, 325ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 3264a9671a0SJoel Fernandes "buddy_alloc passed with bias(%x-%x), size=%u\n", 3274a9671a0SJoel Fernandes bias_start, bias_end, ps); 3284a9671a0SJoel Fernandes 329ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, 0); 330ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 3314a9671a0SJoel Fernandes 3324a9671a0SJoel Fernandes /* 333ba110db8SJoel Fernandes * Allocate cleared blocks in the bias range when the GPU buddy's clear avail is 3344a9671a0SJoel Fernandes * zero. This will validate the bias range allocation in scenarios like system boot 3354a9671a0SJoel Fernandes * when no cleared blocks are available and exercise the fallback path too. The resulting 3364a9671a0SJoel Fernandes * blocks should always be dirty. 3374a9671a0SJoel Fernandes */ 3384a9671a0SJoel Fernandes 339ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps), 3404a9671a0SJoel Fernandes "buddy_init failed\n"); 3414a9671a0SJoel Fernandes 3424a9671a0SJoel Fernandes bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps); 3434a9671a0SJoel Fernandes bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps); 3444a9671a0SJoel Fernandes bias_end = max(bias_end, bias_start + ps); 3454a9671a0SJoel Fernandes bias_rem = bias_end - bias_start; 3464a9671a0SJoel Fernandes 347ba110db8SJoel Fernandes flags = GPU_BUDDY_CLEAR_ALLOCATION | GPU_BUDDY_RANGE_ALLOCATION; 3484a9671a0SJoel Fernandes size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps); 3494a9671a0SJoel Fernandes 3504a9671a0SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, 351ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, bias_start, 3524a9671a0SJoel Fernandes bias_end, size, ps, 3534a9671a0SJoel Fernandes &allocated, 3544a9671a0SJoel Fernandes flags), 3554a9671a0SJoel Fernandes "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n", 3564a9671a0SJoel Fernandes bias_start, bias_end, size, ps); 3574a9671a0SJoel Fernandes 3584a9671a0SJoel Fernandes list_for_each_entry(block, &allocated, link) 359ba110db8SJoel Fernandes KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false); 3604a9671a0SJoel Fernandes 361ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, 0); 362ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 3634a9671a0SJoel Fernandes } 3644a9671a0SJoel Fernandes 365ba110db8SJoel Fernandes static void gpu_test_buddy_alloc_clear(struct kunit *test) 3664a9671a0SJoel Fernandes { 3674a9671a0SJoel Fernandes unsigned long n_pages, total, i = 0; 3684a9671a0SJoel Fernandes const unsigned long ps = SZ_4K; 369ba110db8SJoel Fernandes struct gpu_buddy_block *block; 3704a9671a0SJoel Fernandes const int max_order = 12; 3714a9671a0SJoel Fernandes LIST_HEAD(allocated); 372ba110db8SJoel Fernandes struct gpu_buddy mm; 3734a9671a0SJoel Fernandes unsigned int order; 3744a9671a0SJoel Fernandes u32 mm_size, size; 3754a9671a0SJoel Fernandes LIST_HEAD(dirty); 3764a9671a0SJoel Fernandes LIST_HEAD(clean); 3774a9671a0SJoel Fernandes 3784a9671a0SJoel Fernandes mm_size = SZ_4K << max_order; 379ba110db8SJoel Fernandes KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps)); 3804a9671a0SJoel Fernandes 3814a9671a0SJoel Fernandes KUNIT_EXPECT_EQ(test, mm.max_order, max_order); 3824a9671a0SJoel Fernandes 3834a9671a0SJoel Fernandes /* 3844a9671a0SJoel Fernandes * Idea is to allocate and free some random portion of the address space, 3854a9671a0SJoel Fernandes * returning those pages as non-dirty and randomly alternate between 3864a9671a0SJoel Fernandes * requesting dirty and non-dirty pages (not going over the limit 3874a9671a0SJoel Fernandes * we freed as non-dirty), putting that into two separate lists. 3884a9671a0SJoel Fernandes * Loop over both lists at the end checking that the dirty list 3894a9671a0SJoel Fernandes * is indeed all dirty pages and vice versa. Free it all again, 3904a9671a0SJoel Fernandes * keeping the dirty/clear status. 3914a9671a0SJoel Fernandes */ 392ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 3934a9671a0SJoel Fernandes 5 * ps, ps, &allocated, 394ba110db8SJoel Fernandes GPU_BUDDY_TOPDOWN_ALLOCATION), 3954a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", 5 * ps); 396ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED); 3974a9671a0SJoel Fernandes 3984a9671a0SJoel Fernandes n_pages = 10; 3994a9671a0SJoel Fernandes do { 4004a9671a0SJoel Fernandes unsigned long flags; 4014a9671a0SJoel Fernandes struct list_head *list; 4024a9671a0SJoel Fernandes int slot = i % 2; 4034a9671a0SJoel Fernandes 4044a9671a0SJoel Fernandes if (slot == 0) { 4054a9671a0SJoel Fernandes list = &dirty; 4064a9671a0SJoel Fernandes flags = 0; 4074a9671a0SJoel Fernandes } else { 4084a9671a0SJoel Fernandes list = &clean; 409ba110db8SJoel Fernandes flags = GPU_BUDDY_CLEAR_ALLOCATION; 4104a9671a0SJoel Fernandes } 4114a9671a0SJoel Fernandes 412ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 4134a9671a0SJoel Fernandes ps, ps, list, 4144a9671a0SJoel Fernandes flags), 4154a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", ps); 4164a9671a0SJoel Fernandes } while (++i < n_pages); 4174a9671a0SJoel Fernandes 4184a9671a0SJoel Fernandes list_for_each_entry(block, &clean, link) 419ba110db8SJoel Fernandes KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), true); 4204a9671a0SJoel Fernandes 4214a9671a0SJoel Fernandes list_for_each_entry(block, &dirty, link) 422ba110db8SJoel Fernandes KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false); 4234a9671a0SJoel Fernandes 424ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED); 4254a9671a0SJoel Fernandes 4264a9671a0SJoel Fernandes /* 4274a9671a0SJoel Fernandes * Trying to go over the clear limit for some allocation. 4284a9671a0SJoel Fernandes * The allocation should never fail with reasonable page-size. 4294a9671a0SJoel Fernandes */ 430ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 4314a9671a0SJoel Fernandes 10 * ps, ps, &clean, 432ba110db8SJoel Fernandes GPU_BUDDY_CLEAR_ALLOCATION), 4334a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", 10 * ps); 4344a9671a0SJoel Fernandes 435ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED); 436ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &dirty, 0); 437ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 4384a9671a0SJoel Fernandes 439ba110db8SJoel Fernandes KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps)); 4404a9671a0SJoel Fernandes 4414a9671a0SJoel Fernandes /* 4424a9671a0SJoel Fernandes * Create a new mm. Intentionally fragment the address space by creating 4434a9671a0SJoel Fernandes * two alternating lists. Free both lists, one as dirty the other as clean. 4444a9671a0SJoel Fernandes * Try to allocate double the previous size with matching min_page_size. The 4454a9671a0SJoel Fernandes * allocation should never fail as it calls the force_merge. Also check that 4464a9671a0SJoel Fernandes * the page is always dirty after force_merge. Free the page as dirty, then 4474a9671a0SJoel Fernandes * repeat the whole thing, increment the order until we hit the max_order. 4484a9671a0SJoel Fernandes */ 4494a9671a0SJoel Fernandes 4504a9671a0SJoel Fernandes i = 0; 4514a9671a0SJoel Fernandes n_pages = mm_size / ps; 4524a9671a0SJoel Fernandes do { 4534a9671a0SJoel Fernandes struct list_head *list; 4544a9671a0SJoel Fernandes int slot = i % 2; 4554a9671a0SJoel Fernandes 4564a9671a0SJoel Fernandes if (slot == 0) 4574a9671a0SJoel Fernandes list = &dirty; 4584a9671a0SJoel Fernandes else 4594a9671a0SJoel Fernandes list = &clean; 4604a9671a0SJoel Fernandes 461ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 4624a9671a0SJoel Fernandes ps, ps, list, 0), 4634a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", ps); 4644a9671a0SJoel Fernandes } while (++i < n_pages); 4654a9671a0SJoel Fernandes 466ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED); 467ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &dirty, 0); 4684a9671a0SJoel Fernandes 4694a9671a0SJoel Fernandes order = 1; 4704a9671a0SJoel Fernandes do { 4714a9671a0SJoel Fernandes size = SZ_4K << order; 4724a9671a0SJoel Fernandes 473ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 4744a9671a0SJoel Fernandes size, size, &allocated, 475ba110db8SJoel Fernandes GPU_BUDDY_CLEAR_ALLOCATION), 4764a9671a0SJoel Fernandes "buddy_alloc hit an error size=%u\n", size); 4774a9671a0SJoel Fernandes total = 0; 4784a9671a0SJoel Fernandes list_for_each_entry(block, &allocated, link) { 4794a9671a0SJoel Fernandes if (size != mm_size) 480ba110db8SJoel Fernandes KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false); 481ba110db8SJoel Fernandes total += gpu_buddy_block_size(&mm, block); 4824a9671a0SJoel Fernandes } 4834a9671a0SJoel Fernandes KUNIT_EXPECT_EQ(test, total, size); 4844a9671a0SJoel Fernandes 485ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, 0); 4864a9671a0SJoel Fernandes } while (++order <= max_order); 4874a9671a0SJoel Fernandes 488ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 4894a9671a0SJoel Fernandes 4904a9671a0SJoel Fernandes /* 4914a9671a0SJoel Fernandes * Create a new mm with a non power-of-two size. Allocate a random size from each 4924a9671a0SJoel Fernandes * root, free as cleared and then call fini. This will ensure the multi-root 4934a9671a0SJoel Fernandes * force merge during fini. 4944a9671a0SJoel Fernandes */ 4954a9671a0SJoel Fernandes mm_size = (SZ_4K << max_order) + (SZ_4K << (max_order - 2)); 4964a9671a0SJoel Fernandes 497ba110db8SJoel Fernandes KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps)); 4984a9671a0SJoel Fernandes KUNIT_EXPECT_EQ(test, mm.max_order, max_order); 499ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order, 5004a9671a0SJoel Fernandes 4 * ps, ps, &allocated, 501ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 5024a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", 4 * ps); 503ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED); 504ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order, 5054a9671a0SJoel Fernandes 2 * ps, ps, &allocated, 506ba110db8SJoel Fernandes GPU_BUDDY_CLEAR_ALLOCATION), 5074a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", 2 * ps); 508ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED); 509ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, SZ_4K << max_order, mm_size, 5104a9671a0SJoel Fernandes ps, ps, &allocated, 511ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION), 5124a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", ps); 513ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED); 514ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 5154a9671a0SJoel Fernandes } 5164a9671a0SJoel Fernandes 517ba110db8SJoel Fernandes static void gpu_test_buddy_alloc_contiguous(struct kunit *test) 5184a9671a0SJoel Fernandes { 5194a9671a0SJoel Fernandes const unsigned long ps = SZ_4K, mm_size = 16 * 3 * SZ_4K; 5204a9671a0SJoel Fernandes unsigned long i, n_pages, total; 521ba110db8SJoel Fernandes struct gpu_buddy_block *block; 522ba110db8SJoel Fernandes struct gpu_buddy mm; 5234a9671a0SJoel Fernandes LIST_HEAD(left); 5244a9671a0SJoel Fernandes LIST_HEAD(middle); 5254a9671a0SJoel Fernandes LIST_HEAD(right); 5264a9671a0SJoel Fernandes LIST_HEAD(allocated); 5274a9671a0SJoel Fernandes 528ba110db8SJoel Fernandes KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps)); 5294a9671a0SJoel Fernandes 5304a9671a0SJoel Fernandes /* 5314a9671a0SJoel Fernandes * Idea is to fragment the address space by alternating block 5324a9671a0SJoel Fernandes * allocations between three different lists; one for left, middle and 5334a9671a0SJoel Fernandes * right. We can then free a list to simulate fragmentation. In 534ba110db8SJoel Fernandes * particular we want to exercise the GPU_BUDDY_CONTIGUOUS_ALLOCATION, 5354a9671a0SJoel Fernandes * including the try_harder path. 5364a9671a0SJoel Fernandes */ 5374a9671a0SJoel Fernandes 5384a9671a0SJoel Fernandes i = 0; 5394a9671a0SJoel Fernandes n_pages = mm_size / ps; 5404a9671a0SJoel Fernandes do { 5414a9671a0SJoel Fernandes struct list_head *list; 5424a9671a0SJoel Fernandes int slot = i % 3; 5434a9671a0SJoel Fernandes 5444a9671a0SJoel Fernandes if (slot == 0) 5454a9671a0SJoel Fernandes list = &left; 5464a9671a0SJoel Fernandes else if (slot == 1) 5474a9671a0SJoel Fernandes list = &middle; 5484a9671a0SJoel Fernandes else 5494a9671a0SJoel Fernandes list = &right; 5504a9671a0SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, 551ba110db8SJoel Fernandes gpu_buddy_alloc_blocks(&mm, 0, mm_size, 5524a9671a0SJoel Fernandes ps, ps, list, 0), 5534a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", 5544a9671a0SJoel Fernandes ps); 5554a9671a0SJoel Fernandes } while (++i < n_pages); 5564a9671a0SJoel Fernandes 557ba110db8SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 5584a9671a0SJoel Fernandes 3 * ps, ps, &allocated, 559ba110db8SJoel Fernandes GPU_BUDDY_CONTIGUOUS_ALLOCATION), 5604a9671a0SJoel Fernandes "buddy_alloc didn't error size=%lu\n", 3 * ps); 5614a9671a0SJoel Fernandes 562ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &middle, 0); 563ba110db8SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 5644a9671a0SJoel Fernandes 3 * ps, ps, &allocated, 565ba110db8SJoel Fernandes GPU_BUDDY_CONTIGUOUS_ALLOCATION), 5664a9671a0SJoel Fernandes "buddy_alloc didn't error size=%lu\n", 3 * ps); 567ba110db8SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 5684a9671a0SJoel Fernandes 2 * ps, ps, &allocated, 569ba110db8SJoel Fernandes GPU_BUDDY_CONTIGUOUS_ALLOCATION), 5704a9671a0SJoel Fernandes "buddy_alloc didn't error size=%lu\n", 2 * ps); 5714a9671a0SJoel Fernandes 572ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &right, 0); 573ba110db8SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 5744a9671a0SJoel Fernandes 3 * ps, ps, &allocated, 575ba110db8SJoel Fernandes GPU_BUDDY_CONTIGUOUS_ALLOCATION), 5764a9671a0SJoel Fernandes "buddy_alloc didn't error size=%lu\n", 3 * ps); 5774a9671a0SJoel Fernandes /* 5784a9671a0SJoel Fernandes * At this point we should have enough contiguous space for 2 blocks, 5794a9671a0SJoel Fernandes * however they are never buddies (since we freed middle and right) so 5804a9671a0SJoel Fernandes * will require the try_harder logic to find them. 5814a9671a0SJoel Fernandes */ 582ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 5834a9671a0SJoel Fernandes 2 * ps, ps, &allocated, 584ba110db8SJoel Fernandes GPU_BUDDY_CONTIGUOUS_ALLOCATION), 5854a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", 2 * ps); 5864a9671a0SJoel Fernandes 587ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &left, 0); 588ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, 5894a9671a0SJoel Fernandes 3 * ps, ps, &allocated, 590ba110db8SJoel Fernandes GPU_BUDDY_CONTIGUOUS_ALLOCATION), 5914a9671a0SJoel Fernandes "buddy_alloc hit an error size=%lu\n", 3 * ps); 5924a9671a0SJoel Fernandes 5934a9671a0SJoel Fernandes total = 0; 5944a9671a0SJoel Fernandes list_for_each_entry(block, &allocated, link) 595ba110db8SJoel Fernandes total += gpu_buddy_block_size(&mm, block); 5964a9671a0SJoel Fernandes 5974a9671a0SJoel Fernandes KUNIT_ASSERT_EQ(test, total, ps * 2 + ps * 3); 5984a9671a0SJoel Fernandes 599ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, 0); 600ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 6014a9671a0SJoel Fernandes } 6024a9671a0SJoel Fernandes 603ba110db8SJoel Fernandes static void gpu_test_buddy_alloc_pathological(struct kunit *test) 6044a9671a0SJoel Fernandes { 6054a9671a0SJoel Fernandes u64 mm_size, size, start = 0; 606ba110db8SJoel Fernandes struct gpu_buddy_block *block; 6074a9671a0SJoel Fernandes const int max_order = 3; 6084a9671a0SJoel Fernandes unsigned long flags = 0; 6094a9671a0SJoel Fernandes int order, top; 610ba110db8SJoel Fernandes struct gpu_buddy mm; 6114a9671a0SJoel Fernandes LIST_HEAD(blocks); 6124a9671a0SJoel Fernandes LIST_HEAD(holes); 6134a9671a0SJoel Fernandes LIST_HEAD(tmp); 6144a9671a0SJoel Fernandes 6154a9671a0SJoel Fernandes /* 6164a9671a0SJoel Fernandes * Create a pot-sized mm, then allocate one of each possible 6174a9671a0SJoel Fernandes * order within. This should leave the mm with exactly one 6184a9671a0SJoel Fernandes * page left. Free the largest block, then whittle down again. 6194a9671a0SJoel Fernandes * Eventually we will have a fully 50% fragmented mm. 6204a9671a0SJoel Fernandes */ 6214a9671a0SJoel Fernandes 6224a9671a0SJoel Fernandes mm_size = SZ_4K << max_order; 623ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K), 6244a9671a0SJoel Fernandes "buddy_init failed\n"); 6254a9671a0SJoel Fernandes 6264a9671a0SJoel Fernandes KUNIT_EXPECT_EQ(test, mm.max_order, max_order); 6274a9671a0SJoel Fernandes 6284a9671a0SJoel Fernandes for (top = max_order; top; top--) { 6294a9671a0SJoel Fernandes /* Make room by freeing the largest allocated block */ 6304a9671a0SJoel Fernandes block = list_first_entry_or_null(&blocks, typeof(*block), link); 6314a9671a0SJoel Fernandes if (block) { 6324a9671a0SJoel Fernandes list_del(&block->link); 633ba110db8SJoel Fernandes gpu_buddy_free_block(&mm, block); 6344a9671a0SJoel Fernandes } 6354a9671a0SJoel Fernandes 6364a9671a0SJoel Fernandes for (order = top; order--;) { 6374a9671a0SJoel Fernandes size = get_size(order, mm.chunk_size); 638ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, 6394a9671a0SJoel Fernandes mm_size, size, size, 6404a9671a0SJoel Fernandes &tmp, flags), 6414a9671a0SJoel Fernandes "buddy_alloc hit -ENOMEM with order=%d, top=%d\n", 6424a9671a0SJoel Fernandes order, top); 6434a9671a0SJoel Fernandes 644ba110db8SJoel Fernandes block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link); 6454a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n"); 6464a9671a0SJoel Fernandes 6474a9671a0SJoel Fernandes list_move_tail(&block->link, &blocks); 6484a9671a0SJoel Fernandes } 6494a9671a0SJoel Fernandes 6504a9671a0SJoel Fernandes /* There should be one final page for this sub-allocation */ 6514a9671a0SJoel Fernandes size = get_size(0, mm.chunk_size); 652ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 6534a9671a0SJoel Fernandes size, size, &tmp, flags), 6544a9671a0SJoel Fernandes "buddy_alloc hit -ENOMEM for hole\n"); 6554a9671a0SJoel Fernandes 656ba110db8SJoel Fernandes block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link); 6574a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n"); 6584a9671a0SJoel Fernandes 6594a9671a0SJoel Fernandes list_move_tail(&block->link, &holes); 6604a9671a0SJoel Fernandes 6614a9671a0SJoel Fernandes size = get_size(top, mm.chunk_size); 662ba110db8SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 6634a9671a0SJoel Fernandes size, size, &tmp, flags), 6644a9671a0SJoel Fernandes "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!", 6654a9671a0SJoel Fernandes top, max_order); 6664a9671a0SJoel Fernandes } 6674a9671a0SJoel Fernandes 668ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &holes, 0); 6694a9671a0SJoel Fernandes 6704a9671a0SJoel Fernandes /* Nothing larger than blocks of chunk_size now available */ 6714a9671a0SJoel Fernandes for (order = 1; order <= max_order; order++) { 6724a9671a0SJoel Fernandes size = get_size(order, mm.chunk_size); 673ba110db8SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 6744a9671a0SJoel Fernandes size, size, &tmp, flags), 6754a9671a0SJoel Fernandes "buddy_alloc unexpectedly succeeded at order %d, it should be full!", 6764a9671a0SJoel Fernandes order); 6774a9671a0SJoel Fernandes } 6784a9671a0SJoel Fernandes 6794a9671a0SJoel Fernandes list_splice_tail(&holes, &blocks); 680ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &blocks, 0); 681ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 6824a9671a0SJoel Fernandes } 6834a9671a0SJoel Fernandes 684ba110db8SJoel Fernandes static void gpu_test_buddy_alloc_pessimistic(struct kunit *test) 6854a9671a0SJoel Fernandes { 6864a9671a0SJoel Fernandes u64 mm_size, size, start = 0; 687ba110db8SJoel Fernandes struct gpu_buddy_block *block, *bn; 6884a9671a0SJoel Fernandes const unsigned int max_order = 16; 6894a9671a0SJoel Fernandes unsigned long flags = 0; 690ba110db8SJoel Fernandes struct gpu_buddy mm; 6914a9671a0SJoel Fernandes unsigned int order; 6924a9671a0SJoel Fernandes LIST_HEAD(blocks); 6934a9671a0SJoel Fernandes LIST_HEAD(tmp); 6944a9671a0SJoel Fernandes 6954a9671a0SJoel Fernandes /* 6964a9671a0SJoel Fernandes * Create a pot-sized mm, then allocate one of each possible 6974a9671a0SJoel Fernandes * order within. This should leave the mm with exactly one 6984a9671a0SJoel Fernandes * page left. 6994a9671a0SJoel Fernandes */ 7004a9671a0SJoel Fernandes 7014a9671a0SJoel Fernandes mm_size = SZ_4K << max_order; 702ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K), 7034a9671a0SJoel Fernandes "buddy_init failed\n"); 7044a9671a0SJoel Fernandes 7054a9671a0SJoel Fernandes KUNIT_EXPECT_EQ(test, mm.max_order, max_order); 7064a9671a0SJoel Fernandes 7074a9671a0SJoel Fernandes for (order = 0; order < max_order; order++) { 7084a9671a0SJoel Fernandes size = get_size(order, mm.chunk_size); 709ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 7104a9671a0SJoel Fernandes size, size, &tmp, flags), 7114a9671a0SJoel Fernandes "buddy_alloc hit -ENOMEM with order=%d\n", 7124a9671a0SJoel Fernandes order); 7134a9671a0SJoel Fernandes 714ba110db8SJoel Fernandes block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link); 7154a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n"); 7164a9671a0SJoel Fernandes 7174a9671a0SJoel Fernandes list_move_tail(&block->link, &blocks); 7184a9671a0SJoel Fernandes } 7194a9671a0SJoel Fernandes 7204a9671a0SJoel Fernandes /* And now the last remaining block available */ 7214a9671a0SJoel Fernandes size = get_size(0, mm.chunk_size); 722ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 7234a9671a0SJoel Fernandes size, size, &tmp, flags), 7244a9671a0SJoel Fernandes "buddy_alloc hit -ENOMEM on final alloc\n"); 7254a9671a0SJoel Fernandes 726ba110db8SJoel Fernandes block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link); 7274a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n"); 7284a9671a0SJoel Fernandes 7294a9671a0SJoel Fernandes list_move_tail(&block->link, &blocks); 7304a9671a0SJoel Fernandes 7314a9671a0SJoel Fernandes /* Should be completely full! */ 7324a9671a0SJoel Fernandes for (order = max_order; order--;) { 7334a9671a0SJoel Fernandes size = get_size(order, mm.chunk_size); 734ba110db8SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 7354a9671a0SJoel Fernandes size, size, &tmp, flags), 7364a9671a0SJoel Fernandes "buddy_alloc unexpectedly succeeded, it should be full!"); 7374a9671a0SJoel Fernandes } 7384a9671a0SJoel Fernandes 7394a9671a0SJoel Fernandes block = list_last_entry(&blocks, typeof(*block), link); 7404a9671a0SJoel Fernandes list_del(&block->link); 741ba110db8SJoel Fernandes gpu_buddy_free_block(&mm, block); 7424a9671a0SJoel Fernandes 7434a9671a0SJoel Fernandes /* As we free in increasing size, we make available larger blocks */ 7444a9671a0SJoel Fernandes order = 1; 7454a9671a0SJoel Fernandes list_for_each_entry_safe(block, bn, &blocks, link) { 7464a9671a0SJoel Fernandes list_del(&block->link); 747ba110db8SJoel Fernandes gpu_buddy_free_block(&mm, block); 7484a9671a0SJoel Fernandes 7494a9671a0SJoel Fernandes size = get_size(order, mm.chunk_size); 750ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 7514a9671a0SJoel Fernandes size, size, &tmp, flags), 7524a9671a0SJoel Fernandes "buddy_alloc hit -ENOMEM with order=%d\n", 7534a9671a0SJoel Fernandes order); 7544a9671a0SJoel Fernandes 755ba110db8SJoel Fernandes block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link); 7564a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n"); 7574a9671a0SJoel Fernandes 7584a9671a0SJoel Fernandes list_del(&block->link); 759ba110db8SJoel Fernandes gpu_buddy_free_block(&mm, block); 7604a9671a0SJoel Fernandes order++; 7614a9671a0SJoel Fernandes } 7624a9671a0SJoel Fernandes 7634a9671a0SJoel Fernandes /* To confirm, now the whole mm should be available */ 7644a9671a0SJoel Fernandes size = get_size(max_order, mm.chunk_size); 765ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 7664a9671a0SJoel Fernandes size, size, &tmp, flags), 7674a9671a0SJoel Fernandes "buddy_alloc (realloc) hit -ENOMEM with order=%d\n", 7684a9671a0SJoel Fernandes max_order); 7694a9671a0SJoel Fernandes 770ba110db8SJoel Fernandes block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link); 7714a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n"); 7724a9671a0SJoel Fernandes 7734a9671a0SJoel Fernandes list_del(&block->link); 774ba110db8SJoel Fernandes gpu_buddy_free_block(&mm, block); 775ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &blocks, 0); 776ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 7774a9671a0SJoel Fernandes } 7784a9671a0SJoel Fernandes 779ba110db8SJoel Fernandes static void gpu_test_buddy_alloc_optimistic(struct kunit *test) 7804a9671a0SJoel Fernandes { 7814a9671a0SJoel Fernandes u64 mm_size, size, start = 0; 782ba110db8SJoel Fernandes struct gpu_buddy_block *block; 7834a9671a0SJoel Fernandes unsigned long flags = 0; 7844a9671a0SJoel Fernandes const int max_order = 16; 785ba110db8SJoel Fernandes struct gpu_buddy mm; 7864a9671a0SJoel Fernandes LIST_HEAD(blocks); 7874a9671a0SJoel Fernandes LIST_HEAD(tmp); 7884a9671a0SJoel Fernandes int order; 7894a9671a0SJoel Fernandes 7904a9671a0SJoel Fernandes /* 7914a9671a0SJoel Fernandes * Create a mm with one block of each order available, and 7924a9671a0SJoel Fernandes * try to allocate them all. 7934a9671a0SJoel Fernandes */ 7944a9671a0SJoel Fernandes 7954a9671a0SJoel Fernandes mm_size = SZ_4K * ((1 << (max_order + 1)) - 1); 7964a9671a0SJoel Fernandes 797ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K), 7984a9671a0SJoel Fernandes "buddy_init failed\n"); 7994a9671a0SJoel Fernandes 8004a9671a0SJoel Fernandes KUNIT_EXPECT_EQ(test, mm.max_order, max_order); 8014a9671a0SJoel Fernandes 8024a9671a0SJoel Fernandes for (order = 0; order <= max_order; order++) { 8034a9671a0SJoel Fernandes size = get_size(order, mm.chunk_size); 804ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 8054a9671a0SJoel Fernandes size, size, &tmp, flags), 8064a9671a0SJoel Fernandes "buddy_alloc hit -ENOMEM with order=%d\n", 8074a9671a0SJoel Fernandes order); 8084a9671a0SJoel Fernandes 809ba110db8SJoel Fernandes block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link); 8104a9671a0SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n"); 8114a9671a0SJoel Fernandes 8124a9671a0SJoel Fernandes list_move_tail(&block->link, &blocks); 8134a9671a0SJoel Fernandes } 8144a9671a0SJoel Fernandes 8154a9671a0SJoel Fernandes /* Should be completely full! */ 8164a9671a0SJoel Fernandes size = get_size(0, mm.chunk_size); 817ba110db8SJoel Fernandes KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size, 8184a9671a0SJoel Fernandes size, size, &tmp, flags), 8194a9671a0SJoel Fernandes "buddy_alloc unexpectedly succeeded, it should be full!"); 8204a9671a0SJoel Fernandes 821ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &blocks, 0); 822ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 8234a9671a0SJoel Fernandes } 8244a9671a0SJoel Fernandes 825ba110db8SJoel Fernandes static void gpu_test_buddy_alloc_limit(struct kunit *test) 8264a9671a0SJoel Fernandes { 8274a9671a0SJoel Fernandes u64 size = U64_MAX, start = 0; 828ba110db8SJoel Fernandes struct gpu_buddy_block *block; 8294a9671a0SJoel Fernandes unsigned long flags = 0; 8304a9671a0SJoel Fernandes LIST_HEAD(allocated); 831ba110db8SJoel Fernandes struct gpu_buddy mm; 8324a9671a0SJoel Fernandes 833ba110db8SJoel Fernandes KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, size, SZ_4K)); 8344a9671a0SJoel Fernandes 835ba110db8SJoel Fernandes KUNIT_EXPECT_EQ_MSG(test, mm.max_order, GPU_BUDDY_MAX_ORDER, 8364a9671a0SJoel Fernandes "mm.max_order(%d) != %d\n", mm.max_order, 837ba110db8SJoel Fernandes GPU_BUDDY_MAX_ORDER); 8384a9671a0SJoel Fernandes 8394a9671a0SJoel Fernandes size = mm.chunk_size << mm.max_order; 840ba110db8SJoel Fernandes KUNIT_EXPECT_FALSE(test, gpu_buddy_alloc_blocks(&mm, start, size, size, 8414a9671a0SJoel Fernandes mm.chunk_size, &allocated, flags)); 8424a9671a0SJoel Fernandes 843ba110db8SJoel Fernandes block = list_first_entry_or_null(&allocated, struct gpu_buddy_block, link); 8444a9671a0SJoel Fernandes KUNIT_EXPECT_TRUE(test, block); 8454a9671a0SJoel Fernandes 846ba110db8SJoel Fernandes KUNIT_EXPECT_EQ_MSG(test, gpu_buddy_block_order(block), mm.max_order, 8474a9671a0SJoel Fernandes "block order(%d) != %d\n", 848ba110db8SJoel Fernandes gpu_buddy_block_order(block), mm.max_order); 8494a9671a0SJoel Fernandes 850ba110db8SJoel Fernandes KUNIT_EXPECT_EQ_MSG(test, gpu_buddy_block_size(&mm, block), 8514a9671a0SJoel Fernandes BIT_ULL(mm.max_order) * mm.chunk_size, 8524a9671a0SJoel Fernandes "block size(%llu) != %llu\n", 853ba110db8SJoel Fernandes gpu_buddy_block_size(&mm, block), 8544a9671a0SJoel Fernandes BIT_ULL(mm.max_order) * mm.chunk_size); 8554a9671a0SJoel Fernandes 856ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &allocated, 0); 857ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 8584a9671a0SJoel Fernandes } 8594a9671a0SJoel Fernandes 860ba110db8SJoel Fernandes static void gpu_test_buddy_alloc_exceeds_max_order(struct kunit *test) 8614a9671a0SJoel Fernandes { 8624a9671a0SJoel Fernandes u64 mm_size = SZ_8G + SZ_2G, size = SZ_8G + SZ_1G, min_block_size = SZ_8G; 863ba110db8SJoel Fernandes struct gpu_buddy mm; 8644a9671a0SJoel Fernandes LIST_HEAD(blocks); 8654a9671a0SJoel Fernandes int err; 8664a9671a0SJoel Fernandes 867ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K), 8684a9671a0SJoel Fernandes "buddy_init failed\n"); 8694a9671a0SJoel Fernandes 8704a9671a0SJoel Fernandes /* CONTIGUOUS allocation should succeed via try_harder fallback */ 871ba110db8SJoel Fernandes KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, size, 8724a9671a0SJoel Fernandes SZ_4K, &blocks, 873ba110db8SJoel Fernandes GPU_BUDDY_CONTIGUOUS_ALLOCATION), 8744a9671a0SJoel Fernandes "buddy_alloc hit an error size=%llu\n", size); 875ba110db8SJoel Fernandes gpu_buddy_free_list(&mm, &blocks, 0); 8764a9671a0SJoel Fernandes 8774a9671a0SJoel Fernandes /* Non-CONTIGUOUS with large min_block_size should return -EINVAL */ 878ba110db8SJoel Fernandes err = gpu_buddy_alloc_blocks(&mm, 0, mm_size, size, min_block_size, &blocks, 0); 8794a9671a0SJoel Fernandes KUNIT_EXPECT_EQ(test, err, -EINVAL); 8804a9671a0SJoel Fernandes 8814a9671a0SJoel Fernandes /* Non-CONTIGUOUS + RANGE with large min_block_size should return -EINVAL */ 882ba110db8SJoel Fernandes err = gpu_buddy_alloc_blocks(&mm, 0, mm_size, size, min_block_size, &blocks, 883ba110db8SJoel Fernandes GPU_BUDDY_RANGE_ALLOCATION); 8844a9671a0SJoel Fernandes KUNIT_EXPECT_EQ(test, err, -EINVAL); 8854a9671a0SJoel Fernandes 8864a9671a0SJoel Fernandes /* CONTIGUOUS + RANGE should return -EINVAL (no try_harder for RANGE) */ 887ba110db8SJoel Fernandes err = gpu_buddy_alloc_blocks(&mm, 0, mm_size, size, SZ_4K, &blocks, 888ba110db8SJoel Fernandes GPU_BUDDY_CONTIGUOUS_ALLOCATION | GPU_BUDDY_RANGE_ALLOCATION); 8894a9671a0SJoel Fernandes KUNIT_EXPECT_EQ(test, err, -EINVAL); 8904a9671a0SJoel Fernandes 891ba110db8SJoel Fernandes gpu_buddy_fini(&mm); 8924a9671a0SJoel Fernandes } 8934a9671a0SJoel Fernandes 894ba110db8SJoel Fernandes static int gpu_buddy_suite_init(struct kunit_suite *suite) 8954a9671a0SJoel Fernandes { 8964a9671a0SJoel Fernandes while (!random_seed) 8974a9671a0SJoel Fernandes random_seed = get_random_u32(); 8984a9671a0SJoel Fernandes 899ba110db8SJoel Fernandes kunit_info(suite, "Testing GPU buddy manager, with random_seed=0x%x\n", 9004a9671a0SJoel Fernandes random_seed); 9014a9671a0SJoel Fernandes 9024a9671a0SJoel Fernandes return 0; 9034a9671a0SJoel Fernandes } 9044a9671a0SJoel Fernandes 905ba110db8SJoel Fernandes static struct kunit_case gpu_buddy_tests[] = { 906ba110db8SJoel Fernandes KUNIT_CASE(gpu_test_buddy_alloc_limit), 907ba110db8SJoel Fernandes KUNIT_CASE(gpu_test_buddy_alloc_optimistic), 908ba110db8SJoel Fernandes KUNIT_CASE(gpu_test_buddy_alloc_pessimistic), 909ba110db8SJoel Fernandes KUNIT_CASE(gpu_test_buddy_alloc_pathological), 910ba110db8SJoel Fernandes KUNIT_CASE(gpu_test_buddy_alloc_contiguous), 911ba110db8SJoel Fernandes KUNIT_CASE(gpu_test_buddy_alloc_clear), 912ba110db8SJoel Fernandes KUNIT_CASE(gpu_test_buddy_alloc_range_bias), 913*5ea5b6ffSMaxime Ripard KUNIT_CASE_SLOW(gpu_test_buddy_fragmentation_performance), 914ba110db8SJoel Fernandes KUNIT_CASE(gpu_test_buddy_alloc_exceeds_max_order), 9154a9671a0SJoel Fernandes {} 9164a9671a0SJoel Fernandes }; 9174a9671a0SJoel Fernandes 918ba110db8SJoel Fernandes static struct kunit_suite gpu_buddy_test_suite = { 919ba110db8SJoel Fernandes .name = "gpu_buddy", 920ba110db8SJoel Fernandes .suite_init = gpu_buddy_suite_init, 921ba110db8SJoel Fernandes .test_cases = gpu_buddy_tests, 9224a9671a0SJoel Fernandes }; 9234a9671a0SJoel Fernandes 924ba110db8SJoel Fernandes kunit_test_suite(gpu_buddy_test_suite); 9254a9671a0SJoel Fernandes 9264a9671a0SJoel Fernandes MODULE_AUTHOR("Intel Corporation"); 927ba110db8SJoel Fernandes MODULE_DESCRIPTION("Kunit test for gpu_buddy functions"); 9284a9671a0SJoel Fernandes MODULE_LICENSE("GPL"); 929