1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Test cases for the drm_mm range manager 4 * 5 * Copyright (c) 2022 Arthur Grillo <arthur.grillo@usp.br> 6 */ 7 8 #include <kunit/test.h> 9 10 #include <linux/prime_numbers.h> 11 #include <linux/slab.h> 12 #include <linux/random.h> 13 #include <linux/vmalloc.h> 14 #include <linux/ktime.h> 15 16 #include <drm/drm_mm.h> 17 #include <drm/drm_print.h> 18 19 #include "../lib/drm_random.h" 20 21 enum { 22 BEST, 23 BOTTOMUP, 24 TOPDOWN, 25 EVICT, 26 }; 27 28 static const struct insert_mode { 29 const char *name; 30 enum drm_mm_insert_mode mode; 31 } insert_modes[] = { 32 [BEST] = { "best", DRM_MM_INSERT_BEST }, 33 [BOTTOMUP] = { "bottom-up", DRM_MM_INSERT_LOW }, 34 [TOPDOWN] = { "top-down", DRM_MM_INSERT_HIGH }, 35 [EVICT] = { "evict", DRM_MM_INSERT_EVICT }, 36 {} 37 }; 38 39 static bool assert_no_holes(struct kunit *test, const struct drm_mm *mm) 40 { 41 struct drm_mm_node *hole; 42 u64 hole_start, __always_unused hole_end; 43 unsigned long count; 44 45 count = 0; 46 drm_mm_for_each_hole(hole, mm, hole_start, hole_end) 47 count++; 48 if (count) { 49 KUNIT_FAIL(test, 50 "Expected to find no holes (after reserve), found %lu instead\n", count); 51 return false; 52 } 53 54 drm_mm_for_each_node(hole, mm) { 55 if (drm_mm_hole_follows(hole)) { 56 KUNIT_FAIL(test, "Hole follows node, expected none!\n"); 57 return false; 58 } 59 } 60 61 return true; 62 } 63 64 static bool assert_one_hole(struct kunit *test, const struct drm_mm *mm, u64 start, u64 end) 65 { 66 struct drm_mm_node *hole; 67 u64 hole_start, hole_end; 68 unsigned long count; 69 bool ok = true; 70 71 if (end <= start) 72 return true; 73 74 count = 0; 75 drm_mm_for_each_hole(hole, mm, hole_start, hole_end) { 76 if (start != hole_start || end != hole_end) { 77 if (ok) 78 KUNIT_FAIL(test, 79 "empty mm has incorrect hole, found (%llx, %llx), expect (%llx, %llx)\n", 80 hole_start, hole_end, start, end); 81 ok = false; 82 } 83 count++; 84 } 85 if (count != 1) { 86 KUNIT_FAIL(test, "Expected to find one hole, found %lu instead\n", count); 87 ok = false; 88 } 89 90 return ok; 91 } 92 93 static u64 misalignment(struct drm_mm_node *node, u64 alignment) 94 { 95 u64 rem; 96 97 if (!alignment) 98 return 0; 99 100 div64_u64_rem(node->start, alignment, &rem); 101 return rem; 102 } 103 104 static bool assert_node(struct kunit *test, struct drm_mm_node *node, struct drm_mm *mm, 105 u64 size, u64 alignment, unsigned long color) 106 { 107 bool ok = true; 108 109 if (!drm_mm_node_allocated(node) || node->mm != mm) { 110 KUNIT_FAIL(test, "node not allocated\n"); 111 ok = false; 112 } 113 114 if (node->size != size) { 115 KUNIT_FAIL(test, "node has wrong size, found %llu, expected %llu\n", 116 node->size, size); 117 ok = false; 118 } 119 120 if (misalignment(node, alignment)) { 121 KUNIT_FAIL(test, 122 "node is misaligned, start %llx rem %llu, expected alignment %llu\n", 123 node->start, misalignment(node, alignment), alignment); 124 ok = false; 125 } 126 127 if (node->color != color) { 128 KUNIT_FAIL(test, "node has wrong color, found %lu, expected %lu\n", 129 node->color, color); 130 ok = false; 131 } 132 133 return ok; 134 } 135 136 static void drm_test_mm_init(struct kunit *test) 137 { 138 const unsigned int size = 4096; 139 struct drm_mm mm; 140 struct drm_mm_node tmp; 141 142 /* Start with some simple checks on initialising the struct drm_mm */ 143 memset(&mm, 0, sizeof(mm)); 144 KUNIT_ASSERT_FALSE_MSG(test, drm_mm_initialized(&mm), 145 "zeroed mm claims to be initialized\n"); 146 147 memset(&mm, 0xff, sizeof(mm)); 148 drm_mm_init(&mm, 0, size); 149 if (!drm_mm_initialized(&mm)) { 150 KUNIT_FAIL(test, "mm claims not to be initialized\n"); 151 goto out; 152 } 153 154 if (!drm_mm_clean(&mm)) { 155 KUNIT_FAIL(test, "mm not empty on creation\n"); 156 goto out; 157 } 158 159 /* After creation, it should all be one massive hole */ 160 if (!assert_one_hole(test, &mm, 0, size)) { 161 KUNIT_FAIL(test, "mm not one hole on creation"); 162 goto out; 163 } 164 165 memset(&tmp, 0, sizeof(tmp)); 166 tmp.start = 0; 167 tmp.size = size; 168 if (drm_mm_reserve_node(&mm, &tmp)) { 169 KUNIT_FAIL(test, "failed to reserve whole drm_mm\n"); 170 goto out; 171 } 172 173 /* After filling the range entirely, there should be no holes */ 174 if (!assert_no_holes(test, &mm)) { 175 KUNIT_FAIL(test, "mm has holes when filled"); 176 goto out; 177 } 178 179 /* And then after emptying it again, the massive hole should be back */ 180 drm_mm_remove_node(&tmp); 181 if (!assert_one_hole(test, &mm, 0, size)) { 182 KUNIT_FAIL(test, "mm does not have single hole after emptying"); 183 goto out; 184 } 185 186 out: 187 drm_mm_takedown(&mm); 188 } 189 190 static void drm_test_mm_debug(struct kunit *test) 191 { 192 struct drm_printer p = drm_dbg_printer(NULL, DRM_UT_CORE, test->name); 193 struct drm_mm mm; 194 struct drm_mm_node nodes[2]; 195 196 /* Create a small drm_mm with a couple of nodes and a few holes, and 197 * check that the debug iterator doesn't explode over a trivial drm_mm. 198 */ 199 drm_mm_init(&mm, 0, 4096); 200 201 memset(nodes, 0, sizeof(nodes)); 202 nodes[0].start = 512; 203 nodes[0].size = 1024; 204 KUNIT_ASSERT_FALSE_MSG(test, drm_mm_reserve_node(&mm, &nodes[0]), 205 "failed to reserve node[0] {start=%lld, size=%lld)\n", 206 nodes[0].start, nodes[0].size); 207 208 nodes[1].size = 1024; 209 nodes[1].start = 4096 - 512 - nodes[1].size; 210 KUNIT_ASSERT_FALSE_MSG(test, drm_mm_reserve_node(&mm, &nodes[1]), 211 "failed to reserve node[0] {start=%lld, size=%lld)\n", 212 nodes[0].start, nodes[0].size); 213 214 drm_mm_print(&mm, &p); 215 KUNIT_SUCCEED(test); 216 } 217 218 static bool expect_insert(struct kunit *test, struct drm_mm *mm, 219 struct drm_mm_node *node, u64 size, u64 alignment, unsigned long color, 220 const struct insert_mode *mode) 221 { 222 int err; 223 224 err = drm_mm_insert_node_generic(mm, node, 225 size, alignment, color, 226 mode->mode); 227 if (err) { 228 KUNIT_FAIL(test, 229 "insert (size=%llu, alignment=%llu, color=%lu, mode=%s) failed with err=%d\n", 230 size, alignment, color, mode->name, err); 231 return false; 232 } 233 234 if (!assert_node(test, node, mm, size, alignment, color)) { 235 drm_mm_remove_node(node); 236 return false; 237 } 238 239 return true; 240 } 241 242 static void drm_test_mm_align_pot(struct kunit *test, int max) 243 { 244 struct drm_mm mm; 245 struct drm_mm_node *node, *next; 246 int bit; 247 248 /* Check that we can align to the full u64 address space */ 249 250 drm_mm_init(&mm, 1, U64_MAX - 2); 251 252 for (bit = max - 1; bit; bit--) { 253 u64 align, size; 254 255 node = kzalloc(sizeof(*node), GFP_KERNEL); 256 if (!node) { 257 KUNIT_FAIL(test, "failed to allocate node"); 258 goto out; 259 } 260 261 align = BIT_ULL(bit); 262 size = BIT_ULL(bit - 1) + 1; 263 if (!expect_insert(test, &mm, node, size, align, bit, &insert_modes[0])) { 264 KUNIT_FAIL(test, "insert failed with alignment=%llx [%d]", align, bit); 265 goto out; 266 } 267 268 cond_resched(); 269 } 270 271 out: 272 drm_mm_for_each_node_safe(node, next, &mm) { 273 drm_mm_remove_node(node); 274 kfree(node); 275 } 276 drm_mm_takedown(&mm); 277 } 278 279 static void drm_test_mm_align32(struct kunit *test) 280 { 281 drm_test_mm_align_pot(test, 32); 282 } 283 284 static void drm_test_mm_align64(struct kunit *test) 285 { 286 drm_test_mm_align_pot(test, 64); 287 } 288 289 static void drm_test_mm_once(struct kunit *test, unsigned int mode) 290 { 291 struct drm_mm mm; 292 struct drm_mm_node rsvd_lo, rsvd_hi, node; 293 294 drm_mm_init(&mm, 0, 7); 295 296 memset(&rsvd_lo, 0, sizeof(rsvd_lo)); 297 rsvd_lo.start = 1; 298 rsvd_lo.size = 1; 299 if (drm_mm_reserve_node(&mm, &rsvd_lo)) { 300 KUNIT_FAIL(test, "Could not reserve low node\n"); 301 goto err; 302 } 303 304 memset(&rsvd_hi, 0, sizeof(rsvd_hi)); 305 rsvd_hi.start = 5; 306 rsvd_hi.size = 1; 307 if (drm_mm_reserve_node(&mm, &rsvd_hi)) { 308 KUNIT_FAIL(test, "Could not reserve low node\n"); 309 goto err_lo; 310 } 311 312 if (!drm_mm_hole_follows(&rsvd_lo) || !drm_mm_hole_follows(&rsvd_hi)) { 313 KUNIT_FAIL(test, "Expected a hole after lo and high nodes!\n"); 314 goto err_hi; 315 } 316 317 memset(&node, 0, sizeof(node)); 318 if (drm_mm_insert_node_generic(&mm, &node, 2, 0, 0, mode)) { 319 KUNIT_FAIL(test, "Could not insert the node into the available hole!\n"); 320 goto err_hi; 321 } 322 323 drm_mm_remove_node(&node); 324 err_hi: 325 drm_mm_remove_node(&rsvd_hi); 326 err_lo: 327 drm_mm_remove_node(&rsvd_lo); 328 err: 329 drm_mm_takedown(&mm); 330 } 331 332 static void drm_test_mm_lowest(struct kunit *test) 333 { 334 drm_test_mm_once(test, DRM_MM_INSERT_LOW); 335 } 336 337 static void drm_test_mm_highest(struct kunit *test) 338 { 339 drm_test_mm_once(test, DRM_MM_INSERT_HIGH); 340 } 341 342 static struct kunit_case drm_mm_tests[] = { 343 KUNIT_CASE(drm_test_mm_init), 344 KUNIT_CASE(drm_test_mm_debug), 345 KUNIT_CASE(drm_test_mm_align32), 346 KUNIT_CASE(drm_test_mm_align64), 347 KUNIT_CASE(drm_test_mm_lowest), 348 KUNIT_CASE(drm_test_mm_highest), 349 {} 350 }; 351 352 static struct kunit_suite drm_mm_test_suite = { 353 .name = "drm_mm", 354 .test_cases = drm_mm_tests, 355 }; 356 357 kunit_test_suite(drm_mm_test_suite); 358 359 MODULE_AUTHOR("Intel Corporation"); 360 MODULE_DESCRIPTION("Test cases for the drm_mm range manager"); 361 MODULE_LICENSE("GPL"); 362