11bb76ff1Sjsg // SPDX-License-Identifier: MIT
21bb76ff1Sjsg /*
31bb76ff1Sjsg * Copyright © 2021 Intel Corporation
41bb76ff1Sjsg */
51bb76ff1Sjsg
61bb76ff1Sjsg #include <linux/kmemleak.h>
71bb76ff1Sjsg #include <linux/module.h>
81bb76ff1Sjsg #include <linux/sizes.h>
91bb76ff1Sjsg
101bb76ff1Sjsg #include <sys/pool.h>
111bb76ff1Sjsg
121bb76ff1Sjsg #include <drm/drm_buddy.h>
131bb76ff1Sjsg
141bb76ff1Sjsg static struct pool slab_blocks;
151bb76ff1Sjsg
drm_block_alloc(struct drm_buddy * mm,struct drm_buddy_block * parent,unsigned int order,u64 offset)161bb76ff1Sjsg static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
171bb76ff1Sjsg struct drm_buddy_block *parent,
181bb76ff1Sjsg unsigned int order,
191bb76ff1Sjsg u64 offset)
201bb76ff1Sjsg {
211bb76ff1Sjsg struct drm_buddy_block *block;
221bb76ff1Sjsg
231bb76ff1Sjsg BUG_ON(order > DRM_BUDDY_MAX_ORDER);
241bb76ff1Sjsg
251bb76ff1Sjsg #ifdef __linux__
261bb76ff1Sjsg block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
271bb76ff1Sjsg #else
281bb76ff1Sjsg block = pool_get(&slab_blocks, PR_WAITOK | PR_ZERO);
291bb76ff1Sjsg #endif
301bb76ff1Sjsg if (!block)
311bb76ff1Sjsg return NULL;
321bb76ff1Sjsg
331bb76ff1Sjsg block->header = offset;
341bb76ff1Sjsg block->header |= order;
351bb76ff1Sjsg block->parent = parent;
361bb76ff1Sjsg
371bb76ff1Sjsg BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
381bb76ff1Sjsg return block;
391bb76ff1Sjsg }
401bb76ff1Sjsg
drm_block_free(struct drm_buddy * mm,struct drm_buddy_block * block)411bb76ff1Sjsg static void drm_block_free(struct drm_buddy *mm,
421bb76ff1Sjsg struct drm_buddy_block *block)
431bb76ff1Sjsg {
441bb76ff1Sjsg #ifdef __linux__
451bb76ff1Sjsg kmem_cache_free(slab_blocks, block);
461bb76ff1Sjsg #else
471bb76ff1Sjsg pool_put(&slab_blocks, block);
481bb76ff1Sjsg #endif
491bb76ff1Sjsg }
501bb76ff1Sjsg
list_insert_sorted(struct drm_buddy * mm,struct drm_buddy_block * block)5177f08424Sjsg static void list_insert_sorted(struct drm_buddy *mm,
5277f08424Sjsg struct drm_buddy_block *block)
5377f08424Sjsg {
5477f08424Sjsg struct drm_buddy_block *node;
5577f08424Sjsg struct list_head *head;
5677f08424Sjsg
5777f08424Sjsg head = &mm->free_list[drm_buddy_block_order(block)];
5877f08424Sjsg if (list_empty(head)) {
5977f08424Sjsg list_add(&block->link, head);
6077f08424Sjsg return;
6177f08424Sjsg }
6277f08424Sjsg
6377f08424Sjsg list_for_each_entry(node, head, link)
6477f08424Sjsg if (drm_buddy_block_offset(block) < drm_buddy_block_offset(node))
6577f08424Sjsg break;
6677f08424Sjsg
6777f08424Sjsg __list_add(&block->link, node->link.prev, &node->link);
6877f08424Sjsg }
6977f08424Sjsg
mark_allocated(struct drm_buddy_block * block)701bb76ff1Sjsg static void mark_allocated(struct drm_buddy_block *block)
711bb76ff1Sjsg {
721bb76ff1Sjsg block->header &= ~DRM_BUDDY_HEADER_STATE;
731bb76ff1Sjsg block->header |= DRM_BUDDY_ALLOCATED;
741bb76ff1Sjsg
751bb76ff1Sjsg list_del(&block->link);
761bb76ff1Sjsg }
771bb76ff1Sjsg
mark_free(struct drm_buddy * mm,struct drm_buddy_block * block)781bb76ff1Sjsg static void mark_free(struct drm_buddy *mm,
791bb76ff1Sjsg struct drm_buddy_block *block)
801bb76ff1Sjsg {
811bb76ff1Sjsg block->header &= ~DRM_BUDDY_HEADER_STATE;
821bb76ff1Sjsg block->header |= DRM_BUDDY_FREE;
831bb76ff1Sjsg
8477f08424Sjsg list_insert_sorted(mm, block);
851bb76ff1Sjsg }
861bb76ff1Sjsg
mark_split(struct drm_buddy_block * block)871bb76ff1Sjsg static void mark_split(struct drm_buddy_block *block)
881bb76ff1Sjsg {
891bb76ff1Sjsg block->header &= ~DRM_BUDDY_HEADER_STATE;
901bb76ff1Sjsg block->header |= DRM_BUDDY_SPLIT;
911bb76ff1Sjsg
921bb76ff1Sjsg list_del(&block->link);
931bb76ff1Sjsg }
941bb76ff1Sjsg
951bb76ff1Sjsg /**
961bb76ff1Sjsg * drm_buddy_init - init memory manager
971bb76ff1Sjsg *
981bb76ff1Sjsg * @mm: DRM buddy manager to initialize
991bb76ff1Sjsg * @size: size in bytes to manage
1001bb76ff1Sjsg * @chunk_size: minimum page size in bytes for our allocations
1011bb76ff1Sjsg *
1021bb76ff1Sjsg * Initializes the memory manager and its resources.
1031bb76ff1Sjsg *
1041bb76ff1Sjsg * Returns:
1051bb76ff1Sjsg * 0 on success, error code on failure.
1061bb76ff1Sjsg */
drm_buddy_init(struct drm_buddy * mm,u64 size,u64 chunk_size)1071bb76ff1Sjsg int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
1081bb76ff1Sjsg {
1091bb76ff1Sjsg unsigned int i;
1101bb76ff1Sjsg u64 offset;
1111bb76ff1Sjsg
1121bb76ff1Sjsg if (size < chunk_size)
1131bb76ff1Sjsg return -EINVAL;
1141bb76ff1Sjsg
1151bb76ff1Sjsg if (chunk_size < PAGE_SIZE)
1161bb76ff1Sjsg return -EINVAL;
1171bb76ff1Sjsg
1181bb76ff1Sjsg if (!is_power_of_2(chunk_size))
1191bb76ff1Sjsg return -EINVAL;
1201bb76ff1Sjsg
1211bb76ff1Sjsg size = round_down(size, chunk_size);
1221bb76ff1Sjsg
1231bb76ff1Sjsg mm->size = size;
1241bb76ff1Sjsg mm->avail = size;
1251bb76ff1Sjsg mm->chunk_size = chunk_size;
1261bb76ff1Sjsg mm->max_order = ilog2(size) - ilog2(chunk_size);
1271bb76ff1Sjsg
1281bb76ff1Sjsg BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
1291bb76ff1Sjsg
1301bb76ff1Sjsg mm->free_list = kmalloc_array(mm->max_order + 1,
1311bb76ff1Sjsg sizeof(struct list_head),
1321bb76ff1Sjsg GFP_KERNEL);
1331bb76ff1Sjsg if (!mm->free_list)
1341bb76ff1Sjsg return -ENOMEM;
1351bb76ff1Sjsg
1361bb76ff1Sjsg for (i = 0; i <= mm->max_order; ++i)
1371bb76ff1Sjsg INIT_LIST_HEAD(&mm->free_list[i]);
1381bb76ff1Sjsg
1391bb76ff1Sjsg mm->n_roots = hweight64(size);
1401bb76ff1Sjsg
1411bb76ff1Sjsg mm->roots = kmalloc_array(mm->n_roots,
1421bb76ff1Sjsg sizeof(struct drm_buddy_block *),
1431bb76ff1Sjsg GFP_KERNEL);
1441bb76ff1Sjsg if (!mm->roots)
1451bb76ff1Sjsg goto out_free_list;
1461bb76ff1Sjsg
1471bb76ff1Sjsg offset = 0;
1481bb76ff1Sjsg i = 0;
1491bb76ff1Sjsg
1501bb76ff1Sjsg /*
1511bb76ff1Sjsg * Split into power-of-two blocks, in case we are given a size that is
1521bb76ff1Sjsg * not itself a power-of-two.
1531bb76ff1Sjsg */
1541bb76ff1Sjsg do {
1551bb76ff1Sjsg struct drm_buddy_block *root;
1561bb76ff1Sjsg unsigned int order;
1571bb76ff1Sjsg u64 root_size;
1581bb76ff1Sjsg
1593e7f292eSjsg order = ilog2(size) - ilog2(chunk_size);
1603e7f292eSjsg root_size = chunk_size << order;
1611bb76ff1Sjsg
1621bb76ff1Sjsg root = drm_block_alloc(mm, NULL, order, offset);
1631bb76ff1Sjsg if (!root)
1641bb76ff1Sjsg goto out_free_roots;
1651bb76ff1Sjsg
1661bb76ff1Sjsg mark_free(mm, root);
1671bb76ff1Sjsg
1681bb76ff1Sjsg BUG_ON(i > mm->max_order);
1691bb76ff1Sjsg BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
1701bb76ff1Sjsg
1711bb76ff1Sjsg mm->roots[i] = root;
1721bb76ff1Sjsg
1731bb76ff1Sjsg offset += root_size;
1741bb76ff1Sjsg size -= root_size;
1751bb76ff1Sjsg i++;
1761bb76ff1Sjsg } while (size);
1771bb76ff1Sjsg
1781bb76ff1Sjsg return 0;
1791bb76ff1Sjsg
1801bb76ff1Sjsg out_free_roots:
1811bb76ff1Sjsg while (i--)
1821bb76ff1Sjsg drm_block_free(mm, mm->roots[i]);
1831bb76ff1Sjsg kfree(mm->roots);
1841bb76ff1Sjsg out_free_list:
1851bb76ff1Sjsg kfree(mm->free_list);
1861bb76ff1Sjsg return -ENOMEM;
1871bb76ff1Sjsg }
1881bb76ff1Sjsg EXPORT_SYMBOL(drm_buddy_init);
1891bb76ff1Sjsg
1901bb76ff1Sjsg /**
1911bb76ff1Sjsg * drm_buddy_fini - tear down the memory manager
1921bb76ff1Sjsg *
1931bb76ff1Sjsg * @mm: DRM buddy manager to free
1941bb76ff1Sjsg *
1951bb76ff1Sjsg * Cleanup memory manager resources and the freelist
1961bb76ff1Sjsg */
drm_buddy_fini(struct drm_buddy * mm)1971bb76ff1Sjsg void drm_buddy_fini(struct drm_buddy *mm)
1981bb76ff1Sjsg {
1991bb76ff1Sjsg int i;
2001bb76ff1Sjsg
2011bb76ff1Sjsg for (i = 0; i < mm->n_roots; ++i) {
2021bb76ff1Sjsg WARN_ON(!drm_buddy_block_is_free(mm->roots[i]));
2031bb76ff1Sjsg drm_block_free(mm, mm->roots[i]);
2041bb76ff1Sjsg }
2051bb76ff1Sjsg
2061bb76ff1Sjsg WARN_ON(mm->avail != mm->size);
2071bb76ff1Sjsg
2081bb76ff1Sjsg kfree(mm->roots);
2091bb76ff1Sjsg kfree(mm->free_list);
2101bb76ff1Sjsg }
2111bb76ff1Sjsg EXPORT_SYMBOL(drm_buddy_fini);
2121bb76ff1Sjsg
split_block(struct drm_buddy * mm,struct drm_buddy_block * block)2131bb76ff1Sjsg static int split_block(struct drm_buddy *mm,
2141bb76ff1Sjsg struct drm_buddy_block *block)
2151bb76ff1Sjsg {
2161bb76ff1Sjsg unsigned int block_order = drm_buddy_block_order(block) - 1;
2171bb76ff1Sjsg u64 offset = drm_buddy_block_offset(block);
2181bb76ff1Sjsg
2191bb76ff1Sjsg BUG_ON(!drm_buddy_block_is_free(block));
2201bb76ff1Sjsg BUG_ON(!drm_buddy_block_order(block));
2211bb76ff1Sjsg
2221bb76ff1Sjsg block->left = drm_block_alloc(mm, block, block_order, offset);
2231bb76ff1Sjsg if (!block->left)
2241bb76ff1Sjsg return -ENOMEM;
2251bb76ff1Sjsg
2261bb76ff1Sjsg block->right = drm_block_alloc(mm, block, block_order,
2271bb76ff1Sjsg offset + (mm->chunk_size << block_order));
2281bb76ff1Sjsg if (!block->right) {
2291bb76ff1Sjsg drm_block_free(mm, block->left);
2301bb76ff1Sjsg return -ENOMEM;
2311bb76ff1Sjsg }
2321bb76ff1Sjsg
2331bb76ff1Sjsg mark_free(mm, block->left);
2341bb76ff1Sjsg mark_free(mm, block->right);
2351bb76ff1Sjsg
2361bb76ff1Sjsg mark_split(block);
2371bb76ff1Sjsg
2381bb76ff1Sjsg return 0;
2391bb76ff1Sjsg }
2401bb76ff1Sjsg
2411bb76ff1Sjsg static struct drm_buddy_block *
__get_buddy(struct drm_buddy_block * block)2421bb76ff1Sjsg __get_buddy(struct drm_buddy_block *block)
2431bb76ff1Sjsg {
2441bb76ff1Sjsg struct drm_buddy_block *parent;
2451bb76ff1Sjsg
2461bb76ff1Sjsg parent = block->parent;
2471bb76ff1Sjsg if (!parent)
2481bb76ff1Sjsg return NULL;
2491bb76ff1Sjsg
2501bb76ff1Sjsg if (parent->left == block)
2511bb76ff1Sjsg return parent->right;
2521bb76ff1Sjsg
2531bb76ff1Sjsg return parent->left;
2541bb76ff1Sjsg }
2551bb76ff1Sjsg
2561bb76ff1Sjsg /**
2571bb76ff1Sjsg * drm_get_buddy - get buddy address
2581bb76ff1Sjsg *
2591bb76ff1Sjsg * @block: DRM buddy block
2601bb76ff1Sjsg *
2611bb76ff1Sjsg * Returns the corresponding buddy block for @block, or NULL
2621bb76ff1Sjsg * if this is a root block and can't be merged further.
2631bb76ff1Sjsg * Requires some kind of locking to protect against
2641bb76ff1Sjsg * any concurrent allocate and free operations.
2651bb76ff1Sjsg */
2661bb76ff1Sjsg struct drm_buddy_block *
drm_get_buddy(struct drm_buddy_block * block)2671bb76ff1Sjsg drm_get_buddy(struct drm_buddy_block *block)
2681bb76ff1Sjsg {
2691bb76ff1Sjsg return __get_buddy(block);
2701bb76ff1Sjsg }
2711bb76ff1Sjsg EXPORT_SYMBOL(drm_get_buddy);
2721bb76ff1Sjsg
__drm_buddy_free(struct drm_buddy * mm,struct drm_buddy_block * block)2731bb76ff1Sjsg static void __drm_buddy_free(struct drm_buddy *mm,
2741bb76ff1Sjsg struct drm_buddy_block *block)
2751bb76ff1Sjsg {
2761bb76ff1Sjsg struct drm_buddy_block *parent;
2771bb76ff1Sjsg
2781bb76ff1Sjsg while ((parent = block->parent)) {
2791bb76ff1Sjsg struct drm_buddy_block *buddy;
2801bb76ff1Sjsg
2811bb76ff1Sjsg buddy = __get_buddy(block);
2821bb76ff1Sjsg
2831bb76ff1Sjsg if (!drm_buddy_block_is_free(buddy))
2841bb76ff1Sjsg break;
2851bb76ff1Sjsg
2861bb76ff1Sjsg list_del(&buddy->link);
2871bb76ff1Sjsg
2881bb76ff1Sjsg drm_block_free(mm, block);
2891bb76ff1Sjsg drm_block_free(mm, buddy);
2901bb76ff1Sjsg
2911bb76ff1Sjsg block = parent;
2921bb76ff1Sjsg }
2931bb76ff1Sjsg
2941bb76ff1Sjsg mark_free(mm, block);
2951bb76ff1Sjsg }
2961bb76ff1Sjsg
2971bb76ff1Sjsg /**
2981bb76ff1Sjsg * drm_buddy_free_block - free a block
2991bb76ff1Sjsg *
3001bb76ff1Sjsg * @mm: DRM buddy manager
3011bb76ff1Sjsg * @block: block to be freed
3021bb76ff1Sjsg */
drm_buddy_free_block(struct drm_buddy * mm,struct drm_buddy_block * block)3031bb76ff1Sjsg void drm_buddy_free_block(struct drm_buddy *mm,
3041bb76ff1Sjsg struct drm_buddy_block *block)
3051bb76ff1Sjsg {
3061bb76ff1Sjsg BUG_ON(!drm_buddy_block_is_allocated(block));
3071bb76ff1Sjsg mm->avail += drm_buddy_block_size(mm, block);
3081bb76ff1Sjsg __drm_buddy_free(mm, block);
3091bb76ff1Sjsg }
3101bb76ff1Sjsg EXPORT_SYMBOL(drm_buddy_free_block);
3111bb76ff1Sjsg
3121bb76ff1Sjsg /**
3131bb76ff1Sjsg * drm_buddy_free_list - free blocks
3141bb76ff1Sjsg *
3151bb76ff1Sjsg * @mm: DRM buddy manager
3161bb76ff1Sjsg * @objects: input list head to free blocks
3171bb76ff1Sjsg */
drm_buddy_free_list(struct drm_buddy * mm,struct list_head * objects)3181bb76ff1Sjsg void drm_buddy_free_list(struct drm_buddy *mm, struct list_head *objects)
3191bb76ff1Sjsg {
3201bb76ff1Sjsg struct drm_buddy_block *block, *on;
3211bb76ff1Sjsg
3221bb76ff1Sjsg list_for_each_entry_safe(block, on, objects, link) {
3231bb76ff1Sjsg drm_buddy_free_block(mm, block);
3241bb76ff1Sjsg cond_resched();
3251bb76ff1Sjsg }
3261bb76ff1Sjsg INIT_LIST_HEAD(objects);
3271bb76ff1Sjsg }
3281bb76ff1Sjsg EXPORT_SYMBOL(drm_buddy_free_list);
3291bb76ff1Sjsg
overlaps(u64 s1,u64 e1,u64 s2,u64 e2)3301bb76ff1Sjsg static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
3311bb76ff1Sjsg {
3321bb76ff1Sjsg return s1 <= e2 && e1 >= s2;
3331bb76ff1Sjsg }
3341bb76ff1Sjsg
contains(u64 s1,u64 e1,u64 s2,u64 e2)3351bb76ff1Sjsg static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
3361bb76ff1Sjsg {
3371bb76ff1Sjsg return s1 <= s2 && e1 >= e2;
3381bb76ff1Sjsg }
3391bb76ff1Sjsg
3401bb76ff1Sjsg static struct drm_buddy_block *
alloc_range_bias(struct drm_buddy * mm,u64 start,u64 end,unsigned int order)3411bb76ff1Sjsg alloc_range_bias(struct drm_buddy *mm,
3421bb76ff1Sjsg u64 start, u64 end,
3431bb76ff1Sjsg unsigned int order)
3441bb76ff1Sjsg {
345*1a139995Sjsg u64 req_size = mm->chunk_size << order;
3461bb76ff1Sjsg struct drm_buddy_block *block;
3471bb76ff1Sjsg struct drm_buddy_block *buddy;
3481bb76ff1Sjsg DRM_LIST_HEAD(dfs);
3491bb76ff1Sjsg int err;
3501bb76ff1Sjsg int i;
3511bb76ff1Sjsg
3521bb76ff1Sjsg end = end - 1;
3531bb76ff1Sjsg
3541bb76ff1Sjsg for (i = 0; i < mm->n_roots; ++i)
3551bb76ff1Sjsg list_add_tail(&mm->roots[i]->tmp_link, &dfs);
3561bb76ff1Sjsg
3571bb76ff1Sjsg do {
3581bb76ff1Sjsg u64 block_start;
3591bb76ff1Sjsg u64 block_end;
3601bb76ff1Sjsg
3611bb76ff1Sjsg block = list_first_entry_or_null(&dfs,
3621bb76ff1Sjsg struct drm_buddy_block,
3631bb76ff1Sjsg tmp_link);
3641bb76ff1Sjsg if (!block)
3651bb76ff1Sjsg break;
3661bb76ff1Sjsg
3671bb76ff1Sjsg list_del(&block->tmp_link);
3681bb76ff1Sjsg
3691bb76ff1Sjsg if (drm_buddy_block_order(block) < order)
3701bb76ff1Sjsg continue;
3711bb76ff1Sjsg
3721bb76ff1Sjsg block_start = drm_buddy_block_offset(block);
3731bb76ff1Sjsg block_end = block_start + drm_buddy_block_size(mm, block) - 1;
3741bb76ff1Sjsg
3751bb76ff1Sjsg if (!overlaps(start, end, block_start, block_end))
3761bb76ff1Sjsg continue;
3771bb76ff1Sjsg
3781bb76ff1Sjsg if (drm_buddy_block_is_allocated(block))
3791bb76ff1Sjsg continue;
3801bb76ff1Sjsg
381*1a139995Sjsg if (block_start < start || block_end > end) {
382*1a139995Sjsg u64 adjusted_start = max(block_start, start);
383*1a139995Sjsg u64 adjusted_end = min(block_end, end);
384*1a139995Sjsg
385*1a139995Sjsg if (round_down(adjusted_end + 1, req_size) <=
386*1a139995Sjsg round_up(adjusted_start, req_size))
387*1a139995Sjsg continue;
388*1a139995Sjsg }
389*1a139995Sjsg
3901bb76ff1Sjsg if (contains(start, end, block_start, block_end) &&
3911bb76ff1Sjsg order == drm_buddy_block_order(block)) {
3921bb76ff1Sjsg /*
3931bb76ff1Sjsg * Find the free block within the range.
3941bb76ff1Sjsg */
3951bb76ff1Sjsg if (drm_buddy_block_is_free(block))
3961bb76ff1Sjsg return block;
3971bb76ff1Sjsg
3981bb76ff1Sjsg continue;
3991bb76ff1Sjsg }
4001bb76ff1Sjsg
4011bb76ff1Sjsg if (!drm_buddy_block_is_split(block)) {
4021bb76ff1Sjsg err = split_block(mm, block);
4031bb76ff1Sjsg if (unlikely(err))
4041bb76ff1Sjsg goto err_undo;
4051bb76ff1Sjsg }
4061bb76ff1Sjsg
4071bb76ff1Sjsg list_add(&block->right->tmp_link, &dfs);
4081bb76ff1Sjsg list_add(&block->left->tmp_link, &dfs);
4091bb76ff1Sjsg } while (1);
4101bb76ff1Sjsg
4111bb76ff1Sjsg return ERR_PTR(-ENOSPC);
4121bb76ff1Sjsg
4131bb76ff1Sjsg err_undo:
4141bb76ff1Sjsg /*
4151bb76ff1Sjsg * We really don't want to leave around a bunch of split blocks, since
4161bb76ff1Sjsg * bigger is better, so make sure we merge everything back before we
4171bb76ff1Sjsg * free the allocated blocks.
4181bb76ff1Sjsg */
4191bb76ff1Sjsg buddy = __get_buddy(block);
4201bb76ff1Sjsg if (buddy &&
4211bb76ff1Sjsg (drm_buddy_block_is_free(block) &&
4221bb76ff1Sjsg drm_buddy_block_is_free(buddy)))
4231bb76ff1Sjsg __drm_buddy_free(mm, block);
4241bb76ff1Sjsg return ERR_PTR(err);
4251bb76ff1Sjsg }
4261bb76ff1Sjsg
4271bb76ff1Sjsg static struct drm_buddy_block *
get_maxblock(struct drm_buddy * mm,unsigned int order)42877f08424Sjsg get_maxblock(struct drm_buddy *mm, unsigned int order)
4291bb76ff1Sjsg {
4301bb76ff1Sjsg struct drm_buddy_block *max_block = NULL, *node;
43177f08424Sjsg unsigned int i;
4321bb76ff1Sjsg
43377f08424Sjsg for (i = order; i <= mm->max_order; ++i) {
43477f08424Sjsg if (!list_empty(&mm->free_list[i])) {
43577f08424Sjsg node = list_last_entry(&mm->free_list[i],
4361bb76ff1Sjsg struct drm_buddy_block,
4371bb76ff1Sjsg link);
43877f08424Sjsg if (!max_block) {
4391bb76ff1Sjsg max_block = node;
44077f08424Sjsg continue;
44177f08424Sjsg }
44277f08424Sjsg
44377f08424Sjsg if (drm_buddy_block_offset(node) >
44477f08424Sjsg drm_buddy_block_offset(max_block)) {
44577f08424Sjsg max_block = node;
44677f08424Sjsg }
44777f08424Sjsg }
4481bb76ff1Sjsg }
4491bb76ff1Sjsg
4501bb76ff1Sjsg return max_block;
4511bb76ff1Sjsg }
4521bb76ff1Sjsg
4531bb76ff1Sjsg static struct drm_buddy_block *
alloc_from_freelist(struct drm_buddy * mm,unsigned int order,unsigned long flags)4541bb76ff1Sjsg alloc_from_freelist(struct drm_buddy *mm,
4551bb76ff1Sjsg unsigned int order,
4561bb76ff1Sjsg unsigned long flags)
4571bb76ff1Sjsg {
4581bb76ff1Sjsg struct drm_buddy_block *block = NULL;
45977f08424Sjsg unsigned int tmp;
4601bb76ff1Sjsg int err;
4611bb76ff1Sjsg
4621bb76ff1Sjsg if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
46377f08424Sjsg block = get_maxblock(mm, order);
4641bb76ff1Sjsg if (block)
46577f08424Sjsg /* Store the obtained block order */
46677f08424Sjsg tmp = drm_buddy_block_order(block);
4671bb76ff1Sjsg } else {
46877f08424Sjsg for (tmp = order; tmp <= mm->max_order; ++tmp) {
46977f08424Sjsg if (!list_empty(&mm->free_list[tmp])) {
47077f08424Sjsg block = list_last_entry(&mm->free_list[tmp],
4711bb76ff1Sjsg struct drm_buddy_block,
4721bb76ff1Sjsg link);
4731bb76ff1Sjsg if (block)
4741bb76ff1Sjsg break;
4751bb76ff1Sjsg }
4761bb76ff1Sjsg }
47777f08424Sjsg }
4781bb76ff1Sjsg
4791bb76ff1Sjsg if (!block)
4801bb76ff1Sjsg return ERR_PTR(-ENOSPC);
4811bb76ff1Sjsg
4821bb76ff1Sjsg BUG_ON(!drm_buddy_block_is_free(block));
4831bb76ff1Sjsg
48477f08424Sjsg while (tmp != order) {
4851bb76ff1Sjsg err = split_block(mm, block);
4861bb76ff1Sjsg if (unlikely(err))
4871bb76ff1Sjsg goto err_undo;
4881bb76ff1Sjsg
4891bb76ff1Sjsg block = block->right;
49077f08424Sjsg tmp--;
4911bb76ff1Sjsg }
4921bb76ff1Sjsg return block;
4931bb76ff1Sjsg
4941bb76ff1Sjsg err_undo:
49577f08424Sjsg if (tmp != order)
4961bb76ff1Sjsg __drm_buddy_free(mm, block);
4971bb76ff1Sjsg return ERR_PTR(err);
4981bb76ff1Sjsg }
4991bb76ff1Sjsg
__alloc_range(struct drm_buddy * mm,struct list_head * dfs,u64 start,u64 size,struct list_head * blocks)5001bb76ff1Sjsg static int __alloc_range(struct drm_buddy *mm,
5011bb76ff1Sjsg struct list_head *dfs,
5021bb76ff1Sjsg u64 start, u64 size,
5031bb76ff1Sjsg struct list_head *blocks)
5041bb76ff1Sjsg {
5051bb76ff1Sjsg struct drm_buddy_block *block;
5061bb76ff1Sjsg struct drm_buddy_block *buddy;
5071bb76ff1Sjsg DRM_LIST_HEAD(allocated);
5081bb76ff1Sjsg u64 end;
5091bb76ff1Sjsg int err;
5101bb76ff1Sjsg
5111bb76ff1Sjsg end = start + size - 1;
5121bb76ff1Sjsg
5131bb76ff1Sjsg do {
5141bb76ff1Sjsg u64 block_start;
5151bb76ff1Sjsg u64 block_end;
5161bb76ff1Sjsg
5171bb76ff1Sjsg block = list_first_entry_or_null(dfs,
5181bb76ff1Sjsg struct drm_buddy_block,
5191bb76ff1Sjsg tmp_link);
5201bb76ff1Sjsg if (!block)
5211bb76ff1Sjsg break;
5221bb76ff1Sjsg
5231bb76ff1Sjsg list_del(&block->tmp_link);
5241bb76ff1Sjsg
5251bb76ff1Sjsg block_start = drm_buddy_block_offset(block);
5261bb76ff1Sjsg block_end = block_start + drm_buddy_block_size(mm, block) - 1;
5271bb76ff1Sjsg
5281bb76ff1Sjsg if (!overlaps(start, end, block_start, block_end))
5291bb76ff1Sjsg continue;
5301bb76ff1Sjsg
5311bb76ff1Sjsg if (drm_buddy_block_is_allocated(block)) {
5321bb76ff1Sjsg err = -ENOSPC;
5331bb76ff1Sjsg goto err_free;
5341bb76ff1Sjsg }
5351bb76ff1Sjsg
5361bb76ff1Sjsg if (contains(start, end, block_start, block_end)) {
5371bb76ff1Sjsg if (!drm_buddy_block_is_free(block)) {
5381bb76ff1Sjsg err = -ENOSPC;
5391bb76ff1Sjsg goto err_free;
5401bb76ff1Sjsg }
5411bb76ff1Sjsg
5421bb76ff1Sjsg mark_allocated(block);
5431bb76ff1Sjsg mm->avail -= drm_buddy_block_size(mm, block);
5441bb76ff1Sjsg list_add_tail(&block->link, &allocated);
5451bb76ff1Sjsg continue;
5461bb76ff1Sjsg }
5471bb76ff1Sjsg
5481bb76ff1Sjsg if (!drm_buddy_block_is_split(block)) {
5491bb76ff1Sjsg err = split_block(mm, block);
5501bb76ff1Sjsg if (unlikely(err))
5511bb76ff1Sjsg goto err_undo;
5521bb76ff1Sjsg }
5531bb76ff1Sjsg
5541bb76ff1Sjsg list_add(&block->right->tmp_link, dfs);
5551bb76ff1Sjsg list_add(&block->left->tmp_link, dfs);
5561bb76ff1Sjsg } while (1);
5571bb76ff1Sjsg
5581bb76ff1Sjsg list_splice_tail(&allocated, blocks);
5591bb76ff1Sjsg return 0;
5601bb76ff1Sjsg
5611bb76ff1Sjsg err_undo:
5621bb76ff1Sjsg /*
5631bb76ff1Sjsg * We really don't want to leave around a bunch of split blocks, since
5641bb76ff1Sjsg * bigger is better, so make sure we merge everything back before we
5651bb76ff1Sjsg * free the allocated blocks.
5661bb76ff1Sjsg */
5671bb76ff1Sjsg buddy = __get_buddy(block);
5681bb76ff1Sjsg if (buddy &&
5691bb76ff1Sjsg (drm_buddy_block_is_free(block) &&
5701bb76ff1Sjsg drm_buddy_block_is_free(buddy)))
5711bb76ff1Sjsg __drm_buddy_free(mm, block);
5721bb76ff1Sjsg
5731bb76ff1Sjsg err_free:
5741bb76ff1Sjsg drm_buddy_free_list(mm, &allocated);
5751bb76ff1Sjsg return err;
5761bb76ff1Sjsg }
5771bb76ff1Sjsg
__drm_buddy_alloc_range(struct drm_buddy * mm,u64 start,u64 size,struct list_head * blocks)5781bb76ff1Sjsg static int __drm_buddy_alloc_range(struct drm_buddy *mm,
5791bb76ff1Sjsg u64 start,
5801bb76ff1Sjsg u64 size,
5811bb76ff1Sjsg struct list_head *blocks)
5821bb76ff1Sjsg {
5831bb76ff1Sjsg DRM_LIST_HEAD(dfs);
5841bb76ff1Sjsg int i;
5851bb76ff1Sjsg
5861bb76ff1Sjsg for (i = 0; i < mm->n_roots; ++i)
5871bb76ff1Sjsg list_add_tail(&mm->roots[i]->tmp_link, &dfs);
5881bb76ff1Sjsg
5891bb76ff1Sjsg return __alloc_range(mm, &dfs, start, size, blocks);
5901bb76ff1Sjsg }
5911bb76ff1Sjsg
5921bb76ff1Sjsg /**
5931bb76ff1Sjsg * drm_buddy_block_trim - free unused pages
5941bb76ff1Sjsg *
5951bb76ff1Sjsg * @mm: DRM buddy manager
5961bb76ff1Sjsg * @new_size: original size requested
5971bb76ff1Sjsg * @blocks: Input and output list of allocated blocks.
5981bb76ff1Sjsg * MUST contain single block as input to be trimmed.
5991bb76ff1Sjsg * On success will contain the newly allocated blocks
6001bb76ff1Sjsg * making up the @new_size. Blocks always appear in
6011bb76ff1Sjsg * ascending order
6021bb76ff1Sjsg *
6031bb76ff1Sjsg * For contiguous allocation, we round up the size to the nearest
6041bb76ff1Sjsg * power of two value, drivers consume *actual* size, so remaining
6051bb76ff1Sjsg * portions are unused and can be optionally freed with this function
6061bb76ff1Sjsg *
6071bb76ff1Sjsg * Returns:
6081bb76ff1Sjsg * 0 on success, error code on failure.
6091bb76ff1Sjsg */
drm_buddy_block_trim(struct drm_buddy * mm,u64 new_size,struct list_head * blocks)6101bb76ff1Sjsg int drm_buddy_block_trim(struct drm_buddy *mm,
6111bb76ff1Sjsg u64 new_size,
6121bb76ff1Sjsg struct list_head *blocks)
6131bb76ff1Sjsg {
6141bb76ff1Sjsg struct drm_buddy_block *parent;
6151bb76ff1Sjsg struct drm_buddy_block *block;
6161bb76ff1Sjsg DRM_LIST_HEAD(dfs);
6171bb76ff1Sjsg u64 new_start;
6181bb76ff1Sjsg int err;
6191bb76ff1Sjsg
6201bb76ff1Sjsg if (!list_is_singular(blocks))
6211bb76ff1Sjsg return -EINVAL;
6221bb76ff1Sjsg
6231bb76ff1Sjsg block = list_first_entry(blocks,
6241bb76ff1Sjsg struct drm_buddy_block,
6251bb76ff1Sjsg link);
6261bb76ff1Sjsg
6271bb76ff1Sjsg if (WARN_ON(!drm_buddy_block_is_allocated(block)))
6281bb76ff1Sjsg return -EINVAL;
6291bb76ff1Sjsg
6301bb76ff1Sjsg if (new_size > drm_buddy_block_size(mm, block))
6311bb76ff1Sjsg return -EINVAL;
6321bb76ff1Sjsg
6331bb76ff1Sjsg if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
6341bb76ff1Sjsg return -EINVAL;
6351bb76ff1Sjsg
6361bb76ff1Sjsg if (new_size == drm_buddy_block_size(mm, block))
6371bb76ff1Sjsg return 0;
6381bb76ff1Sjsg
6391bb76ff1Sjsg list_del(&block->link);
6401bb76ff1Sjsg mark_free(mm, block);
6411bb76ff1Sjsg mm->avail += drm_buddy_block_size(mm, block);
6421bb76ff1Sjsg
6431bb76ff1Sjsg /* Prevent recursively freeing this node */
6441bb76ff1Sjsg parent = block->parent;
6451bb76ff1Sjsg block->parent = NULL;
6461bb76ff1Sjsg
6471bb76ff1Sjsg new_start = drm_buddy_block_offset(block);
6481bb76ff1Sjsg list_add(&block->tmp_link, &dfs);
6491bb76ff1Sjsg err = __alloc_range(mm, &dfs, new_start, new_size, blocks);
6501bb76ff1Sjsg if (err) {
6511bb76ff1Sjsg mark_allocated(block);
6521bb76ff1Sjsg mm->avail -= drm_buddy_block_size(mm, block);
6531bb76ff1Sjsg list_add(&block->link, blocks);
6541bb76ff1Sjsg }
6551bb76ff1Sjsg
6561bb76ff1Sjsg block->parent = parent;
6571bb76ff1Sjsg return err;
6581bb76ff1Sjsg }
6591bb76ff1Sjsg EXPORT_SYMBOL(drm_buddy_block_trim);
6601bb76ff1Sjsg
6611bb76ff1Sjsg /**
6621bb76ff1Sjsg * drm_buddy_alloc_blocks - allocate power-of-two blocks
6631bb76ff1Sjsg *
6641bb76ff1Sjsg * @mm: DRM buddy manager to allocate from
6651bb76ff1Sjsg * @start: start of the allowed range for this block
6661bb76ff1Sjsg * @end: end of the allowed range for this block
6671bb76ff1Sjsg * @size: size of the allocation
6681bb76ff1Sjsg * @min_page_size: alignment of the allocation
6691bb76ff1Sjsg * @blocks: output list head to add allocated blocks
6701bb76ff1Sjsg * @flags: DRM_BUDDY_*_ALLOCATION flags
6711bb76ff1Sjsg *
6721bb76ff1Sjsg * alloc_range_bias() called on range limitations, which traverses
6731bb76ff1Sjsg * the tree and returns the desired block.
6741bb76ff1Sjsg *
6751bb76ff1Sjsg * alloc_from_freelist() called when *no* range restrictions
6761bb76ff1Sjsg * are enforced, which picks the block from the freelist.
6771bb76ff1Sjsg *
6781bb76ff1Sjsg * Returns:
6791bb76ff1Sjsg * 0 on success, error code on failure.
6801bb76ff1Sjsg */
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)6811bb76ff1Sjsg int drm_buddy_alloc_blocks(struct drm_buddy *mm,
6821bb76ff1Sjsg u64 start, u64 end, u64 size,
6831bb76ff1Sjsg u64 min_page_size,
6841bb76ff1Sjsg struct list_head *blocks,
6851bb76ff1Sjsg unsigned long flags)
6861bb76ff1Sjsg {
6871bb76ff1Sjsg struct drm_buddy_block *block = NULL;
6881bb76ff1Sjsg unsigned int min_order, order;
6891bb76ff1Sjsg unsigned long pages;
6901bb76ff1Sjsg DRM_LIST_HEAD(allocated);
6911bb76ff1Sjsg int err;
6921bb76ff1Sjsg
6931bb76ff1Sjsg if (size < mm->chunk_size)
6941bb76ff1Sjsg return -EINVAL;
6951bb76ff1Sjsg
6961bb76ff1Sjsg if (min_page_size < mm->chunk_size)
6971bb76ff1Sjsg return -EINVAL;
6981bb76ff1Sjsg
6991bb76ff1Sjsg if (!is_power_of_2(min_page_size))
7001bb76ff1Sjsg return -EINVAL;
7011bb76ff1Sjsg
7021bb76ff1Sjsg if (!IS_ALIGNED(start | end | size, mm->chunk_size))
7031bb76ff1Sjsg return -EINVAL;
7041bb76ff1Sjsg
7051bb76ff1Sjsg if (end > mm->size)
7061bb76ff1Sjsg return -EINVAL;
7071bb76ff1Sjsg
7081bb76ff1Sjsg if (range_overflows(start, size, mm->size))
7091bb76ff1Sjsg return -EINVAL;
7101bb76ff1Sjsg
7111bb76ff1Sjsg /* Actual range allocation */
7121bb76ff1Sjsg if (start + size == end)
7131bb76ff1Sjsg return __drm_buddy_alloc_range(mm, start, size, blocks);
7141bb76ff1Sjsg
7151bb76ff1Sjsg if (!IS_ALIGNED(size, min_page_size))
7161bb76ff1Sjsg return -EINVAL;
7171bb76ff1Sjsg
7181bb76ff1Sjsg pages = size >> ilog2(mm->chunk_size);
7191bb76ff1Sjsg order = fls(pages) - 1;
7201bb76ff1Sjsg min_order = ilog2(min_page_size) - ilog2(mm->chunk_size);
7211bb76ff1Sjsg
7221bb76ff1Sjsg do {
7231bb76ff1Sjsg order = min(order, (unsigned int)fls(pages) - 1);
7241bb76ff1Sjsg BUG_ON(order > mm->max_order);
7251bb76ff1Sjsg BUG_ON(order < min_order);
7261bb76ff1Sjsg
7271bb76ff1Sjsg do {
7281bb76ff1Sjsg if (flags & DRM_BUDDY_RANGE_ALLOCATION)
7291bb76ff1Sjsg /* Allocate traversing within the range */
7301bb76ff1Sjsg block = alloc_range_bias(mm, start, end, order);
7311bb76ff1Sjsg else
7321bb76ff1Sjsg /* Allocate from freelist */
7331bb76ff1Sjsg block = alloc_from_freelist(mm, order, flags);
7341bb76ff1Sjsg
7351bb76ff1Sjsg if (!IS_ERR(block))
7361bb76ff1Sjsg break;
7371bb76ff1Sjsg
7381bb76ff1Sjsg if (order-- == min_order) {
7391bb76ff1Sjsg err = -ENOSPC;
7401bb76ff1Sjsg goto err_free;
7411bb76ff1Sjsg }
7421bb76ff1Sjsg } while (1);
7431bb76ff1Sjsg
7441bb76ff1Sjsg mark_allocated(block);
7451bb76ff1Sjsg mm->avail -= drm_buddy_block_size(mm, block);
7461bb76ff1Sjsg kmemleak_update_trace(block);
7471bb76ff1Sjsg list_add_tail(&block->link, &allocated);
7481bb76ff1Sjsg
7491bb76ff1Sjsg pages -= BIT(order);
7501bb76ff1Sjsg
7511bb76ff1Sjsg if (!pages)
7521bb76ff1Sjsg break;
7531bb76ff1Sjsg } while (1);
7541bb76ff1Sjsg
7551bb76ff1Sjsg list_splice_tail(&allocated, blocks);
7561bb76ff1Sjsg return 0;
7571bb76ff1Sjsg
7581bb76ff1Sjsg err_free:
7591bb76ff1Sjsg drm_buddy_free_list(mm, &allocated);
7601bb76ff1Sjsg return err;
7611bb76ff1Sjsg }
7621bb76ff1Sjsg EXPORT_SYMBOL(drm_buddy_alloc_blocks);
7631bb76ff1Sjsg
7641bb76ff1Sjsg /**
7651bb76ff1Sjsg * drm_buddy_block_print - print block information
7661bb76ff1Sjsg *
7671bb76ff1Sjsg * @mm: DRM buddy manager
7681bb76ff1Sjsg * @block: DRM buddy block
7691bb76ff1Sjsg * @p: DRM printer to use
7701bb76ff1Sjsg */
drm_buddy_block_print(struct drm_buddy * mm,struct drm_buddy_block * block,struct drm_printer * p)7711bb76ff1Sjsg void drm_buddy_block_print(struct drm_buddy *mm,
7721bb76ff1Sjsg struct drm_buddy_block *block,
7731bb76ff1Sjsg struct drm_printer *p)
7741bb76ff1Sjsg {
7751bb76ff1Sjsg u64 start = drm_buddy_block_offset(block);
7761bb76ff1Sjsg u64 size = drm_buddy_block_size(mm, block);
7771bb76ff1Sjsg
7781bb76ff1Sjsg drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
7791bb76ff1Sjsg }
7801bb76ff1Sjsg EXPORT_SYMBOL(drm_buddy_block_print);
7811bb76ff1Sjsg
7821bb76ff1Sjsg /**
7831bb76ff1Sjsg * drm_buddy_print - print allocator state
7841bb76ff1Sjsg *
7851bb76ff1Sjsg * @mm: DRM buddy manager
7861bb76ff1Sjsg * @p: DRM printer to use
7871bb76ff1Sjsg */
drm_buddy_print(struct drm_buddy * mm,struct drm_printer * p)7881bb76ff1Sjsg void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
7891bb76ff1Sjsg {
7901bb76ff1Sjsg int order;
7911bb76ff1Sjsg
7921bb76ff1Sjsg drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB\n",
7931bb76ff1Sjsg mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20);
7941bb76ff1Sjsg
7951bb76ff1Sjsg for (order = mm->max_order; order >= 0; order--) {
7961bb76ff1Sjsg struct drm_buddy_block *block;
7971bb76ff1Sjsg u64 count = 0, free;
7981bb76ff1Sjsg
7991bb76ff1Sjsg list_for_each_entry(block, &mm->free_list[order], link) {
8001bb76ff1Sjsg BUG_ON(!drm_buddy_block_is_free(block));
8011bb76ff1Sjsg count++;
8021bb76ff1Sjsg }
8031bb76ff1Sjsg
804f005ef32Sjsg drm_printf(p, "order-%2d ", order);
8051bb76ff1Sjsg
8061bb76ff1Sjsg free = count * (mm->chunk_size << order);
8071bb76ff1Sjsg if (free < SZ_1M)
808f005ef32Sjsg drm_printf(p, "free: %8llu KiB", free >> 10);
8091bb76ff1Sjsg else
810f005ef32Sjsg drm_printf(p, "free: %8llu MiB", free >> 20);
8111bb76ff1Sjsg
812f005ef32Sjsg drm_printf(p, ", blocks: %llu\n", count);
8131bb76ff1Sjsg }
8141bb76ff1Sjsg }
8151bb76ff1Sjsg EXPORT_SYMBOL(drm_buddy_print);
8161bb76ff1Sjsg
drm_buddy_module_exit(void)8171bb76ff1Sjsg void drm_buddy_module_exit(void)
8181bb76ff1Sjsg {
8191bb76ff1Sjsg #ifdef __linux__
8201bb76ff1Sjsg kmem_cache_destroy(slab_blocks);
8211bb76ff1Sjsg #else
8221bb76ff1Sjsg pool_destroy(&slab_blocks);
8231bb76ff1Sjsg #endif
8241bb76ff1Sjsg }
8251bb76ff1Sjsg
drm_buddy_module_init(void)8261bb76ff1Sjsg int __init drm_buddy_module_init(void)
8271bb76ff1Sjsg {
8281bb76ff1Sjsg #ifdef __linux__
8291bb76ff1Sjsg slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
8301bb76ff1Sjsg if (!slab_blocks)
8311bb76ff1Sjsg return -ENOMEM;
8321bb76ff1Sjsg #else
8331bb76ff1Sjsg pool_init(&slab_blocks, sizeof(struct drm_buddy_block),
8341bb76ff1Sjsg CACHELINESIZE, IPL_NONE, 0, "drmbb", NULL);
8351bb76ff1Sjsg #endif
8361bb76ff1Sjsg
8371bb76ff1Sjsg return 0;
8381bb76ff1Sjsg }
8391bb76ff1Sjsg
8401bb76ff1Sjsg module_init(drm_buddy_module_init);
8411bb76ff1Sjsg module_exit(drm_buddy_module_exit);
8421bb76ff1Sjsg
8431bb76ff1Sjsg MODULE_DESCRIPTION("DRM Buddy Allocator");
8441bb76ff1Sjsg MODULE_LICENSE("Dual MIT/GPL");
845