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