16387a3c4SArunpravin // SPDX-License-Identifier: MIT
26387a3c4SArunpravin /*
36387a3c4SArunpravin * Copyright © 2021 Intel Corporation
46387a3c4SArunpravin */
56387a3c4SArunpravin
66387a3c4SArunpravin #include <linux/kmemleak.h>
76387a3c4SArunpravin #include <linux/module.h>
86387a3c4SArunpravin #include <linux/sizes.h>
96387a3c4SArunpravin
106387a3c4SArunpravin #include <drm/drm_buddy.h>
116387a3c4SArunpravin
126387a3c4SArunpravin static struct kmem_cache *slab_blocks;
136387a3c4SArunpravin
drm_block_alloc(struct drm_buddy * mm,struct drm_buddy_block * parent,unsigned int order,u64 offset)146387a3c4SArunpravin static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
156387a3c4SArunpravin struct drm_buddy_block *parent,
166387a3c4SArunpravin unsigned int order,
176387a3c4SArunpravin u64 offset)
186387a3c4SArunpravin {
196387a3c4SArunpravin struct drm_buddy_block *block;
206387a3c4SArunpravin
216387a3c4SArunpravin BUG_ON(order > DRM_BUDDY_MAX_ORDER);
226387a3c4SArunpravin
236387a3c4SArunpravin block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
246387a3c4SArunpravin if (!block)
256387a3c4SArunpravin return NULL;
266387a3c4SArunpravin
276387a3c4SArunpravin block->header = offset;
286387a3c4SArunpravin block->header |= order;
296387a3c4SArunpravin block->parent = parent;
306387a3c4SArunpravin
316387a3c4SArunpravin BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
326387a3c4SArunpravin return block;
336387a3c4SArunpravin }
346387a3c4SArunpravin
drm_block_free(struct drm_buddy * mm,struct drm_buddy_block * block)356387a3c4SArunpravin static void drm_block_free(struct drm_buddy *mm,
366387a3c4SArunpravin struct drm_buddy_block *block)
376387a3c4SArunpravin {
386387a3c4SArunpravin kmem_cache_free(slab_blocks, block);
396387a3c4SArunpravin }
406387a3c4SArunpravin
list_insert_sorted(struct drm_buddy * mm,struct drm_buddy_block * block)415640e816SArunpravin Paneer Selvam static void list_insert_sorted(struct drm_buddy *mm,
425640e816SArunpravin Paneer Selvam struct drm_buddy_block *block)
435640e816SArunpravin Paneer Selvam {
445640e816SArunpravin Paneer Selvam struct drm_buddy_block *node;
455640e816SArunpravin Paneer Selvam struct list_head *head;
465640e816SArunpravin Paneer Selvam
475640e816SArunpravin Paneer Selvam head = &mm->free_list[drm_buddy_block_order(block)];
485640e816SArunpravin Paneer Selvam if (list_empty(head)) {
495640e816SArunpravin Paneer Selvam list_add(&block->link, head);
505640e816SArunpravin Paneer Selvam return;
515640e816SArunpravin Paneer Selvam }
525640e816SArunpravin Paneer Selvam
535640e816SArunpravin Paneer Selvam list_for_each_entry(node, head, link)
545640e816SArunpravin Paneer Selvam if (drm_buddy_block_offset(block) < drm_buddy_block_offset(node))
555640e816SArunpravin Paneer Selvam break;
565640e816SArunpravin Paneer Selvam
575640e816SArunpravin Paneer Selvam __list_add(&block->link, node->link.prev, &node->link);
585640e816SArunpravin Paneer Selvam }
595640e816SArunpravin Paneer Selvam
mark_allocated(struct drm_buddy_block * block)606387a3c4SArunpravin static void mark_allocated(struct drm_buddy_block *block)
616387a3c4SArunpravin {
626387a3c4SArunpravin block->header &= ~DRM_BUDDY_HEADER_STATE;
636387a3c4SArunpravin block->header |= DRM_BUDDY_ALLOCATED;
646387a3c4SArunpravin
656387a3c4SArunpravin list_del(&block->link);
666387a3c4SArunpravin }
676387a3c4SArunpravin
mark_free(struct drm_buddy * mm,struct drm_buddy_block * block)686387a3c4SArunpravin static void mark_free(struct drm_buddy *mm,
696387a3c4SArunpravin struct drm_buddy_block *block)
706387a3c4SArunpravin {
716387a3c4SArunpravin block->header &= ~DRM_BUDDY_HEADER_STATE;
726387a3c4SArunpravin block->header |= DRM_BUDDY_FREE;
736387a3c4SArunpravin
745640e816SArunpravin Paneer Selvam list_insert_sorted(mm, block);
756387a3c4SArunpravin }
766387a3c4SArunpravin
mark_split(struct drm_buddy_block * block)776387a3c4SArunpravin static void mark_split(struct drm_buddy_block *block)
786387a3c4SArunpravin {
796387a3c4SArunpravin block->header &= ~DRM_BUDDY_HEADER_STATE;
806387a3c4SArunpravin block->header |= DRM_BUDDY_SPLIT;
816387a3c4SArunpravin
826387a3c4SArunpravin list_del(&block->link);
836387a3c4SArunpravin }
846387a3c4SArunpravin
856387a3c4SArunpravin /**
866387a3c4SArunpravin * drm_buddy_init - init memory manager
876387a3c4SArunpravin *
886387a3c4SArunpravin * @mm: DRM buddy manager to initialize
896387a3c4SArunpravin * @size: size in bytes to manage
906387a3c4SArunpravin * @chunk_size: minimum page size in bytes for our allocations
916387a3c4SArunpravin *
926387a3c4SArunpravin * Initializes the memory manager and its resources.
936387a3c4SArunpravin *
946387a3c4SArunpravin * Returns:
956387a3c4SArunpravin * 0 on success, error code on failure.
966387a3c4SArunpravin */
drm_buddy_init(struct drm_buddy * mm,u64 size,u64 chunk_size)976387a3c4SArunpravin int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
986387a3c4SArunpravin {
996387a3c4SArunpravin unsigned int i;
1006387a3c4SArunpravin u64 offset;
1016387a3c4SArunpravin
1026387a3c4SArunpravin if (size < chunk_size)
1036387a3c4SArunpravin return -EINVAL;
1046387a3c4SArunpravin
1056387a3c4SArunpravin if (chunk_size < PAGE_SIZE)
1066387a3c4SArunpravin return -EINVAL;
1076387a3c4SArunpravin
1086387a3c4SArunpravin if (!is_power_of_2(chunk_size))
1096387a3c4SArunpravin return -EINVAL;
1106387a3c4SArunpravin
1116387a3c4SArunpravin size = round_down(size, chunk_size);
1126387a3c4SArunpravin
1136387a3c4SArunpravin mm->size = size;
1146387a3c4SArunpravin mm->avail = size;
1156387a3c4SArunpravin mm->chunk_size = chunk_size;
1166387a3c4SArunpravin mm->max_order = ilog2(size) - ilog2(chunk_size);
1176387a3c4SArunpravin
1186387a3c4SArunpravin BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
1196387a3c4SArunpravin
1206387a3c4SArunpravin mm->free_list = kmalloc_array(mm->max_order + 1,
1216387a3c4SArunpravin sizeof(struct list_head),
1226387a3c4SArunpravin GFP_KERNEL);
1236387a3c4SArunpravin if (!mm->free_list)
1246387a3c4SArunpravin return -ENOMEM;
1256387a3c4SArunpravin
1266387a3c4SArunpravin for (i = 0; i <= mm->max_order; ++i)
1276387a3c4SArunpravin INIT_LIST_HEAD(&mm->free_list[i]);
1286387a3c4SArunpravin
1296387a3c4SArunpravin mm->n_roots = hweight64(size);
1306387a3c4SArunpravin
1316387a3c4SArunpravin mm->roots = kmalloc_array(mm->n_roots,
1326387a3c4SArunpravin sizeof(struct drm_buddy_block *),
1336387a3c4SArunpravin GFP_KERNEL);
1346387a3c4SArunpravin if (!mm->roots)
1356387a3c4SArunpravin goto out_free_list;
1366387a3c4SArunpravin
1376387a3c4SArunpravin offset = 0;
1386387a3c4SArunpravin i = 0;
1396387a3c4SArunpravin
1406387a3c4SArunpravin /*
1416387a3c4SArunpravin * Split into power-of-two blocks, in case we are given a size that is
1426387a3c4SArunpravin * not itself a power-of-two.
1436387a3c4SArunpravin */
1446387a3c4SArunpravin do {
1456387a3c4SArunpravin struct drm_buddy_block *root;
1466387a3c4SArunpravin unsigned int order;
1476387a3c4SArunpravin u64 root_size;
1486387a3c4SArunpravin
1494453545bSDavid Gow order = ilog2(size) - ilog2(chunk_size);
1504453545bSDavid Gow root_size = chunk_size << order;
1516387a3c4SArunpravin
1526387a3c4SArunpravin root = drm_block_alloc(mm, NULL, order, offset);
1536387a3c4SArunpravin if (!root)
1546387a3c4SArunpravin goto out_free_roots;
1556387a3c4SArunpravin
1566387a3c4SArunpravin mark_free(mm, root);
1576387a3c4SArunpravin
1586387a3c4SArunpravin BUG_ON(i > mm->max_order);
1596387a3c4SArunpravin BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
1606387a3c4SArunpravin
1616387a3c4SArunpravin mm->roots[i] = root;
1626387a3c4SArunpravin
1636387a3c4SArunpravin offset += root_size;
1646387a3c4SArunpravin size -= root_size;
1656387a3c4SArunpravin i++;
1666387a3c4SArunpravin } while (size);
1676387a3c4SArunpravin
1686387a3c4SArunpravin return 0;
1696387a3c4SArunpravin
1706387a3c4SArunpravin out_free_roots:
1716387a3c4SArunpravin while (i--)
1726387a3c4SArunpravin drm_block_free(mm, mm->roots[i]);
1736387a3c4SArunpravin kfree(mm->roots);
1746387a3c4SArunpravin out_free_list:
1756387a3c4SArunpravin kfree(mm->free_list);
1766387a3c4SArunpravin return -ENOMEM;
1776387a3c4SArunpravin }
1786387a3c4SArunpravin EXPORT_SYMBOL(drm_buddy_init);
1796387a3c4SArunpravin
1806387a3c4SArunpravin /**
1816387a3c4SArunpravin * drm_buddy_fini - tear down the memory manager
1826387a3c4SArunpravin *
1836387a3c4SArunpravin * @mm: DRM buddy manager to free
1846387a3c4SArunpravin *
1856387a3c4SArunpravin * Cleanup memory manager resources and the freelist
1866387a3c4SArunpravin */
drm_buddy_fini(struct drm_buddy * mm)1876387a3c4SArunpravin void drm_buddy_fini(struct drm_buddy *mm)
1886387a3c4SArunpravin {
1896387a3c4SArunpravin int i;
1906387a3c4SArunpravin
1916387a3c4SArunpravin for (i = 0; i < mm->n_roots; ++i) {
1926387a3c4SArunpravin WARN_ON(!drm_buddy_block_is_free(mm->roots[i]));
1936387a3c4SArunpravin drm_block_free(mm, mm->roots[i]);
1946387a3c4SArunpravin }
1956387a3c4SArunpravin
1966387a3c4SArunpravin WARN_ON(mm->avail != mm->size);
1976387a3c4SArunpravin
1986387a3c4SArunpravin kfree(mm->roots);
1996387a3c4SArunpravin kfree(mm->free_list);
2006387a3c4SArunpravin }
2016387a3c4SArunpravin EXPORT_SYMBOL(drm_buddy_fini);
2026387a3c4SArunpravin
split_block(struct drm_buddy * mm,struct drm_buddy_block * block)2036387a3c4SArunpravin static int split_block(struct drm_buddy *mm,
2046387a3c4SArunpravin struct drm_buddy_block *block)
2056387a3c4SArunpravin {
2066387a3c4SArunpravin unsigned int block_order = drm_buddy_block_order(block) - 1;
2076387a3c4SArunpravin u64 offset = drm_buddy_block_offset(block);
2086387a3c4SArunpravin
2096387a3c4SArunpravin BUG_ON(!drm_buddy_block_is_free(block));
2106387a3c4SArunpravin BUG_ON(!drm_buddy_block_order(block));
2116387a3c4SArunpravin
2126387a3c4SArunpravin block->left = drm_block_alloc(mm, block, block_order, offset);
2136387a3c4SArunpravin if (!block->left)
2146387a3c4SArunpravin return -ENOMEM;
2156387a3c4SArunpravin
2166387a3c4SArunpravin block->right = drm_block_alloc(mm, block, block_order,
2176387a3c4SArunpravin offset + (mm->chunk_size << block_order));
2186387a3c4SArunpravin if (!block->right) {
2196387a3c4SArunpravin drm_block_free(mm, block->left);
2206387a3c4SArunpravin return -ENOMEM;
2216387a3c4SArunpravin }
2226387a3c4SArunpravin
2236387a3c4SArunpravin mark_free(mm, block->left);
2246387a3c4SArunpravin mark_free(mm, block->right);
2256387a3c4SArunpravin
2266387a3c4SArunpravin mark_split(block);
2276387a3c4SArunpravin
2286387a3c4SArunpravin return 0;
2296387a3c4SArunpravin }
2306387a3c4SArunpravin
2316387a3c4SArunpravin static struct drm_buddy_block *
__get_buddy(struct drm_buddy_block * block)23292937f17SArunpravin __get_buddy(struct drm_buddy_block *block)
2336387a3c4SArunpravin {
2346387a3c4SArunpravin struct drm_buddy_block *parent;
2356387a3c4SArunpravin
2366387a3c4SArunpravin parent = block->parent;
2376387a3c4SArunpravin if (!parent)
2386387a3c4SArunpravin return NULL;
2396387a3c4SArunpravin
2406387a3c4SArunpravin if (parent->left == block)
2416387a3c4SArunpravin return parent->right;
2426387a3c4SArunpravin
2436387a3c4SArunpravin return parent->left;
2446387a3c4SArunpravin }
2456387a3c4SArunpravin
24692937f17SArunpravin /**
24792937f17SArunpravin * drm_get_buddy - get buddy address
24892937f17SArunpravin *
24992937f17SArunpravin * @block: DRM buddy block
25092937f17SArunpravin *
25192937f17SArunpravin * Returns the corresponding buddy block for @block, or NULL
25292937f17SArunpravin * if this is a root block and can't be merged further.
25392937f17SArunpravin * Requires some kind of locking to protect against
25492937f17SArunpravin * any concurrent allocate and free operations.
25592937f17SArunpravin */
25692937f17SArunpravin struct drm_buddy_block *
drm_get_buddy(struct drm_buddy_block * block)25792937f17SArunpravin drm_get_buddy(struct drm_buddy_block *block)
25892937f17SArunpravin {
25992937f17SArunpravin return __get_buddy(block);
26092937f17SArunpravin }
26192937f17SArunpravin EXPORT_SYMBOL(drm_get_buddy);
26292937f17SArunpravin
__drm_buddy_free(struct drm_buddy * mm,struct drm_buddy_block * block)2636387a3c4SArunpravin static void __drm_buddy_free(struct drm_buddy *mm,
2646387a3c4SArunpravin struct drm_buddy_block *block)
2656387a3c4SArunpravin {
2666387a3c4SArunpravin struct drm_buddy_block *parent;
2676387a3c4SArunpravin
2686387a3c4SArunpravin while ((parent = block->parent)) {
2696387a3c4SArunpravin struct drm_buddy_block *buddy;
2706387a3c4SArunpravin
27192937f17SArunpravin buddy = __get_buddy(block);
2726387a3c4SArunpravin
2736387a3c4SArunpravin if (!drm_buddy_block_is_free(buddy))
2746387a3c4SArunpravin break;
2756387a3c4SArunpravin
2766387a3c4SArunpravin list_del(&buddy->link);
2776387a3c4SArunpravin
2786387a3c4SArunpravin drm_block_free(mm, block);
2796387a3c4SArunpravin drm_block_free(mm, buddy);
2806387a3c4SArunpravin
2816387a3c4SArunpravin block = parent;
2826387a3c4SArunpravin }
2836387a3c4SArunpravin
2846387a3c4SArunpravin mark_free(mm, block);
2856387a3c4SArunpravin }
2866387a3c4SArunpravin
2876387a3c4SArunpravin /**
2886387a3c4SArunpravin * drm_buddy_free_block - free a block
2896387a3c4SArunpravin *
2906387a3c4SArunpravin * @mm: DRM buddy manager
2916387a3c4SArunpravin * @block: block to be freed
2926387a3c4SArunpravin */
drm_buddy_free_block(struct drm_buddy * mm,struct drm_buddy_block * block)2936387a3c4SArunpravin void drm_buddy_free_block(struct drm_buddy *mm,
2946387a3c4SArunpravin struct drm_buddy_block *block)
2956387a3c4SArunpravin {
2966387a3c4SArunpravin BUG_ON(!drm_buddy_block_is_allocated(block));
2976387a3c4SArunpravin mm->avail += drm_buddy_block_size(mm, block);
2986387a3c4SArunpravin __drm_buddy_free(mm, block);
2996387a3c4SArunpravin }
3006387a3c4SArunpravin EXPORT_SYMBOL(drm_buddy_free_block);
3016387a3c4SArunpravin
3026387a3c4SArunpravin /**
3036387a3c4SArunpravin * drm_buddy_free_list - free blocks
3046387a3c4SArunpravin *
3056387a3c4SArunpravin * @mm: DRM buddy manager
3066387a3c4SArunpravin * @objects: input list head to free blocks
3076387a3c4SArunpravin */
drm_buddy_free_list(struct drm_buddy * mm,struct list_head * objects)3086387a3c4SArunpravin void drm_buddy_free_list(struct drm_buddy *mm, struct list_head *objects)
3096387a3c4SArunpravin {
3106387a3c4SArunpravin struct drm_buddy_block *block, *on;
3116387a3c4SArunpravin
3126387a3c4SArunpravin list_for_each_entry_safe(block, on, objects, link) {
3136387a3c4SArunpravin drm_buddy_free_block(mm, block);
3146387a3c4SArunpravin cond_resched();
3156387a3c4SArunpravin }
3166387a3c4SArunpravin INIT_LIST_HEAD(objects);
3176387a3c4SArunpravin }
3186387a3c4SArunpravin EXPORT_SYMBOL(drm_buddy_free_list);
3196387a3c4SArunpravin
overlaps(u64 s1,u64 e1,u64 s2,u64 e2)320afea229fSArunpravin static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
321afea229fSArunpravin {
322afea229fSArunpravin return s1 <= e2 && e1 >= s2;
323afea229fSArunpravin }
324afea229fSArunpravin
contains(u64 s1,u64 e1,u64 s2,u64 e2)325afea229fSArunpravin static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
326afea229fSArunpravin {
327afea229fSArunpravin return s1 <= s2 && e1 >= e2;
328afea229fSArunpravin }
329afea229fSArunpravin
330afea229fSArunpravin static struct drm_buddy_block *
alloc_range_bias(struct drm_buddy * mm,u64 start,u64 end,unsigned int order)331afea229fSArunpravin alloc_range_bias(struct drm_buddy *mm,
332afea229fSArunpravin u64 start, u64 end,
333afea229fSArunpravin unsigned int order)
334afea229fSArunpravin {
335*5e476625SMatthew Auld u64 req_size = mm->chunk_size << order;
336afea229fSArunpravin struct drm_buddy_block *block;
337afea229fSArunpravin struct drm_buddy_block *buddy;
338afea229fSArunpravin LIST_HEAD(dfs);
339afea229fSArunpravin int err;
340afea229fSArunpravin int i;
341afea229fSArunpravin
342afea229fSArunpravin end = end - 1;
343afea229fSArunpravin
344afea229fSArunpravin for (i = 0; i < mm->n_roots; ++i)
345afea229fSArunpravin list_add_tail(&mm->roots[i]->tmp_link, &dfs);
346afea229fSArunpravin
347afea229fSArunpravin do {
348afea229fSArunpravin u64 block_start;
349afea229fSArunpravin u64 block_end;
350afea229fSArunpravin
351afea229fSArunpravin block = list_first_entry_or_null(&dfs,
352afea229fSArunpravin struct drm_buddy_block,
353afea229fSArunpravin tmp_link);
354afea229fSArunpravin if (!block)
355afea229fSArunpravin break;
356afea229fSArunpravin
357afea229fSArunpravin list_del(&block->tmp_link);
358afea229fSArunpravin
359afea229fSArunpravin if (drm_buddy_block_order(block) < order)
360afea229fSArunpravin continue;
361afea229fSArunpravin
362afea229fSArunpravin block_start = drm_buddy_block_offset(block);
363afea229fSArunpravin block_end = block_start + drm_buddy_block_size(mm, block) - 1;
364afea229fSArunpravin
365afea229fSArunpravin if (!overlaps(start, end, block_start, block_end))
366afea229fSArunpravin continue;
367afea229fSArunpravin
368afea229fSArunpravin if (drm_buddy_block_is_allocated(block))
369afea229fSArunpravin continue;
370afea229fSArunpravin
371*5e476625SMatthew Auld if (block_start < start || block_end > end) {
372*5e476625SMatthew Auld u64 adjusted_start = max(block_start, start);
373*5e476625SMatthew Auld u64 adjusted_end = min(block_end, end);
374*5e476625SMatthew Auld
375*5e476625SMatthew Auld if (round_down(adjusted_end + 1, req_size) <=
376*5e476625SMatthew Auld round_up(adjusted_start, req_size))
377*5e476625SMatthew Auld continue;
378*5e476625SMatthew Auld }
379*5e476625SMatthew Auld
380afea229fSArunpravin if (contains(start, end, block_start, block_end) &&
381afea229fSArunpravin order == drm_buddy_block_order(block)) {
382afea229fSArunpravin /*
383afea229fSArunpravin * Find the free block within the range.
3846387a3c4SArunpravin */
385afea229fSArunpravin if (drm_buddy_block_is_free(block))
386afea229fSArunpravin return block;
387afea229fSArunpravin
388afea229fSArunpravin continue;
389afea229fSArunpravin }
390afea229fSArunpravin
391afea229fSArunpravin if (!drm_buddy_block_is_split(block)) {
392afea229fSArunpravin err = split_block(mm, block);
393afea229fSArunpravin if (unlikely(err))
394afea229fSArunpravin goto err_undo;
395afea229fSArunpravin }
396afea229fSArunpravin
397afea229fSArunpravin list_add(&block->right->tmp_link, &dfs);
398afea229fSArunpravin list_add(&block->left->tmp_link, &dfs);
399afea229fSArunpravin } while (1);
400afea229fSArunpravin
401afea229fSArunpravin return ERR_PTR(-ENOSPC);
402afea229fSArunpravin
403afea229fSArunpravin err_undo:
404afea229fSArunpravin /*
405afea229fSArunpravin * We really don't want to leave around a bunch of split blocks, since
406afea229fSArunpravin * bigger is better, so make sure we merge everything back before we
407afea229fSArunpravin * free the allocated blocks.
408afea229fSArunpravin */
40992937f17SArunpravin buddy = __get_buddy(block);
410afea229fSArunpravin if (buddy &&
411afea229fSArunpravin (drm_buddy_block_is_free(block) &&
412afea229fSArunpravin drm_buddy_block_is_free(buddy)))
413afea229fSArunpravin __drm_buddy_free(mm, block);
414afea229fSArunpravin return ERR_PTR(err);
415afea229fSArunpravin }
416afea229fSArunpravin
417afea229fSArunpravin static struct drm_buddy_block *
get_maxblock(struct drm_buddy * mm,unsigned int order)4185640e816SArunpravin Paneer Selvam get_maxblock(struct drm_buddy *mm, unsigned int order)
419476e4063SArunpravin {
420476e4063SArunpravin struct drm_buddy_block *max_block = NULL, *node;
4215640e816SArunpravin Paneer Selvam unsigned int i;
422476e4063SArunpravin
4235640e816SArunpravin Paneer Selvam for (i = order; i <= mm->max_order; ++i) {
4245640e816SArunpravin Paneer Selvam if (!list_empty(&mm->free_list[i])) {
4255640e816SArunpravin Paneer Selvam node = list_last_entry(&mm->free_list[i],
426476e4063SArunpravin struct drm_buddy_block,
427476e4063SArunpravin link);
4285640e816SArunpravin Paneer Selvam if (!max_block) {
429476e4063SArunpravin max_block = node;
4305640e816SArunpravin Paneer Selvam continue;
4315640e816SArunpravin Paneer Selvam }
4325640e816SArunpravin Paneer Selvam
4335640e816SArunpravin Paneer Selvam if (drm_buddy_block_offset(node) >
4345640e816SArunpravin Paneer Selvam drm_buddy_block_offset(max_block)) {
4355640e816SArunpravin Paneer Selvam max_block = node;
4365640e816SArunpravin Paneer Selvam }
4375640e816SArunpravin Paneer Selvam }
438476e4063SArunpravin }
439476e4063SArunpravin
440476e4063SArunpravin return max_block;
441476e4063SArunpravin }
442476e4063SArunpravin
443476e4063SArunpravin static struct drm_buddy_block *
alloc_from_freelist(struct drm_buddy * mm,unsigned int order,unsigned long flags)444afea229fSArunpravin alloc_from_freelist(struct drm_buddy *mm,
445afea229fSArunpravin unsigned int order,
446afea229fSArunpravin unsigned long flags)
4476387a3c4SArunpravin {
4486387a3c4SArunpravin struct drm_buddy_block *block = NULL;
4495640e816SArunpravin Paneer Selvam unsigned int tmp;
4506387a3c4SArunpravin int err;
4516387a3c4SArunpravin
452476e4063SArunpravin if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
4535640e816SArunpravin Paneer Selvam block = get_maxblock(mm, order);
454476e4063SArunpravin if (block)
4555640e816SArunpravin Paneer Selvam /* Store the obtained block order */
4565640e816SArunpravin Paneer Selvam tmp = drm_buddy_block_order(block);
457476e4063SArunpravin } else {
4585640e816SArunpravin Paneer Selvam for (tmp = order; tmp <= mm->max_order; ++tmp) {
4595640e816SArunpravin Paneer Selvam if (!list_empty(&mm->free_list[tmp])) {
4605640e816SArunpravin Paneer Selvam block = list_last_entry(&mm->free_list[tmp],
4616387a3c4SArunpravin struct drm_buddy_block,
4626387a3c4SArunpravin link);
4636387a3c4SArunpravin if (block)
4646387a3c4SArunpravin break;
4656387a3c4SArunpravin }
466476e4063SArunpravin }
4675640e816SArunpravin Paneer Selvam }
4686387a3c4SArunpravin
4696387a3c4SArunpravin if (!block)
4706387a3c4SArunpravin return ERR_PTR(-ENOSPC);
4716387a3c4SArunpravin
4726387a3c4SArunpravin BUG_ON(!drm_buddy_block_is_free(block));
4736387a3c4SArunpravin
4745640e816SArunpravin Paneer Selvam while (tmp != order) {
4756387a3c4SArunpravin err = split_block(mm, block);
4766387a3c4SArunpravin if (unlikely(err))
477afea229fSArunpravin goto err_undo;
4786387a3c4SArunpravin
479afea229fSArunpravin block = block->right;
4805640e816SArunpravin Paneer Selvam tmp--;
4816387a3c4SArunpravin }
4826387a3c4SArunpravin return block;
4836387a3c4SArunpravin
484afea229fSArunpravin err_undo:
4855640e816SArunpravin Paneer Selvam if (tmp != order)
4866387a3c4SArunpravin __drm_buddy_free(mm, block);
4876387a3c4SArunpravin return ERR_PTR(err);
4886387a3c4SArunpravin }
4896387a3c4SArunpravin
__alloc_range(struct drm_buddy * mm,struct list_head * dfs,u64 start,u64 size,struct list_head * blocks)490afea229fSArunpravin static int __alloc_range(struct drm_buddy *mm,
491afea229fSArunpravin struct list_head *dfs,
492afea229fSArunpravin u64 start, u64 size,
493afea229fSArunpravin struct list_head *blocks)
4946387a3c4SArunpravin {
4956387a3c4SArunpravin struct drm_buddy_block *block;
4966387a3c4SArunpravin struct drm_buddy_block *buddy;
4976387a3c4SArunpravin LIST_HEAD(allocated);
4986387a3c4SArunpravin u64 end;
4996387a3c4SArunpravin int err;
5006387a3c4SArunpravin
5016387a3c4SArunpravin end = start + size - 1;
5026387a3c4SArunpravin
5036387a3c4SArunpravin do {
5046387a3c4SArunpravin u64 block_start;
5056387a3c4SArunpravin u64 block_end;
5066387a3c4SArunpravin
507afea229fSArunpravin block = list_first_entry_or_null(dfs,
5086387a3c4SArunpravin struct drm_buddy_block,
5096387a3c4SArunpravin tmp_link);
5106387a3c4SArunpravin if (!block)
5116387a3c4SArunpravin break;
5126387a3c4SArunpravin
5136387a3c4SArunpravin list_del(&block->tmp_link);
5146387a3c4SArunpravin
5156387a3c4SArunpravin block_start = drm_buddy_block_offset(block);
5166387a3c4SArunpravin block_end = block_start + drm_buddy_block_size(mm, block) - 1;
5176387a3c4SArunpravin
5186387a3c4SArunpravin if (!overlaps(start, end, block_start, block_end))
5196387a3c4SArunpravin continue;
5206387a3c4SArunpravin
5216387a3c4SArunpravin if (drm_buddy_block_is_allocated(block)) {
5226387a3c4SArunpravin err = -ENOSPC;
5236387a3c4SArunpravin goto err_free;
5246387a3c4SArunpravin }
5256387a3c4SArunpravin
5266387a3c4SArunpravin if (contains(start, end, block_start, block_end)) {
5276387a3c4SArunpravin if (!drm_buddy_block_is_free(block)) {
5286387a3c4SArunpravin err = -ENOSPC;
5296387a3c4SArunpravin goto err_free;
5306387a3c4SArunpravin }
5316387a3c4SArunpravin
5326387a3c4SArunpravin mark_allocated(block);
5336387a3c4SArunpravin mm->avail -= drm_buddy_block_size(mm, block);
5346387a3c4SArunpravin list_add_tail(&block->link, &allocated);
5356387a3c4SArunpravin continue;
5366387a3c4SArunpravin }
5376387a3c4SArunpravin
5386387a3c4SArunpravin if (!drm_buddy_block_is_split(block)) {
5396387a3c4SArunpravin err = split_block(mm, block);
5406387a3c4SArunpravin if (unlikely(err))
5416387a3c4SArunpravin goto err_undo;
5426387a3c4SArunpravin }
5436387a3c4SArunpravin
544afea229fSArunpravin list_add(&block->right->tmp_link, dfs);
545afea229fSArunpravin list_add(&block->left->tmp_link, dfs);
5466387a3c4SArunpravin } while (1);
5476387a3c4SArunpravin
5486387a3c4SArunpravin list_splice_tail(&allocated, blocks);
5496387a3c4SArunpravin return 0;
5506387a3c4SArunpravin
5516387a3c4SArunpravin err_undo:
5526387a3c4SArunpravin /*
5536387a3c4SArunpravin * We really don't want to leave around a bunch of split blocks, since
5546387a3c4SArunpravin * bigger is better, so make sure we merge everything back before we
5556387a3c4SArunpravin * free the allocated blocks.
5566387a3c4SArunpravin */
55792937f17SArunpravin buddy = __get_buddy(block);
5586387a3c4SArunpravin if (buddy &&
5596387a3c4SArunpravin (drm_buddy_block_is_free(block) &&
5606387a3c4SArunpravin drm_buddy_block_is_free(buddy)))
5616387a3c4SArunpravin __drm_buddy_free(mm, block);
5626387a3c4SArunpravin
5636387a3c4SArunpravin err_free:
5646387a3c4SArunpravin drm_buddy_free_list(mm, &allocated);
5656387a3c4SArunpravin return err;
5666387a3c4SArunpravin }
567afea229fSArunpravin
__drm_buddy_alloc_range(struct drm_buddy * mm,u64 start,u64 size,struct list_head * blocks)568afea229fSArunpravin static int __drm_buddy_alloc_range(struct drm_buddy *mm,
569afea229fSArunpravin u64 start,
570afea229fSArunpravin u64 size,
571afea229fSArunpravin struct list_head *blocks)
572afea229fSArunpravin {
573afea229fSArunpravin LIST_HEAD(dfs);
574afea229fSArunpravin int i;
575afea229fSArunpravin
576afea229fSArunpravin for (i = 0; i < mm->n_roots; ++i)
577afea229fSArunpravin list_add_tail(&mm->roots[i]->tmp_link, &dfs);
578afea229fSArunpravin
579afea229fSArunpravin return __alloc_range(mm, &dfs, start, size, blocks);
580afea229fSArunpravin }
581afea229fSArunpravin
582afea229fSArunpravin /**
58395ee2a8bSArunpravin * drm_buddy_block_trim - free unused pages
58495ee2a8bSArunpravin *
58595ee2a8bSArunpravin * @mm: DRM buddy manager
58695ee2a8bSArunpravin * @new_size: original size requested
58795ee2a8bSArunpravin * @blocks: Input and output list of allocated blocks.
58895ee2a8bSArunpravin * MUST contain single block as input to be trimmed.
58995ee2a8bSArunpravin * On success will contain the newly allocated blocks
59095ee2a8bSArunpravin * making up the @new_size. Blocks always appear in
59195ee2a8bSArunpravin * ascending order
59295ee2a8bSArunpravin *
59395ee2a8bSArunpravin * For contiguous allocation, we round up the size to the nearest
59495ee2a8bSArunpravin * power of two value, drivers consume *actual* size, so remaining
59595ee2a8bSArunpravin * portions are unused and can be optionally freed with this function
59695ee2a8bSArunpravin *
59795ee2a8bSArunpravin * Returns:
59895ee2a8bSArunpravin * 0 on success, error code on failure.
59995ee2a8bSArunpravin */
drm_buddy_block_trim(struct drm_buddy * mm,u64 new_size,struct list_head * blocks)60095ee2a8bSArunpravin int drm_buddy_block_trim(struct drm_buddy *mm,
60195ee2a8bSArunpravin u64 new_size,
60295ee2a8bSArunpravin struct list_head *blocks)
60395ee2a8bSArunpravin {
60495ee2a8bSArunpravin struct drm_buddy_block *parent;
60595ee2a8bSArunpravin struct drm_buddy_block *block;
60695ee2a8bSArunpravin LIST_HEAD(dfs);
60795ee2a8bSArunpravin u64 new_start;
60895ee2a8bSArunpravin int err;
60995ee2a8bSArunpravin
61095ee2a8bSArunpravin if (!list_is_singular(blocks))
61195ee2a8bSArunpravin return -EINVAL;
61295ee2a8bSArunpravin
61395ee2a8bSArunpravin block = list_first_entry(blocks,
61495ee2a8bSArunpravin struct drm_buddy_block,
61595ee2a8bSArunpravin link);
61695ee2a8bSArunpravin
61795ee2a8bSArunpravin if (WARN_ON(!drm_buddy_block_is_allocated(block)))
61895ee2a8bSArunpravin return -EINVAL;
61995ee2a8bSArunpravin
62095ee2a8bSArunpravin if (new_size > drm_buddy_block_size(mm, block))
62195ee2a8bSArunpravin return -EINVAL;
62295ee2a8bSArunpravin
62395ee2a8bSArunpravin if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
62495ee2a8bSArunpravin return -EINVAL;
62595ee2a8bSArunpravin
62695ee2a8bSArunpravin if (new_size == drm_buddy_block_size(mm, block))
62795ee2a8bSArunpravin return 0;
62895ee2a8bSArunpravin
62995ee2a8bSArunpravin list_del(&block->link);
63095ee2a8bSArunpravin mark_free(mm, block);
63195ee2a8bSArunpravin mm->avail += drm_buddy_block_size(mm, block);
63295ee2a8bSArunpravin
63395ee2a8bSArunpravin /* Prevent recursively freeing this node */
63495ee2a8bSArunpravin parent = block->parent;
63595ee2a8bSArunpravin block->parent = NULL;
63695ee2a8bSArunpravin
63795ee2a8bSArunpravin new_start = drm_buddy_block_offset(block);
63895ee2a8bSArunpravin list_add(&block->tmp_link, &dfs);
63995ee2a8bSArunpravin err = __alloc_range(mm, &dfs, new_start, new_size, blocks);
64095ee2a8bSArunpravin if (err) {
64195ee2a8bSArunpravin mark_allocated(block);
64295ee2a8bSArunpravin mm->avail -= drm_buddy_block_size(mm, block);
64395ee2a8bSArunpravin list_add(&block->link, blocks);
64495ee2a8bSArunpravin }
64595ee2a8bSArunpravin
64695ee2a8bSArunpravin block->parent = parent;
64795ee2a8bSArunpravin return err;
64895ee2a8bSArunpravin }
64995ee2a8bSArunpravin EXPORT_SYMBOL(drm_buddy_block_trim);
65095ee2a8bSArunpravin
65195ee2a8bSArunpravin /**
652afea229fSArunpravin * drm_buddy_alloc_blocks - allocate power-of-two blocks
653afea229fSArunpravin *
654afea229fSArunpravin * @mm: DRM buddy manager to allocate from
655afea229fSArunpravin * @start: start of the allowed range for this block
656afea229fSArunpravin * @end: end of the allowed range for this block
657afea229fSArunpravin * @size: size of the allocation
658afea229fSArunpravin * @min_page_size: alignment of the allocation
659afea229fSArunpravin * @blocks: output list head to add allocated blocks
660afea229fSArunpravin * @flags: DRM_BUDDY_*_ALLOCATION flags
661afea229fSArunpravin *
662afea229fSArunpravin * alloc_range_bias() called on range limitations, which traverses
663afea229fSArunpravin * the tree and returns the desired block.
664afea229fSArunpravin *
665afea229fSArunpravin * alloc_from_freelist() called when *no* range restrictions
666afea229fSArunpravin * are enforced, which picks the block from the freelist.
667afea229fSArunpravin *
668afea229fSArunpravin * Returns:
669afea229fSArunpravin * 0 on success, error code on failure.
670afea229fSArunpravin */
drm_buddy_alloc_blocks(struct drm_buddy * mm,u64 start,u64 end,u64 size,u64 min_page_size,struct list_head * blocks,unsigned long flags)671afea229fSArunpravin int drm_buddy_alloc_blocks(struct drm_buddy *mm,
672afea229fSArunpravin u64 start, u64 end, u64 size,
673afea229fSArunpravin u64 min_page_size,
674afea229fSArunpravin struct list_head *blocks,
675afea229fSArunpravin unsigned long flags)
676afea229fSArunpravin {
677afea229fSArunpravin struct drm_buddy_block *block = NULL;
678afea229fSArunpravin unsigned int min_order, order;
679afea229fSArunpravin unsigned long pages;
680afea229fSArunpravin LIST_HEAD(allocated);
681afea229fSArunpravin int err;
682afea229fSArunpravin
683afea229fSArunpravin if (size < mm->chunk_size)
684afea229fSArunpravin return -EINVAL;
685afea229fSArunpravin
686afea229fSArunpravin if (min_page_size < mm->chunk_size)
687afea229fSArunpravin return -EINVAL;
688afea229fSArunpravin
689afea229fSArunpravin if (!is_power_of_2(min_page_size))
690afea229fSArunpravin return -EINVAL;
691afea229fSArunpravin
692afea229fSArunpravin if (!IS_ALIGNED(start | end | size, mm->chunk_size))
693afea229fSArunpravin return -EINVAL;
694afea229fSArunpravin
695afea229fSArunpravin if (end > mm->size)
696afea229fSArunpravin return -EINVAL;
697afea229fSArunpravin
698afea229fSArunpravin if (range_overflows(start, size, mm->size))
699afea229fSArunpravin return -EINVAL;
700afea229fSArunpravin
701afea229fSArunpravin /* Actual range allocation */
702afea229fSArunpravin if (start + size == end)
703afea229fSArunpravin return __drm_buddy_alloc_range(mm, start, size, blocks);
704afea229fSArunpravin
7055f778760SArunpravin Paneer Selvam if (!IS_ALIGNED(size, min_page_size))
7065f778760SArunpravin Paneer Selvam return -EINVAL;
7075f778760SArunpravin Paneer Selvam
708afea229fSArunpravin pages = size >> ilog2(mm->chunk_size);
709afea229fSArunpravin order = fls(pages) - 1;
710afea229fSArunpravin min_order = ilog2(min_page_size) - ilog2(mm->chunk_size);
711afea229fSArunpravin
712afea229fSArunpravin do {
713afea229fSArunpravin order = min(order, (unsigned int)fls(pages) - 1);
714afea229fSArunpravin BUG_ON(order > mm->max_order);
715afea229fSArunpravin BUG_ON(order < min_order);
716afea229fSArunpravin
717afea229fSArunpravin do {
718afea229fSArunpravin if (flags & DRM_BUDDY_RANGE_ALLOCATION)
719afea229fSArunpravin /* Allocate traversing within the range */
720afea229fSArunpravin block = alloc_range_bias(mm, start, end, order);
721afea229fSArunpravin else
722afea229fSArunpravin /* Allocate from freelist */
723afea229fSArunpravin block = alloc_from_freelist(mm, order, flags);
724afea229fSArunpravin
725afea229fSArunpravin if (!IS_ERR(block))
726afea229fSArunpravin break;
727afea229fSArunpravin
728afea229fSArunpravin if (order-- == min_order) {
729afea229fSArunpravin err = -ENOSPC;
730afea229fSArunpravin goto err_free;
731afea229fSArunpravin }
732afea229fSArunpravin } while (1);
733afea229fSArunpravin
734afea229fSArunpravin mark_allocated(block);
735afea229fSArunpravin mm->avail -= drm_buddy_block_size(mm, block);
736afea229fSArunpravin kmemleak_update_trace(block);
737afea229fSArunpravin list_add_tail(&block->link, &allocated);
738afea229fSArunpravin
739afea229fSArunpravin pages -= BIT(order);
740afea229fSArunpravin
741afea229fSArunpravin if (!pages)
742afea229fSArunpravin break;
743afea229fSArunpravin } while (1);
744afea229fSArunpravin
745afea229fSArunpravin list_splice_tail(&allocated, blocks);
746afea229fSArunpravin return 0;
747afea229fSArunpravin
748afea229fSArunpravin err_free:
749afea229fSArunpravin drm_buddy_free_list(mm, &allocated);
750afea229fSArunpravin return err;
751afea229fSArunpravin }
752afea229fSArunpravin EXPORT_SYMBOL(drm_buddy_alloc_blocks);
7536387a3c4SArunpravin
7546387a3c4SArunpravin /**
7556387a3c4SArunpravin * drm_buddy_block_print - print block information
7566387a3c4SArunpravin *
7576387a3c4SArunpravin * @mm: DRM buddy manager
7586387a3c4SArunpravin * @block: DRM buddy block
7596387a3c4SArunpravin * @p: DRM printer to use
7606387a3c4SArunpravin */
drm_buddy_block_print(struct drm_buddy * mm,struct drm_buddy_block * block,struct drm_printer * p)7616387a3c4SArunpravin void drm_buddy_block_print(struct drm_buddy *mm,
7626387a3c4SArunpravin struct drm_buddy_block *block,
7636387a3c4SArunpravin struct drm_printer *p)
7646387a3c4SArunpravin {
7656387a3c4SArunpravin u64 start = drm_buddy_block_offset(block);
7666387a3c4SArunpravin u64 size = drm_buddy_block_size(mm, block);
7676387a3c4SArunpravin
7686387a3c4SArunpravin drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
7696387a3c4SArunpravin }
7706387a3c4SArunpravin EXPORT_SYMBOL(drm_buddy_block_print);
7716387a3c4SArunpravin
7726387a3c4SArunpravin /**
7736387a3c4SArunpravin * drm_buddy_print - print allocator state
7746387a3c4SArunpravin *
7756387a3c4SArunpravin * @mm: DRM buddy manager
7766387a3c4SArunpravin * @p: DRM printer to use
7776387a3c4SArunpravin */
drm_buddy_print(struct drm_buddy * mm,struct drm_printer * p)7786387a3c4SArunpravin void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
7796387a3c4SArunpravin {
7806387a3c4SArunpravin int order;
7816387a3c4SArunpravin
7826387a3c4SArunpravin drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB\n",
7836387a3c4SArunpravin mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20);
7846387a3c4SArunpravin
7856387a3c4SArunpravin for (order = mm->max_order; order >= 0; order--) {
7866387a3c4SArunpravin struct drm_buddy_block *block;
7876387a3c4SArunpravin u64 count = 0, free;
7886387a3c4SArunpravin
7896387a3c4SArunpravin list_for_each_entry(block, &mm->free_list[order], link) {
7906387a3c4SArunpravin BUG_ON(!drm_buddy_block_is_free(block));
7916387a3c4SArunpravin count++;
7926387a3c4SArunpravin }
7936387a3c4SArunpravin
794cd11589bSMa Jun drm_printf(p, "order-%2d ", order);
7956387a3c4SArunpravin
7966387a3c4SArunpravin free = count * (mm->chunk_size << order);
7976387a3c4SArunpravin if (free < SZ_1M)
798cd11589bSMa Jun drm_printf(p, "free: %8llu KiB", free >> 10);
7996387a3c4SArunpravin else
800cd11589bSMa Jun drm_printf(p, "free: %8llu MiB", free >> 20);
8016387a3c4SArunpravin
802cd11589bSMa Jun drm_printf(p, ", blocks: %llu\n", count);
8036387a3c4SArunpravin }
8046387a3c4SArunpravin }
8056387a3c4SArunpravin EXPORT_SYMBOL(drm_buddy_print);
8066387a3c4SArunpravin
drm_buddy_module_exit(void)8076387a3c4SArunpravin static void drm_buddy_module_exit(void)
8086387a3c4SArunpravin {
8096387a3c4SArunpravin kmem_cache_destroy(slab_blocks);
8106387a3c4SArunpravin }
8116387a3c4SArunpravin
drm_buddy_module_init(void)8126387a3c4SArunpravin static int __init drm_buddy_module_init(void)
8136387a3c4SArunpravin {
8146387a3c4SArunpravin slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
8156387a3c4SArunpravin if (!slab_blocks)
8166387a3c4SArunpravin return -ENOMEM;
8176387a3c4SArunpravin
8186387a3c4SArunpravin return 0;
8196387a3c4SArunpravin }
8206387a3c4SArunpravin
8216387a3c4SArunpravin module_init(drm_buddy_module_init);
8226387a3c4SArunpravin module_exit(drm_buddy_module_exit);
8236387a3c4SArunpravin
8246387a3c4SArunpravin MODULE_DESCRIPTION("DRM Buddy Allocator");
8256387a3c4SArunpravin MODULE_LICENSE("Dual MIT/GPL");
826