drm_buddy.c (17205B)
1// SPDX-License-Identifier: MIT 2/* 3 * Copyright © 2021 Intel Corporation 4 */ 5 6#include <linux/kmemleak.h> 7#include <linux/module.h> 8#include <linux/sizes.h> 9 10#include <drm/drm_buddy.h> 11 12static struct kmem_cache *slab_blocks; 13 14static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm, 15 struct drm_buddy_block *parent, 16 unsigned int order, 17 u64 offset) 18{ 19 struct drm_buddy_block *block; 20 21 BUG_ON(order > DRM_BUDDY_MAX_ORDER); 22 23 block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL); 24 if (!block) 25 return NULL; 26 27 block->header = offset; 28 block->header |= order; 29 block->parent = parent; 30 31 BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED); 32 return block; 33} 34 35static void drm_block_free(struct drm_buddy *mm, 36 struct drm_buddy_block *block) 37{ 38 kmem_cache_free(slab_blocks, block); 39} 40 41static void mark_allocated(struct drm_buddy_block *block) 42{ 43 block->header &= ~DRM_BUDDY_HEADER_STATE; 44 block->header |= DRM_BUDDY_ALLOCATED; 45 46 list_del(&block->link); 47} 48 49static void mark_free(struct drm_buddy *mm, 50 struct drm_buddy_block *block) 51{ 52 block->header &= ~DRM_BUDDY_HEADER_STATE; 53 block->header |= DRM_BUDDY_FREE; 54 55 list_add(&block->link, 56 &mm->free_list[drm_buddy_block_order(block)]); 57} 58 59static void mark_split(struct drm_buddy_block *block) 60{ 61 block->header &= ~DRM_BUDDY_HEADER_STATE; 62 block->header |= DRM_BUDDY_SPLIT; 63 64 list_del(&block->link); 65} 66 67/** 68 * drm_buddy_init - init memory manager 69 * 70 * @mm: DRM buddy manager to initialize 71 * @size: size in bytes to manage 72 * @chunk_size: minimum page size in bytes for our allocations 73 * 74 * Initializes the memory manager and its resources. 75 * 76 * Returns: 77 * 0 on success, error code on failure. 78 */ 79int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) 80{ 81 unsigned int i; 82 u64 offset; 83 84 if (size < chunk_size) 85 return -EINVAL; 86 87 if (chunk_size < PAGE_SIZE) 88 return -EINVAL; 89 90 if (!is_power_of_2(chunk_size)) 91 return -EINVAL; 92 93 size = round_down(size, chunk_size); 94 95 mm->size = size; 96 mm->avail = size; 97 mm->chunk_size = chunk_size; 98 mm->max_order = ilog2(size) - ilog2(chunk_size); 99 100 BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER); 101 102 mm->free_list = kmalloc_array(mm->max_order + 1, 103 sizeof(struct list_head), 104 GFP_KERNEL); 105 if (!mm->free_list) 106 return -ENOMEM; 107 108 for (i = 0; i <= mm->max_order; ++i) 109 INIT_LIST_HEAD(&mm->free_list[i]); 110 111 mm->n_roots = hweight64(size); 112 113 mm->roots = kmalloc_array(mm->n_roots, 114 sizeof(struct drm_buddy_block *), 115 GFP_KERNEL); 116 if (!mm->roots) 117 goto out_free_list; 118 119 offset = 0; 120 i = 0; 121 122 /* 123 * Split into power-of-two blocks, in case we are given a size that is 124 * not itself a power-of-two. 125 */ 126 do { 127 struct drm_buddy_block *root; 128 unsigned int order; 129 u64 root_size; 130 131 root_size = rounddown_pow_of_two(size); 132 order = ilog2(root_size) - ilog2(chunk_size); 133 134 root = drm_block_alloc(mm, NULL, order, offset); 135 if (!root) 136 goto out_free_roots; 137 138 mark_free(mm, root); 139 140 BUG_ON(i > mm->max_order); 141 BUG_ON(drm_buddy_block_size(mm, root) < chunk_size); 142 143 mm->roots[i] = root; 144 145 offset += root_size; 146 size -= root_size; 147 i++; 148 } while (size); 149 150 return 0; 151 152out_free_roots: 153 while (i--) 154 drm_block_free(mm, mm->roots[i]); 155 kfree(mm->roots); 156out_free_list: 157 kfree(mm->free_list); 158 return -ENOMEM; 159} 160EXPORT_SYMBOL(drm_buddy_init); 161 162/** 163 * drm_buddy_fini - tear down the memory manager 164 * 165 * @mm: DRM buddy manager to free 166 * 167 * Cleanup memory manager resources and the freelist 168 */ 169void drm_buddy_fini(struct drm_buddy *mm) 170{ 171 int i; 172 173 for (i = 0; i < mm->n_roots; ++i) { 174 WARN_ON(!drm_buddy_block_is_free(mm->roots[i])); 175 drm_block_free(mm, mm->roots[i]); 176 } 177 178 WARN_ON(mm->avail != mm->size); 179 180 kfree(mm->roots); 181 kfree(mm->free_list); 182} 183EXPORT_SYMBOL(drm_buddy_fini); 184 185static int split_block(struct drm_buddy *mm, 186 struct drm_buddy_block *block) 187{ 188 unsigned int block_order = drm_buddy_block_order(block) - 1; 189 u64 offset = drm_buddy_block_offset(block); 190 191 BUG_ON(!drm_buddy_block_is_free(block)); 192 BUG_ON(!drm_buddy_block_order(block)); 193 194 block->left = drm_block_alloc(mm, block, block_order, offset); 195 if (!block->left) 196 return -ENOMEM; 197 198 block->right = drm_block_alloc(mm, block, block_order, 199 offset + (mm->chunk_size << block_order)); 200 if (!block->right) { 201 drm_block_free(mm, block->left); 202 return -ENOMEM; 203 } 204 205 mark_free(mm, block->left); 206 mark_free(mm, block->right); 207 208 mark_split(block); 209 210 return 0; 211} 212 213static struct drm_buddy_block * 214__get_buddy(struct drm_buddy_block *block) 215{ 216 struct drm_buddy_block *parent; 217 218 parent = block->parent; 219 if (!parent) 220 return NULL; 221 222 if (parent->left == block) 223 return parent->right; 224 225 return parent->left; 226} 227 228/** 229 * drm_get_buddy - get buddy address 230 * 231 * @block: DRM buddy block 232 * 233 * Returns the corresponding buddy block for @block, or NULL 234 * if this is a root block and can't be merged further. 235 * Requires some kind of locking to protect against 236 * any concurrent allocate and free operations. 237 */ 238struct drm_buddy_block * 239drm_get_buddy(struct drm_buddy_block *block) 240{ 241 return __get_buddy(block); 242} 243EXPORT_SYMBOL(drm_get_buddy); 244 245static void __drm_buddy_free(struct drm_buddy *mm, 246 struct drm_buddy_block *block) 247{ 248 struct drm_buddy_block *parent; 249 250 while ((parent = block->parent)) { 251 struct drm_buddy_block *buddy; 252 253 buddy = __get_buddy(block); 254 255 if (!drm_buddy_block_is_free(buddy)) 256 break; 257 258 list_del(&buddy->link); 259 260 drm_block_free(mm, block); 261 drm_block_free(mm, buddy); 262 263 block = parent; 264 } 265 266 mark_free(mm, block); 267} 268 269/** 270 * drm_buddy_free_block - free a block 271 * 272 * @mm: DRM buddy manager 273 * @block: block to be freed 274 */ 275void drm_buddy_free_block(struct drm_buddy *mm, 276 struct drm_buddy_block *block) 277{ 278 BUG_ON(!drm_buddy_block_is_allocated(block)); 279 mm->avail += drm_buddy_block_size(mm, block); 280 __drm_buddy_free(mm, block); 281} 282EXPORT_SYMBOL(drm_buddy_free_block); 283 284/** 285 * drm_buddy_free_list - free blocks 286 * 287 * @mm: DRM buddy manager 288 * @objects: input list head to free blocks 289 */ 290void drm_buddy_free_list(struct drm_buddy *mm, struct list_head *objects) 291{ 292 struct drm_buddy_block *block, *on; 293 294 list_for_each_entry_safe(block, on, objects, link) { 295 drm_buddy_free_block(mm, block); 296 cond_resched(); 297 } 298 INIT_LIST_HEAD(objects); 299} 300EXPORT_SYMBOL(drm_buddy_free_list); 301 302static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2) 303{ 304 return s1 <= e2 && e1 >= s2; 305} 306 307static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2) 308{ 309 return s1 <= s2 && e1 >= e2; 310} 311 312static struct drm_buddy_block * 313alloc_range_bias(struct drm_buddy *mm, 314 u64 start, u64 end, 315 unsigned int order) 316{ 317 struct drm_buddy_block *block; 318 struct drm_buddy_block *buddy; 319 LIST_HEAD(dfs); 320 int err; 321 int i; 322 323 end = end - 1; 324 325 for (i = 0; i < mm->n_roots; ++i) 326 list_add_tail(&mm->roots[i]->tmp_link, &dfs); 327 328 do { 329 u64 block_start; 330 u64 block_end; 331 332 block = list_first_entry_or_null(&dfs, 333 struct drm_buddy_block, 334 tmp_link); 335 if (!block) 336 break; 337 338 list_del(&block->tmp_link); 339 340 if (drm_buddy_block_order(block) < order) 341 continue; 342 343 block_start = drm_buddy_block_offset(block); 344 block_end = block_start + drm_buddy_block_size(mm, block) - 1; 345 346 if (!overlaps(start, end, block_start, block_end)) 347 continue; 348 349 if (drm_buddy_block_is_allocated(block)) 350 continue; 351 352 if (contains(start, end, block_start, block_end) && 353 order == drm_buddy_block_order(block)) { 354 /* 355 * Find the free block within the range. 356 */ 357 if (drm_buddy_block_is_free(block)) 358 return block; 359 360 continue; 361 } 362 363 if (!drm_buddy_block_is_split(block)) { 364 err = split_block(mm, block); 365 if (unlikely(err)) 366 goto err_undo; 367 } 368 369 list_add(&block->right->tmp_link, &dfs); 370 list_add(&block->left->tmp_link, &dfs); 371 } while (1); 372 373 return ERR_PTR(-ENOSPC); 374 375err_undo: 376 /* 377 * We really don't want to leave around a bunch of split blocks, since 378 * bigger is better, so make sure we merge everything back before we 379 * free the allocated blocks. 380 */ 381 buddy = __get_buddy(block); 382 if (buddy && 383 (drm_buddy_block_is_free(block) && 384 drm_buddy_block_is_free(buddy))) 385 __drm_buddy_free(mm, block); 386 return ERR_PTR(err); 387} 388 389static struct drm_buddy_block * 390get_maxblock(struct list_head *head) 391{ 392 struct drm_buddy_block *max_block = NULL, *node; 393 394 max_block = list_first_entry_or_null(head, 395 struct drm_buddy_block, 396 link); 397 if (!max_block) 398 return NULL; 399 400 list_for_each_entry(node, head, link) { 401 if (drm_buddy_block_offset(node) > 402 drm_buddy_block_offset(max_block)) 403 max_block = node; 404 } 405 406 return max_block; 407} 408 409static struct drm_buddy_block * 410alloc_from_freelist(struct drm_buddy *mm, 411 unsigned int order, 412 unsigned long flags) 413{ 414 struct drm_buddy_block *block = NULL; 415 unsigned int i; 416 int err; 417 418 for (i = order; i <= mm->max_order; ++i) { 419 if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) { 420 block = get_maxblock(&mm->free_list[i]); 421 if (block) 422 break; 423 } else { 424 block = list_first_entry_or_null(&mm->free_list[i], 425 struct drm_buddy_block, 426 link); 427 if (block) 428 break; 429 } 430 } 431 432 if (!block) 433 return ERR_PTR(-ENOSPC); 434 435 BUG_ON(!drm_buddy_block_is_free(block)); 436 437 while (i != order) { 438 err = split_block(mm, block); 439 if (unlikely(err)) 440 goto err_undo; 441 442 block = block->right; 443 i--; 444 } 445 return block; 446 447err_undo: 448 if (i != order) 449 __drm_buddy_free(mm, block); 450 return ERR_PTR(err); 451} 452 453static int __alloc_range(struct drm_buddy *mm, 454 struct list_head *dfs, 455 u64 start, u64 size, 456 struct list_head *blocks) 457{ 458 struct drm_buddy_block *block; 459 struct drm_buddy_block *buddy; 460 LIST_HEAD(allocated); 461 u64 end; 462 int err; 463 464 end = start + size - 1; 465 466 do { 467 u64 block_start; 468 u64 block_end; 469 470 block = list_first_entry_or_null(dfs, 471 struct drm_buddy_block, 472 tmp_link); 473 if (!block) 474 break; 475 476 list_del(&block->tmp_link); 477 478 block_start = drm_buddy_block_offset(block); 479 block_end = block_start + drm_buddy_block_size(mm, block) - 1; 480 481 if (!overlaps(start, end, block_start, block_end)) 482 continue; 483 484 if (drm_buddy_block_is_allocated(block)) { 485 err = -ENOSPC; 486 goto err_free; 487 } 488 489 if (contains(start, end, block_start, block_end)) { 490 if (!drm_buddy_block_is_free(block)) { 491 err = -ENOSPC; 492 goto err_free; 493 } 494 495 mark_allocated(block); 496 mm->avail -= drm_buddy_block_size(mm, block); 497 list_add_tail(&block->link, &allocated); 498 continue; 499 } 500 501 if (!drm_buddy_block_is_split(block)) { 502 err = split_block(mm, block); 503 if (unlikely(err)) 504 goto err_undo; 505 } 506 507 list_add(&block->right->tmp_link, dfs); 508 list_add(&block->left->tmp_link, dfs); 509 } while (1); 510 511 list_splice_tail(&allocated, blocks); 512 return 0; 513 514err_undo: 515 /* 516 * We really don't want to leave around a bunch of split blocks, since 517 * bigger is better, so make sure we merge everything back before we 518 * free the allocated blocks. 519 */ 520 buddy = __get_buddy(block); 521 if (buddy && 522 (drm_buddy_block_is_free(block) && 523 drm_buddy_block_is_free(buddy))) 524 __drm_buddy_free(mm, block); 525 526err_free: 527 drm_buddy_free_list(mm, &allocated); 528 return err; 529} 530 531static int __drm_buddy_alloc_range(struct drm_buddy *mm, 532 u64 start, 533 u64 size, 534 struct list_head *blocks) 535{ 536 LIST_HEAD(dfs); 537 int i; 538 539 for (i = 0; i < mm->n_roots; ++i) 540 list_add_tail(&mm->roots[i]->tmp_link, &dfs); 541 542 return __alloc_range(mm, &dfs, start, size, blocks); 543} 544 545/** 546 * drm_buddy_block_trim - free unused pages 547 * 548 * @mm: DRM buddy manager 549 * @new_size: original size requested 550 * @blocks: Input and output list of allocated blocks. 551 * MUST contain single block as input to be trimmed. 552 * On success will contain the newly allocated blocks 553 * making up the @new_size. Blocks always appear in 554 * ascending order 555 * 556 * For contiguous allocation, we round up the size to the nearest 557 * power of two value, drivers consume *actual* size, so remaining 558 * portions are unused and can be optionally freed with this function 559 * 560 * Returns: 561 * 0 on success, error code on failure. 562 */ 563int drm_buddy_block_trim(struct drm_buddy *mm, 564 u64 new_size, 565 struct list_head *blocks) 566{ 567 struct drm_buddy_block *parent; 568 struct drm_buddy_block *block; 569 LIST_HEAD(dfs); 570 u64 new_start; 571 int err; 572 573 if (!list_is_singular(blocks)) 574 return -EINVAL; 575 576 block = list_first_entry(blocks, 577 struct drm_buddy_block, 578 link); 579 580 if (WARN_ON(!drm_buddy_block_is_allocated(block))) 581 return -EINVAL; 582 583 if (new_size > drm_buddy_block_size(mm, block)) 584 return -EINVAL; 585 586 if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size)) 587 return -EINVAL; 588 589 if (new_size == drm_buddy_block_size(mm, block)) 590 return 0; 591 592 list_del(&block->link); 593 mark_free(mm, block); 594 mm->avail += drm_buddy_block_size(mm, block); 595 596 /* Prevent recursively freeing this node */ 597 parent = block->parent; 598 block->parent = NULL; 599 600 new_start = drm_buddy_block_offset(block); 601 list_add(&block->tmp_link, &dfs); 602 err = __alloc_range(mm, &dfs, new_start, new_size, blocks); 603 if (err) { 604 mark_allocated(block); 605 mm->avail -= drm_buddy_block_size(mm, block); 606 list_add(&block->link, blocks); 607 } 608 609 block->parent = parent; 610 return err; 611} 612EXPORT_SYMBOL(drm_buddy_block_trim); 613 614/** 615 * drm_buddy_alloc_blocks - allocate power-of-two blocks 616 * 617 * @mm: DRM buddy manager to allocate from 618 * @start: start of the allowed range for this block 619 * @end: end of the allowed range for this block 620 * @size: size of the allocation 621 * @min_page_size: alignment of the allocation 622 * @blocks: output list head to add allocated blocks 623 * @flags: DRM_BUDDY_*_ALLOCATION flags 624 * 625 * alloc_range_bias() called on range limitations, which traverses 626 * the tree and returns the desired block. 627 * 628 * alloc_from_freelist() called when *no* range restrictions 629 * are enforced, which picks the block from the freelist. 630 * 631 * Returns: 632 * 0 on success, error code on failure. 633 */ 634int drm_buddy_alloc_blocks(struct drm_buddy *mm, 635 u64 start, u64 end, u64 size, 636 u64 min_page_size, 637 struct list_head *blocks, 638 unsigned long flags) 639{ 640 struct drm_buddy_block *block = NULL; 641 unsigned int min_order, order; 642 unsigned long pages; 643 LIST_HEAD(allocated); 644 int err; 645 646 if (size < mm->chunk_size) 647 return -EINVAL; 648 649 if (min_page_size < mm->chunk_size) 650 return -EINVAL; 651 652 if (!is_power_of_2(min_page_size)) 653 return -EINVAL; 654 655 if (!IS_ALIGNED(start | end | size, mm->chunk_size)) 656 return -EINVAL; 657 658 if (end > mm->size) 659 return -EINVAL; 660 661 if (range_overflows(start, size, mm->size)) 662 return -EINVAL; 663 664 /* Actual range allocation */ 665 if (start + size == end) 666 return __drm_buddy_alloc_range(mm, start, size, blocks); 667 668 if (!IS_ALIGNED(size, min_page_size)) 669 return -EINVAL; 670 671 pages = size >> ilog2(mm->chunk_size); 672 order = fls(pages) - 1; 673 min_order = ilog2(min_page_size) - ilog2(mm->chunk_size); 674 675 do { 676 order = min(order, (unsigned int)fls(pages) - 1); 677 BUG_ON(order > mm->max_order); 678 BUG_ON(order < min_order); 679 680 do { 681 if (flags & DRM_BUDDY_RANGE_ALLOCATION) 682 /* Allocate traversing within the range */ 683 block = alloc_range_bias(mm, start, end, order); 684 else 685 /* Allocate from freelist */ 686 block = alloc_from_freelist(mm, order, flags); 687 688 if (!IS_ERR(block)) 689 break; 690 691 if (order-- == min_order) { 692 err = -ENOSPC; 693 goto err_free; 694 } 695 } while (1); 696 697 mark_allocated(block); 698 mm->avail -= drm_buddy_block_size(mm, block); 699 kmemleak_update_trace(block); 700 list_add_tail(&block->link, &allocated); 701 702 pages -= BIT(order); 703 704 if (!pages) 705 break; 706 } while (1); 707 708 list_splice_tail(&allocated, blocks); 709 return 0; 710 711err_free: 712 drm_buddy_free_list(mm, &allocated); 713 return err; 714} 715EXPORT_SYMBOL(drm_buddy_alloc_blocks); 716 717/** 718 * drm_buddy_block_print - print block information 719 * 720 * @mm: DRM buddy manager 721 * @block: DRM buddy block 722 * @p: DRM printer to use 723 */ 724void drm_buddy_block_print(struct drm_buddy *mm, 725 struct drm_buddy_block *block, 726 struct drm_printer *p) 727{ 728 u64 start = drm_buddy_block_offset(block); 729 u64 size = drm_buddy_block_size(mm, block); 730 731 drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size); 732} 733EXPORT_SYMBOL(drm_buddy_block_print); 734 735/** 736 * drm_buddy_print - print allocator state 737 * 738 * @mm: DRM buddy manager 739 * @p: DRM printer to use 740 */ 741void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p) 742{ 743 int order; 744 745 drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB\n", 746 mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20); 747 748 for (order = mm->max_order; order >= 0; order--) { 749 struct drm_buddy_block *block; 750 u64 count = 0, free; 751 752 list_for_each_entry(block, &mm->free_list[order], link) { 753 BUG_ON(!drm_buddy_block_is_free(block)); 754 count++; 755 } 756 757 drm_printf(p, "order-%d ", order); 758 759 free = count * (mm->chunk_size << order); 760 if (free < SZ_1M) 761 drm_printf(p, "free: %lluKiB", free >> 10); 762 else 763 drm_printf(p, "free: %lluMiB", free >> 20); 764 765 drm_printf(p, ", pages: %llu\n", count); 766 } 767} 768EXPORT_SYMBOL(drm_buddy_print); 769 770static void drm_buddy_module_exit(void) 771{ 772 kmem_cache_destroy(slab_blocks); 773} 774 775static int __init drm_buddy_module_init(void) 776{ 777 slab_blocks = KMEM_CACHE(drm_buddy_block, 0); 778 if (!slab_blocks) 779 return -ENOMEM; 780 781 return 0; 782} 783 784module_init(drm_buddy_module_init); 785module_exit(drm_buddy_module_exit); 786 787MODULE_DESCRIPTION("DRM Buddy Allocator"); 788MODULE_LICENSE("Dual MIT/GPL");