xref: /linux/drivers/gpu/drm/drm_buddy.c (revision 260f6f4fda93c8485c8037865c941b42b9cba5d2)
1 // SPDX-License-Identifier: MIT
2 /*
3  * Copyright © 2021 Intel Corporation
4  */
5 
6 #include <kunit/test-bug.h>
7 
8 #include <linux/export.h>
9 #include <linux/kmemleak.h>
10 #include <linux/module.h>
11 #include <linux/sizes.h>
12 
13 #include <drm/drm_buddy.h>
14 
15 static struct kmem_cache *slab_blocks;
16 
drm_block_alloc(struct drm_buddy * mm,struct drm_buddy_block * parent,unsigned int order,u64 offset)17 static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
18 					       struct drm_buddy_block *parent,
19 					       unsigned int order,
20 					       u64 offset)
21 {
22 	struct drm_buddy_block *block;
23 
24 	BUG_ON(order > DRM_BUDDY_MAX_ORDER);
25 
26 	block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
27 	if (!block)
28 		return NULL;
29 
30 	block->header = offset;
31 	block->header |= order;
32 	block->parent = parent;
33 
34 	BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
35 	return block;
36 }
37 
drm_block_free(struct drm_buddy * mm,struct drm_buddy_block * block)38 static void drm_block_free(struct drm_buddy *mm,
39 			   struct drm_buddy_block *block)
40 {
41 	kmem_cache_free(slab_blocks, block);
42 }
43 
list_insert_sorted(struct drm_buddy * mm,struct drm_buddy_block * block)44 static void list_insert_sorted(struct drm_buddy *mm,
45 			       struct drm_buddy_block *block)
46 {
47 	struct drm_buddy_block *node;
48 	struct list_head *head;
49 
50 	head = &mm->free_list[drm_buddy_block_order(block)];
51 	if (list_empty(head)) {
52 		list_add(&block->link, head);
53 		return;
54 	}
55 
56 	list_for_each_entry(node, head, link)
57 		if (drm_buddy_block_offset(block) < drm_buddy_block_offset(node))
58 			break;
59 
60 	__list_add(&block->link, node->link.prev, &node->link);
61 }
62 
clear_reset(struct drm_buddy_block * block)63 static void clear_reset(struct drm_buddy_block *block)
64 {
65 	block->header &= ~DRM_BUDDY_HEADER_CLEAR;
66 }
67 
mark_cleared(struct drm_buddy_block * block)68 static void mark_cleared(struct drm_buddy_block *block)
69 {
70 	block->header |= DRM_BUDDY_HEADER_CLEAR;
71 }
72 
mark_allocated(struct drm_buddy_block * block)73 static void mark_allocated(struct drm_buddy_block *block)
74 {
75 	block->header &= ~DRM_BUDDY_HEADER_STATE;
76 	block->header |= DRM_BUDDY_ALLOCATED;
77 
78 	list_del(&block->link);
79 }
80 
mark_free(struct drm_buddy * mm,struct drm_buddy_block * block)81 static void mark_free(struct drm_buddy *mm,
82 		      struct drm_buddy_block *block)
83 {
84 	block->header &= ~DRM_BUDDY_HEADER_STATE;
85 	block->header |= DRM_BUDDY_FREE;
86 
87 	list_insert_sorted(mm, block);
88 }
89 
mark_split(struct drm_buddy_block * block)90 static void mark_split(struct drm_buddy_block *block)
91 {
92 	block->header &= ~DRM_BUDDY_HEADER_STATE;
93 	block->header |= DRM_BUDDY_SPLIT;
94 
95 	list_del(&block->link);
96 }
97 
overlaps(u64 s1,u64 e1,u64 s2,u64 e2)98 static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
99 {
100 	return s1 <= e2 && e1 >= s2;
101 }
102 
contains(u64 s1,u64 e1,u64 s2,u64 e2)103 static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
104 {
105 	return s1 <= s2 && e1 >= e2;
106 }
107 
108 static struct drm_buddy_block *
__get_buddy(struct drm_buddy_block * block)109 __get_buddy(struct drm_buddy_block *block)
110 {
111 	struct drm_buddy_block *parent;
112 
113 	parent = block->parent;
114 	if (!parent)
115 		return NULL;
116 
117 	if (parent->left == block)
118 		return parent->right;
119 
120 	return parent->left;
121 }
122 
__drm_buddy_free(struct drm_buddy * mm,struct drm_buddy_block * block,bool force_merge)123 static unsigned int __drm_buddy_free(struct drm_buddy *mm,
124 				     struct drm_buddy_block *block,
125 				     bool force_merge)
126 {
127 	struct drm_buddy_block *parent;
128 	unsigned int order;
129 
130 	while ((parent = block->parent)) {
131 		struct drm_buddy_block *buddy;
132 
133 		buddy = __get_buddy(block);
134 
135 		if (!drm_buddy_block_is_free(buddy))
136 			break;
137 
138 		if (!force_merge) {
139 			/*
140 			 * Check the block and its buddy clear state and exit
141 			 * the loop if they both have the dissimilar state.
142 			 */
143 			if (drm_buddy_block_is_clear(block) !=
144 			    drm_buddy_block_is_clear(buddy))
145 				break;
146 
147 			if (drm_buddy_block_is_clear(block))
148 				mark_cleared(parent);
149 		}
150 
151 		list_del(&buddy->link);
152 		if (force_merge && drm_buddy_block_is_clear(buddy))
153 			mm->clear_avail -= drm_buddy_block_size(mm, buddy);
154 
155 		drm_block_free(mm, block);
156 		drm_block_free(mm, buddy);
157 
158 		block = parent;
159 	}
160 
161 	order = drm_buddy_block_order(block);
162 	mark_free(mm, block);
163 
164 	return order;
165 }
166 
__force_merge(struct drm_buddy * mm,u64 start,u64 end,unsigned int min_order)167 static int __force_merge(struct drm_buddy *mm,
168 			 u64 start,
169 			 u64 end,
170 			 unsigned int min_order)
171 {
172 	unsigned int order;
173 	int i;
174 
175 	if (!min_order)
176 		return -ENOMEM;
177 
178 	if (min_order > mm->max_order)
179 		return -EINVAL;
180 
181 	for (i = min_order - 1; i >= 0; i--) {
182 		struct drm_buddy_block *block, *prev;
183 
184 		list_for_each_entry_safe_reverse(block, prev, &mm->free_list[i], link) {
185 			struct drm_buddy_block *buddy;
186 			u64 block_start, block_end;
187 
188 			if (!block->parent)
189 				continue;
190 
191 			block_start = drm_buddy_block_offset(block);
192 			block_end = block_start + drm_buddy_block_size(mm, block) - 1;
193 
194 			if (!contains(start, end, block_start, block_end))
195 				continue;
196 
197 			buddy = __get_buddy(block);
198 			if (!drm_buddy_block_is_free(buddy))
199 				continue;
200 
201 			WARN_ON(drm_buddy_block_is_clear(block) ==
202 				drm_buddy_block_is_clear(buddy));
203 
204 			/*
205 			 * If the prev block is same as buddy, don't access the
206 			 * block in the next iteration as we would free the
207 			 * buddy block as part of the free function.
208 			 */
209 			if (prev == buddy)
210 				prev = list_prev_entry(prev, link);
211 
212 			list_del(&block->link);
213 			if (drm_buddy_block_is_clear(block))
214 				mm->clear_avail -= drm_buddy_block_size(mm, block);
215 
216 			order = __drm_buddy_free(mm, block, true);
217 			if (order >= min_order)
218 				return 0;
219 		}
220 	}
221 
222 	return -ENOMEM;
223 }
224 
225 /**
226  * drm_buddy_init - init memory manager
227  *
228  * @mm: DRM buddy manager to initialize
229  * @size: size in bytes to manage
230  * @chunk_size: minimum page size in bytes for our allocations
231  *
232  * Initializes the memory manager and its resources.
233  *
234  * Returns:
235  * 0 on success, error code on failure.
236  */
drm_buddy_init(struct drm_buddy * mm,u64 size,u64 chunk_size)237 int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
238 {
239 	unsigned int i;
240 	u64 offset;
241 
242 	if (size < chunk_size)
243 		return -EINVAL;
244 
245 	if (chunk_size < SZ_4K)
246 		return -EINVAL;
247 
248 	if (!is_power_of_2(chunk_size))
249 		return -EINVAL;
250 
251 	size = round_down(size, chunk_size);
252 
253 	mm->size = size;
254 	mm->avail = size;
255 	mm->clear_avail = 0;
256 	mm->chunk_size = chunk_size;
257 	mm->max_order = ilog2(size) - ilog2(chunk_size);
258 
259 	BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
260 
261 	mm->free_list = kmalloc_array(mm->max_order + 1,
262 				      sizeof(struct list_head),
263 				      GFP_KERNEL);
264 	if (!mm->free_list)
265 		return -ENOMEM;
266 
267 	for (i = 0; i <= mm->max_order; ++i)
268 		INIT_LIST_HEAD(&mm->free_list[i]);
269 
270 	mm->n_roots = hweight64(size);
271 
272 	mm->roots = kmalloc_array(mm->n_roots,
273 				  sizeof(struct drm_buddy_block *),
274 				  GFP_KERNEL);
275 	if (!mm->roots)
276 		goto out_free_list;
277 
278 	offset = 0;
279 	i = 0;
280 
281 	/*
282 	 * Split into power-of-two blocks, in case we are given a size that is
283 	 * not itself a power-of-two.
284 	 */
285 	do {
286 		struct drm_buddy_block *root;
287 		unsigned int order;
288 		u64 root_size;
289 
290 		order = ilog2(size) - ilog2(chunk_size);
291 		root_size = chunk_size << order;
292 
293 		root = drm_block_alloc(mm, NULL, order, offset);
294 		if (!root)
295 			goto out_free_roots;
296 
297 		mark_free(mm, root);
298 
299 		BUG_ON(i > mm->max_order);
300 		BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
301 
302 		mm->roots[i] = root;
303 
304 		offset += root_size;
305 		size -= root_size;
306 		i++;
307 	} while (size);
308 
309 	return 0;
310 
311 out_free_roots:
312 	while (i--)
313 		drm_block_free(mm, mm->roots[i]);
314 	kfree(mm->roots);
315 out_free_list:
316 	kfree(mm->free_list);
317 	return -ENOMEM;
318 }
319 EXPORT_SYMBOL(drm_buddy_init);
320 
321 /**
322  * drm_buddy_fini - tear down the memory manager
323  *
324  * @mm: DRM buddy manager to free
325  *
326  * Cleanup memory manager resources and the freelist
327  */
drm_buddy_fini(struct drm_buddy * mm)328 void drm_buddy_fini(struct drm_buddy *mm)
329 {
330 	u64 root_size, size, start;
331 	unsigned int order;
332 	int i;
333 
334 	size = mm->size;
335 
336 	for (i = 0; i < mm->n_roots; ++i) {
337 		order = ilog2(size) - ilog2(mm->chunk_size);
338 		start = drm_buddy_block_offset(mm->roots[i]);
339 		__force_merge(mm, start, start + size, order);
340 
341 		if (WARN_ON(!drm_buddy_block_is_free(mm->roots[i])))
342 			kunit_fail_current_test("buddy_fini() root");
343 
344 		drm_block_free(mm, mm->roots[i]);
345 
346 		root_size = mm->chunk_size << order;
347 		size -= root_size;
348 	}
349 
350 	WARN_ON(mm->avail != mm->size);
351 
352 	kfree(mm->roots);
353 	kfree(mm->free_list);
354 }
355 EXPORT_SYMBOL(drm_buddy_fini);
356 
split_block(struct drm_buddy * mm,struct drm_buddy_block * block)357 static int split_block(struct drm_buddy *mm,
358 		       struct drm_buddy_block *block)
359 {
360 	unsigned int block_order = drm_buddy_block_order(block) - 1;
361 	u64 offset = drm_buddy_block_offset(block);
362 
363 	BUG_ON(!drm_buddy_block_is_free(block));
364 	BUG_ON(!drm_buddy_block_order(block));
365 
366 	block->left = drm_block_alloc(mm, block, block_order, offset);
367 	if (!block->left)
368 		return -ENOMEM;
369 
370 	block->right = drm_block_alloc(mm, block, block_order,
371 				       offset + (mm->chunk_size << block_order));
372 	if (!block->right) {
373 		drm_block_free(mm, block->left);
374 		return -ENOMEM;
375 	}
376 
377 	mark_free(mm, block->left);
378 	mark_free(mm, block->right);
379 
380 	if (drm_buddy_block_is_clear(block)) {
381 		mark_cleared(block->left);
382 		mark_cleared(block->right);
383 		clear_reset(block);
384 	}
385 
386 	mark_split(block);
387 
388 	return 0;
389 }
390 
391 /**
392  * drm_get_buddy - get buddy address
393  *
394  * @block: DRM buddy block
395  *
396  * Returns the corresponding buddy block for @block, or NULL
397  * if this is a root block and can't be merged further.
398  * Requires some kind of locking to protect against
399  * any concurrent allocate and free operations.
400  */
401 struct drm_buddy_block *
drm_get_buddy(struct drm_buddy_block * block)402 drm_get_buddy(struct drm_buddy_block *block)
403 {
404 	return __get_buddy(block);
405 }
406 EXPORT_SYMBOL(drm_get_buddy);
407 
408 /**
409  * drm_buddy_reset_clear - reset blocks clear state
410  *
411  * @mm: DRM buddy manager
412  * @is_clear: blocks clear state
413  *
414  * Reset the clear state based on @is_clear value for each block
415  * in the freelist.
416  */
drm_buddy_reset_clear(struct drm_buddy * mm,bool is_clear)417 void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear)
418 {
419 	u64 root_size, size, start;
420 	unsigned int order;
421 	int i;
422 
423 	size = mm->size;
424 	for (i = 0; i < mm->n_roots; ++i) {
425 		order = ilog2(size) - ilog2(mm->chunk_size);
426 		start = drm_buddy_block_offset(mm->roots[i]);
427 		__force_merge(mm, start, start + size, order);
428 
429 		root_size = mm->chunk_size << order;
430 		size -= root_size;
431 	}
432 
433 	for (i = 0; i <= mm->max_order; ++i) {
434 		struct drm_buddy_block *block;
435 
436 		list_for_each_entry_reverse(block, &mm->free_list[i], link) {
437 			if (is_clear != drm_buddy_block_is_clear(block)) {
438 				if (is_clear) {
439 					mark_cleared(block);
440 					mm->clear_avail += drm_buddy_block_size(mm, block);
441 				} else {
442 					clear_reset(block);
443 					mm->clear_avail -= drm_buddy_block_size(mm, block);
444 				}
445 			}
446 		}
447 	}
448 }
449 EXPORT_SYMBOL(drm_buddy_reset_clear);
450 
451 /**
452  * drm_buddy_free_block - free a block
453  *
454  * @mm: DRM buddy manager
455  * @block: block to be freed
456  */
drm_buddy_free_block(struct drm_buddy * mm,struct drm_buddy_block * block)457 void drm_buddy_free_block(struct drm_buddy *mm,
458 			  struct drm_buddy_block *block)
459 {
460 	BUG_ON(!drm_buddy_block_is_allocated(block));
461 	mm->avail += drm_buddy_block_size(mm, block);
462 	if (drm_buddy_block_is_clear(block))
463 		mm->clear_avail += drm_buddy_block_size(mm, block);
464 
465 	__drm_buddy_free(mm, block, false);
466 }
467 EXPORT_SYMBOL(drm_buddy_free_block);
468 
__drm_buddy_free_list(struct drm_buddy * mm,struct list_head * objects,bool mark_clear,bool mark_dirty)469 static void __drm_buddy_free_list(struct drm_buddy *mm,
470 				  struct list_head *objects,
471 				  bool mark_clear,
472 				  bool mark_dirty)
473 {
474 	struct drm_buddy_block *block, *on;
475 
476 	WARN_ON(mark_dirty && mark_clear);
477 
478 	list_for_each_entry_safe(block, on, objects, link) {
479 		if (mark_clear)
480 			mark_cleared(block);
481 		else if (mark_dirty)
482 			clear_reset(block);
483 		drm_buddy_free_block(mm, block);
484 		cond_resched();
485 	}
486 	INIT_LIST_HEAD(objects);
487 }
488 
drm_buddy_free_list_internal(struct drm_buddy * mm,struct list_head * objects)489 static void drm_buddy_free_list_internal(struct drm_buddy *mm,
490 					 struct list_head *objects)
491 {
492 	/*
493 	 * Don't touch the clear/dirty bit, since allocation is still internal
494 	 * at this point. For example we might have just failed part of the
495 	 * allocation.
496 	 */
497 	__drm_buddy_free_list(mm, objects, false, false);
498 }
499 
500 /**
501  * drm_buddy_free_list - free blocks
502  *
503  * @mm: DRM buddy manager
504  * @objects: input list head to free blocks
505  * @flags: optional flags like DRM_BUDDY_CLEARED
506  */
drm_buddy_free_list(struct drm_buddy * mm,struct list_head * objects,unsigned int flags)507 void drm_buddy_free_list(struct drm_buddy *mm,
508 			 struct list_head *objects,
509 			 unsigned int flags)
510 {
511 	bool mark_clear = flags & DRM_BUDDY_CLEARED;
512 
513 	__drm_buddy_free_list(mm, objects, mark_clear, !mark_clear);
514 }
515 EXPORT_SYMBOL(drm_buddy_free_list);
516 
block_incompatible(struct drm_buddy_block * block,unsigned int flags)517 static bool block_incompatible(struct drm_buddy_block *block, unsigned int flags)
518 {
519 	bool needs_clear = flags & DRM_BUDDY_CLEAR_ALLOCATION;
520 
521 	return needs_clear != drm_buddy_block_is_clear(block);
522 }
523 
524 static struct drm_buddy_block *
__alloc_range_bias(struct drm_buddy * mm,u64 start,u64 end,unsigned int order,unsigned long flags,bool fallback)525 __alloc_range_bias(struct drm_buddy *mm,
526 		   u64 start, u64 end,
527 		   unsigned int order,
528 		   unsigned long flags,
529 		   bool fallback)
530 {
531 	u64 req_size = mm->chunk_size << order;
532 	struct drm_buddy_block *block;
533 	struct drm_buddy_block *buddy;
534 	LIST_HEAD(dfs);
535 	int err;
536 	int i;
537 
538 	end = end - 1;
539 
540 	for (i = 0; i < mm->n_roots; ++i)
541 		list_add_tail(&mm->roots[i]->tmp_link, &dfs);
542 
543 	do {
544 		u64 block_start;
545 		u64 block_end;
546 
547 		block = list_first_entry_or_null(&dfs,
548 						 struct drm_buddy_block,
549 						 tmp_link);
550 		if (!block)
551 			break;
552 
553 		list_del(&block->tmp_link);
554 
555 		if (drm_buddy_block_order(block) < order)
556 			continue;
557 
558 		block_start = drm_buddy_block_offset(block);
559 		block_end = block_start + drm_buddy_block_size(mm, block) - 1;
560 
561 		if (!overlaps(start, end, block_start, block_end))
562 			continue;
563 
564 		if (drm_buddy_block_is_allocated(block))
565 			continue;
566 
567 		if (block_start < start || block_end > end) {
568 			u64 adjusted_start = max(block_start, start);
569 			u64 adjusted_end = min(block_end, end);
570 
571 			if (round_down(adjusted_end + 1, req_size) <=
572 			    round_up(adjusted_start, req_size))
573 				continue;
574 		}
575 
576 		if (!fallback && block_incompatible(block, flags))
577 			continue;
578 
579 		if (contains(start, end, block_start, block_end) &&
580 		    order == drm_buddy_block_order(block)) {
581 			/*
582 			 * Find the free block within the range.
583 			 */
584 			if (drm_buddy_block_is_free(block))
585 				return block;
586 
587 			continue;
588 		}
589 
590 		if (!drm_buddy_block_is_split(block)) {
591 			err = split_block(mm, block);
592 			if (unlikely(err))
593 				goto err_undo;
594 		}
595 
596 		list_add(&block->right->tmp_link, &dfs);
597 		list_add(&block->left->tmp_link, &dfs);
598 	} while (1);
599 
600 	return ERR_PTR(-ENOSPC);
601 
602 err_undo:
603 	/*
604 	 * We really don't want to leave around a bunch of split blocks, since
605 	 * bigger is better, so make sure we merge everything back before we
606 	 * free the allocated blocks.
607 	 */
608 	buddy = __get_buddy(block);
609 	if (buddy &&
610 	    (drm_buddy_block_is_free(block) &&
611 	     drm_buddy_block_is_free(buddy)))
612 		__drm_buddy_free(mm, block, false);
613 	return ERR_PTR(err);
614 }
615 
616 static struct drm_buddy_block *
__drm_buddy_alloc_range_bias(struct drm_buddy * mm,u64 start,u64 end,unsigned int order,unsigned long flags)617 __drm_buddy_alloc_range_bias(struct drm_buddy *mm,
618 			     u64 start, u64 end,
619 			     unsigned int order,
620 			     unsigned long flags)
621 {
622 	struct drm_buddy_block *block;
623 	bool fallback = false;
624 
625 	block = __alloc_range_bias(mm, start, end, order,
626 				   flags, fallback);
627 	if (IS_ERR(block))
628 		return __alloc_range_bias(mm, start, end, order,
629 					  flags, !fallback);
630 
631 	return block;
632 }
633 
634 static struct drm_buddy_block *
get_maxblock(struct drm_buddy * mm,unsigned int order,unsigned long flags)635 get_maxblock(struct drm_buddy *mm, unsigned int order,
636 	     unsigned long flags)
637 {
638 	struct drm_buddy_block *max_block = NULL, *block = NULL;
639 	unsigned int i;
640 
641 	for (i = order; i <= mm->max_order; ++i) {
642 		struct drm_buddy_block *tmp_block;
643 
644 		list_for_each_entry_reverse(tmp_block, &mm->free_list[i], link) {
645 			if (block_incompatible(tmp_block, flags))
646 				continue;
647 
648 			block = tmp_block;
649 			break;
650 		}
651 
652 		if (!block)
653 			continue;
654 
655 		if (!max_block) {
656 			max_block = block;
657 			continue;
658 		}
659 
660 		if (drm_buddy_block_offset(block) >
661 		    drm_buddy_block_offset(max_block)) {
662 			max_block = block;
663 		}
664 	}
665 
666 	return max_block;
667 }
668 
669 static struct drm_buddy_block *
alloc_from_freelist(struct drm_buddy * mm,unsigned int order,unsigned long flags)670 alloc_from_freelist(struct drm_buddy *mm,
671 		    unsigned int order,
672 		    unsigned long flags)
673 {
674 	struct drm_buddy_block *block = NULL;
675 	unsigned int tmp;
676 	int err;
677 
678 	if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
679 		block = get_maxblock(mm, order, flags);
680 		if (block)
681 			/* Store the obtained block order */
682 			tmp = drm_buddy_block_order(block);
683 	} else {
684 		for (tmp = order; tmp <= mm->max_order; ++tmp) {
685 			struct drm_buddy_block *tmp_block;
686 
687 			list_for_each_entry_reverse(tmp_block, &mm->free_list[tmp], link) {
688 				if (block_incompatible(tmp_block, flags))
689 					continue;
690 
691 				block = tmp_block;
692 				break;
693 			}
694 
695 			if (block)
696 				break;
697 		}
698 	}
699 
700 	if (!block) {
701 		/* Fallback method */
702 		for (tmp = order; tmp <= mm->max_order; ++tmp) {
703 			if (!list_empty(&mm->free_list[tmp])) {
704 				block = list_last_entry(&mm->free_list[tmp],
705 							struct drm_buddy_block,
706 							link);
707 				if (block)
708 					break;
709 			}
710 		}
711 
712 		if (!block)
713 			return ERR_PTR(-ENOSPC);
714 	}
715 
716 	BUG_ON(!drm_buddy_block_is_free(block));
717 
718 	while (tmp != order) {
719 		err = split_block(mm, block);
720 		if (unlikely(err))
721 			goto err_undo;
722 
723 		block = block->right;
724 		tmp--;
725 	}
726 	return block;
727 
728 err_undo:
729 	if (tmp != order)
730 		__drm_buddy_free(mm, block, false);
731 	return ERR_PTR(err);
732 }
733 
__alloc_range(struct drm_buddy * mm,struct list_head * dfs,u64 start,u64 size,struct list_head * blocks,u64 * total_allocated_on_err)734 static int __alloc_range(struct drm_buddy *mm,
735 			 struct list_head *dfs,
736 			 u64 start, u64 size,
737 			 struct list_head *blocks,
738 			 u64 *total_allocated_on_err)
739 {
740 	struct drm_buddy_block *block;
741 	struct drm_buddy_block *buddy;
742 	u64 total_allocated = 0;
743 	LIST_HEAD(allocated);
744 	u64 end;
745 	int err;
746 
747 	end = start + size - 1;
748 
749 	do {
750 		u64 block_start;
751 		u64 block_end;
752 
753 		block = list_first_entry_or_null(dfs,
754 						 struct drm_buddy_block,
755 						 tmp_link);
756 		if (!block)
757 			break;
758 
759 		list_del(&block->tmp_link);
760 
761 		block_start = drm_buddy_block_offset(block);
762 		block_end = block_start + drm_buddy_block_size(mm, block) - 1;
763 
764 		if (!overlaps(start, end, block_start, block_end))
765 			continue;
766 
767 		if (drm_buddy_block_is_allocated(block)) {
768 			err = -ENOSPC;
769 			goto err_free;
770 		}
771 
772 		if (contains(start, end, block_start, block_end)) {
773 			if (drm_buddy_block_is_free(block)) {
774 				mark_allocated(block);
775 				total_allocated += drm_buddy_block_size(mm, block);
776 				mm->avail -= drm_buddy_block_size(mm, block);
777 				if (drm_buddy_block_is_clear(block))
778 					mm->clear_avail -= drm_buddy_block_size(mm, block);
779 				list_add_tail(&block->link, &allocated);
780 				continue;
781 			} else if (!mm->clear_avail) {
782 				err = -ENOSPC;
783 				goto err_free;
784 			}
785 		}
786 
787 		if (!drm_buddy_block_is_split(block)) {
788 			err = split_block(mm, block);
789 			if (unlikely(err))
790 				goto err_undo;
791 		}
792 
793 		list_add(&block->right->tmp_link, dfs);
794 		list_add(&block->left->tmp_link, dfs);
795 	} while (1);
796 
797 	if (total_allocated < size) {
798 		err = -ENOSPC;
799 		goto err_free;
800 	}
801 
802 	list_splice_tail(&allocated, blocks);
803 
804 	return 0;
805 
806 err_undo:
807 	/*
808 	 * We really don't want to leave around a bunch of split blocks, since
809 	 * bigger is better, so make sure we merge everything back before we
810 	 * free the allocated blocks.
811 	 */
812 	buddy = __get_buddy(block);
813 	if (buddy &&
814 	    (drm_buddy_block_is_free(block) &&
815 	     drm_buddy_block_is_free(buddy)))
816 		__drm_buddy_free(mm, block, false);
817 
818 err_free:
819 	if (err == -ENOSPC && total_allocated_on_err) {
820 		list_splice_tail(&allocated, blocks);
821 		*total_allocated_on_err = total_allocated;
822 	} else {
823 		drm_buddy_free_list_internal(mm, &allocated);
824 	}
825 
826 	return err;
827 }
828 
__drm_buddy_alloc_range(struct drm_buddy * mm,u64 start,u64 size,u64 * total_allocated_on_err,struct list_head * blocks)829 static int __drm_buddy_alloc_range(struct drm_buddy *mm,
830 				   u64 start,
831 				   u64 size,
832 				   u64 *total_allocated_on_err,
833 				   struct list_head *blocks)
834 {
835 	LIST_HEAD(dfs);
836 	int i;
837 
838 	for (i = 0; i < mm->n_roots; ++i)
839 		list_add_tail(&mm->roots[i]->tmp_link, &dfs);
840 
841 	return __alloc_range(mm, &dfs, start, size,
842 			     blocks, total_allocated_on_err);
843 }
844 
__alloc_contig_try_harder(struct drm_buddy * mm,u64 size,u64 min_block_size,struct list_head * blocks)845 static int __alloc_contig_try_harder(struct drm_buddy *mm,
846 				     u64 size,
847 				     u64 min_block_size,
848 				     struct list_head *blocks)
849 {
850 	u64 rhs_offset, lhs_offset, lhs_size, filled;
851 	struct drm_buddy_block *block;
852 	struct list_head *list;
853 	LIST_HEAD(blocks_lhs);
854 	unsigned long pages;
855 	unsigned int order;
856 	u64 modify_size;
857 	int err;
858 
859 	modify_size = rounddown_pow_of_two(size);
860 	pages = modify_size >> ilog2(mm->chunk_size);
861 	order = fls(pages) - 1;
862 	if (order == 0)
863 		return -ENOSPC;
864 
865 	list = &mm->free_list[order];
866 	if (list_empty(list))
867 		return -ENOSPC;
868 
869 	list_for_each_entry_reverse(block, list, link) {
870 		/* Allocate blocks traversing RHS */
871 		rhs_offset = drm_buddy_block_offset(block);
872 		err =  __drm_buddy_alloc_range(mm, rhs_offset, size,
873 					       &filled, blocks);
874 		if (!err || err != -ENOSPC)
875 			return err;
876 
877 		lhs_size = max((size - filled), min_block_size);
878 		if (!IS_ALIGNED(lhs_size, min_block_size))
879 			lhs_size = round_up(lhs_size, min_block_size);
880 
881 		/* Allocate blocks traversing LHS */
882 		lhs_offset = drm_buddy_block_offset(block) - lhs_size;
883 		err =  __drm_buddy_alloc_range(mm, lhs_offset, lhs_size,
884 					       NULL, &blocks_lhs);
885 		if (!err) {
886 			list_splice(&blocks_lhs, blocks);
887 			return 0;
888 		} else if (err != -ENOSPC) {
889 			drm_buddy_free_list_internal(mm, blocks);
890 			return err;
891 		}
892 		/* Free blocks for the next iteration */
893 		drm_buddy_free_list_internal(mm, blocks);
894 	}
895 
896 	return -ENOSPC;
897 }
898 
899 /**
900  * drm_buddy_block_trim - free unused pages
901  *
902  * @mm: DRM buddy manager
903  * @start: start address to begin the trimming.
904  * @new_size: original size requested
905  * @blocks: Input and output list of allocated blocks.
906  * MUST contain single block as input to be trimmed.
907  * On success will contain the newly allocated blocks
908  * making up the @new_size. Blocks always appear in
909  * ascending order
910  *
911  * For contiguous allocation, we round up the size to the nearest
912  * power of two value, drivers consume *actual* size, so remaining
913  * portions are unused and can be optionally freed with this function
914  *
915  * Returns:
916  * 0 on success, error code on failure.
917  */
drm_buddy_block_trim(struct drm_buddy * mm,u64 * start,u64 new_size,struct list_head * blocks)918 int drm_buddy_block_trim(struct drm_buddy *mm,
919 			 u64 *start,
920 			 u64 new_size,
921 			 struct list_head *blocks)
922 {
923 	struct drm_buddy_block *parent;
924 	struct drm_buddy_block *block;
925 	u64 block_start, block_end;
926 	LIST_HEAD(dfs);
927 	u64 new_start;
928 	int err;
929 
930 	if (!list_is_singular(blocks))
931 		return -EINVAL;
932 
933 	block = list_first_entry(blocks,
934 				 struct drm_buddy_block,
935 				 link);
936 
937 	block_start = drm_buddy_block_offset(block);
938 	block_end = block_start + drm_buddy_block_size(mm, block);
939 
940 	if (WARN_ON(!drm_buddy_block_is_allocated(block)))
941 		return -EINVAL;
942 
943 	if (new_size > drm_buddy_block_size(mm, block))
944 		return -EINVAL;
945 
946 	if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
947 		return -EINVAL;
948 
949 	if (new_size == drm_buddy_block_size(mm, block))
950 		return 0;
951 
952 	new_start = block_start;
953 	if (start) {
954 		new_start = *start;
955 
956 		if (new_start < block_start)
957 			return -EINVAL;
958 
959 		if (!IS_ALIGNED(new_start, mm->chunk_size))
960 			return -EINVAL;
961 
962 		if (range_overflows(new_start, new_size, block_end))
963 			return -EINVAL;
964 	}
965 
966 	list_del(&block->link);
967 	mark_free(mm, block);
968 	mm->avail += drm_buddy_block_size(mm, block);
969 	if (drm_buddy_block_is_clear(block))
970 		mm->clear_avail += drm_buddy_block_size(mm, block);
971 
972 	/* Prevent recursively freeing this node */
973 	parent = block->parent;
974 	block->parent = NULL;
975 
976 	list_add(&block->tmp_link, &dfs);
977 	err =  __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
978 	if (err) {
979 		mark_allocated(block);
980 		mm->avail -= drm_buddy_block_size(mm, block);
981 		if (drm_buddy_block_is_clear(block))
982 			mm->clear_avail -= drm_buddy_block_size(mm, block);
983 		list_add(&block->link, blocks);
984 	}
985 
986 	block->parent = parent;
987 	return err;
988 }
989 EXPORT_SYMBOL(drm_buddy_block_trim);
990 
991 static struct drm_buddy_block *
__drm_buddy_alloc_blocks(struct drm_buddy * mm,u64 start,u64 end,unsigned int order,unsigned long flags)992 __drm_buddy_alloc_blocks(struct drm_buddy *mm,
993 			 u64 start, u64 end,
994 			 unsigned int order,
995 			 unsigned long flags)
996 {
997 	if (flags & DRM_BUDDY_RANGE_ALLOCATION)
998 		/* Allocate traversing within the range */
999 		return  __drm_buddy_alloc_range_bias(mm, start, end,
1000 						     order, flags);
1001 	else
1002 		/* Allocate from freelist */
1003 		return alloc_from_freelist(mm, order, flags);
1004 }
1005 
1006 /**
1007  * drm_buddy_alloc_blocks - allocate power-of-two blocks
1008  *
1009  * @mm: DRM buddy manager to allocate from
1010  * @start: start of the allowed range for this block
1011  * @end: end of the allowed range for this block
1012  * @size: size of the allocation in bytes
1013  * @min_block_size: alignment of the allocation
1014  * @blocks: output list head to add allocated blocks
1015  * @flags: DRM_BUDDY_*_ALLOCATION flags
1016  *
1017  * alloc_range_bias() called on range limitations, which traverses
1018  * the tree and returns the desired block.
1019  *
1020  * alloc_from_freelist() called when *no* range restrictions
1021  * are enforced, which picks the block from the freelist.
1022  *
1023  * Returns:
1024  * 0 on success, error code on failure.
1025  */
drm_buddy_alloc_blocks(struct drm_buddy * mm,u64 start,u64 end,u64 size,u64 min_block_size,struct list_head * blocks,unsigned long flags)1026 int drm_buddy_alloc_blocks(struct drm_buddy *mm,
1027 			   u64 start, u64 end, u64 size,
1028 			   u64 min_block_size,
1029 			   struct list_head *blocks,
1030 			   unsigned long flags)
1031 {
1032 	struct drm_buddy_block *block = NULL;
1033 	u64 original_size, original_min_size;
1034 	unsigned int min_order, order;
1035 	LIST_HEAD(allocated);
1036 	unsigned long pages;
1037 	int err;
1038 
1039 	if (size < mm->chunk_size)
1040 		return -EINVAL;
1041 
1042 	if (min_block_size < mm->chunk_size)
1043 		return -EINVAL;
1044 
1045 	if (!is_power_of_2(min_block_size))
1046 		return -EINVAL;
1047 
1048 	if (!IS_ALIGNED(start | end | size, mm->chunk_size))
1049 		return -EINVAL;
1050 
1051 	if (end > mm->size)
1052 		return -EINVAL;
1053 
1054 	if (range_overflows(start, size, mm->size))
1055 		return -EINVAL;
1056 
1057 	/* Actual range allocation */
1058 	if (start + size == end) {
1059 		if (!IS_ALIGNED(start | end, min_block_size))
1060 			return -EINVAL;
1061 
1062 		return __drm_buddy_alloc_range(mm, start, size, NULL, blocks);
1063 	}
1064 
1065 	original_size = size;
1066 	original_min_size = min_block_size;
1067 
1068 	/* Roundup the size to power of 2 */
1069 	if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) {
1070 		size = roundup_pow_of_two(size);
1071 		min_block_size = size;
1072 	/* Align size value to min_block_size */
1073 	} else if (!IS_ALIGNED(size, min_block_size)) {
1074 		size = round_up(size, min_block_size);
1075 	}
1076 
1077 	pages = size >> ilog2(mm->chunk_size);
1078 	order = fls(pages) - 1;
1079 	min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
1080 
1081 	do {
1082 		order = min(order, (unsigned int)fls(pages) - 1);
1083 		BUG_ON(order > mm->max_order);
1084 		BUG_ON(order < min_order);
1085 
1086 		do {
1087 			block = __drm_buddy_alloc_blocks(mm, start,
1088 							 end,
1089 							 order,
1090 							 flags);
1091 			if (!IS_ERR(block))
1092 				break;
1093 
1094 			if (order-- == min_order) {
1095 				/* Try allocation through force merge method */
1096 				if (mm->clear_avail &&
1097 				    !__force_merge(mm, start, end, min_order)) {
1098 					block = __drm_buddy_alloc_blocks(mm, start,
1099 									 end,
1100 									 min_order,
1101 									 flags);
1102 					if (!IS_ERR(block)) {
1103 						order = min_order;
1104 						break;
1105 					}
1106 				}
1107 
1108 				/*
1109 				 * Try contiguous block allocation through
1110 				 * try harder method.
1111 				 */
1112 				if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION &&
1113 				    !(flags & DRM_BUDDY_RANGE_ALLOCATION))
1114 					return __alloc_contig_try_harder(mm,
1115 									 original_size,
1116 									 original_min_size,
1117 									 blocks);
1118 				err = -ENOSPC;
1119 				goto err_free;
1120 			}
1121 		} while (1);
1122 
1123 		mark_allocated(block);
1124 		mm->avail -= drm_buddy_block_size(mm, block);
1125 		if (drm_buddy_block_is_clear(block))
1126 			mm->clear_avail -= drm_buddy_block_size(mm, block);
1127 		kmemleak_update_trace(block);
1128 		list_add_tail(&block->link, &allocated);
1129 
1130 		pages -= BIT(order);
1131 
1132 		if (!pages)
1133 			break;
1134 	} while (1);
1135 
1136 	/* Trim the allocated block to the required size */
1137 	if (!(flags & DRM_BUDDY_TRIM_DISABLE) &&
1138 	    original_size != size) {
1139 		struct list_head *trim_list;
1140 		LIST_HEAD(temp);
1141 		u64 trim_size;
1142 
1143 		trim_list = &allocated;
1144 		trim_size = original_size;
1145 
1146 		if (!list_is_singular(&allocated)) {
1147 			block = list_last_entry(&allocated, typeof(*block), link);
1148 			list_move(&block->link, &temp);
1149 			trim_list = &temp;
1150 			trim_size = drm_buddy_block_size(mm, block) -
1151 				(size - original_size);
1152 		}
1153 
1154 		drm_buddy_block_trim(mm,
1155 				     NULL,
1156 				     trim_size,
1157 				     trim_list);
1158 
1159 		if (!list_empty(&temp))
1160 			list_splice_tail(trim_list, &allocated);
1161 	}
1162 
1163 	list_splice_tail(&allocated, blocks);
1164 	return 0;
1165 
1166 err_free:
1167 	drm_buddy_free_list_internal(mm, &allocated);
1168 	return err;
1169 }
1170 EXPORT_SYMBOL(drm_buddy_alloc_blocks);
1171 
1172 /**
1173  * drm_buddy_block_print - print block information
1174  *
1175  * @mm: DRM buddy manager
1176  * @block: DRM buddy block
1177  * @p: DRM printer to use
1178  */
drm_buddy_block_print(struct drm_buddy * mm,struct drm_buddy_block * block,struct drm_printer * p)1179 void drm_buddy_block_print(struct drm_buddy *mm,
1180 			   struct drm_buddy_block *block,
1181 			   struct drm_printer *p)
1182 {
1183 	u64 start = drm_buddy_block_offset(block);
1184 	u64 size = drm_buddy_block_size(mm, block);
1185 
1186 	drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
1187 }
1188 EXPORT_SYMBOL(drm_buddy_block_print);
1189 
1190 /**
1191  * drm_buddy_print - print allocator state
1192  *
1193  * @mm: DRM buddy manager
1194  * @p: DRM printer to use
1195  */
drm_buddy_print(struct drm_buddy * mm,struct drm_printer * p)1196 void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
1197 {
1198 	int order;
1199 
1200 	drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB, clear_free: %lluMiB\n",
1201 		   mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
1202 
1203 	for (order = mm->max_order; order >= 0; order--) {
1204 		struct drm_buddy_block *block;
1205 		u64 count = 0, free;
1206 
1207 		list_for_each_entry(block, &mm->free_list[order], link) {
1208 			BUG_ON(!drm_buddy_block_is_free(block));
1209 			count++;
1210 		}
1211 
1212 		drm_printf(p, "order-%2d ", order);
1213 
1214 		free = count * (mm->chunk_size << order);
1215 		if (free < SZ_1M)
1216 			drm_printf(p, "free: %8llu KiB", free >> 10);
1217 		else
1218 			drm_printf(p, "free: %8llu MiB", free >> 20);
1219 
1220 		drm_printf(p, ", blocks: %llu\n", count);
1221 	}
1222 }
1223 EXPORT_SYMBOL(drm_buddy_print);
1224 
drm_buddy_module_exit(void)1225 static void drm_buddy_module_exit(void)
1226 {
1227 	kmem_cache_destroy(slab_blocks);
1228 }
1229 
drm_buddy_module_init(void)1230 static int __init drm_buddy_module_init(void)
1231 {
1232 	slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
1233 	if (!slab_blocks)
1234 		return -ENOMEM;
1235 
1236 	return 0;
1237 }
1238 
1239 module_init(drm_buddy_module_init);
1240 module_exit(drm_buddy_module_exit);
1241 
1242 MODULE_DESCRIPTION("DRM Buddy Allocator");
1243 MODULE_LICENSE("Dual MIT/GPL");
1244