]> git.hungrycats.org Git - linux/commitdiff
gpu: Move DRM buddy allocator one level up (part one)
authorJoel Fernandes <joelagnelf@nvidia.com>
Thu, 5 Feb 2026 22:52:38 +0000 (08:52 +1000)
committerDave Airlie <airlied@redhat.com>
Fri, 6 Feb 2026 01:34:02 +0000 (11:34 +1000)
Move the DRM buddy allocator one level up so that it can be used by GPU
drivers (example, nova-core) that have usecases other than DRM (such as
VFIO vGPU support). Modify the API, structures and Kconfigs to use
"gpu_buddy" terminology. Adapt the drivers and tests to use the new API.

The commit cannot be split due to bisectability, however no functional
change is intended. Verified by running K-UNIT tests and build tested
various configurations.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
Reviewed-by: Dave Airlie <airlied@redhat.com>
[airlied: I've split this into two so git can find copies easier.
I've also just nuked drm_random library, that stuff needs to be done
elsewhere and only the buddy tests seem to be using it].
Signed-off-by: Dave Airlie <airlied@redhat.com>
25 files changed:
Documentation/gpu/drm-mm.rst
drivers/gpu/Makefile
drivers/gpu/buddy.c [new file with mode: 0644]
drivers/gpu/drm/Kconfig
drivers/gpu/drm/Kconfig.debug
drivers/gpu/drm/Makefile
drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h
drivers/gpu/drm/drm_buddy.c [deleted file]
drivers/gpu/drm/i915/gem/i915_gem_ttm.c
drivers/gpu/drm/i915/i915_scatterlist.c
drivers/gpu/drm/i915/i915_ttm_buddy_manager.c
drivers/gpu/drm/lib/drm_random.c [deleted file]
drivers/gpu/drm/lib/drm_random.h [deleted file]
drivers/gpu/drm/tests/Makefile
drivers/gpu/drm/tests/drm_buddy_test.c [deleted file]
drivers/gpu/drm/tests/drm_exec_test.c
drivers/gpu/drm/tests/drm_mm_test.c
drivers/gpu/drm/ttm/tests/ttm_mock_manager.h
drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
drivers/gpu/tests/Makefile [new file with mode: 0644]
drivers/gpu/tests/gpu_buddy_test.c [new file with mode: 0644]
drivers/gpu/tests/gpu_random.c [new file with mode: 0644]
drivers/gpu/tests/gpu_random.h [new file with mode: 0644]
include/drm/drm_buddy.h [deleted file]
include/linux/gpu_buddy.h [new file with mode: 0644]

index f22433470c7614745bea7f8d087688a7e35da9ee..ceee0e6632373cb8944be872fb613991ecc139c6 100644 (file)
@@ -526,10 +526,10 @@ DRM GPUVM Function References
 DRM Buddy Allocator
 ===================
 
-DRM Buddy Function References
------------------------------
+Buddy Allocator Function References (GPU buddy)
+-----------------------------------------------
 
-.. kernel-doc:: drivers/gpu/drm/drm_buddy.c
+.. kernel-doc:: drivers/gpu/buddy.c
    :export:
 
 DRM Cache Handling and Fast WC memcpy()
index 36a54d45663030c33405e297868a41c2d287c224..c5292ee2c85245782e2cc4380f9a714b57cf82d9 100644 (file)
@@ -2,7 +2,7 @@
 # drm/tegra depends on host1x, so if both drivers are built-in care must be
 # taken to initialize them in the correct order. Link order is the only way
 # to ensure this currently.
-obj-y                  += host1x/ drm/ vga/
+obj-y                  += host1x/ drm/ vga/ tests/
 obj-$(CONFIG_IMX_IPUV3_CORE)   += ipu-v3/
 obj-$(CONFIG_TRACE_GPU_MEM)            += trace/
 obj-$(CONFIG_NOVA_CORE)                += nova-core/
diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c
new file mode 100644 (file)
index 0000000..4cc63d9
--- /dev/null
@@ -0,0 +1,1336 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2021 Intel Corporation
+ */
+
+#include <kunit/test-bug.h>
+
+#include <linux/export.h>
+#include <linux/kmemleak.h>
+#include <linux/module.h>
+#include <linux/sizes.h>
+
+#include <linux/gpu_buddy.h>
+#include <drm/drm_print.h>
+
+enum drm_buddy_free_tree {
+       DRM_BUDDY_CLEAR_TREE = 0,
+       DRM_BUDDY_DIRTY_TREE,
+       DRM_BUDDY_MAX_FREE_TREES,
+};
+
+static struct kmem_cache *slab_blocks;
+
+#define for_each_free_tree(tree) \
+       for ((tree) = 0; (tree) < DRM_BUDDY_MAX_FREE_TREES; (tree)++)
+
+static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
+                                              struct drm_buddy_block *parent,
+                                              unsigned int order,
+                                              u64 offset)
+{
+       struct drm_buddy_block *block;
+
+       BUG_ON(order > DRM_BUDDY_MAX_ORDER);
+
+       block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
+       if (!block)
+               return NULL;
+
+       block->header = offset;
+       block->header |= order;
+       block->parent = parent;
+
+       RB_CLEAR_NODE(&block->rb);
+
+       BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
+       return block;
+}
+
+static void drm_block_free(struct drm_buddy *mm,
+                          struct drm_buddy_block *block)
+{
+       kmem_cache_free(slab_blocks, block);
+}
+
+static enum drm_buddy_free_tree
+get_block_tree(struct drm_buddy_block *block)
+{
+       return drm_buddy_block_is_clear(block) ?
+              DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+}
+
+static struct drm_buddy_block *
+rbtree_get_free_block(const struct rb_node *node)
+{
+       return node ? rb_entry(node, struct drm_buddy_block, rb) : NULL;
+}
+
+static struct drm_buddy_block *
+rbtree_last_free_block(struct rb_root *root)
+{
+       return rbtree_get_free_block(rb_last(root));
+}
+
+static bool rbtree_is_empty(struct rb_root *root)
+{
+       return RB_EMPTY_ROOT(root);
+}
+
+static bool drm_buddy_block_offset_less(const struct drm_buddy_block *block,
+                                       const struct drm_buddy_block *node)
+{
+       return drm_buddy_block_offset(block) < drm_buddy_block_offset(node);
+}
+
+static bool rbtree_block_offset_less(struct rb_node *block,
+                                    const struct rb_node *node)
+{
+       return drm_buddy_block_offset_less(rbtree_get_free_block(block),
+                                          rbtree_get_free_block(node));
+}
+
+static void rbtree_insert(struct drm_buddy *mm,
+                         struct drm_buddy_block *block,
+                         enum drm_buddy_free_tree tree)
+{
+       rb_add(&block->rb,
+              &mm->free_trees[tree][drm_buddy_block_order(block)],
+              rbtree_block_offset_less);
+}
+
+static void rbtree_remove(struct drm_buddy *mm,
+                         struct drm_buddy_block *block)
+{
+       unsigned int order = drm_buddy_block_order(block);
+       enum drm_buddy_free_tree tree;
+       struct rb_root *root;
+
+       tree = get_block_tree(block);
+       root = &mm->free_trees[tree][order];
+
+       rb_erase(&block->rb, root);
+       RB_CLEAR_NODE(&block->rb);
+}
+
+static void clear_reset(struct drm_buddy_block *block)
+{
+       block->header &= ~DRM_BUDDY_HEADER_CLEAR;
+}
+
+static void mark_cleared(struct drm_buddy_block *block)
+{
+       block->header |= DRM_BUDDY_HEADER_CLEAR;
+}
+
+static void mark_allocated(struct drm_buddy *mm,
+                          struct drm_buddy_block *block)
+{
+       block->header &= ~DRM_BUDDY_HEADER_STATE;
+       block->header |= DRM_BUDDY_ALLOCATED;
+
+       rbtree_remove(mm, block);
+}
+
+static void mark_free(struct drm_buddy *mm,
+                     struct drm_buddy_block *block)
+{
+       enum drm_buddy_free_tree tree;
+
+       block->header &= ~DRM_BUDDY_HEADER_STATE;
+       block->header |= DRM_BUDDY_FREE;
+
+       tree = get_block_tree(block);
+       rbtree_insert(mm, block, tree);
+}
+
+static void mark_split(struct drm_buddy *mm,
+                      struct drm_buddy_block *block)
+{
+       block->header &= ~DRM_BUDDY_HEADER_STATE;
+       block->header |= DRM_BUDDY_SPLIT;
+
+       rbtree_remove(mm, block);
+}
+
+static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
+{
+       return s1 <= e2 && e1 >= s2;
+}
+
+static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
+{
+       return s1 <= s2 && e1 >= e2;
+}
+
+static struct drm_buddy_block *
+__get_buddy(struct drm_buddy_block *block)
+{
+       struct drm_buddy_block *parent;
+
+       parent = block->parent;
+       if (!parent)
+               return NULL;
+
+       if (parent->left == block)
+               return parent->right;
+
+       return parent->left;
+}
+
+static unsigned int __drm_buddy_free(struct drm_buddy *mm,
+                                    struct drm_buddy_block *block,
+                                    bool force_merge)
+{
+       struct drm_buddy_block *parent;
+       unsigned int order;
+
+       while ((parent = block->parent)) {
+               struct drm_buddy_block *buddy;
+
+               buddy = __get_buddy(block);
+
+               if (!drm_buddy_block_is_free(buddy))
+                       break;
+
+               if (!force_merge) {
+                       /*
+                        * Check the block and its buddy clear state and exit
+                        * the loop if they both have the dissimilar state.
+                        */
+                       if (drm_buddy_block_is_clear(block) !=
+                           drm_buddy_block_is_clear(buddy))
+                               break;
+
+                       if (drm_buddy_block_is_clear(block))
+                               mark_cleared(parent);
+               }
+
+               rbtree_remove(mm, buddy);
+               if (force_merge && drm_buddy_block_is_clear(buddy))
+                       mm->clear_avail -= drm_buddy_block_size(mm, buddy);
+
+               drm_block_free(mm, block);
+               drm_block_free(mm, buddy);
+
+               block = parent;
+       }
+
+       order = drm_buddy_block_order(block);
+       mark_free(mm, block);
+
+       return order;
+}
+
+static int __force_merge(struct drm_buddy *mm,
+                        u64 start,
+                        u64 end,
+                        unsigned int min_order)
+{
+       unsigned int tree, order;
+       int i;
+
+       if (!min_order)
+               return -ENOMEM;
+
+       if (min_order > mm->max_order)
+               return -EINVAL;
+
+       for_each_free_tree(tree) {
+               for (i = min_order - 1; i >= 0; i--) {
+                       struct rb_node *iter = rb_last(&mm->free_trees[tree][i]);
+
+                       while (iter) {
+                               struct drm_buddy_block *block, *buddy;
+                               u64 block_start, block_end;
+
+                               block = rbtree_get_free_block(iter);
+                               iter = rb_prev(iter);
+
+                               if (!block || !block->parent)
+                                       continue;
+
+                               block_start = drm_buddy_block_offset(block);
+                               block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+
+                               if (!contains(start, end, block_start, block_end))
+                                       continue;
+
+                               buddy = __get_buddy(block);
+                               if (!drm_buddy_block_is_free(buddy))
+                                       continue;
+
+                               WARN_ON(drm_buddy_block_is_clear(block) ==
+                                       drm_buddy_block_is_clear(buddy));
+
+                               /*
+                                * Advance to the next node when the current node is the buddy,
+                                * as freeing the block will also remove its buddy from the tree.
+                                */
+                               if (iter == &buddy->rb)
+                                       iter = rb_prev(iter);
+
+                               rbtree_remove(mm, block);
+                               if (drm_buddy_block_is_clear(block))
+                                       mm->clear_avail -= drm_buddy_block_size(mm, block);
+
+                               order = __drm_buddy_free(mm, block, true);
+                               if (order >= min_order)
+                                       return 0;
+                       }
+               }
+       }
+
+       return -ENOMEM;
+}
+
+/**
+ * drm_buddy_init - init memory manager
+ *
+ * @mm: DRM buddy manager to initialize
+ * @size: size in bytes to manage
+ * @chunk_size: minimum page size in bytes for our allocations
+ *
+ * Initializes the memory manager and its resources.
+ *
+ * Returns:
+ * 0 on success, error code on failure.
+ */
+int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
+{
+       unsigned int i, j, root_count = 0;
+       u64 offset = 0;
+
+       if (size < chunk_size)
+               return -EINVAL;
+
+       if (chunk_size < SZ_4K)
+               return -EINVAL;
+
+       if (!is_power_of_2(chunk_size))
+               return -EINVAL;
+
+       size = round_down(size, chunk_size);
+
+       mm->size = size;
+       mm->avail = size;
+       mm->clear_avail = 0;
+       mm->chunk_size = chunk_size;
+       mm->max_order = ilog2(size) - ilog2(chunk_size);
+
+       BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
+
+       mm->free_trees = kmalloc_array(DRM_BUDDY_MAX_FREE_TREES,
+                                      sizeof(*mm->free_trees),
+                                      GFP_KERNEL);
+       if (!mm->free_trees)
+               return -ENOMEM;
+
+       for_each_free_tree(i) {
+               mm->free_trees[i] = kmalloc_array(mm->max_order + 1,
+                                                 sizeof(struct rb_root),
+                                                 GFP_KERNEL);
+               if (!mm->free_trees[i])
+                       goto out_free_tree;
+
+               for (j = 0; j <= mm->max_order; ++j)
+                       mm->free_trees[i][j] = RB_ROOT;
+       }
+
+       mm->n_roots = hweight64(size);
+
+       mm->roots = kmalloc_array(mm->n_roots,
+                                 sizeof(struct drm_buddy_block *),
+                                 GFP_KERNEL);
+       if (!mm->roots)
+               goto out_free_tree;
+
+       /*
+        * Split into power-of-two blocks, in case we are given a size that is
+        * not itself a power-of-two.
+        */
+       do {
+               struct drm_buddy_block *root;
+               unsigned int order;
+               u64 root_size;
+
+               order = ilog2(size) - ilog2(chunk_size);
+               root_size = chunk_size << order;
+
+               root = drm_block_alloc(mm, NULL, order, offset);
+               if (!root)
+                       goto out_free_roots;
+
+               mark_free(mm, root);
+
+               BUG_ON(root_count > mm->max_order);
+               BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
+
+               mm->roots[root_count] = root;
+
+               offset += root_size;
+               size -= root_size;
+               root_count++;
+       } while (size);
+
+       return 0;
+
+out_free_roots:
+       while (root_count--)
+               drm_block_free(mm, mm->roots[root_count]);
+       kfree(mm->roots);
+out_free_tree:
+       while (i--)
+               kfree(mm->free_trees[i]);
+       kfree(mm->free_trees);
+       return -ENOMEM;
+}
+EXPORT_SYMBOL(drm_buddy_init);
+
+/**
+ * drm_buddy_fini - tear down the memory manager
+ *
+ * @mm: DRM buddy manager to free
+ *
+ * Cleanup memory manager resources and the freetree
+ */
+void drm_buddy_fini(struct drm_buddy *mm)
+{
+       u64 root_size, size, start;
+       unsigned int order;
+       int i;
+
+       size = mm->size;
+
+       for (i = 0; i < mm->n_roots; ++i) {
+               order = ilog2(size) - ilog2(mm->chunk_size);
+               start = drm_buddy_block_offset(mm->roots[i]);
+               __force_merge(mm, start, start + size, order);
+
+               if (WARN_ON(!drm_buddy_block_is_free(mm->roots[i])))
+                       kunit_fail_current_test("buddy_fini() root");
+
+               drm_block_free(mm, mm->roots[i]);
+
+               root_size = mm->chunk_size << order;
+               size -= root_size;
+       }
+
+       WARN_ON(mm->avail != mm->size);
+
+       for_each_free_tree(i)
+               kfree(mm->free_trees[i]);
+       kfree(mm->free_trees);
+       kfree(mm->roots);
+}
+EXPORT_SYMBOL(drm_buddy_fini);
+
+static int split_block(struct drm_buddy *mm,
+                      struct drm_buddy_block *block)
+{
+       unsigned int block_order = drm_buddy_block_order(block) - 1;
+       u64 offset = drm_buddy_block_offset(block);
+
+       BUG_ON(!drm_buddy_block_is_free(block));
+       BUG_ON(!drm_buddy_block_order(block));
+
+       block->left = drm_block_alloc(mm, block, block_order, offset);
+       if (!block->left)
+               return -ENOMEM;
+
+       block->right = drm_block_alloc(mm, block, block_order,
+                                      offset + (mm->chunk_size << block_order));
+       if (!block->right) {
+               drm_block_free(mm, block->left);
+               return -ENOMEM;
+       }
+
+       mark_split(mm, block);
+
+       if (drm_buddy_block_is_clear(block)) {
+               mark_cleared(block->left);
+               mark_cleared(block->right);
+               clear_reset(block);
+       }
+
+       mark_free(mm, block->left);
+       mark_free(mm, block->right);
+
+       return 0;
+}
+
+/**
+ * drm_get_buddy - get buddy address
+ *
+ * @block: DRM buddy block
+ *
+ * Returns the corresponding buddy block for @block, or NULL
+ * if this is a root block and can't be merged further.
+ * Requires some kind of locking to protect against
+ * any concurrent allocate and free operations.
+ */
+struct drm_buddy_block *
+drm_get_buddy(struct drm_buddy_block *block)
+{
+       return __get_buddy(block);
+}
+EXPORT_SYMBOL(drm_get_buddy);
+
+/**
+ * drm_buddy_reset_clear - reset blocks clear state
+ *
+ * @mm: DRM buddy manager
+ * @is_clear: blocks clear state
+ *
+ * Reset the clear state based on @is_clear value for each block
+ * in the freetree.
+ */
+void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear)
+{
+       enum drm_buddy_free_tree src_tree, dst_tree;
+       u64 root_size, size, start;
+       unsigned int order;
+       int i;
+
+       size = mm->size;
+       for (i = 0; i < mm->n_roots; ++i) {
+               order = ilog2(size) - ilog2(mm->chunk_size);
+               start = drm_buddy_block_offset(mm->roots[i]);
+               __force_merge(mm, start, start + size, order);
+
+               root_size = mm->chunk_size << order;
+               size -= root_size;
+       }
+
+       src_tree = is_clear ? DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
+       dst_tree = is_clear ? DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+
+       for (i = 0; i <= mm->max_order; ++i) {
+               struct rb_root *root = &mm->free_trees[src_tree][i];
+               struct drm_buddy_block *block, *tmp;
+
+               rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
+                       rbtree_remove(mm, block);
+                       if (is_clear) {
+                               mark_cleared(block);
+                               mm->clear_avail += drm_buddy_block_size(mm, block);
+                       } else {
+                               clear_reset(block);
+                               mm->clear_avail -= drm_buddy_block_size(mm, block);
+                       }
+
+                       rbtree_insert(mm, block, dst_tree);
+               }
+       }
+}
+EXPORT_SYMBOL(drm_buddy_reset_clear);
+
+/**
+ * drm_buddy_free_block - free a block
+ *
+ * @mm: DRM buddy manager
+ * @block: block to be freed
+ */
+void drm_buddy_free_block(struct drm_buddy *mm,
+                         struct drm_buddy_block *block)
+{
+       BUG_ON(!drm_buddy_block_is_allocated(block));
+       mm->avail += drm_buddy_block_size(mm, block);
+       if (drm_buddy_block_is_clear(block))
+               mm->clear_avail += drm_buddy_block_size(mm, block);
+
+       __drm_buddy_free(mm, block, false);
+}
+EXPORT_SYMBOL(drm_buddy_free_block);
+
+static void __drm_buddy_free_list(struct drm_buddy *mm,
+                                 struct list_head *objects,
+                                 bool mark_clear,
+                                 bool mark_dirty)
+{
+       struct drm_buddy_block *block, *on;
+
+       WARN_ON(mark_dirty && mark_clear);
+
+       list_for_each_entry_safe(block, on, objects, link) {
+               if (mark_clear)
+                       mark_cleared(block);
+               else if (mark_dirty)
+                       clear_reset(block);
+               drm_buddy_free_block(mm, block);
+               cond_resched();
+       }
+       INIT_LIST_HEAD(objects);
+}
+
+static void drm_buddy_free_list_internal(struct drm_buddy *mm,
+                                        struct list_head *objects)
+{
+       /*
+        * Don't touch the clear/dirty bit, since allocation is still internal
+        * at this point. For example we might have just failed part of the
+        * allocation.
+        */
+       __drm_buddy_free_list(mm, objects, false, false);
+}
+
+/**
+ * drm_buddy_free_list - free blocks
+ *
+ * @mm: DRM buddy manager
+ * @objects: input list head to free blocks
+ * @flags: optional flags like DRM_BUDDY_CLEARED
+ */
+void drm_buddy_free_list(struct drm_buddy *mm,
+                        struct list_head *objects,
+                        unsigned int flags)
+{
+       bool mark_clear = flags & DRM_BUDDY_CLEARED;
+
+       __drm_buddy_free_list(mm, objects, mark_clear, !mark_clear);
+}
+EXPORT_SYMBOL(drm_buddy_free_list);
+
+static bool block_incompatible(struct drm_buddy_block *block, unsigned int flags)
+{
+       bool needs_clear = flags & DRM_BUDDY_CLEAR_ALLOCATION;
+
+       return needs_clear != drm_buddy_block_is_clear(block);
+}
+
+static struct drm_buddy_block *
+__alloc_range_bias(struct drm_buddy *mm,
+                  u64 start, u64 end,
+                  unsigned int order,
+                  unsigned long flags,
+                  bool fallback)
+{
+       u64 req_size = mm->chunk_size << order;
+       struct drm_buddy_block *block;
+       struct drm_buddy_block *buddy;
+       LIST_HEAD(dfs);
+       int err;
+       int i;
+
+       end = end - 1;
+
+       for (i = 0; i < mm->n_roots; ++i)
+               list_add_tail(&mm->roots[i]->tmp_link, &dfs);
+
+       do {
+               u64 block_start;
+               u64 block_end;
+
+               block = list_first_entry_or_null(&dfs,
+                                                struct drm_buddy_block,
+                                                tmp_link);
+               if (!block)
+                       break;
+
+               list_del(&block->tmp_link);
+
+               if (drm_buddy_block_order(block) < order)
+                       continue;
+
+               block_start = drm_buddy_block_offset(block);
+               block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+
+               if (!overlaps(start, end, block_start, block_end))
+                       continue;
+
+               if (drm_buddy_block_is_allocated(block))
+                       continue;
+
+               if (block_start < start || block_end > end) {
+                       u64 adjusted_start = max(block_start, start);
+                       u64 adjusted_end = min(block_end, end);
+
+                       if (round_down(adjusted_end + 1, req_size) <=
+                           round_up(adjusted_start, req_size))
+                               continue;
+               }
+
+               if (!fallback && block_incompatible(block, flags))
+                       continue;
+
+               if (contains(start, end, block_start, block_end) &&
+                   order == drm_buddy_block_order(block)) {
+                       /*
+                        * Find the free block within the range.
+                        */
+                       if (drm_buddy_block_is_free(block))
+                               return block;
+
+                       continue;
+               }
+
+               if (!drm_buddy_block_is_split(block)) {
+                       err = split_block(mm, block);
+                       if (unlikely(err))
+                               goto err_undo;
+               }
+
+               list_add(&block->right->tmp_link, &dfs);
+               list_add(&block->left->tmp_link, &dfs);
+       } while (1);
+
+       return ERR_PTR(-ENOSPC);
+
+err_undo:
+       /*
+        * We really don't want to leave around a bunch of split blocks, since
+        * bigger is better, so make sure we merge everything back before we
+        * free the allocated blocks.
+        */
+       buddy = __get_buddy(block);
+       if (buddy &&
+           (drm_buddy_block_is_free(block) &&
+            drm_buddy_block_is_free(buddy)))
+               __drm_buddy_free(mm, block, false);
+       return ERR_PTR(err);
+}
+
+static struct drm_buddy_block *
+__drm_buddy_alloc_range_bias(struct drm_buddy *mm,
+                            u64 start, u64 end,
+                            unsigned int order,
+                            unsigned long flags)
+{
+       struct drm_buddy_block *block;
+       bool fallback = false;
+
+       block = __alloc_range_bias(mm, start, end, order,
+                                  flags, fallback);
+       if (IS_ERR(block))
+               return __alloc_range_bias(mm, start, end, order,
+                                         flags, !fallback);
+
+       return block;
+}
+
+static struct drm_buddy_block *
+get_maxblock(struct drm_buddy *mm,
+            unsigned int order,
+            enum drm_buddy_free_tree tree)
+{
+       struct drm_buddy_block *max_block = NULL, *block = NULL;
+       struct rb_root *root;
+       unsigned int i;
+
+       for (i = order; i <= mm->max_order; ++i) {
+               root = &mm->free_trees[tree][i];
+               block = rbtree_last_free_block(root);
+               if (!block)
+                       continue;
+
+               if (!max_block) {
+                       max_block = block;
+                       continue;
+               }
+
+               if (drm_buddy_block_offset(block) >
+                   drm_buddy_block_offset(max_block)) {
+                       max_block = block;
+               }
+       }
+
+       return max_block;
+}
+
+static struct drm_buddy_block *
+alloc_from_freetree(struct drm_buddy *mm,
+                   unsigned int order,
+                   unsigned long flags)
+{
+       struct drm_buddy_block *block = NULL;
+       struct rb_root *root;
+       enum drm_buddy_free_tree tree;
+       unsigned int tmp;
+       int err;
+
+       tree = (flags & DRM_BUDDY_CLEAR_ALLOCATION) ?
+               DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+
+       if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
+               block = get_maxblock(mm, order, tree);
+               if (block)
+                       /* Store the obtained block order */
+                       tmp = drm_buddy_block_order(block);
+       } else {
+               for (tmp = order; tmp <= mm->max_order; ++tmp) {
+                       /* Get RB tree root for this order and tree */
+                       root = &mm->free_trees[tree][tmp];
+                       block = rbtree_last_free_block(root);
+                       if (block)
+                               break;
+               }
+       }
+
+       if (!block) {
+               /* Try allocating from the other tree */
+               tree = (tree == DRM_BUDDY_CLEAR_TREE) ?
+                       DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
+
+               for (tmp = order; tmp <= mm->max_order; ++tmp) {
+                       root = &mm->free_trees[tree][tmp];
+                       block = rbtree_last_free_block(root);
+                       if (block)
+                               break;
+               }
+
+               if (!block)
+                       return ERR_PTR(-ENOSPC);
+       }
+
+       BUG_ON(!drm_buddy_block_is_free(block));
+
+       while (tmp != order) {
+               err = split_block(mm, block);
+               if (unlikely(err))
+                       goto err_undo;
+
+               block = block->right;
+               tmp--;
+       }
+       return block;
+
+err_undo:
+       if (tmp != order)
+               __drm_buddy_free(mm, block, false);
+       return ERR_PTR(err);
+}
+
+static int __alloc_range(struct drm_buddy *mm,
+                        struct list_head *dfs,
+                        u64 start, u64 size,
+                        struct list_head *blocks,
+                        u64 *total_allocated_on_err)
+{
+       struct drm_buddy_block *block;
+       struct drm_buddy_block *buddy;
+       u64 total_allocated = 0;
+       LIST_HEAD(allocated);
+       u64 end;
+       int err;
+
+       end = start + size - 1;
+
+       do {
+               u64 block_start;
+               u64 block_end;
+
+               block = list_first_entry_or_null(dfs,
+                                                struct drm_buddy_block,
+                                                tmp_link);
+               if (!block)
+                       break;
+
+               list_del(&block->tmp_link);
+
+               block_start = drm_buddy_block_offset(block);
+               block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+
+               if (!overlaps(start, end, block_start, block_end))
+                       continue;
+
+               if (drm_buddy_block_is_allocated(block)) {
+                       err = -ENOSPC;
+                       goto err_free;
+               }
+
+               if (contains(start, end, block_start, block_end)) {
+                       if (drm_buddy_block_is_free(block)) {
+                               mark_allocated(mm, block);
+                               total_allocated += drm_buddy_block_size(mm, block);
+                               mm->avail -= drm_buddy_block_size(mm, block);
+                               if (drm_buddy_block_is_clear(block))
+                                       mm->clear_avail -= drm_buddy_block_size(mm, block);
+                               list_add_tail(&block->link, &allocated);
+                               continue;
+                       } else if (!mm->clear_avail) {
+                               err = -ENOSPC;
+                               goto err_free;
+                       }
+               }
+
+               if (!drm_buddy_block_is_split(block)) {
+                       err = split_block(mm, block);
+                       if (unlikely(err))
+                               goto err_undo;
+               }
+
+               list_add(&block->right->tmp_link, dfs);
+               list_add(&block->left->tmp_link, dfs);
+       } while (1);
+
+       if (total_allocated < size) {
+               err = -ENOSPC;
+               goto err_free;
+       }
+
+       list_splice_tail(&allocated, blocks);
+
+       return 0;
+
+err_undo:
+       /*
+        * We really don't want to leave around a bunch of split blocks, since
+        * bigger is better, so make sure we merge everything back before we
+        * free the allocated blocks.
+        */
+       buddy = __get_buddy(block);
+       if (buddy &&
+           (drm_buddy_block_is_free(block) &&
+            drm_buddy_block_is_free(buddy)))
+               __drm_buddy_free(mm, block, false);
+
+err_free:
+       if (err == -ENOSPC && total_allocated_on_err) {
+               list_splice_tail(&allocated, blocks);
+               *total_allocated_on_err = total_allocated;
+       } else {
+               drm_buddy_free_list_internal(mm, &allocated);
+       }
+
+       return err;
+}
+
+static int __drm_buddy_alloc_range(struct drm_buddy *mm,
+                                  u64 start,
+                                  u64 size,
+                                  u64 *total_allocated_on_err,
+                                  struct list_head *blocks)
+{
+       LIST_HEAD(dfs);
+       int i;
+
+       for (i = 0; i < mm->n_roots; ++i)
+               list_add_tail(&mm->roots[i]->tmp_link, &dfs);
+
+       return __alloc_range(mm, &dfs, start, size,
+                            blocks, total_allocated_on_err);
+}
+
+static int __alloc_contig_try_harder(struct drm_buddy *mm,
+                                    u64 size,
+                                    u64 min_block_size,
+                                    struct list_head *blocks)
+{
+       u64 rhs_offset, lhs_offset, lhs_size, filled;
+       struct drm_buddy_block *block;
+       unsigned int tree, order;
+       LIST_HEAD(blocks_lhs);
+       unsigned long pages;
+       u64 modify_size;
+       int err;
+
+       modify_size = rounddown_pow_of_two(size);
+       pages = modify_size >> ilog2(mm->chunk_size);
+       order = fls(pages) - 1;
+       if (order == 0)
+               return -ENOSPC;
+
+       for_each_free_tree(tree) {
+               struct rb_root *root;
+               struct rb_node *iter;
+
+               root = &mm->free_trees[tree][order];
+               if (rbtree_is_empty(root))
+                       continue;
+
+               iter = rb_last(root);
+               while (iter) {
+                       block = rbtree_get_free_block(iter);
+
+                       /* Allocate blocks traversing RHS */
+                       rhs_offset = drm_buddy_block_offset(block);
+                       err =  __drm_buddy_alloc_range(mm, rhs_offset, size,
+                                                      &filled, blocks);
+                       if (!err || err != -ENOSPC)
+                               return err;
+
+                       lhs_size = max((size - filled), min_block_size);
+                       if (!IS_ALIGNED(lhs_size, min_block_size))
+                               lhs_size = round_up(lhs_size, min_block_size);
+
+                       /* Allocate blocks traversing LHS */
+                       lhs_offset = drm_buddy_block_offset(block) - lhs_size;
+                       err =  __drm_buddy_alloc_range(mm, lhs_offset, lhs_size,
+                                                      NULL, &blocks_lhs);
+                       if (!err) {
+                               list_splice(&blocks_lhs, blocks);
+                               return 0;
+                       } else if (err != -ENOSPC) {
+                               drm_buddy_free_list_internal(mm, blocks);
+                               return err;
+                       }
+                       /* Free blocks for the next iteration */
+                       drm_buddy_free_list_internal(mm, blocks);
+
+                       iter = rb_prev(iter);
+               }
+       }
+
+       return -ENOSPC;
+}
+
+/**
+ * drm_buddy_block_trim - free unused pages
+ *
+ * @mm: DRM buddy manager
+ * @start: start address to begin the trimming.
+ * @new_size: original size requested
+ * @blocks: Input and output list of allocated blocks.
+ * MUST contain single block as input to be trimmed.
+ * On success will contain the newly allocated blocks
+ * making up the @new_size. Blocks always appear in
+ * ascending order
+ *
+ * For contiguous allocation, we round up the size to the nearest
+ * power of two value, drivers consume *actual* size, so remaining
+ * portions are unused and can be optionally freed with this function
+ *
+ * Returns:
+ * 0 on success, error code on failure.
+ */
+int drm_buddy_block_trim(struct drm_buddy *mm,
+                        u64 *start,
+                        u64 new_size,
+                        struct list_head *blocks)
+{
+       struct drm_buddy_block *parent;
+       struct drm_buddy_block *block;
+       u64 block_start, block_end;
+       LIST_HEAD(dfs);
+       u64 new_start;
+       int err;
+
+       if (!list_is_singular(blocks))
+               return -EINVAL;
+
+       block = list_first_entry(blocks,
+                                struct drm_buddy_block,
+                                link);
+
+       block_start = drm_buddy_block_offset(block);
+       block_end = block_start + drm_buddy_block_size(mm, block);
+
+       if (WARN_ON(!drm_buddy_block_is_allocated(block)))
+               return -EINVAL;
+
+       if (new_size > drm_buddy_block_size(mm, block))
+               return -EINVAL;
+
+       if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
+               return -EINVAL;
+
+       if (new_size == drm_buddy_block_size(mm, block))
+               return 0;
+
+       new_start = block_start;
+       if (start) {
+               new_start = *start;
+
+               if (new_start < block_start)
+                       return -EINVAL;
+
+               if (!IS_ALIGNED(new_start, mm->chunk_size))
+                       return -EINVAL;
+
+               if (range_overflows(new_start, new_size, block_end))
+                       return -EINVAL;
+       }
+
+       list_del(&block->link);
+       mark_free(mm, block);
+       mm->avail += drm_buddy_block_size(mm, block);
+       if (drm_buddy_block_is_clear(block))
+               mm->clear_avail += drm_buddy_block_size(mm, block);
+
+       /* Prevent recursively freeing this node */
+       parent = block->parent;
+       block->parent = NULL;
+
+       list_add(&block->tmp_link, &dfs);
+       err =  __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
+       if (err) {
+               mark_allocated(mm, block);
+               mm->avail -= drm_buddy_block_size(mm, block);
+               if (drm_buddy_block_is_clear(block))
+                       mm->clear_avail -= drm_buddy_block_size(mm, block);
+               list_add(&block->link, blocks);
+       }
+
+       block->parent = parent;
+       return err;
+}
+EXPORT_SYMBOL(drm_buddy_block_trim);
+
+static struct drm_buddy_block *
+__drm_buddy_alloc_blocks(struct drm_buddy *mm,
+                        u64 start, u64 end,
+                        unsigned int order,
+                        unsigned long flags)
+{
+       if (flags & DRM_BUDDY_RANGE_ALLOCATION)
+               /* Allocate traversing within the range */
+               return  __drm_buddy_alloc_range_bias(mm, start, end,
+                                                    order, flags);
+       else
+               /* Allocate from freetree */
+               return alloc_from_freetree(mm, order, flags);
+}
+
+/**
+ * drm_buddy_alloc_blocks - allocate power-of-two blocks
+ *
+ * @mm: DRM buddy manager to allocate from
+ * @start: start of the allowed range for this block
+ * @end: end of the allowed range for this block
+ * @size: size of the allocation in bytes
+ * @min_block_size: alignment of the allocation
+ * @blocks: output list head to add allocated blocks
+ * @flags: DRM_BUDDY_*_ALLOCATION flags
+ *
+ * alloc_range_bias() called on range limitations, which traverses
+ * the tree and returns the desired block.
+ *
+ * alloc_from_freetree() called when *no* range restrictions
+ * are enforced, which picks the block from the freetree.
+ *
+ * Returns:
+ * 0 on success, error code on failure.
+ */
+int 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)
+{
+       struct drm_buddy_block *block = NULL;
+       u64 original_size, original_min_size;
+       unsigned int min_order, order;
+       LIST_HEAD(allocated);
+       unsigned long pages;
+       int err;
+
+       if (size < mm->chunk_size)
+               return -EINVAL;
+
+       if (min_block_size < mm->chunk_size)
+               return -EINVAL;
+
+       if (!is_power_of_2(min_block_size))
+               return -EINVAL;
+
+       if (!IS_ALIGNED(start | end | size, mm->chunk_size))
+               return -EINVAL;
+
+       if (end > mm->size)
+               return -EINVAL;
+
+       if (range_overflows(start, size, mm->size))
+               return -EINVAL;
+
+       /* Actual range allocation */
+       if (start + size == end) {
+               if (!IS_ALIGNED(start | end, min_block_size))
+                       return -EINVAL;
+
+               return __drm_buddy_alloc_range(mm, start, size, NULL, blocks);
+       }
+
+       original_size = size;
+       original_min_size = min_block_size;
+
+       /* Roundup the size to power of 2 */
+       if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) {
+               size = roundup_pow_of_two(size);
+               min_block_size = size;
+       /* Align size value to min_block_size */
+       } else if (!IS_ALIGNED(size, min_block_size)) {
+               size = round_up(size, min_block_size);
+       }
+
+       pages = size >> ilog2(mm->chunk_size);
+       order = fls(pages) - 1;
+       min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
+
+       if (order > mm->max_order || size > mm->size) {
+               if ((flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) &&
+                   !(flags & DRM_BUDDY_RANGE_ALLOCATION))
+                       return __alloc_contig_try_harder(mm, original_size,
+                                                        original_min_size, blocks);
+
+               return -EINVAL;
+       }
+
+       do {
+               order = min(order, (unsigned int)fls(pages) - 1);
+               BUG_ON(order > mm->max_order);
+               BUG_ON(order < min_order);
+
+               do {
+                       block = __drm_buddy_alloc_blocks(mm, start,
+                                                        end,
+                                                        order,
+                                                        flags);
+                       if (!IS_ERR(block))
+                               break;
+
+                       if (order-- == min_order) {
+                               /* Try allocation through force merge method */
+                               if (mm->clear_avail &&
+                                   !__force_merge(mm, start, end, min_order)) {
+                                       block = __drm_buddy_alloc_blocks(mm, start,
+                                                                        end,
+                                                                        min_order,
+                                                                        flags);
+                                       if (!IS_ERR(block)) {
+                                               order = min_order;
+                                               break;
+                                       }
+                               }
+
+                               /*
+                                * Try contiguous block allocation through
+                                * try harder method.
+                                */
+                               if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION &&
+                                   !(flags & DRM_BUDDY_RANGE_ALLOCATION))
+                                       return __alloc_contig_try_harder(mm,
+                                                                        original_size,
+                                                                        original_min_size,
+                                                                        blocks);
+                               err = -ENOSPC;
+                               goto err_free;
+                       }
+               } while (1);
+
+               mark_allocated(mm, block);
+               mm->avail -= drm_buddy_block_size(mm, block);
+               if (drm_buddy_block_is_clear(block))
+                       mm->clear_avail -= drm_buddy_block_size(mm, block);
+               kmemleak_update_trace(block);
+               list_add_tail(&block->link, &allocated);
+
+               pages -= BIT(order);
+
+               if (!pages)
+                       break;
+       } while (1);
+
+       /* Trim the allocated block to the required size */
+       if (!(flags & DRM_BUDDY_TRIM_DISABLE) &&
+           original_size != size) {
+               struct list_head *trim_list;
+               LIST_HEAD(temp);
+               u64 trim_size;
+
+               trim_list = &allocated;
+               trim_size = original_size;
+
+               if (!list_is_singular(&allocated)) {
+                       block = list_last_entry(&allocated, typeof(*block), link);
+                       list_move(&block->link, &temp);
+                       trim_list = &temp;
+                       trim_size = drm_buddy_block_size(mm, block) -
+                               (size - original_size);
+               }
+
+               drm_buddy_block_trim(mm,
+                                    NULL,
+                                    trim_size,
+                                    trim_list);
+
+               if (!list_empty(&temp))
+                       list_splice_tail(trim_list, &allocated);
+       }
+
+       list_splice_tail(&allocated, blocks);
+       return 0;
+
+err_free:
+       drm_buddy_free_list_internal(mm, &allocated);
+       return err;
+}
+EXPORT_SYMBOL(drm_buddy_alloc_blocks);
+
+/**
+ * drm_buddy_block_print - print block information
+ *
+ * @mm: DRM buddy manager
+ * @block: DRM buddy block
+ * @p: DRM printer to use
+ */
+void drm_buddy_block_print(struct drm_buddy *mm,
+                          struct drm_buddy_block *block,
+                          struct drm_printer *p)
+{
+       u64 start = drm_buddy_block_offset(block);
+       u64 size = drm_buddy_block_size(mm, block);
+
+       drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
+}
+EXPORT_SYMBOL(drm_buddy_block_print);
+
+/**
+ * drm_buddy_print - print allocator state
+ *
+ * @mm: DRM buddy manager
+ * @p: DRM printer to use
+ */
+void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
+{
+       int order;
+
+       drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB, clear_free: %lluMiB\n",
+                  mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
+
+       for (order = mm->max_order; order >= 0; order--) {
+               struct drm_buddy_block *block, *tmp;
+               struct rb_root *root;
+               u64 count = 0, free;
+               unsigned int tree;
+
+               for_each_free_tree(tree) {
+                       root = &mm->free_trees[tree][order];
+
+                       rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
+                               BUG_ON(!drm_buddy_block_is_free(block));
+                               count++;
+                       }
+               }
+
+               drm_printf(p, "order-%2d ", order);
+
+               free = count * (mm->chunk_size << order);
+               if (free < SZ_1M)
+                       drm_printf(p, "free: %8llu KiB", free >> 10);
+               else
+                       drm_printf(p, "free: %8llu MiB", free >> 20);
+
+               drm_printf(p, ", blocks: %llu\n", count);
+       }
+}
+EXPORT_SYMBOL(drm_buddy_print);
+
+static void drm_buddy_module_exit(void)
+{
+       kmem_cache_destroy(slab_blocks);
+}
+
+static int __init drm_buddy_module_init(void)
+{
+       slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
+       if (!slab_blocks)
+               return -ENOMEM;
+
+       return 0;
+}
+
+module_init(drm_buddy_module_init);
+module_exit(drm_buddy_module_exit);
+
+MODULE_DESCRIPTION("DRM Buddy Allocator");
+MODULE_LICENSE("Dual MIT/GPL");
index 5888eb147ed14ae81912fbae25448d6155dded31..862ff4000969a2cec81f0538a53d5602ab8cc911 100644 (file)
@@ -269,10 +269,6 @@ config DRM_SCHED
 config DRM_PANEL_BACKLIGHT_QUIRKS
        tristate
 
-config DRM_LIB_RANDOM
-       bool
-       default n
-
 config DRM_PRIVACY_SCREEN
        bool
        default n
index 05dc43c0b8c5af4fe2a1a615e5c607d466758b30..3b7886865335a353330d39074b6bbf62aa9dde4d 100644 (file)
@@ -69,7 +69,6 @@ config DRM_KUNIT_TEST
        select DRM_EXPORT_FOR_TESTS if m
        select DRM_GEM_SHMEM_HELPER
        select DRM_KUNIT_TEST_HELPERS
-       select DRM_LIB_RANDOM
        select DRM_SYSFB_HELPER
        select PRIME_NUMBERS
        default KUNIT_ALL_TESTS
index 75840ec4d78226b27c2a3116391c449b36b5bde1..892859cfe95fe480951783029052d0f0eebf1be6 100644 (file)
@@ -79,7 +79,6 @@ drm-$(CONFIG_DRM_CLIENT) += \
        drm_client_event.o \
        drm_client_modeset.o \
        drm_client_sysrq.o
-drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
 drm-$(CONFIG_COMPAT) += drm_ioc32.o
 drm-$(CONFIG_DRM_PANEL) += drm_panel.o
 drm-$(CONFIG_OF) += drm_of.o
@@ -115,7 +114,7 @@ drm_gpusvm_helper-$(CONFIG_ZONE_DEVICE) += \
 
 obj-$(CONFIG_DRM_GPUSVM) += drm_gpusvm_helper.o
 
-obj-$(CONFIG_DRM_BUDDY) += drm_buddy.o
+obj-$(CONFIG_DRM_BUDDY) += ../buddy.o
 
 drm_dma_helper-y := drm_gem_dma_helper.o
 drm_dma_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fbdev_dma.o
index 5f5fd9a911c2680610eba5bdfd0b2d32811103e9..8747796180565be483f2727b94218618b79f5b24 100644 (file)
@@ -24,7 +24,7 @@
 #ifndef __AMDGPU_VRAM_MGR_H__
 #define __AMDGPU_VRAM_MGR_H__
 
-#include <drm/drm_buddy.h>
+#include <linux/gpu_buddy.h>
 
 struct amdgpu_vram_mgr {
        struct ttm_resource_manager manager;
diff --git a/drivers/gpu/drm/drm_buddy.c b/drivers/gpu/drm/drm_buddy.c
deleted file mode 100644 (file)
index fd34d37..0000000
+++ /dev/null
@@ -1,1336 +0,0 @@
-// SPDX-License-Identifier: MIT
-/*
- * Copyright © 2021 Intel Corporation
- */
-
-#include <kunit/test-bug.h>
-
-#include <linux/export.h>
-#include <linux/kmemleak.h>
-#include <linux/module.h>
-#include <linux/sizes.h>
-
-#include <drm/drm_buddy.h>
-#include <drm/drm_print.h>
-
-enum drm_buddy_free_tree {
-       DRM_BUDDY_CLEAR_TREE = 0,
-       DRM_BUDDY_DIRTY_TREE,
-       DRM_BUDDY_MAX_FREE_TREES,
-};
-
-static struct kmem_cache *slab_blocks;
-
-#define for_each_free_tree(tree) \
-       for ((tree) = 0; (tree) < DRM_BUDDY_MAX_FREE_TREES; (tree)++)
-
-static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
-                                              struct drm_buddy_block *parent,
-                                              unsigned int order,
-                                              u64 offset)
-{
-       struct drm_buddy_block *block;
-
-       BUG_ON(order > DRM_BUDDY_MAX_ORDER);
-
-       block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
-       if (!block)
-               return NULL;
-
-       block->header = offset;
-       block->header |= order;
-       block->parent = parent;
-
-       RB_CLEAR_NODE(&block->rb);
-
-       BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
-       return block;
-}
-
-static void drm_block_free(struct drm_buddy *mm,
-                          struct drm_buddy_block *block)
-{
-       kmem_cache_free(slab_blocks, block);
-}
-
-static enum drm_buddy_free_tree
-get_block_tree(struct drm_buddy_block *block)
-{
-       return drm_buddy_block_is_clear(block) ?
-              DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
-}
-
-static struct drm_buddy_block *
-rbtree_get_free_block(const struct rb_node *node)
-{
-       return node ? rb_entry(node, struct drm_buddy_block, rb) : NULL;
-}
-
-static struct drm_buddy_block *
-rbtree_last_free_block(struct rb_root *root)
-{
-       return rbtree_get_free_block(rb_last(root));
-}
-
-static bool rbtree_is_empty(struct rb_root *root)
-{
-       return RB_EMPTY_ROOT(root);
-}
-
-static bool drm_buddy_block_offset_less(const struct drm_buddy_block *block,
-                                       const struct drm_buddy_block *node)
-{
-       return drm_buddy_block_offset(block) < drm_buddy_block_offset(node);
-}
-
-static bool rbtree_block_offset_less(struct rb_node *block,
-                                    const struct rb_node *node)
-{
-       return drm_buddy_block_offset_less(rbtree_get_free_block(block),
-                                          rbtree_get_free_block(node));
-}
-
-static void rbtree_insert(struct drm_buddy *mm,
-                         struct drm_buddy_block *block,
-                         enum drm_buddy_free_tree tree)
-{
-       rb_add(&block->rb,
-              &mm->free_trees[tree][drm_buddy_block_order(block)],
-              rbtree_block_offset_less);
-}
-
-static void rbtree_remove(struct drm_buddy *mm,
-                         struct drm_buddy_block *block)
-{
-       unsigned int order = drm_buddy_block_order(block);
-       enum drm_buddy_free_tree tree;
-       struct rb_root *root;
-
-       tree = get_block_tree(block);
-       root = &mm->free_trees[tree][order];
-
-       rb_erase(&block->rb, root);
-       RB_CLEAR_NODE(&block->rb);
-}
-
-static void clear_reset(struct drm_buddy_block *block)
-{
-       block->header &= ~DRM_BUDDY_HEADER_CLEAR;
-}
-
-static void mark_cleared(struct drm_buddy_block *block)
-{
-       block->header |= DRM_BUDDY_HEADER_CLEAR;
-}
-
-static void mark_allocated(struct drm_buddy *mm,
-                          struct drm_buddy_block *block)
-{
-       block->header &= ~DRM_BUDDY_HEADER_STATE;
-       block->header |= DRM_BUDDY_ALLOCATED;
-
-       rbtree_remove(mm, block);
-}
-
-static void mark_free(struct drm_buddy *mm,
-                     struct drm_buddy_block *block)
-{
-       enum drm_buddy_free_tree tree;
-
-       block->header &= ~DRM_BUDDY_HEADER_STATE;
-       block->header |= DRM_BUDDY_FREE;
-
-       tree = get_block_tree(block);
-       rbtree_insert(mm, block, tree);
-}
-
-static void mark_split(struct drm_buddy *mm,
-                      struct drm_buddy_block *block)
-{
-       block->header &= ~DRM_BUDDY_HEADER_STATE;
-       block->header |= DRM_BUDDY_SPLIT;
-
-       rbtree_remove(mm, block);
-}
-
-static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
-{
-       return s1 <= e2 && e1 >= s2;
-}
-
-static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
-{
-       return s1 <= s2 && e1 >= e2;
-}
-
-static struct drm_buddy_block *
-__get_buddy(struct drm_buddy_block *block)
-{
-       struct drm_buddy_block *parent;
-
-       parent = block->parent;
-       if (!parent)
-               return NULL;
-
-       if (parent->left == block)
-               return parent->right;
-
-       return parent->left;
-}
-
-static unsigned int __drm_buddy_free(struct drm_buddy *mm,
-                                    struct drm_buddy_block *block,
-                                    bool force_merge)
-{
-       struct drm_buddy_block *parent;
-       unsigned int order;
-
-       while ((parent = block->parent)) {
-               struct drm_buddy_block *buddy;
-
-               buddy = __get_buddy(block);
-
-               if (!drm_buddy_block_is_free(buddy))
-                       break;
-
-               if (!force_merge) {
-                       /*
-                        * Check the block and its buddy clear state and exit
-                        * the loop if they both have the dissimilar state.
-                        */
-                       if (drm_buddy_block_is_clear(block) !=
-                           drm_buddy_block_is_clear(buddy))
-                               break;
-
-                       if (drm_buddy_block_is_clear(block))
-                               mark_cleared(parent);
-               }
-
-               rbtree_remove(mm, buddy);
-               if (force_merge && drm_buddy_block_is_clear(buddy))
-                       mm->clear_avail -= drm_buddy_block_size(mm, buddy);
-
-               drm_block_free(mm, block);
-               drm_block_free(mm, buddy);
-
-               block = parent;
-       }
-
-       order = drm_buddy_block_order(block);
-       mark_free(mm, block);
-
-       return order;
-}
-
-static int __force_merge(struct drm_buddy *mm,
-                        u64 start,
-                        u64 end,
-                        unsigned int min_order)
-{
-       unsigned int tree, order;
-       int i;
-
-       if (!min_order)
-               return -ENOMEM;
-
-       if (min_order > mm->max_order)
-               return -EINVAL;
-
-       for_each_free_tree(tree) {
-               for (i = min_order - 1; i >= 0; i--) {
-                       struct rb_node *iter = rb_last(&mm->free_trees[tree][i]);
-
-                       while (iter) {
-                               struct drm_buddy_block *block, *buddy;
-                               u64 block_start, block_end;
-
-                               block = rbtree_get_free_block(iter);
-                               iter = rb_prev(iter);
-
-                               if (!block || !block->parent)
-                                       continue;
-
-                               block_start = drm_buddy_block_offset(block);
-                               block_end = block_start + drm_buddy_block_size(mm, block) - 1;
-
-                               if (!contains(start, end, block_start, block_end))
-                                       continue;
-
-                               buddy = __get_buddy(block);
-                               if (!drm_buddy_block_is_free(buddy))
-                                       continue;
-
-                               WARN_ON(drm_buddy_block_is_clear(block) ==
-                                       drm_buddy_block_is_clear(buddy));
-
-                               /*
-                                * Advance to the next node when the current node is the buddy,
-                                * as freeing the block will also remove its buddy from the tree.
-                                */
-                               if (iter == &buddy->rb)
-                                       iter = rb_prev(iter);
-
-                               rbtree_remove(mm, block);
-                               if (drm_buddy_block_is_clear(block))
-                                       mm->clear_avail -= drm_buddy_block_size(mm, block);
-
-                               order = __drm_buddy_free(mm, block, true);
-                               if (order >= min_order)
-                                       return 0;
-                       }
-               }
-       }
-
-       return -ENOMEM;
-}
-
-/**
- * drm_buddy_init - init memory manager
- *
- * @mm: DRM buddy manager to initialize
- * @size: size in bytes to manage
- * @chunk_size: minimum page size in bytes for our allocations
- *
- * Initializes the memory manager and its resources.
- *
- * Returns:
- * 0 on success, error code on failure.
- */
-int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
-{
-       unsigned int i, j, root_count = 0;
-       u64 offset = 0;
-
-       if (size < chunk_size)
-               return -EINVAL;
-
-       if (chunk_size < SZ_4K)
-               return -EINVAL;
-
-       if (!is_power_of_2(chunk_size))
-               return -EINVAL;
-
-       size = round_down(size, chunk_size);
-
-       mm->size = size;
-       mm->avail = size;
-       mm->clear_avail = 0;
-       mm->chunk_size = chunk_size;
-       mm->max_order = ilog2(size) - ilog2(chunk_size);
-
-       BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
-
-       mm->free_trees = kmalloc_array(DRM_BUDDY_MAX_FREE_TREES,
-                                      sizeof(*mm->free_trees),
-                                      GFP_KERNEL);
-       if (!mm->free_trees)
-               return -ENOMEM;
-
-       for_each_free_tree(i) {
-               mm->free_trees[i] = kmalloc_array(mm->max_order + 1,
-                                                 sizeof(struct rb_root),
-                                                 GFP_KERNEL);
-               if (!mm->free_trees[i])
-                       goto out_free_tree;
-
-               for (j = 0; j <= mm->max_order; ++j)
-                       mm->free_trees[i][j] = RB_ROOT;
-       }
-
-       mm->n_roots = hweight64(size);
-
-       mm->roots = kmalloc_array(mm->n_roots,
-                                 sizeof(struct drm_buddy_block *),
-                                 GFP_KERNEL);
-       if (!mm->roots)
-               goto out_free_tree;
-
-       /*
-        * Split into power-of-two blocks, in case we are given a size that is
-        * not itself a power-of-two.
-        */
-       do {
-               struct drm_buddy_block *root;
-               unsigned int order;
-               u64 root_size;
-
-               order = ilog2(size) - ilog2(chunk_size);
-               root_size = chunk_size << order;
-
-               root = drm_block_alloc(mm, NULL, order, offset);
-               if (!root)
-                       goto out_free_roots;
-
-               mark_free(mm, root);
-
-               BUG_ON(root_count > mm->max_order);
-               BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
-
-               mm->roots[root_count] = root;
-
-               offset += root_size;
-               size -= root_size;
-               root_count++;
-       } while (size);
-
-       return 0;
-
-out_free_roots:
-       while (root_count--)
-               drm_block_free(mm, mm->roots[root_count]);
-       kfree(mm->roots);
-out_free_tree:
-       while (i--)
-               kfree(mm->free_trees[i]);
-       kfree(mm->free_trees);
-       return -ENOMEM;
-}
-EXPORT_SYMBOL(drm_buddy_init);
-
-/**
- * drm_buddy_fini - tear down the memory manager
- *
- * @mm: DRM buddy manager to free
- *
- * Cleanup memory manager resources and the freetree
- */
-void drm_buddy_fini(struct drm_buddy *mm)
-{
-       u64 root_size, size, start;
-       unsigned int order;
-       int i;
-
-       size = mm->size;
-
-       for (i = 0; i < mm->n_roots; ++i) {
-               order = ilog2(size) - ilog2(mm->chunk_size);
-               start = drm_buddy_block_offset(mm->roots[i]);
-               __force_merge(mm, start, start + size, order);
-
-               if (WARN_ON(!drm_buddy_block_is_free(mm->roots[i])))
-                       kunit_fail_current_test("buddy_fini() root");
-
-               drm_block_free(mm, mm->roots[i]);
-
-               root_size = mm->chunk_size << order;
-               size -= root_size;
-       }
-
-       WARN_ON(mm->avail != mm->size);
-
-       for_each_free_tree(i)
-               kfree(mm->free_trees[i]);
-       kfree(mm->free_trees);
-       kfree(mm->roots);
-}
-EXPORT_SYMBOL(drm_buddy_fini);
-
-static int split_block(struct drm_buddy *mm,
-                      struct drm_buddy_block *block)
-{
-       unsigned int block_order = drm_buddy_block_order(block) - 1;
-       u64 offset = drm_buddy_block_offset(block);
-
-       BUG_ON(!drm_buddy_block_is_free(block));
-       BUG_ON(!drm_buddy_block_order(block));
-
-       block->left = drm_block_alloc(mm, block, block_order, offset);
-       if (!block->left)
-               return -ENOMEM;
-
-       block->right = drm_block_alloc(mm, block, block_order,
-                                      offset + (mm->chunk_size << block_order));
-       if (!block->right) {
-               drm_block_free(mm, block->left);
-               return -ENOMEM;
-       }
-
-       mark_split(mm, block);
-
-       if (drm_buddy_block_is_clear(block)) {
-               mark_cleared(block->left);
-               mark_cleared(block->right);
-               clear_reset(block);
-       }
-
-       mark_free(mm, block->left);
-       mark_free(mm, block->right);
-
-       return 0;
-}
-
-/**
- * drm_get_buddy - get buddy address
- *
- * @block: DRM buddy block
- *
- * Returns the corresponding buddy block for @block, or NULL
- * if this is a root block and can't be merged further.
- * Requires some kind of locking to protect against
- * any concurrent allocate and free operations.
- */
-struct drm_buddy_block *
-drm_get_buddy(struct drm_buddy_block *block)
-{
-       return __get_buddy(block);
-}
-EXPORT_SYMBOL(drm_get_buddy);
-
-/**
- * drm_buddy_reset_clear - reset blocks clear state
- *
- * @mm: DRM buddy manager
- * @is_clear: blocks clear state
- *
- * Reset the clear state based on @is_clear value for each block
- * in the freetree.
- */
-void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear)
-{
-       enum drm_buddy_free_tree src_tree, dst_tree;
-       u64 root_size, size, start;
-       unsigned int order;
-       int i;
-
-       size = mm->size;
-       for (i = 0; i < mm->n_roots; ++i) {
-               order = ilog2(size) - ilog2(mm->chunk_size);
-               start = drm_buddy_block_offset(mm->roots[i]);
-               __force_merge(mm, start, start + size, order);
-
-               root_size = mm->chunk_size << order;
-               size -= root_size;
-       }
-
-       src_tree = is_clear ? DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
-       dst_tree = is_clear ? DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
-
-       for (i = 0; i <= mm->max_order; ++i) {
-               struct rb_root *root = &mm->free_trees[src_tree][i];
-               struct drm_buddy_block *block, *tmp;
-
-               rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
-                       rbtree_remove(mm, block);
-                       if (is_clear) {
-                               mark_cleared(block);
-                               mm->clear_avail += drm_buddy_block_size(mm, block);
-                       } else {
-                               clear_reset(block);
-                               mm->clear_avail -= drm_buddy_block_size(mm, block);
-                       }
-
-                       rbtree_insert(mm, block, dst_tree);
-               }
-       }
-}
-EXPORT_SYMBOL(drm_buddy_reset_clear);
-
-/**
- * drm_buddy_free_block - free a block
- *
- * @mm: DRM buddy manager
- * @block: block to be freed
- */
-void drm_buddy_free_block(struct drm_buddy *mm,
-                         struct drm_buddy_block *block)
-{
-       BUG_ON(!drm_buddy_block_is_allocated(block));
-       mm->avail += drm_buddy_block_size(mm, block);
-       if (drm_buddy_block_is_clear(block))
-               mm->clear_avail += drm_buddy_block_size(mm, block);
-
-       __drm_buddy_free(mm, block, false);
-}
-EXPORT_SYMBOL(drm_buddy_free_block);
-
-static void __drm_buddy_free_list(struct drm_buddy *mm,
-                                 struct list_head *objects,
-                                 bool mark_clear,
-                                 bool mark_dirty)
-{
-       struct drm_buddy_block *block, *on;
-
-       WARN_ON(mark_dirty && mark_clear);
-
-       list_for_each_entry_safe(block, on, objects, link) {
-               if (mark_clear)
-                       mark_cleared(block);
-               else if (mark_dirty)
-                       clear_reset(block);
-               drm_buddy_free_block(mm, block);
-               cond_resched();
-       }
-       INIT_LIST_HEAD(objects);
-}
-
-static void drm_buddy_free_list_internal(struct drm_buddy *mm,
-                                        struct list_head *objects)
-{
-       /*
-        * Don't touch the clear/dirty bit, since allocation is still internal
-        * at this point. For example we might have just failed part of the
-        * allocation.
-        */
-       __drm_buddy_free_list(mm, objects, false, false);
-}
-
-/**
- * drm_buddy_free_list - free blocks
- *
- * @mm: DRM buddy manager
- * @objects: input list head to free blocks
- * @flags: optional flags like DRM_BUDDY_CLEARED
- */
-void drm_buddy_free_list(struct drm_buddy *mm,
-                        struct list_head *objects,
-                        unsigned int flags)
-{
-       bool mark_clear = flags & DRM_BUDDY_CLEARED;
-
-       __drm_buddy_free_list(mm, objects, mark_clear, !mark_clear);
-}
-EXPORT_SYMBOL(drm_buddy_free_list);
-
-static bool block_incompatible(struct drm_buddy_block *block, unsigned int flags)
-{
-       bool needs_clear = flags & DRM_BUDDY_CLEAR_ALLOCATION;
-
-       return needs_clear != drm_buddy_block_is_clear(block);
-}
-
-static struct drm_buddy_block *
-__alloc_range_bias(struct drm_buddy *mm,
-                  u64 start, u64 end,
-                  unsigned int order,
-                  unsigned long flags,
-                  bool fallback)
-{
-       u64 req_size = mm->chunk_size << order;
-       struct drm_buddy_block *block;
-       struct drm_buddy_block *buddy;
-       LIST_HEAD(dfs);
-       int err;
-       int i;
-
-       end = end - 1;
-
-       for (i = 0; i < mm->n_roots; ++i)
-               list_add_tail(&mm->roots[i]->tmp_link, &dfs);
-
-       do {
-               u64 block_start;
-               u64 block_end;
-
-               block = list_first_entry_or_null(&dfs,
-                                                struct drm_buddy_block,
-                                                tmp_link);
-               if (!block)
-                       break;
-
-               list_del(&block->tmp_link);
-
-               if (drm_buddy_block_order(block) < order)
-                       continue;
-
-               block_start = drm_buddy_block_offset(block);
-               block_end = block_start + drm_buddy_block_size(mm, block) - 1;
-
-               if (!overlaps(start, end, block_start, block_end))
-                       continue;
-
-               if (drm_buddy_block_is_allocated(block))
-                       continue;
-
-               if (block_start < start || block_end > end) {
-                       u64 adjusted_start = max(block_start, start);
-                       u64 adjusted_end = min(block_end, end);
-
-                       if (round_down(adjusted_end + 1, req_size) <=
-                           round_up(adjusted_start, req_size))
-                               continue;
-               }
-
-               if (!fallback && block_incompatible(block, flags))
-                       continue;
-
-               if (contains(start, end, block_start, block_end) &&
-                   order == drm_buddy_block_order(block)) {
-                       /*
-                        * Find the free block within the range.
-                        */
-                       if (drm_buddy_block_is_free(block))
-                               return block;
-
-                       continue;
-               }
-
-               if (!drm_buddy_block_is_split(block)) {
-                       err = split_block(mm, block);
-                       if (unlikely(err))
-                               goto err_undo;
-               }
-
-               list_add(&block->right->tmp_link, &dfs);
-               list_add(&block->left->tmp_link, &dfs);
-       } while (1);
-
-       return ERR_PTR(-ENOSPC);
-
-err_undo:
-       /*
-        * We really don't want to leave around a bunch of split blocks, since
-        * bigger is better, so make sure we merge everything back before we
-        * free the allocated blocks.
-        */
-       buddy = __get_buddy(block);
-       if (buddy &&
-           (drm_buddy_block_is_free(block) &&
-            drm_buddy_block_is_free(buddy)))
-               __drm_buddy_free(mm, block, false);
-       return ERR_PTR(err);
-}
-
-static struct drm_buddy_block *
-__drm_buddy_alloc_range_bias(struct drm_buddy *mm,
-                            u64 start, u64 end,
-                            unsigned int order,
-                            unsigned long flags)
-{
-       struct drm_buddy_block *block;
-       bool fallback = false;
-
-       block = __alloc_range_bias(mm, start, end, order,
-                                  flags, fallback);
-       if (IS_ERR(block))
-               return __alloc_range_bias(mm, start, end, order,
-                                         flags, !fallback);
-
-       return block;
-}
-
-static struct drm_buddy_block *
-get_maxblock(struct drm_buddy *mm,
-            unsigned int order,
-            enum drm_buddy_free_tree tree)
-{
-       struct drm_buddy_block *max_block = NULL, *block = NULL;
-       struct rb_root *root;
-       unsigned int i;
-
-       for (i = order; i <= mm->max_order; ++i) {
-               root = &mm->free_trees[tree][i];
-               block = rbtree_last_free_block(root);
-               if (!block)
-                       continue;
-
-               if (!max_block) {
-                       max_block = block;
-                       continue;
-               }
-
-               if (drm_buddy_block_offset(block) >
-                   drm_buddy_block_offset(max_block)) {
-                       max_block = block;
-               }
-       }
-
-       return max_block;
-}
-
-static struct drm_buddy_block *
-alloc_from_freetree(struct drm_buddy *mm,
-                   unsigned int order,
-                   unsigned long flags)
-{
-       struct drm_buddy_block *block = NULL;
-       struct rb_root *root;
-       enum drm_buddy_free_tree tree;
-       unsigned int tmp;
-       int err;
-
-       tree = (flags & DRM_BUDDY_CLEAR_ALLOCATION) ?
-               DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
-
-       if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
-               block = get_maxblock(mm, order, tree);
-               if (block)
-                       /* Store the obtained block order */
-                       tmp = drm_buddy_block_order(block);
-       } else {
-               for (tmp = order; tmp <= mm->max_order; ++tmp) {
-                       /* Get RB tree root for this order and tree */
-                       root = &mm->free_trees[tree][tmp];
-                       block = rbtree_last_free_block(root);
-                       if (block)
-                               break;
-               }
-       }
-
-       if (!block) {
-               /* Try allocating from the other tree */
-               tree = (tree == DRM_BUDDY_CLEAR_TREE) ?
-                       DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
-
-               for (tmp = order; tmp <= mm->max_order; ++tmp) {
-                       root = &mm->free_trees[tree][tmp];
-                       block = rbtree_last_free_block(root);
-                       if (block)
-                               break;
-               }
-
-               if (!block)
-                       return ERR_PTR(-ENOSPC);
-       }
-
-       BUG_ON(!drm_buddy_block_is_free(block));
-
-       while (tmp != order) {
-               err = split_block(mm, block);
-               if (unlikely(err))
-                       goto err_undo;
-
-               block = block->right;
-               tmp--;
-       }
-       return block;
-
-err_undo:
-       if (tmp != order)
-               __drm_buddy_free(mm, block, false);
-       return ERR_PTR(err);
-}
-
-static int __alloc_range(struct drm_buddy *mm,
-                        struct list_head *dfs,
-                        u64 start, u64 size,
-                        struct list_head *blocks,
-                        u64 *total_allocated_on_err)
-{
-       struct drm_buddy_block *block;
-       struct drm_buddy_block *buddy;
-       u64 total_allocated = 0;
-       LIST_HEAD(allocated);
-       u64 end;
-       int err;
-
-       end = start + size - 1;
-
-       do {
-               u64 block_start;
-               u64 block_end;
-
-               block = list_first_entry_or_null(dfs,
-                                                struct drm_buddy_block,
-                                                tmp_link);
-               if (!block)
-                       break;
-
-               list_del(&block->tmp_link);
-
-               block_start = drm_buddy_block_offset(block);
-               block_end = block_start + drm_buddy_block_size(mm, block) - 1;
-
-               if (!overlaps(start, end, block_start, block_end))
-                       continue;
-
-               if (drm_buddy_block_is_allocated(block)) {
-                       err = -ENOSPC;
-                       goto err_free;
-               }
-
-               if (contains(start, end, block_start, block_end)) {
-                       if (drm_buddy_block_is_free(block)) {
-                               mark_allocated(mm, block);
-                               total_allocated += drm_buddy_block_size(mm, block);
-                               mm->avail -= drm_buddy_block_size(mm, block);
-                               if (drm_buddy_block_is_clear(block))
-                                       mm->clear_avail -= drm_buddy_block_size(mm, block);
-                               list_add_tail(&block->link, &allocated);
-                               continue;
-                       } else if (!mm->clear_avail) {
-                               err = -ENOSPC;
-                               goto err_free;
-                       }
-               }
-
-               if (!drm_buddy_block_is_split(block)) {
-                       err = split_block(mm, block);
-                       if (unlikely(err))
-                               goto err_undo;
-               }
-
-               list_add(&block->right->tmp_link, dfs);
-               list_add(&block->left->tmp_link, dfs);
-       } while (1);
-
-       if (total_allocated < size) {
-               err = -ENOSPC;
-               goto err_free;
-       }
-
-       list_splice_tail(&allocated, blocks);
-
-       return 0;
-
-err_undo:
-       /*
-        * We really don't want to leave around a bunch of split blocks, since
-        * bigger is better, so make sure we merge everything back before we
-        * free the allocated blocks.
-        */
-       buddy = __get_buddy(block);
-       if (buddy &&
-           (drm_buddy_block_is_free(block) &&
-            drm_buddy_block_is_free(buddy)))
-               __drm_buddy_free(mm, block, false);
-
-err_free:
-       if (err == -ENOSPC && total_allocated_on_err) {
-               list_splice_tail(&allocated, blocks);
-               *total_allocated_on_err = total_allocated;
-       } else {
-               drm_buddy_free_list_internal(mm, &allocated);
-       }
-
-       return err;
-}
-
-static int __drm_buddy_alloc_range(struct drm_buddy *mm,
-                                  u64 start,
-                                  u64 size,
-                                  u64 *total_allocated_on_err,
-                                  struct list_head *blocks)
-{
-       LIST_HEAD(dfs);
-       int i;
-
-       for (i = 0; i < mm->n_roots; ++i)
-               list_add_tail(&mm->roots[i]->tmp_link, &dfs);
-
-       return __alloc_range(mm, &dfs, start, size,
-                            blocks, total_allocated_on_err);
-}
-
-static int __alloc_contig_try_harder(struct drm_buddy *mm,
-                                    u64 size,
-                                    u64 min_block_size,
-                                    struct list_head *blocks)
-{
-       u64 rhs_offset, lhs_offset, lhs_size, filled;
-       struct drm_buddy_block *block;
-       unsigned int tree, order;
-       LIST_HEAD(blocks_lhs);
-       unsigned long pages;
-       u64 modify_size;
-       int err;
-
-       modify_size = rounddown_pow_of_two(size);
-       pages = modify_size >> ilog2(mm->chunk_size);
-       order = fls(pages) - 1;
-       if (order == 0)
-               return -ENOSPC;
-
-       for_each_free_tree(tree) {
-               struct rb_root *root;
-               struct rb_node *iter;
-
-               root = &mm->free_trees[tree][order];
-               if (rbtree_is_empty(root))
-                       continue;
-
-               iter = rb_last(root);
-               while (iter) {
-                       block = rbtree_get_free_block(iter);
-
-                       /* Allocate blocks traversing RHS */
-                       rhs_offset = drm_buddy_block_offset(block);
-                       err =  __drm_buddy_alloc_range(mm, rhs_offset, size,
-                                                      &filled, blocks);
-                       if (!err || err != -ENOSPC)
-                               return err;
-
-                       lhs_size = max((size - filled), min_block_size);
-                       if (!IS_ALIGNED(lhs_size, min_block_size))
-                               lhs_size = round_up(lhs_size, min_block_size);
-
-                       /* Allocate blocks traversing LHS */
-                       lhs_offset = drm_buddy_block_offset(block) - lhs_size;
-                       err =  __drm_buddy_alloc_range(mm, lhs_offset, lhs_size,
-                                                      NULL, &blocks_lhs);
-                       if (!err) {
-                               list_splice(&blocks_lhs, blocks);
-                               return 0;
-                       } else if (err != -ENOSPC) {
-                               drm_buddy_free_list_internal(mm, blocks);
-                               return err;
-                       }
-                       /* Free blocks for the next iteration */
-                       drm_buddy_free_list_internal(mm, blocks);
-
-                       iter = rb_prev(iter);
-               }
-       }
-
-       return -ENOSPC;
-}
-
-/**
- * drm_buddy_block_trim - free unused pages
- *
- * @mm: DRM buddy manager
- * @start: start address to begin the trimming.
- * @new_size: original size requested
- * @blocks: Input and output list of allocated blocks.
- * MUST contain single block as input to be trimmed.
- * On success will contain the newly allocated blocks
- * making up the @new_size. Blocks always appear in
- * ascending order
- *
- * For contiguous allocation, we round up the size to the nearest
- * power of two value, drivers consume *actual* size, so remaining
- * portions are unused and can be optionally freed with this function
- *
- * Returns:
- * 0 on success, error code on failure.
- */
-int drm_buddy_block_trim(struct drm_buddy *mm,
-                        u64 *start,
-                        u64 new_size,
-                        struct list_head *blocks)
-{
-       struct drm_buddy_block *parent;
-       struct drm_buddy_block *block;
-       u64 block_start, block_end;
-       LIST_HEAD(dfs);
-       u64 new_start;
-       int err;
-
-       if (!list_is_singular(blocks))
-               return -EINVAL;
-
-       block = list_first_entry(blocks,
-                                struct drm_buddy_block,
-                                link);
-
-       block_start = drm_buddy_block_offset(block);
-       block_end = block_start + drm_buddy_block_size(mm, block);
-
-       if (WARN_ON(!drm_buddy_block_is_allocated(block)))
-               return -EINVAL;
-
-       if (new_size > drm_buddy_block_size(mm, block))
-               return -EINVAL;
-
-       if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
-               return -EINVAL;
-
-       if (new_size == drm_buddy_block_size(mm, block))
-               return 0;
-
-       new_start = block_start;
-       if (start) {
-               new_start = *start;
-
-               if (new_start < block_start)
-                       return -EINVAL;
-
-               if (!IS_ALIGNED(new_start, mm->chunk_size))
-                       return -EINVAL;
-
-               if (range_overflows(new_start, new_size, block_end))
-                       return -EINVAL;
-       }
-
-       list_del(&block->link);
-       mark_free(mm, block);
-       mm->avail += drm_buddy_block_size(mm, block);
-       if (drm_buddy_block_is_clear(block))
-               mm->clear_avail += drm_buddy_block_size(mm, block);
-
-       /* Prevent recursively freeing this node */
-       parent = block->parent;
-       block->parent = NULL;
-
-       list_add(&block->tmp_link, &dfs);
-       err =  __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
-       if (err) {
-               mark_allocated(mm, block);
-               mm->avail -= drm_buddy_block_size(mm, block);
-               if (drm_buddy_block_is_clear(block))
-                       mm->clear_avail -= drm_buddy_block_size(mm, block);
-               list_add(&block->link, blocks);
-       }
-
-       block->parent = parent;
-       return err;
-}
-EXPORT_SYMBOL(drm_buddy_block_trim);
-
-static struct drm_buddy_block *
-__drm_buddy_alloc_blocks(struct drm_buddy *mm,
-                        u64 start, u64 end,
-                        unsigned int order,
-                        unsigned long flags)
-{
-       if (flags & DRM_BUDDY_RANGE_ALLOCATION)
-               /* Allocate traversing within the range */
-               return  __drm_buddy_alloc_range_bias(mm, start, end,
-                                                    order, flags);
-       else
-               /* Allocate from freetree */
-               return alloc_from_freetree(mm, order, flags);
-}
-
-/**
- * drm_buddy_alloc_blocks - allocate power-of-two blocks
- *
- * @mm: DRM buddy manager to allocate from
- * @start: start of the allowed range for this block
- * @end: end of the allowed range for this block
- * @size: size of the allocation in bytes
- * @min_block_size: alignment of the allocation
- * @blocks: output list head to add allocated blocks
- * @flags: DRM_BUDDY_*_ALLOCATION flags
- *
- * alloc_range_bias() called on range limitations, which traverses
- * the tree and returns the desired block.
- *
- * alloc_from_freetree() called when *no* range restrictions
- * are enforced, which picks the block from the freetree.
- *
- * Returns:
- * 0 on success, error code on failure.
- */
-int 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)
-{
-       struct drm_buddy_block *block = NULL;
-       u64 original_size, original_min_size;
-       unsigned int min_order, order;
-       LIST_HEAD(allocated);
-       unsigned long pages;
-       int err;
-
-       if (size < mm->chunk_size)
-               return -EINVAL;
-
-       if (min_block_size < mm->chunk_size)
-               return -EINVAL;
-
-       if (!is_power_of_2(min_block_size))
-               return -EINVAL;
-
-       if (!IS_ALIGNED(start | end | size, mm->chunk_size))
-               return -EINVAL;
-
-       if (end > mm->size)
-               return -EINVAL;
-
-       if (range_overflows(start, size, mm->size))
-               return -EINVAL;
-
-       /* Actual range allocation */
-       if (start + size == end) {
-               if (!IS_ALIGNED(start | end, min_block_size))
-                       return -EINVAL;
-
-               return __drm_buddy_alloc_range(mm, start, size, NULL, blocks);
-       }
-
-       original_size = size;
-       original_min_size = min_block_size;
-
-       /* Roundup the size to power of 2 */
-       if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) {
-               size = roundup_pow_of_two(size);
-               min_block_size = size;
-       /* Align size value to min_block_size */
-       } else if (!IS_ALIGNED(size, min_block_size)) {
-               size = round_up(size, min_block_size);
-       }
-
-       pages = size >> ilog2(mm->chunk_size);
-       order = fls(pages) - 1;
-       min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
-
-       if (order > mm->max_order || size > mm->size) {
-               if ((flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) &&
-                   !(flags & DRM_BUDDY_RANGE_ALLOCATION))
-                       return __alloc_contig_try_harder(mm, original_size,
-                                                        original_min_size, blocks);
-
-               return -EINVAL;
-       }
-
-       do {
-               order = min(order, (unsigned int)fls(pages) - 1);
-               BUG_ON(order > mm->max_order);
-               BUG_ON(order < min_order);
-
-               do {
-                       block = __drm_buddy_alloc_blocks(mm, start,
-                                                        end,
-                                                        order,
-                                                        flags);
-                       if (!IS_ERR(block))
-                               break;
-
-                       if (order-- == min_order) {
-                               /* Try allocation through force merge method */
-                               if (mm->clear_avail &&
-                                   !__force_merge(mm, start, end, min_order)) {
-                                       block = __drm_buddy_alloc_blocks(mm, start,
-                                                                        end,
-                                                                        min_order,
-                                                                        flags);
-                                       if (!IS_ERR(block)) {
-                                               order = min_order;
-                                               break;
-                                       }
-                               }
-
-                               /*
-                                * Try contiguous block allocation through
-                                * try harder method.
-                                */
-                               if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION &&
-                                   !(flags & DRM_BUDDY_RANGE_ALLOCATION))
-                                       return __alloc_contig_try_harder(mm,
-                                                                        original_size,
-                                                                        original_min_size,
-                                                                        blocks);
-                               err = -ENOSPC;
-                               goto err_free;
-                       }
-               } while (1);
-
-               mark_allocated(mm, block);
-               mm->avail -= drm_buddy_block_size(mm, block);
-               if (drm_buddy_block_is_clear(block))
-                       mm->clear_avail -= drm_buddy_block_size(mm, block);
-               kmemleak_update_trace(block);
-               list_add_tail(&block->link, &allocated);
-
-               pages -= BIT(order);
-
-               if (!pages)
-                       break;
-       } while (1);
-
-       /* Trim the allocated block to the required size */
-       if (!(flags & DRM_BUDDY_TRIM_DISABLE) &&
-           original_size != size) {
-               struct list_head *trim_list;
-               LIST_HEAD(temp);
-               u64 trim_size;
-
-               trim_list = &allocated;
-               trim_size = original_size;
-
-               if (!list_is_singular(&allocated)) {
-                       block = list_last_entry(&allocated, typeof(*block), link);
-                       list_move(&block->link, &temp);
-                       trim_list = &temp;
-                       trim_size = drm_buddy_block_size(mm, block) -
-                               (size - original_size);
-               }
-
-               drm_buddy_block_trim(mm,
-                                    NULL,
-                                    trim_size,
-                                    trim_list);
-
-               if (!list_empty(&temp))
-                       list_splice_tail(trim_list, &allocated);
-       }
-
-       list_splice_tail(&allocated, blocks);
-       return 0;
-
-err_free:
-       drm_buddy_free_list_internal(mm, &allocated);
-       return err;
-}
-EXPORT_SYMBOL(drm_buddy_alloc_blocks);
-
-/**
- * drm_buddy_block_print - print block information
- *
- * @mm: DRM buddy manager
- * @block: DRM buddy block
- * @p: DRM printer to use
- */
-void drm_buddy_block_print(struct drm_buddy *mm,
-                          struct drm_buddy_block *block,
-                          struct drm_printer *p)
-{
-       u64 start = drm_buddy_block_offset(block);
-       u64 size = drm_buddy_block_size(mm, block);
-
-       drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
-}
-EXPORT_SYMBOL(drm_buddy_block_print);
-
-/**
- * drm_buddy_print - print allocator state
- *
- * @mm: DRM buddy manager
- * @p: DRM printer to use
- */
-void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
-{
-       int order;
-
-       drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB, clear_free: %lluMiB\n",
-                  mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
-
-       for (order = mm->max_order; order >= 0; order--) {
-               struct drm_buddy_block *block, *tmp;
-               struct rb_root *root;
-               u64 count = 0, free;
-               unsigned int tree;
-
-               for_each_free_tree(tree) {
-                       root = &mm->free_trees[tree][order];
-
-                       rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
-                               BUG_ON(!drm_buddy_block_is_free(block));
-                               count++;
-                       }
-               }
-
-               drm_printf(p, "order-%2d ", order);
-
-               free = count * (mm->chunk_size << order);
-               if (free < SZ_1M)
-                       drm_printf(p, "free: %8llu KiB", free >> 10);
-               else
-                       drm_printf(p, "free: %8llu MiB", free >> 20);
-
-               drm_printf(p, ", blocks: %llu\n", count);
-       }
-}
-EXPORT_SYMBOL(drm_buddy_print);
-
-static void drm_buddy_module_exit(void)
-{
-       kmem_cache_destroy(slab_blocks);
-}
-
-static int __init drm_buddy_module_init(void)
-{
-       slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
-       if (!slab_blocks)
-               return -ENOMEM;
-
-       return 0;
-}
-
-module_init(drm_buddy_module_init);
-module_exit(drm_buddy_module_exit);
-
-MODULE_DESCRIPTION("DRM Buddy Allocator");
-MODULE_LICENSE("Dual MIT/GPL");
index f65fe86c02b5b316a24872f57f32a6fb112b7988..eeda5daa544f0f374636abc679b228affc09adaf 100644 (file)
@@ -5,7 +5,7 @@
 
 #include <linux/shmem_fs.h>
 
-#include <drm/drm_buddy.h>
+#include <linux/gpu_buddy.h>
 #include <drm/drm_print.h>
 #include <drm/ttm/ttm_placement.h>
 #include <drm/ttm/ttm_tt.h>
index 4d830740946da39d9f690bbb1daa86814ea3a1ac..30246f02bcfe793631819e7c17ca7a1e67203344 100644 (file)
@@ -7,7 +7,7 @@
 #include "i915_scatterlist.h"
 #include "i915_ttm_buddy_manager.h"
 
-#include <drm/drm_buddy.h>
+#include <linux/gpu_buddy.h>
 #include <drm/drm_mm.h>
 
 #include <linux/slab.h>
index d5c6e6605086fec49634deb034ce2986ab5b6c37..6b256d95badda36cc083d06bb70f483b60ecb2b7 100644 (file)
@@ -5,7 +5,7 @@
 
 #include <linux/slab.h>
 
-#include <drm/drm_buddy.h>
+#include <linux/gpu_buddy.h>
 #include <drm/drm_print.h>
 #include <drm/ttm/ttm_placement.h>
 #include <drm/ttm/ttm_bo.h>
diff --git a/drivers/gpu/drm/lib/drm_random.c b/drivers/gpu/drm/lib/drm_random.c
deleted file mode 100644 (file)
index 0e9dba1..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-#include <linux/bitops.h>
-#include <linux/export.h>
-#include <linux/kernel.h>
-#include <linux/random.h>
-#include <linux/slab.h>
-#include <linux/types.h>
-
-#include "drm_random.h"
-
-u32 drm_prandom_u32_max_state(u32 ep_ro, struct rnd_state *state)
-{
-       return upper_32_bits((u64)prandom_u32_state(state) * ep_ro);
-}
-EXPORT_SYMBOL(drm_prandom_u32_max_state);
-
-void drm_random_reorder(unsigned int *order, unsigned int count,
-                       struct rnd_state *state)
-{
-       unsigned int i, j;
-
-       for (i = 0; i < count; ++i) {
-               BUILD_BUG_ON(sizeof(unsigned int) > sizeof(u32));
-               j = drm_prandom_u32_max_state(count, state);
-               swap(order[i], order[j]);
-       }
-}
-EXPORT_SYMBOL(drm_random_reorder);
-
-unsigned int *drm_random_order(unsigned int count, struct rnd_state *state)
-{
-       unsigned int *order, i;
-
-       order = kmalloc_array(count, sizeof(*order), GFP_KERNEL);
-       if (!order)
-               return order;
-
-       for (i = 0; i < count; i++)
-               order[i] = i;
-
-       drm_random_reorder(order, count, state);
-       return order;
-}
-EXPORT_SYMBOL(drm_random_order);
diff --git a/drivers/gpu/drm/lib/drm_random.h b/drivers/gpu/drm/lib/drm_random.h
deleted file mode 100644 (file)
index 9f82726..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __DRM_RANDOM_H__
-#define __DRM_RANDOM_H__
-
-/* This is a temporary home for a couple of utility functions that should
- * be transposed to lib/ at the earliest convenience.
- */
-
-#include <linux/prandom.h>
-
-#define DRM_RND_STATE_INITIALIZER(seed__) ({                           \
-       struct rnd_state state__;                                       \
-       prandom_seed_state(&state__, (seed__));                         \
-       state__;                                                        \
-})
-
-#define DRM_RND_STATE(name__, seed__) \
-       struct rnd_state name__ = DRM_RND_STATE_INITIALIZER(seed__)
-
-unsigned int *drm_random_order(unsigned int count,
-                              struct rnd_state *state);
-void drm_random_reorder(unsigned int *order,
-                       unsigned int count,
-                       struct rnd_state *state);
-u32 drm_prandom_u32_max_state(u32 ep_ro,
-                             struct rnd_state *state);
-
-#endif /* !__DRM_RANDOM_H__ */
index 87d5d5f9332acbf730bd28c1de049a6855167c9b..d2e2e3d8349aee4189209ccfd15039baa1a2f0a9 100644 (file)
@@ -7,7 +7,6 @@ obj-$(CONFIG_DRM_KUNIT_TEST) += \
        drm_atomic_test.o \
        drm_atomic_state_test.o \
        drm_bridge_test.o \
-       drm_buddy_test.o \
        drm_cmdline_parser_test.o \
        drm_connector_test.o \
        drm_damage_helper_test.o \
diff --git a/drivers/gpu/drm/tests/drm_buddy_test.c b/drivers/gpu/drm/tests/drm_buddy_test.c
deleted file mode 100644 (file)
index e6f8459..0000000
+++ /dev/null
@@ -1,928 +0,0 @@
-// SPDX-License-Identifier: MIT
-/*
- * Copyright © 2019 Intel Corporation
- * Copyright © 2022 Maíra Canal <mairacanal@riseup.net>
- */
-
-#include <kunit/test.h>
-
-#include <linux/prime_numbers.h>
-#include <linux/sched/signal.h>
-#include <linux/sizes.h>
-
-#include <drm/drm_buddy.h>
-
-#include "../lib/drm_random.h"
-
-static unsigned int random_seed;
-
-static inline u64 get_size(int order, u64 chunk_size)
-{
-       return (1 << order) * chunk_size;
-}
-
-static void drm_test_buddy_fragmentation_performance(struct kunit *test)
-{
-       struct drm_buddy_block *block, *tmp;
-       int num_blocks, i, ret, count = 0;
-       LIST_HEAD(allocated_blocks);
-       unsigned long elapsed_ms;
-       LIST_HEAD(reverse_list);
-       LIST_HEAD(test_blocks);
-       LIST_HEAD(clear_list);
-       LIST_HEAD(dirty_list);
-       LIST_HEAD(free_list);
-       struct drm_buddy mm;
-       u64 mm_size = SZ_4G;
-       ktime_t start, end;
-
-       /*
-        * Allocation under severe fragmentation
-        *
-        * Create severe fragmentation by allocating the entire 4 GiB address space
-        * as tiny 8 KiB blocks but forcing a 64 KiB alignment. The resulting pattern
-        * leaves many scattered holes. Split the allocations into two groups and
-        * return them with different flags to block coalescing, then repeatedly
-        * allocate and free 64 KiB blocks while timing the loop. This stresses how
-        * quickly the allocator can satisfy larger, aligned requests from a pool of
-        * highly fragmented space.
-        */
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
-                              "buddy_init failed\n");
-
-       num_blocks = mm_size / SZ_64K;
-
-       start = ktime_get();
-       /* Allocate with maximum fragmentation - 8K blocks with 64K alignment */
-       for (i = 0; i < num_blocks; i++)
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size, SZ_8K, SZ_64K,
-                                                                   &allocated_blocks, 0),
-                                       "buddy_alloc hit an error size=%u\n", SZ_8K);
-
-       list_for_each_entry_safe(block, tmp, &allocated_blocks, link) {
-               if (count % 4 == 0 || count % 4 == 3)
-                       list_move_tail(&block->link, &clear_list);
-               else
-                       list_move_tail(&block->link, &dirty_list);
-               count++;
-       }
-
-       /* Free with different flags to ensure no coalescing */
-       drm_buddy_free_list(&mm, &clear_list, DRM_BUDDY_CLEARED);
-       drm_buddy_free_list(&mm, &dirty_list, 0);
-
-       for (i = 0; i < num_blocks; i++)
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size, SZ_64K, SZ_64K,
-                                                                   &test_blocks, 0),
-                                       "buddy_alloc hit an error size=%u\n", SZ_64K);
-       drm_buddy_free_list(&mm, &test_blocks, 0);
-
-       end = ktime_get();
-       elapsed_ms = ktime_to_ms(ktime_sub(end, start));
-
-       kunit_info(test, "Fragmented allocation took %lu ms\n", elapsed_ms);
-
-       drm_buddy_fini(&mm);
-
-       /*
-        * Reverse free order under fragmentation
-        *
-        * Construct a fragmented 4 GiB space by allocating every 8 KiB block with
-        * 64 KiB alignment, creating a dense scatter of small regions. Half of the
-        * blocks are selectively freed to form sparse gaps, while the remaining
-        * allocations are preserved, reordered in reverse, and released back with
-        * the cleared flag. This models a pathological reverse-ordered free pattern
-        * and measures how quickly the allocator can merge and reclaim space when
-        * deallocation occurs in the opposite order of allocation, exposing the
-        * cost difference between a linear freelist scan and an ordered tree lookup.
-        */
-       ret = drm_buddy_init(&mm, mm_size, SZ_4K);
-       KUNIT_ASSERT_EQ(test, ret, 0);
-
-       start = ktime_get();
-       /* Allocate maximum fragmentation */
-       for (i = 0; i < num_blocks; i++)
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size, SZ_8K, SZ_64K,
-                                                                   &allocated_blocks, 0),
-                                       "buddy_alloc hit an error size=%u\n", SZ_8K);
-
-       list_for_each_entry_safe(block, tmp, &allocated_blocks, link) {
-               if (count % 2 == 0)
-                       list_move_tail(&block->link, &free_list);
-               count++;
-       }
-       drm_buddy_free_list(&mm, &free_list, DRM_BUDDY_CLEARED);
-
-       list_for_each_entry_safe_reverse(block, tmp, &allocated_blocks, link)
-               list_move(&block->link, &reverse_list);
-       drm_buddy_free_list(&mm, &reverse_list, DRM_BUDDY_CLEARED);
-
-       end = ktime_get();
-       elapsed_ms = ktime_to_ms(ktime_sub(end, start));
-
-       kunit_info(test, "Reverse-ordered free took %lu ms\n", elapsed_ms);
-
-       drm_buddy_fini(&mm);
-}
-
-static void drm_test_buddy_alloc_range_bias(struct kunit *test)
-{
-       u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem;
-       DRM_RND_STATE(prng, random_seed);
-       unsigned int i, count, *order;
-       struct drm_buddy_block *block;
-       unsigned long flags;
-       struct drm_buddy mm;
-       LIST_HEAD(allocated);
-
-       bias_size = SZ_1M;
-       ps = roundup_pow_of_two(prandom_u32_state(&prng) % bias_size);
-       ps = max(SZ_4K, ps);
-       mm_size = (SZ_8M-1) & ~(ps-1); /* Multiple roots */
-
-       kunit_info(test, "mm_size=%u, ps=%u\n", mm_size, ps);
-
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
-                              "buddy_init failed\n");
-
-       count = mm_size / bias_size;
-       order = drm_random_order(count, &prng);
-       KUNIT_EXPECT_TRUE(test, order);
-
-       /*
-        * Idea is to split the address space into uniform bias ranges, and then
-        * in some random order allocate within each bias, using various
-        * patterns within. This should detect if allocations leak out from a
-        * given bias, for example.
-        */
-
-       for (i = 0; i < count; i++) {
-               LIST_HEAD(tmp);
-               u32 size;
-
-               bias_start = order[i] * bias_size;
-               bias_end = bias_start + bias_size;
-               bias_rem = bias_size;
-
-               /* internal round_up too big */
-               KUNIT_ASSERT_TRUE_MSG(test,
-                                     drm_buddy_alloc_blocks(&mm, bias_start,
-                                                            bias_end, bias_size + ps, bias_size,
-                                                            &allocated,
-                                                            DRM_BUDDY_RANGE_ALLOCATION),
-                                     "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
-                                     bias_start, bias_end, bias_size, bias_size);
-
-               /* size too big */
-               KUNIT_ASSERT_TRUE_MSG(test,
-                                     drm_buddy_alloc_blocks(&mm, bias_start,
-                                                            bias_end, bias_size + ps, ps,
-                                                            &allocated,
-                                                            DRM_BUDDY_RANGE_ALLOCATION),
-                                     "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
-                                     bias_start, bias_end, bias_size + ps, ps);
-
-               /* bias range too small for size */
-               KUNIT_ASSERT_TRUE_MSG(test,
-                                     drm_buddy_alloc_blocks(&mm, bias_start + ps,
-                                                            bias_end, bias_size, ps,
-                                                            &allocated,
-                                                            DRM_BUDDY_RANGE_ALLOCATION),
-                                     "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
-                                     bias_start + ps, bias_end, bias_size, ps);
-
-               /* bias misaligned */
-               KUNIT_ASSERT_TRUE_MSG(test,
-                                     drm_buddy_alloc_blocks(&mm, bias_start + ps,
-                                                            bias_end - ps,
-                                                            bias_size >> 1, bias_size >> 1,
-                                                            &allocated,
-                                                            DRM_BUDDY_RANGE_ALLOCATION),
-                                     "buddy_alloc h didn't fail with bias(%x-%x), size=%u, ps=%u\n",
-                                     bias_start + ps, bias_end - ps, bias_size >> 1, bias_size >> 1);
-
-               /* single big page */
-               KUNIT_ASSERT_FALSE_MSG(test,
-                                      drm_buddy_alloc_blocks(&mm, bias_start,
-                                                             bias_end, bias_size, bias_size,
-                                                             &tmp,
-                                                             DRM_BUDDY_RANGE_ALLOCATION),
-                                      "buddy_alloc i failed with bias(%x-%x), size=%u, ps=%u\n",
-                                      bias_start, bias_end, bias_size, bias_size);
-               drm_buddy_free_list(&mm, &tmp, 0);
-
-               /* single page with internal round_up */
-               KUNIT_ASSERT_FALSE_MSG(test,
-                                      drm_buddy_alloc_blocks(&mm, bias_start,
-                                                             bias_end, ps, bias_size,
-                                                             &tmp,
-                                                             DRM_BUDDY_RANGE_ALLOCATION),
-                                      "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
-                                      bias_start, bias_end, ps, bias_size);
-               drm_buddy_free_list(&mm, &tmp, 0);
-
-               /* random size within */
-               size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
-               if (size)
-                       KUNIT_ASSERT_FALSE_MSG(test,
-                                              drm_buddy_alloc_blocks(&mm, bias_start,
-                                                                     bias_end, size, ps,
-                                                                     &tmp,
-                                                                     DRM_BUDDY_RANGE_ALLOCATION),
-                                              "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
-                                              bias_start, bias_end, size, ps);
-
-               bias_rem -= size;
-               /* too big for current avail */
-               KUNIT_ASSERT_TRUE_MSG(test,
-                                     drm_buddy_alloc_blocks(&mm, bias_start,
-                                                            bias_end, bias_rem + ps, ps,
-                                                            &allocated,
-                                                            DRM_BUDDY_RANGE_ALLOCATION),
-                                     "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
-                                     bias_start, bias_end, bias_rem + ps, ps);
-
-               if (bias_rem) {
-                       /* random fill of the remainder */
-                       size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
-                       size = max(size, ps);
-
-                       KUNIT_ASSERT_FALSE_MSG(test,
-                                              drm_buddy_alloc_blocks(&mm, bias_start,
-                                                                     bias_end, size, ps,
-                                                                     &allocated,
-                                                                     DRM_BUDDY_RANGE_ALLOCATION),
-                                              "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
-                                              bias_start, bias_end, size, ps);
-                       /*
-                        * Intentionally allow some space to be left
-                        * unallocated, and ideally not always on the bias
-                        * boundaries.
-                        */
-                       drm_buddy_free_list(&mm, &tmp, 0);
-               } else {
-                       list_splice_tail(&tmp, &allocated);
-               }
-       }
-
-       kfree(order);
-       drm_buddy_free_list(&mm, &allocated, 0);
-       drm_buddy_fini(&mm);
-
-       /*
-        * Something more free-form. Idea is to pick a random starting bias
-        * range within the address space and then start filling it up. Also
-        * randomly grow the bias range in both directions as we go along. This
-        * should give us bias start/end which is not always uniform like above,
-        * and in some cases will require the allocator to jump over already
-        * allocated nodes in the middle of the address space.
-        */
-
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
-                              "buddy_init failed\n");
-
-       bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
-       bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
-       bias_end = max(bias_end, bias_start + ps);
-       bias_rem = bias_end - bias_start;
-
-       do {
-               u32 size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
-
-               KUNIT_ASSERT_FALSE_MSG(test,
-                                      drm_buddy_alloc_blocks(&mm, bias_start,
-                                                             bias_end, size, ps,
-                                                             &allocated,
-                                                             DRM_BUDDY_RANGE_ALLOCATION),
-                                      "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
-                                      bias_start, bias_end, size, ps);
-               bias_rem -= size;
-
-               /*
-                * Try to randomly grow the bias range in both directions, or
-                * only one, or perhaps don't grow at all.
-                */
-               do {
-                       u32 old_bias_start = bias_start;
-                       u32 old_bias_end = bias_end;
-
-                       if (bias_start)
-                               bias_start -= round_up(prandom_u32_state(&prng) % bias_start, ps);
-                       if (bias_end != mm_size)
-                               bias_end += round_up(prandom_u32_state(&prng) % (mm_size - bias_end), ps);
-
-                       bias_rem += old_bias_start - bias_start;
-                       bias_rem += bias_end - old_bias_end;
-               } while (!bias_rem && (bias_start || bias_end != mm_size));
-       } while (bias_rem);
-
-       KUNIT_ASSERT_EQ(test, bias_start, 0);
-       KUNIT_ASSERT_EQ(test, bias_end, mm_size);
-       KUNIT_ASSERT_TRUE_MSG(test,
-                             drm_buddy_alloc_blocks(&mm, bias_start, bias_end,
-                                                    ps, ps,
-                                                    &allocated,
-                                                    DRM_BUDDY_RANGE_ALLOCATION),
-                             "buddy_alloc passed with bias(%x-%x), size=%u\n",
-                             bias_start, bias_end, ps);
-
-       drm_buddy_free_list(&mm, &allocated, 0);
-       drm_buddy_fini(&mm);
-
-       /*
-        * Allocate cleared blocks in the bias range when the DRM buddy's clear avail is
-        * zero. This will validate the bias range allocation in scenarios like system boot
-        * when no cleared blocks are available and exercise the fallback path too. The resulting
-        * blocks should always be dirty.
-        */
-
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
-                              "buddy_init failed\n");
-
-       bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
-       bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
-       bias_end = max(bias_end, bias_start + ps);
-       bias_rem = bias_end - bias_start;
-
-       flags = DRM_BUDDY_CLEAR_ALLOCATION | DRM_BUDDY_RANGE_ALLOCATION;
-       size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
-
-       KUNIT_ASSERT_FALSE_MSG(test,
-                              drm_buddy_alloc_blocks(&mm, bias_start,
-                                                     bias_end, size, ps,
-                                                     &allocated,
-                                                     flags),
-                              "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
-                              bias_start, bias_end, size, ps);
-
-       list_for_each_entry(block, &allocated, link)
-               KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
-
-       drm_buddy_free_list(&mm, &allocated, 0);
-       drm_buddy_fini(&mm);
-}
-
-static void drm_test_buddy_alloc_clear(struct kunit *test)
-{
-       unsigned long n_pages, total, i = 0;
-       const unsigned long ps = SZ_4K;
-       struct drm_buddy_block *block;
-       const int max_order = 12;
-       LIST_HEAD(allocated);
-       struct drm_buddy mm;
-       unsigned int order;
-       u32 mm_size, size;
-       LIST_HEAD(dirty);
-       LIST_HEAD(clean);
-
-       mm_size = SZ_4K << max_order;
-       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
-
-       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
-
-       /*
-        * Idea is to allocate and free some random portion of the address space,
-        * returning those pages as non-dirty and randomly alternate between
-        * requesting dirty and non-dirty pages (not going over the limit
-        * we freed as non-dirty), putting that into two separate lists.
-        * Loop over both lists at the end checking that the dirty list
-        * is indeed all dirty pages and vice versa. Free it all again,
-        * keeping the dirty/clear status.
-        */
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                           5 * ps, ps, &allocated,
-                                                           DRM_BUDDY_TOPDOWN_ALLOCATION),
-                               "buddy_alloc hit an error size=%lu\n", 5 * ps);
-       drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
-
-       n_pages = 10;
-       do {
-               unsigned long flags;
-               struct list_head *list;
-               int slot = i % 2;
-
-               if (slot == 0) {
-                       list = &dirty;
-                       flags = 0;
-               } else {
-                       list = &clean;
-                       flags = DRM_BUDDY_CLEAR_ALLOCATION;
-               }
-
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                                   ps, ps, list,
-                                                                   flags),
-                                       "buddy_alloc hit an error size=%lu\n", ps);
-       } while (++i < n_pages);
-
-       list_for_each_entry(block, &clean, link)
-               KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), true);
-
-       list_for_each_entry(block, &dirty, link)
-               KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
-
-       drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
-
-       /*
-        * Trying to go over the clear limit for some allocation.
-        * The allocation should never fail with reasonable page-size.
-        */
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                           10 * ps, ps, &clean,
-                                                           DRM_BUDDY_CLEAR_ALLOCATION),
-                               "buddy_alloc hit an error size=%lu\n", 10 * ps);
-
-       drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
-       drm_buddy_free_list(&mm, &dirty, 0);
-       drm_buddy_fini(&mm);
-
-       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
-
-       /*
-        * Create a new mm. Intentionally fragment the address space by creating
-        * two alternating lists. Free both lists, one as dirty the other as clean.
-        * Try to allocate double the previous size with matching min_page_size. The
-        * allocation should never fail as it calls the force_merge. Also check that
-        * the page is always dirty after force_merge. Free the page as dirty, then
-        * repeat the whole thing, increment the order until we hit the max_order.
-        */
-
-       i = 0;
-       n_pages = mm_size / ps;
-       do {
-               struct list_head *list;
-               int slot = i % 2;
-
-               if (slot == 0)
-                       list = &dirty;
-               else
-                       list = &clean;
-
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                                   ps, ps, list, 0),
-                                       "buddy_alloc hit an error size=%lu\n", ps);
-       } while (++i < n_pages);
-
-       drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
-       drm_buddy_free_list(&mm, &dirty, 0);
-
-       order = 1;
-       do {
-               size = SZ_4K << order;
-
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                                   size, size, &allocated,
-                                                                   DRM_BUDDY_CLEAR_ALLOCATION),
-                                       "buddy_alloc hit an error size=%u\n", size);
-               total = 0;
-               list_for_each_entry(block, &allocated, link) {
-                       if (size != mm_size)
-                               KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
-                       total += drm_buddy_block_size(&mm, block);
-               }
-               KUNIT_EXPECT_EQ(test, total, size);
-
-               drm_buddy_free_list(&mm, &allocated, 0);
-       } while (++order <= max_order);
-
-       drm_buddy_fini(&mm);
-
-       /*
-        * Create a new mm with a non power-of-two size. Allocate a random size from each
-        * root, free as cleared and then call fini. This will ensure the multi-root
-        * force merge during fini.
-        */
-       mm_size = (SZ_4K << max_order) + (SZ_4K << (max_order - 2));
-
-       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
-       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
-                                                           4 * ps, ps, &allocated,
-                                                           DRM_BUDDY_RANGE_ALLOCATION),
-                               "buddy_alloc hit an error size=%lu\n", 4 * ps);
-       drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
-                                                           2 * ps, ps, &allocated,
-                                                           DRM_BUDDY_CLEAR_ALLOCATION),
-                               "buddy_alloc hit an error size=%lu\n", 2 * ps);
-       drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, SZ_4K << max_order, mm_size,
-                                                           ps, ps, &allocated,
-                                                           DRM_BUDDY_RANGE_ALLOCATION),
-                               "buddy_alloc hit an error size=%lu\n", ps);
-       drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
-       drm_buddy_fini(&mm);
-}
-
-static void drm_test_buddy_alloc_contiguous(struct kunit *test)
-{
-       const unsigned long ps = SZ_4K, mm_size = 16 * 3 * SZ_4K;
-       unsigned long i, n_pages, total;
-       struct drm_buddy_block *block;
-       struct drm_buddy mm;
-       LIST_HEAD(left);
-       LIST_HEAD(middle);
-       LIST_HEAD(right);
-       LIST_HEAD(allocated);
-
-       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
-
-       /*
-        * Idea is to fragment the address space by alternating block
-        * allocations between three different lists; one for left, middle and
-        * right. We can then free a list to simulate fragmentation. In
-        * particular we want to exercise the DRM_BUDDY_CONTIGUOUS_ALLOCATION,
-        * including the try_harder path.
-        */
-
-       i = 0;
-       n_pages = mm_size / ps;
-       do {
-               struct list_head *list;
-               int slot = i % 3;
-
-               if (slot == 0)
-                       list = &left;
-               else if (slot == 1)
-                       list = &middle;
-               else
-                       list = &right;
-               KUNIT_ASSERT_FALSE_MSG(test,
-                                      drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                             ps, ps, list, 0),
-                                      "buddy_alloc hit an error size=%lu\n",
-                                      ps);
-       } while (++i < n_pages);
-
-       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                          3 * ps, ps, &allocated,
-                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
-                              "buddy_alloc didn't error size=%lu\n", 3 * ps);
-
-       drm_buddy_free_list(&mm, &middle, 0);
-       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                          3 * ps, ps, &allocated,
-                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
-                              "buddy_alloc didn't error size=%lu\n", 3 * ps);
-       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                          2 * ps, ps, &allocated,
-                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
-                              "buddy_alloc didn't error size=%lu\n", 2 * ps);
-
-       drm_buddy_free_list(&mm, &right, 0);
-       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                          3 * ps, ps, &allocated,
-                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
-                              "buddy_alloc didn't error size=%lu\n", 3 * ps);
-       /*
-        * At this point we should have enough contiguous space for 2 blocks,
-        * however they are never buddies (since we freed middle and right) so
-        * will require the try_harder logic to find them.
-        */
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                           2 * ps, ps, &allocated,
-                                                           DRM_BUDDY_CONTIGUOUS_ALLOCATION),
-                              "buddy_alloc hit an error size=%lu\n", 2 * ps);
-
-       drm_buddy_free_list(&mm, &left, 0);
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
-                                                           3 * ps, ps, &allocated,
-                                                           DRM_BUDDY_CONTIGUOUS_ALLOCATION),
-                              "buddy_alloc hit an error size=%lu\n", 3 * ps);
-
-       total = 0;
-       list_for_each_entry(block, &allocated, link)
-               total += drm_buddy_block_size(&mm, block);
-
-       KUNIT_ASSERT_EQ(test, total, ps * 2 + ps * 3);
-
-       drm_buddy_free_list(&mm, &allocated, 0);
-       drm_buddy_fini(&mm);
-}
-
-static void drm_test_buddy_alloc_pathological(struct kunit *test)
-{
-       u64 mm_size, size, start = 0;
-       struct drm_buddy_block *block;
-       const int max_order = 3;
-       unsigned long flags = 0;
-       int order, top;
-       struct drm_buddy mm;
-       LIST_HEAD(blocks);
-       LIST_HEAD(holes);
-       LIST_HEAD(tmp);
-
-       /*
-        * Create a pot-sized mm, then allocate one of each possible
-        * order within. This should leave the mm with exactly one
-        * page left. Free the largest block, then whittle down again.
-        * Eventually we will have a fully 50% fragmented mm.
-        */
-
-       mm_size = SZ_4K << max_order;
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
-                              "buddy_init failed\n");
-
-       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
-
-       for (top = max_order; top; top--) {
-               /* Make room by freeing the largest allocated block */
-               block = list_first_entry_or_null(&blocks, typeof(*block), link);
-               if (block) {
-                       list_del(&block->link);
-                       drm_buddy_free_block(&mm, block);
-               }
-
-               for (order = top; order--;) {
-                       size = get_size(order, mm.chunk_size);
-                       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start,
-                                                                           mm_size, size, size,
-                                                                               &tmp, flags),
-                                       "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
-                                       order, top);
-
-                       block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
-                       KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
-
-                       list_move_tail(&block->link, &blocks);
-               }
-
-               /* There should be one final page for this sub-allocation */
-               size = get_size(0, mm.chunk_size);
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                                   size, size, &tmp, flags),
-                                                          "buddy_alloc hit -ENOMEM for hole\n");
-
-               block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
-               KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
-
-               list_move_tail(&block->link, &holes);
-
-               size = get_size(top, mm.chunk_size);
-               KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                                  size, size, &tmp, flags),
-                                                         "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
-                                                         top, max_order);
-       }
-
-       drm_buddy_free_list(&mm, &holes, 0);
-
-       /* Nothing larger than blocks of chunk_size now available */
-       for (order = 1; order <= max_order; order++) {
-               size = get_size(order, mm.chunk_size);
-               KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                                  size, size, &tmp, flags),
-                                                         "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
-                                                         order);
-       }
-
-       list_splice_tail(&holes, &blocks);
-       drm_buddy_free_list(&mm, &blocks, 0);
-       drm_buddy_fini(&mm);
-}
-
-static void drm_test_buddy_alloc_pessimistic(struct kunit *test)
-{
-       u64 mm_size, size, start = 0;
-       struct drm_buddy_block *block, *bn;
-       const unsigned int max_order = 16;
-       unsigned long flags = 0;
-       struct drm_buddy mm;
-       unsigned int order;
-       LIST_HEAD(blocks);
-       LIST_HEAD(tmp);
-
-       /*
-        * Create a pot-sized mm, then allocate one of each possible
-        * order within. This should leave the mm with exactly one
-        * page left.
-        */
-
-       mm_size = SZ_4K << max_order;
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
-                              "buddy_init failed\n");
-
-       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
-
-       for (order = 0; order < max_order; order++) {
-               size = get_size(order, mm.chunk_size);
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                                   size, size, &tmp, flags),
-                                                          "buddy_alloc hit -ENOMEM with order=%d\n",
-                                                          order);
-
-               block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
-               KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
-
-               list_move_tail(&block->link, &blocks);
-       }
-
-       /* And now the last remaining block available */
-       size = get_size(0, mm.chunk_size);
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                           size, size, &tmp, flags),
-                                                  "buddy_alloc hit -ENOMEM on final alloc\n");
-
-       block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
-       KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
-
-       list_move_tail(&block->link, &blocks);
-
-       /* Should be completely full! */
-       for (order = max_order; order--;) {
-               size = get_size(order, mm.chunk_size);
-               KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                                  size, size, &tmp, flags),
-                                                         "buddy_alloc unexpectedly succeeded, it should be full!");
-       }
-
-       block = list_last_entry(&blocks, typeof(*block), link);
-       list_del(&block->link);
-       drm_buddy_free_block(&mm, block);
-
-       /* As we free in increasing size, we make available larger blocks */
-       order = 1;
-       list_for_each_entry_safe(block, bn, &blocks, link) {
-               list_del(&block->link);
-               drm_buddy_free_block(&mm, block);
-
-               size = get_size(order, mm.chunk_size);
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                                   size, size, &tmp, flags),
-                                                          "buddy_alloc hit -ENOMEM with order=%d\n",
-                                                          order);
-
-               block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
-               KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
-
-               list_del(&block->link);
-               drm_buddy_free_block(&mm, block);
-               order++;
-       }
-
-       /* To confirm, now the whole mm should be available */
-       size = get_size(max_order, mm.chunk_size);
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                           size, size, &tmp, flags),
-                                                  "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
-                                                  max_order);
-
-       block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
-       KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
-
-       list_del(&block->link);
-       drm_buddy_free_block(&mm, block);
-       drm_buddy_free_list(&mm, &blocks, 0);
-       drm_buddy_fini(&mm);
-}
-
-static void drm_test_buddy_alloc_optimistic(struct kunit *test)
-{
-       u64 mm_size, size, start = 0;
-       struct drm_buddy_block *block;
-       unsigned long flags = 0;
-       const int max_order = 16;
-       struct drm_buddy mm;
-       LIST_HEAD(blocks);
-       LIST_HEAD(tmp);
-       int order;
-
-       /*
-        * Create a mm with one block of each order available, and
-        * try to allocate them all.
-        */
-
-       mm_size = SZ_4K * ((1 << (max_order + 1)) - 1);
-
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
-                              "buddy_init failed\n");
-
-       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
-
-       for (order = 0; order <= max_order; order++) {
-               size = get_size(order, mm.chunk_size);
-               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                                   size, size, &tmp, flags),
-                                                          "buddy_alloc hit -ENOMEM with order=%d\n",
-                                                          order);
-
-               block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
-               KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
-
-               list_move_tail(&block->link, &blocks);
-       }
-
-       /* Should be completely full! */
-       size = get_size(0, mm.chunk_size);
-       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
-                                                          size, size, &tmp, flags),
-                                                 "buddy_alloc unexpectedly succeeded, it should be full!");
-
-       drm_buddy_free_list(&mm, &blocks, 0);
-       drm_buddy_fini(&mm);
-}
-
-static void drm_test_buddy_alloc_limit(struct kunit *test)
-{
-       u64 size = U64_MAX, start = 0;
-       struct drm_buddy_block *block;
-       unsigned long flags = 0;
-       LIST_HEAD(allocated);
-       struct drm_buddy mm;
-
-       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, size, SZ_4K));
-
-       KUNIT_EXPECT_EQ_MSG(test, mm.max_order, DRM_BUDDY_MAX_ORDER,
-                           "mm.max_order(%d) != %d\n", mm.max_order,
-                                               DRM_BUDDY_MAX_ORDER);
-
-       size = mm.chunk_size << mm.max_order;
-       KUNIT_EXPECT_FALSE(test, drm_buddy_alloc_blocks(&mm, start, size, size,
-                                                       mm.chunk_size, &allocated, flags));
-
-       block = list_first_entry_or_null(&allocated, struct drm_buddy_block, link);
-       KUNIT_EXPECT_TRUE(test, block);
-
-       KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_order(block), mm.max_order,
-                           "block order(%d) != %d\n",
-                                               drm_buddy_block_order(block), mm.max_order);
-
-       KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_size(&mm, block),
-                           BIT_ULL(mm.max_order) * mm.chunk_size,
-                                               "block size(%llu) != %llu\n",
-                                               drm_buddy_block_size(&mm, block),
-                                               BIT_ULL(mm.max_order) * mm.chunk_size);
-
-       drm_buddy_free_list(&mm, &allocated, 0);
-       drm_buddy_fini(&mm);
-}
-
-static void drm_test_buddy_alloc_exceeds_max_order(struct kunit *test)
-{
-       u64 mm_size = SZ_8G + SZ_2G, size = SZ_8G + SZ_1G, min_block_size = SZ_8G;
-       struct drm_buddy mm;
-       LIST_HEAD(blocks);
-       int err;
-
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
-                              "buddy_init failed\n");
-
-       /* CONTIGUOUS allocation should succeed via try_harder fallback */
-       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size, size,
-                                                           SZ_4K, &blocks,
-                                                           DRM_BUDDY_CONTIGUOUS_ALLOCATION),
-                              "buddy_alloc hit an error size=%llu\n", size);
-       drm_buddy_free_list(&mm, &blocks, 0);
-
-       /* Non-CONTIGUOUS with large min_block_size should return -EINVAL */
-       err = drm_buddy_alloc_blocks(&mm, 0, mm_size, size, min_block_size, &blocks, 0);
-       KUNIT_EXPECT_EQ(test, err, -EINVAL);
-
-       /* Non-CONTIGUOUS + RANGE with large min_block_size should return -EINVAL */
-       err = drm_buddy_alloc_blocks(&mm, 0, mm_size, size, min_block_size, &blocks,
-                                    DRM_BUDDY_RANGE_ALLOCATION);
-       KUNIT_EXPECT_EQ(test, err, -EINVAL);
-
-       /* CONTIGUOUS + RANGE should return -EINVAL (no try_harder for RANGE) */
-       err = drm_buddy_alloc_blocks(&mm, 0, mm_size, size, SZ_4K, &blocks,
-                                    DRM_BUDDY_CONTIGUOUS_ALLOCATION | DRM_BUDDY_RANGE_ALLOCATION);
-       KUNIT_EXPECT_EQ(test, err, -EINVAL);
-
-       drm_buddy_fini(&mm);
-}
-
-static int drm_buddy_suite_init(struct kunit_suite *suite)
-{
-       while (!random_seed)
-               random_seed = get_random_u32();
-
-       kunit_info(suite, "Testing DRM buddy manager, with random_seed=0x%x\n",
-                  random_seed);
-
-       return 0;
-}
-
-static struct kunit_case drm_buddy_tests[] = {
-       KUNIT_CASE(drm_test_buddy_alloc_limit),
-       KUNIT_CASE(drm_test_buddy_alloc_optimistic),
-       KUNIT_CASE(drm_test_buddy_alloc_pessimistic),
-       KUNIT_CASE(drm_test_buddy_alloc_pathological),
-       KUNIT_CASE(drm_test_buddy_alloc_contiguous),
-       KUNIT_CASE(drm_test_buddy_alloc_clear),
-       KUNIT_CASE(drm_test_buddy_alloc_range_bias),
-       KUNIT_CASE(drm_test_buddy_fragmentation_performance),
-       KUNIT_CASE(drm_test_buddy_alloc_exceeds_max_order),
-       {}
-};
-
-static struct kunit_suite drm_buddy_test_suite = {
-       .name = "drm_buddy",
-       .suite_init = drm_buddy_suite_init,
-       .test_cases = drm_buddy_tests,
-};
-
-kunit_test_suite(drm_buddy_test_suite);
-
-MODULE_AUTHOR("Intel Corporation");
-MODULE_DESCRIPTION("Kunit test for drm_buddy functions");
-MODULE_LICENSE("GPL");
index 3a20c788c51f89719207e096d28a184ee84909bf..2fc47f3b463b7e99b2ac5773683d5ffc333f03b6 100644 (file)
@@ -16,8 +16,6 @@
 #include <drm/drm_gem.h>
 #include <drm/drm_kunit_helpers.h>
 
-#include "../lib/drm_random.h"
-
 struct drm_exec_priv {
        struct device *dev;
        struct drm_device *drm;
index aec9eccdeae9e0bd8e0f4fda1cec9b562a99c6c9..e24a619059d85a3f57b768560acfab5207da02be 100644 (file)
@@ -16,8 +16,6 @@
 #include <drm/drm_mm.h>
 #include <drm/drm_print.h>
 
-#include "../lib/drm_random.h"
-
 enum {
        BEST,
        BOTTOMUP,
index e4c95f86a4670aca2a35c8b3d4bfc5314cca5b79..96ea8c9aae343314d87f1fdb4f162f2d8cc5ae13 100644 (file)
@@ -5,7 +5,7 @@
 #ifndef TTM_MOCK_MANAGER_H
 #define TTM_MOCK_MANAGER_H
 
-#include <drm/drm_buddy.h>
+#include <linux/gpu_buddy.h>
 
 struct ttm_mock_manager {
        struct ttm_resource_manager man;
index a71e14818ec2a6c4685f0292bd368966ee0a462a..babeec5511d944de41bef5ae44e913de1de130b3 100644 (file)
@@ -6,7 +6,7 @@
 #ifndef _XE_TTM_VRAM_MGR_TYPES_H_
 #define _XE_TTM_VRAM_MGR_TYPES_H_
 
-#include <drm/drm_buddy.h>
+#include <linux/gpu_buddy.h>
 #include <drm/ttm/ttm_device.h>
 
 /**
diff --git a/drivers/gpu/tests/Makefile b/drivers/gpu/tests/Makefile
new file mode 100644 (file)
index 0000000..8e7654e
--- /dev/null
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+gpu_buddy_tests-y = gpu_buddy_test.o gpu_random.o
+obj-$(CONFIG_DRM_KUNIT_TEST) += gpu_buddy_tests.o
diff --git a/drivers/gpu/tests/gpu_buddy_test.c b/drivers/gpu/tests/gpu_buddy_test.c
new file mode 100644 (file)
index 0000000..b905932
--- /dev/null
@@ -0,0 +1,928 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2019 Intel Corporation
+ * Copyright © 2022 Maíra Canal <mairacanal@riseup.net>
+ */
+
+#include <kunit/test.h>
+
+#include <linux/prime_numbers.h>
+#include <linux/sched/signal.h>
+#include <linux/sizes.h>
+
+#include <linux/gpu_buddy.h>
+
+#include "gpu_random.h"
+
+static unsigned int random_seed;
+
+static inline u64 get_size(int order, u64 chunk_size)
+{
+       return (1 << order) * chunk_size;
+}
+
+static void drm_test_buddy_fragmentation_performance(struct kunit *test)
+{
+       struct drm_buddy_block *block, *tmp;
+       int num_blocks, i, ret, count = 0;
+       LIST_HEAD(allocated_blocks);
+       unsigned long elapsed_ms;
+       LIST_HEAD(reverse_list);
+       LIST_HEAD(test_blocks);
+       LIST_HEAD(clear_list);
+       LIST_HEAD(dirty_list);
+       LIST_HEAD(free_list);
+       struct drm_buddy mm;
+       u64 mm_size = SZ_4G;
+       ktime_t start, end;
+
+       /*
+        * Allocation under severe fragmentation
+        *
+        * Create severe fragmentation by allocating the entire 4 GiB address space
+        * as tiny 8 KiB blocks but forcing a 64 KiB alignment. The resulting pattern
+        * leaves many scattered holes. Split the allocations into two groups and
+        * return them with different flags to block coalescing, then repeatedly
+        * allocate and free 64 KiB blocks while timing the loop. This stresses how
+        * quickly the allocator can satisfy larger, aligned requests from a pool of
+        * highly fragmented space.
+        */
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+                              "buddy_init failed\n");
+
+       num_blocks = mm_size / SZ_64K;
+
+       start = ktime_get();
+       /* Allocate with maximum fragmentation - 8K blocks with 64K alignment */
+       for (i = 0; i < num_blocks; i++)
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size, SZ_8K, SZ_64K,
+                                                                   &allocated_blocks, 0),
+                                       "buddy_alloc hit an error size=%u\n", SZ_8K);
+
+       list_for_each_entry_safe(block, tmp, &allocated_blocks, link) {
+               if (count % 4 == 0 || count % 4 == 3)
+                       list_move_tail(&block->link, &clear_list);
+               else
+                       list_move_tail(&block->link, &dirty_list);
+               count++;
+       }
+
+       /* Free with different flags to ensure no coalescing */
+       drm_buddy_free_list(&mm, &clear_list, DRM_BUDDY_CLEARED);
+       drm_buddy_free_list(&mm, &dirty_list, 0);
+
+       for (i = 0; i < num_blocks; i++)
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size, SZ_64K, SZ_64K,
+                                                                   &test_blocks, 0),
+                                       "buddy_alloc hit an error size=%u\n", SZ_64K);
+       drm_buddy_free_list(&mm, &test_blocks, 0);
+
+       end = ktime_get();
+       elapsed_ms = ktime_to_ms(ktime_sub(end, start));
+
+       kunit_info(test, "Fragmented allocation took %lu ms\n", elapsed_ms);
+
+       drm_buddy_fini(&mm);
+
+       /*
+        * Reverse free order under fragmentation
+        *
+        * Construct a fragmented 4 GiB space by allocating every 8 KiB block with
+        * 64 KiB alignment, creating a dense scatter of small regions. Half of the
+        * blocks are selectively freed to form sparse gaps, while the remaining
+        * allocations are preserved, reordered in reverse, and released back with
+        * the cleared flag. This models a pathological reverse-ordered free pattern
+        * and measures how quickly the allocator can merge and reclaim space when
+        * deallocation occurs in the opposite order of allocation, exposing the
+        * cost difference between a linear freelist scan and an ordered tree lookup.
+        */
+       ret = drm_buddy_init(&mm, mm_size, SZ_4K);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       start = ktime_get();
+       /* Allocate maximum fragmentation */
+       for (i = 0; i < num_blocks; i++)
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size, SZ_8K, SZ_64K,
+                                                                   &allocated_blocks, 0),
+                                       "buddy_alloc hit an error size=%u\n", SZ_8K);
+
+       list_for_each_entry_safe(block, tmp, &allocated_blocks, link) {
+               if (count % 2 == 0)
+                       list_move_tail(&block->link, &free_list);
+               count++;
+       }
+       drm_buddy_free_list(&mm, &free_list, DRM_BUDDY_CLEARED);
+
+       list_for_each_entry_safe_reverse(block, tmp, &allocated_blocks, link)
+               list_move(&block->link, &reverse_list);
+       drm_buddy_free_list(&mm, &reverse_list, DRM_BUDDY_CLEARED);
+
+       end = ktime_get();
+       elapsed_ms = ktime_to_ms(ktime_sub(end, start));
+
+       kunit_info(test, "Reverse-ordered free took %lu ms\n", elapsed_ms);
+
+       drm_buddy_fini(&mm);
+}
+
+static void drm_test_buddy_alloc_range_bias(struct kunit *test)
+{
+       u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem;
+       DRM_RND_STATE(prng, random_seed);
+       unsigned int i, count, *order;
+       struct drm_buddy_block *block;
+       unsigned long flags;
+       struct drm_buddy mm;
+       LIST_HEAD(allocated);
+
+       bias_size = SZ_1M;
+       ps = roundup_pow_of_two(prandom_u32_state(&prng) % bias_size);
+       ps = max(SZ_4K, ps);
+       mm_size = (SZ_8M-1) & ~(ps-1); /* Multiple roots */
+
+       kunit_info(test, "mm_size=%u, ps=%u\n", mm_size, ps);
+
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+                              "buddy_init failed\n");
+
+       count = mm_size / bias_size;
+       order = drm_random_order(count, &prng);
+       KUNIT_EXPECT_TRUE(test, order);
+
+       /*
+        * Idea is to split the address space into uniform bias ranges, and then
+        * in some random order allocate within each bias, using various
+        * patterns within. This should detect if allocations leak out from a
+        * given bias, for example.
+        */
+
+       for (i = 0; i < count; i++) {
+               LIST_HEAD(tmp);
+               u32 size;
+
+               bias_start = order[i] * bias_size;
+               bias_end = bias_start + bias_size;
+               bias_rem = bias_size;
+
+               /* internal round_up too big */
+               KUNIT_ASSERT_TRUE_MSG(test,
+                                     drm_buddy_alloc_blocks(&mm, bias_start,
+                                                            bias_end, bias_size + ps, bias_size,
+                                                            &allocated,
+                                                            DRM_BUDDY_RANGE_ALLOCATION),
+                                     "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+                                     bias_start, bias_end, bias_size, bias_size);
+
+               /* size too big */
+               KUNIT_ASSERT_TRUE_MSG(test,
+                                     drm_buddy_alloc_blocks(&mm, bias_start,
+                                                            bias_end, bias_size + ps, ps,
+                                                            &allocated,
+                                                            DRM_BUDDY_RANGE_ALLOCATION),
+                                     "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+                                     bias_start, bias_end, bias_size + ps, ps);
+
+               /* bias range too small for size */
+               KUNIT_ASSERT_TRUE_MSG(test,
+                                     drm_buddy_alloc_blocks(&mm, bias_start + ps,
+                                                            bias_end, bias_size, ps,
+                                                            &allocated,
+                                                            DRM_BUDDY_RANGE_ALLOCATION),
+                                     "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+                                     bias_start + ps, bias_end, bias_size, ps);
+
+               /* bias misaligned */
+               KUNIT_ASSERT_TRUE_MSG(test,
+                                     drm_buddy_alloc_blocks(&mm, bias_start + ps,
+                                                            bias_end - ps,
+                                                            bias_size >> 1, bias_size >> 1,
+                                                            &allocated,
+                                                            DRM_BUDDY_RANGE_ALLOCATION),
+                                     "buddy_alloc h didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+                                     bias_start + ps, bias_end - ps, bias_size >> 1, bias_size >> 1);
+
+               /* single big page */
+               KUNIT_ASSERT_FALSE_MSG(test,
+                                      drm_buddy_alloc_blocks(&mm, bias_start,
+                                                             bias_end, bias_size, bias_size,
+                                                             &tmp,
+                                                             DRM_BUDDY_RANGE_ALLOCATION),
+                                      "buddy_alloc i failed with bias(%x-%x), size=%u, ps=%u\n",
+                                      bias_start, bias_end, bias_size, bias_size);
+               drm_buddy_free_list(&mm, &tmp, 0);
+
+               /* single page with internal round_up */
+               KUNIT_ASSERT_FALSE_MSG(test,
+                                      drm_buddy_alloc_blocks(&mm, bias_start,
+                                                             bias_end, ps, bias_size,
+                                                             &tmp,
+                                                             DRM_BUDDY_RANGE_ALLOCATION),
+                                      "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+                                      bias_start, bias_end, ps, bias_size);
+               drm_buddy_free_list(&mm, &tmp, 0);
+
+               /* random size within */
+               size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+               if (size)
+                       KUNIT_ASSERT_FALSE_MSG(test,
+                                              drm_buddy_alloc_blocks(&mm, bias_start,
+                                                                     bias_end, size, ps,
+                                                                     &tmp,
+                                                                     DRM_BUDDY_RANGE_ALLOCATION),
+                                              "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+                                              bias_start, bias_end, size, ps);
+
+               bias_rem -= size;
+               /* too big for current avail */
+               KUNIT_ASSERT_TRUE_MSG(test,
+                                     drm_buddy_alloc_blocks(&mm, bias_start,
+                                                            bias_end, bias_rem + ps, ps,
+                                                            &allocated,
+                                                            DRM_BUDDY_RANGE_ALLOCATION),
+                                     "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+                                     bias_start, bias_end, bias_rem + ps, ps);
+
+               if (bias_rem) {
+                       /* random fill of the remainder */
+                       size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+                       size = max(size, ps);
+
+                       KUNIT_ASSERT_FALSE_MSG(test,
+                                              drm_buddy_alloc_blocks(&mm, bias_start,
+                                                                     bias_end, size, ps,
+                                                                     &allocated,
+                                                                     DRM_BUDDY_RANGE_ALLOCATION),
+                                              "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+                                              bias_start, bias_end, size, ps);
+                       /*
+                        * Intentionally allow some space to be left
+                        * unallocated, and ideally not always on the bias
+                        * boundaries.
+                        */
+                       drm_buddy_free_list(&mm, &tmp, 0);
+               } else {
+                       list_splice_tail(&tmp, &allocated);
+               }
+       }
+
+       kfree(order);
+       drm_buddy_free_list(&mm, &allocated, 0);
+       drm_buddy_fini(&mm);
+
+       /*
+        * Something more free-form. Idea is to pick a random starting bias
+        * range within the address space and then start filling it up. Also
+        * randomly grow the bias range in both directions as we go along. This
+        * should give us bias start/end which is not always uniform like above,
+        * and in some cases will require the allocator to jump over already
+        * allocated nodes in the middle of the address space.
+        */
+
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+                              "buddy_init failed\n");
+
+       bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
+       bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
+       bias_end = max(bias_end, bias_start + ps);
+       bias_rem = bias_end - bias_start;
+
+       do {
+               u32 size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+
+               KUNIT_ASSERT_FALSE_MSG(test,
+                                      drm_buddy_alloc_blocks(&mm, bias_start,
+                                                             bias_end, size, ps,
+                                                             &allocated,
+                                                             DRM_BUDDY_RANGE_ALLOCATION),
+                                      "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+                                      bias_start, bias_end, size, ps);
+               bias_rem -= size;
+
+               /*
+                * Try to randomly grow the bias range in both directions, or
+                * only one, or perhaps don't grow at all.
+                */
+               do {
+                       u32 old_bias_start = bias_start;
+                       u32 old_bias_end = bias_end;
+
+                       if (bias_start)
+                               bias_start -= round_up(prandom_u32_state(&prng) % bias_start, ps);
+                       if (bias_end != mm_size)
+                               bias_end += round_up(prandom_u32_state(&prng) % (mm_size - bias_end), ps);
+
+                       bias_rem += old_bias_start - bias_start;
+                       bias_rem += bias_end - old_bias_end;
+               } while (!bias_rem && (bias_start || bias_end != mm_size));
+       } while (bias_rem);
+
+       KUNIT_ASSERT_EQ(test, bias_start, 0);
+       KUNIT_ASSERT_EQ(test, bias_end, mm_size);
+       KUNIT_ASSERT_TRUE_MSG(test,
+                             drm_buddy_alloc_blocks(&mm, bias_start, bias_end,
+                                                    ps, ps,
+                                                    &allocated,
+                                                    DRM_BUDDY_RANGE_ALLOCATION),
+                             "buddy_alloc passed with bias(%x-%x), size=%u\n",
+                             bias_start, bias_end, ps);
+
+       drm_buddy_free_list(&mm, &allocated, 0);
+       drm_buddy_fini(&mm);
+
+       /*
+        * Allocate cleared blocks in the bias range when the DRM buddy's clear avail is
+        * zero. This will validate the bias range allocation in scenarios like system boot
+        * when no cleared blocks are available and exercise the fallback path too. The resulting
+        * blocks should always be dirty.
+        */
+
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+                              "buddy_init failed\n");
+
+       bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
+       bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
+       bias_end = max(bias_end, bias_start + ps);
+       bias_rem = bias_end - bias_start;
+
+       flags = DRM_BUDDY_CLEAR_ALLOCATION | DRM_BUDDY_RANGE_ALLOCATION;
+       size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+
+       KUNIT_ASSERT_FALSE_MSG(test,
+                              drm_buddy_alloc_blocks(&mm, bias_start,
+                                                     bias_end, size, ps,
+                                                     &allocated,
+                                                     flags),
+                              "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+                              bias_start, bias_end, size, ps);
+
+       list_for_each_entry(block, &allocated, link)
+               KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+
+       drm_buddy_free_list(&mm, &allocated, 0);
+       drm_buddy_fini(&mm);
+}
+
+static void drm_test_buddy_alloc_clear(struct kunit *test)
+{
+       unsigned long n_pages, total, i = 0;
+       const unsigned long ps = SZ_4K;
+       struct drm_buddy_block *block;
+       const int max_order = 12;
+       LIST_HEAD(allocated);
+       struct drm_buddy mm;
+       unsigned int order;
+       u32 mm_size, size;
+       LIST_HEAD(dirty);
+       LIST_HEAD(clean);
+
+       mm_size = SZ_4K << max_order;
+       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+
+       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+
+       /*
+        * Idea is to allocate and free some random portion of the address space,
+        * returning those pages as non-dirty and randomly alternate between
+        * requesting dirty and non-dirty pages (not going over the limit
+        * we freed as non-dirty), putting that into two separate lists.
+        * Loop over both lists at the end checking that the dirty list
+        * is indeed all dirty pages and vice versa. Free it all again,
+        * keeping the dirty/clear status.
+        */
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                           5 * ps, ps, &allocated,
+                                                           DRM_BUDDY_TOPDOWN_ALLOCATION),
+                               "buddy_alloc hit an error size=%lu\n", 5 * ps);
+       drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+
+       n_pages = 10;
+       do {
+               unsigned long flags;
+               struct list_head *list;
+               int slot = i % 2;
+
+               if (slot == 0) {
+                       list = &dirty;
+                       flags = 0;
+               } else {
+                       list = &clean;
+                       flags = DRM_BUDDY_CLEAR_ALLOCATION;
+               }
+
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                                   ps, ps, list,
+                                                                   flags),
+                                       "buddy_alloc hit an error size=%lu\n", ps);
+       } while (++i < n_pages);
+
+       list_for_each_entry(block, &clean, link)
+               KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), true);
+
+       list_for_each_entry(block, &dirty, link)
+               KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+
+       drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+
+       /*
+        * Trying to go over the clear limit for some allocation.
+        * The allocation should never fail with reasonable page-size.
+        */
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                           10 * ps, ps, &clean,
+                                                           DRM_BUDDY_CLEAR_ALLOCATION),
+                               "buddy_alloc hit an error size=%lu\n", 10 * ps);
+
+       drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+       drm_buddy_free_list(&mm, &dirty, 0);
+       drm_buddy_fini(&mm);
+
+       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+
+       /*
+        * Create a new mm. Intentionally fragment the address space by creating
+        * two alternating lists. Free both lists, one as dirty the other as clean.
+        * Try to allocate double the previous size with matching min_page_size. The
+        * allocation should never fail as it calls the force_merge. Also check that
+        * the page is always dirty after force_merge. Free the page as dirty, then
+        * repeat the whole thing, increment the order until we hit the max_order.
+        */
+
+       i = 0;
+       n_pages = mm_size / ps;
+       do {
+               struct list_head *list;
+               int slot = i % 2;
+
+               if (slot == 0)
+                       list = &dirty;
+               else
+                       list = &clean;
+
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                                   ps, ps, list, 0),
+                                       "buddy_alloc hit an error size=%lu\n", ps);
+       } while (++i < n_pages);
+
+       drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+       drm_buddy_free_list(&mm, &dirty, 0);
+
+       order = 1;
+       do {
+               size = SZ_4K << order;
+
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                                   size, size, &allocated,
+                                                                   DRM_BUDDY_CLEAR_ALLOCATION),
+                                       "buddy_alloc hit an error size=%u\n", size);
+               total = 0;
+               list_for_each_entry(block, &allocated, link) {
+                       if (size != mm_size)
+                               KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+                       total += drm_buddy_block_size(&mm, block);
+               }
+               KUNIT_EXPECT_EQ(test, total, size);
+
+               drm_buddy_free_list(&mm, &allocated, 0);
+       } while (++order <= max_order);
+
+       drm_buddy_fini(&mm);
+
+       /*
+        * Create a new mm with a non power-of-two size. Allocate a random size from each
+        * root, free as cleared and then call fini. This will ensure the multi-root
+        * force merge during fini.
+        */
+       mm_size = (SZ_4K << max_order) + (SZ_4K << (max_order - 2));
+
+       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
+                                                           4 * ps, ps, &allocated,
+                                                           DRM_BUDDY_RANGE_ALLOCATION),
+                               "buddy_alloc hit an error size=%lu\n", 4 * ps);
+       drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
+                                                           2 * ps, ps, &allocated,
+                                                           DRM_BUDDY_CLEAR_ALLOCATION),
+                               "buddy_alloc hit an error size=%lu\n", 2 * ps);
+       drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, SZ_4K << max_order, mm_size,
+                                                           ps, ps, &allocated,
+                                                           DRM_BUDDY_RANGE_ALLOCATION),
+                               "buddy_alloc hit an error size=%lu\n", ps);
+       drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+       drm_buddy_fini(&mm);
+}
+
+static void drm_test_buddy_alloc_contiguous(struct kunit *test)
+{
+       const unsigned long ps = SZ_4K, mm_size = 16 * 3 * SZ_4K;
+       unsigned long i, n_pages, total;
+       struct drm_buddy_block *block;
+       struct drm_buddy mm;
+       LIST_HEAD(left);
+       LIST_HEAD(middle);
+       LIST_HEAD(right);
+       LIST_HEAD(allocated);
+
+       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+
+       /*
+        * Idea is to fragment the address space by alternating block
+        * allocations between three different lists; one for left, middle and
+        * right. We can then free a list to simulate fragmentation. In
+        * particular we want to exercise the DRM_BUDDY_CONTIGUOUS_ALLOCATION,
+        * including the try_harder path.
+        */
+
+       i = 0;
+       n_pages = mm_size / ps;
+       do {
+               struct list_head *list;
+               int slot = i % 3;
+
+               if (slot == 0)
+                       list = &left;
+               else if (slot == 1)
+                       list = &middle;
+               else
+                       list = &right;
+               KUNIT_ASSERT_FALSE_MSG(test,
+                                      drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                             ps, ps, list, 0),
+                                      "buddy_alloc hit an error size=%lu\n",
+                                      ps);
+       } while (++i < n_pages);
+
+       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                          3 * ps, ps, &allocated,
+                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+                              "buddy_alloc didn't error size=%lu\n", 3 * ps);
+
+       drm_buddy_free_list(&mm, &middle, 0);
+       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                          3 * ps, ps, &allocated,
+                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+                              "buddy_alloc didn't error size=%lu\n", 3 * ps);
+       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                          2 * ps, ps, &allocated,
+                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+                              "buddy_alloc didn't error size=%lu\n", 2 * ps);
+
+       drm_buddy_free_list(&mm, &right, 0);
+       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                          3 * ps, ps, &allocated,
+                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+                              "buddy_alloc didn't error size=%lu\n", 3 * ps);
+       /*
+        * At this point we should have enough contiguous space for 2 blocks,
+        * however they are never buddies (since we freed middle and right) so
+        * will require the try_harder logic to find them.
+        */
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                           2 * ps, ps, &allocated,
+                                                           DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+                              "buddy_alloc hit an error size=%lu\n", 2 * ps);
+
+       drm_buddy_free_list(&mm, &left, 0);
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+                                                           3 * ps, ps, &allocated,
+                                                           DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+                              "buddy_alloc hit an error size=%lu\n", 3 * ps);
+
+       total = 0;
+       list_for_each_entry(block, &allocated, link)
+               total += drm_buddy_block_size(&mm, block);
+
+       KUNIT_ASSERT_EQ(test, total, ps * 2 + ps * 3);
+
+       drm_buddy_free_list(&mm, &allocated, 0);
+       drm_buddy_fini(&mm);
+}
+
+static void drm_test_buddy_alloc_pathological(struct kunit *test)
+{
+       u64 mm_size, size, start = 0;
+       struct drm_buddy_block *block;
+       const int max_order = 3;
+       unsigned long flags = 0;
+       int order, top;
+       struct drm_buddy mm;
+       LIST_HEAD(blocks);
+       LIST_HEAD(holes);
+       LIST_HEAD(tmp);
+
+       /*
+        * Create a pot-sized mm, then allocate one of each possible
+        * order within. This should leave the mm with exactly one
+        * page left. Free the largest block, then whittle down again.
+        * Eventually we will have a fully 50% fragmented mm.
+        */
+
+       mm_size = SZ_4K << max_order;
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+                              "buddy_init failed\n");
+
+       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+
+       for (top = max_order; top; top--) {
+               /* Make room by freeing the largest allocated block */
+               block = list_first_entry_or_null(&blocks, typeof(*block), link);
+               if (block) {
+                       list_del(&block->link);
+                       drm_buddy_free_block(&mm, block);
+               }
+
+               for (order = top; order--;) {
+                       size = get_size(order, mm.chunk_size);
+                       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start,
+                                                                           mm_size, size, size,
+                                                                               &tmp, flags),
+                                       "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
+                                       order, top);
+
+                       block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+                       KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+
+                       list_move_tail(&block->link, &blocks);
+               }
+
+               /* There should be one final page for this sub-allocation */
+               size = get_size(0, mm.chunk_size);
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                                   size, size, &tmp, flags),
+                                                          "buddy_alloc hit -ENOMEM for hole\n");
+
+               block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+               KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+
+               list_move_tail(&block->link, &holes);
+
+               size = get_size(top, mm.chunk_size);
+               KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                                  size, size, &tmp, flags),
+                                                         "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
+                                                         top, max_order);
+       }
+
+       drm_buddy_free_list(&mm, &holes, 0);
+
+       /* Nothing larger than blocks of chunk_size now available */
+       for (order = 1; order <= max_order; order++) {
+               size = get_size(order, mm.chunk_size);
+               KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                                  size, size, &tmp, flags),
+                                                         "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
+                                                         order);
+       }
+
+       list_splice_tail(&holes, &blocks);
+       drm_buddy_free_list(&mm, &blocks, 0);
+       drm_buddy_fini(&mm);
+}
+
+static void drm_test_buddy_alloc_pessimistic(struct kunit *test)
+{
+       u64 mm_size, size, start = 0;
+       struct drm_buddy_block *block, *bn;
+       const unsigned int max_order = 16;
+       unsigned long flags = 0;
+       struct drm_buddy mm;
+       unsigned int order;
+       LIST_HEAD(blocks);
+       LIST_HEAD(tmp);
+
+       /*
+        * Create a pot-sized mm, then allocate one of each possible
+        * order within. This should leave the mm with exactly one
+        * page left.
+        */
+
+       mm_size = SZ_4K << max_order;
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+                              "buddy_init failed\n");
+
+       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+
+       for (order = 0; order < max_order; order++) {
+               size = get_size(order, mm.chunk_size);
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                                   size, size, &tmp, flags),
+                                                          "buddy_alloc hit -ENOMEM with order=%d\n",
+                                                          order);
+
+               block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+               KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+
+               list_move_tail(&block->link, &blocks);
+       }
+
+       /* And now the last remaining block available */
+       size = get_size(0, mm.chunk_size);
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                           size, size, &tmp, flags),
+                                                  "buddy_alloc hit -ENOMEM on final alloc\n");
+
+       block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+       KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+
+       list_move_tail(&block->link, &blocks);
+
+       /* Should be completely full! */
+       for (order = max_order; order--;) {
+               size = get_size(order, mm.chunk_size);
+               KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                                  size, size, &tmp, flags),
+                                                         "buddy_alloc unexpectedly succeeded, it should be full!");
+       }
+
+       block = list_last_entry(&blocks, typeof(*block), link);
+       list_del(&block->link);
+       drm_buddy_free_block(&mm, block);
+
+       /* As we free in increasing size, we make available larger blocks */
+       order = 1;
+       list_for_each_entry_safe(block, bn, &blocks, link) {
+               list_del(&block->link);
+               drm_buddy_free_block(&mm, block);
+
+               size = get_size(order, mm.chunk_size);
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                                   size, size, &tmp, flags),
+                                                          "buddy_alloc hit -ENOMEM with order=%d\n",
+                                                          order);
+
+               block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+               KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+
+               list_del(&block->link);
+               drm_buddy_free_block(&mm, block);
+               order++;
+       }
+
+       /* To confirm, now the whole mm should be available */
+       size = get_size(max_order, mm.chunk_size);
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                           size, size, &tmp, flags),
+                                                  "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
+                                                  max_order);
+
+       block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+       KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+
+       list_del(&block->link);
+       drm_buddy_free_block(&mm, block);
+       drm_buddy_free_list(&mm, &blocks, 0);
+       drm_buddy_fini(&mm);
+}
+
+static void drm_test_buddy_alloc_optimistic(struct kunit *test)
+{
+       u64 mm_size, size, start = 0;
+       struct drm_buddy_block *block;
+       unsigned long flags = 0;
+       const int max_order = 16;
+       struct drm_buddy mm;
+       LIST_HEAD(blocks);
+       LIST_HEAD(tmp);
+       int order;
+
+       /*
+        * Create a mm with one block of each order available, and
+        * try to allocate them all.
+        */
+
+       mm_size = SZ_4K * ((1 << (max_order + 1)) - 1);
+
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+                              "buddy_init failed\n");
+
+       KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+
+       for (order = 0; order <= max_order; order++) {
+               size = get_size(order, mm.chunk_size);
+               KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                                   size, size, &tmp, flags),
+                                                          "buddy_alloc hit -ENOMEM with order=%d\n",
+                                                          order);
+
+               block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+               KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+
+               list_move_tail(&block->link, &blocks);
+       }
+
+       /* Should be completely full! */
+       size = get_size(0, mm.chunk_size);
+       KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+                                                          size, size, &tmp, flags),
+                                                 "buddy_alloc unexpectedly succeeded, it should be full!");
+
+       drm_buddy_free_list(&mm, &blocks, 0);
+       drm_buddy_fini(&mm);
+}
+
+static void drm_test_buddy_alloc_limit(struct kunit *test)
+{
+       u64 size = U64_MAX, start = 0;
+       struct drm_buddy_block *block;
+       unsigned long flags = 0;
+       LIST_HEAD(allocated);
+       struct drm_buddy mm;
+
+       KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, size, SZ_4K));
+
+       KUNIT_EXPECT_EQ_MSG(test, mm.max_order, DRM_BUDDY_MAX_ORDER,
+                           "mm.max_order(%d) != %d\n", mm.max_order,
+                                               DRM_BUDDY_MAX_ORDER);
+
+       size = mm.chunk_size << mm.max_order;
+       KUNIT_EXPECT_FALSE(test, drm_buddy_alloc_blocks(&mm, start, size, size,
+                                                       mm.chunk_size, &allocated, flags));
+
+       block = list_first_entry_or_null(&allocated, struct drm_buddy_block, link);
+       KUNIT_EXPECT_TRUE(test, block);
+
+       KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_order(block), mm.max_order,
+                           "block order(%d) != %d\n",
+                                               drm_buddy_block_order(block), mm.max_order);
+
+       KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_size(&mm, block),
+                           BIT_ULL(mm.max_order) * mm.chunk_size,
+                                               "block size(%llu) != %llu\n",
+                                               drm_buddy_block_size(&mm, block),
+                                               BIT_ULL(mm.max_order) * mm.chunk_size);
+
+       drm_buddy_free_list(&mm, &allocated, 0);
+       drm_buddy_fini(&mm);
+}
+
+static void drm_test_buddy_alloc_exceeds_max_order(struct kunit *test)
+{
+       u64 mm_size = SZ_8G + SZ_2G, size = SZ_8G + SZ_1G, min_block_size = SZ_8G;
+       struct drm_buddy mm;
+       LIST_HEAD(blocks);
+       int err;
+
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+                              "buddy_init failed\n");
+
+       /* CONTIGUOUS allocation should succeed via try_harder fallback */
+       KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size, size,
+                                                           SZ_4K, &blocks,
+                                                           DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+                              "buddy_alloc hit an error size=%llu\n", size);
+       drm_buddy_free_list(&mm, &blocks, 0);
+
+       /* Non-CONTIGUOUS with large min_block_size should return -EINVAL */
+       err = drm_buddy_alloc_blocks(&mm, 0, mm_size, size, min_block_size, &blocks, 0);
+       KUNIT_EXPECT_EQ(test, err, -EINVAL);
+
+       /* Non-CONTIGUOUS + RANGE with large min_block_size should return -EINVAL */
+       err = drm_buddy_alloc_blocks(&mm, 0, mm_size, size, min_block_size, &blocks,
+                                    DRM_BUDDY_RANGE_ALLOCATION);
+       KUNIT_EXPECT_EQ(test, err, -EINVAL);
+
+       /* CONTIGUOUS + RANGE should return -EINVAL (no try_harder for RANGE) */
+       err = drm_buddy_alloc_blocks(&mm, 0, mm_size, size, SZ_4K, &blocks,
+                                    DRM_BUDDY_CONTIGUOUS_ALLOCATION | DRM_BUDDY_RANGE_ALLOCATION);
+       KUNIT_EXPECT_EQ(test, err, -EINVAL);
+
+       drm_buddy_fini(&mm);
+}
+
+static int drm_buddy_suite_init(struct kunit_suite *suite)
+{
+       while (!random_seed)
+               random_seed = get_random_u32();
+
+       kunit_info(suite, "Testing DRM buddy manager, with random_seed=0x%x\n",
+                  random_seed);
+
+       return 0;
+}
+
+static struct kunit_case drm_buddy_tests[] = {
+       KUNIT_CASE(drm_test_buddy_alloc_limit),
+       KUNIT_CASE(drm_test_buddy_alloc_optimistic),
+       KUNIT_CASE(drm_test_buddy_alloc_pessimistic),
+       KUNIT_CASE(drm_test_buddy_alloc_pathological),
+       KUNIT_CASE(drm_test_buddy_alloc_contiguous),
+       KUNIT_CASE(drm_test_buddy_alloc_clear),
+       KUNIT_CASE(drm_test_buddy_alloc_range_bias),
+       KUNIT_CASE(drm_test_buddy_fragmentation_performance),
+       KUNIT_CASE(drm_test_buddy_alloc_exceeds_max_order),
+       {}
+};
+
+static struct kunit_suite drm_buddy_test_suite = {
+       .name = "drm_buddy",
+       .suite_init = drm_buddy_suite_init,
+       .test_cases = drm_buddy_tests,
+};
+
+kunit_test_suite(drm_buddy_test_suite);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_DESCRIPTION("Kunit test for drm_buddy functions");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/tests/gpu_random.c b/drivers/gpu/tests/gpu_random.c
new file mode 100644 (file)
index 0000000..ddd1f59
--- /dev/null
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bitops.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/random.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "gpu_random.h"
+
+u32 drm_prandom_u32_max_state(u32 ep_ro, struct rnd_state *state)
+{
+       return upper_32_bits((u64)prandom_u32_state(state) * ep_ro);
+}
+EXPORT_SYMBOL(drm_prandom_u32_max_state);
+
+void drm_random_reorder(unsigned int *order, unsigned int count,
+                       struct rnd_state *state)
+{
+       unsigned int i, j;
+
+       for (i = 0; i < count; ++i) {
+               BUILD_BUG_ON(sizeof(unsigned int) > sizeof(u32));
+               j = drm_prandom_u32_max_state(count, state);
+               swap(order[i], order[j]);
+       }
+}
+EXPORT_SYMBOL(drm_random_reorder);
+
+unsigned int *drm_random_order(unsigned int count, struct rnd_state *state)
+{
+       unsigned int *order, i;
+
+       order = kmalloc_array(count, sizeof(*order), GFP_KERNEL);
+       if (!order)
+               return order;
+
+       for (i = 0; i < count; i++)
+               order[i] = i;
+
+       drm_random_reorder(order, count, state);
+       return order;
+}
+EXPORT_SYMBOL(drm_random_order);
diff --git a/drivers/gpu/tests/gpu_random.h b/drivers/gpu/tests/gpu_random.h
new file mode 100644 (file)
index 0000000..9f82726
--- /dev/null
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DRM_RANDOM_H__
+#define __DRM_RANDOM_H__
+
+/* This is a temporary home for a couple of utility functions that should
+ * be transposed to lib/ at the earliest convenience.
+ */
+
+#include <linux/prandom.h>
+
+#define DRM_RND_STATE_INITIALIZER(seed__) ({                           \
+       struct rnd_state state__;                                       \
+       prandom_seed_state(&state__, (seed__));                         \
+       state__;                                                        \
+})
+
+#define DRM_RND_STATE(name__, seed__) \
+       struct rnd_state name__ = DRM_RND_STATE_INITIALIZER(seed__)
+
+unsigned int *drm_random_order(unsigned int count,
+                              struct rnd_state *state);
+void drm_random_reorder(unsigned int *order,
+                       unsigned int count,
+                       struct rnd_state *state);
+u32 drm_prandom_u32_max_state(u32 ep_ro,
+                             struct rnd_state *state);
+
+#endif /* !__DRM_RANDOM_H__ */
diff --git a/include/drm/drm_buddy.h b/include/drm/drm_buddy.h
deleted file mode 100644 (file)
index b909fa8..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/* SPDX-License-Identifier: MIT */
-/*
- * Copyright © 2021 Intel Corporation
- */
-
-#ifndef __DRM_BUDDY_H__
-#define __DRM_BUDDY_H__
-
-#include <linux/bitops.h>
-#include <linux/list.h>
-#include <linux/slab.h>
-#include <linux/sched.h>
-#include <linux/rbtree.h>
-
-struct drm_printer;
-
-#define DRM_BUDDY_RANGE_ALLOCATION             BIT(0)
-#define DRM_BUDDY_TOPDOWN_ALLOCATION           BIT(1)
-#define DRM_BUDDY_CONTIGUOUS_ALLOCATION                BIT(2)
-#define DRM_BUDDY_CLEAR_ALLOCATION             BIT(3)
-#define DRM_BUDDY_CLEARED                      BIT(4)
-#define DRM_BUDDY_TRIM_DISABLE                 BIT(5)
-
-struct drm_buddy_block {
-#define DRM_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12)
-#define DRM_BUDDY_HEADER_STATE  GENMASK_ULL(11, 10)
-#define   DRM_BUDDY_ALLOCATED     (1 << 10)
-#define   DRM_BUDDY_FREE          (2 << 10)
-#define   DRM_BUDDY_SPLIT         (3 << 10)
-#define DRM_BUDDY_HEADER_CLEAR  GENMASK_ULL(9, 9)
-/* Free to be used, if needed in the future */
-#define DRM_BUDDY_HEADER_UNUSED GENMASK_ULL(8, 6)
-#define DRM_BUDDY_HEADER_ORDER  GENMASK_ULL(5, 0)
-       u64 header;
-
-       struct drm_buddy_block *left;
-       struct drm_buddy_block *right;
-       struct drm_buddy_block *parent;
-
-       void *private; /* owned by creator */
-
-       /*
-        * While the block is allocated by the user through drm_buddy_alloc*,
-        * the user has ownership of the link, for example to maintain within
-        * a list, if so desired. As soon as the block is freed with
-        * drm_buddy_free* ownership is given back to the mm.
-        */
-       union {
-               struct rb_node rb;
-               struct list_head link;
-       };
-
-       struct list_head tmp_link;
-};
-
-/* Order-zero must be at least SZ_4K */
-#define DRM_BUDDY_MAX_ORDER (63 - 12)
-
-/*
- * Binary Buddy System.
- *
- * Locking should be handled by the user, a simple mutex around
- * drm_buddy_alloc* and drm_buddy_free* should suffice.
- */
-struct drm_buddy {
-       /* Maintain a free list for each order. */
-       struct rb_root **free_trees;
-
-       /*
-        * Maintain explicit binary tree(s) to track the allocation of the
-        * address space. This gives us a simple way of finding a buddy block
-        * and performing the potentially recursive merge step when freeing a
-        * block.  Nodes are either allocated or free, in which case they will
-        * also exist on the respective free list.
-        */
-       struct drm_buddy_block **roots;
-
-       /*
-        * Anything from here is public, and remains static for the lifetime of
-        * the mm. Everything above is considered do-not-touch.
-        */
-       unsigned int n_roots;
-       unsigned int max_order;
-
-       /* Must be at least SZ_4K */
-       u64 chunk_size;
-       u64 size;
-       u64 avail;
-       u64 clear_avail;
-};
-
-static inline u64
-drm_buddy_block_offset(const struct drm_buddy_block *block)
-{
-       return block->header & DRM_BUDDY_HEADER_OFFSET;
-}
-
-static inline unsigned int
-drm_buddy_block_order(struct drm_buddy_block *block)
-{
-       return block->header & DRM_BUDDY_HEADER_ORDER;
-}
-
-static inline unsigned int
-drm_buddy_block_state(struct drm_buddy_block *block)
-{
-       return block->header & DRM_BUDDY_HEADER_STATE;
-}
-
-static inline bool
-drm_buddy_block_is_allocated(struct drm_buddy_block *block)
-{
-       return drm_buddy_block_state(block) == DRM_BUDDY_ALLOCATED;
-}
-
-static inline bool
-drm_buddy_block_is_clear(struct drm_buddy_block *block)
-{
-       return block->header & DRM_BUDDY_HEADER_CLEAR;
-}
-
-static inline bool
-drm_buddy_block_is_free(struct drm_buddy_block *block)
-{
-       return drm_buddy_block_state(block) == DRM_BUDDY_FREE;
-}
-
-static inline bool
-drm_buddy_block_is_split(struct drm_buddy_block *block)
-{
-       return drm_buddy_block_state(block) == DRM_BUDDY_SPLIT;
-}
-
-static inline u64
-drm_buddy_block_size(struct drm_buddy *mm,
-                    struct drm_buddy_block *block)
-{
-       return mm->chunk_size << drm_buddy_block_order(block);
-}
-
-int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size);
-
-void drm_buddy_fini(struct drm_buddy *mm);
-
-struct drm_buddy_block *
-drm_get_buddy(struct drm_buddy_block *block);
-
-int 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);
-
-int drm_buddy_block_trim(struct drm_buddy *mm,
-                        u64 *start,
-                        u64 new_size,
-                        struct list_head *blocks);
-
-void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear);
-
-void drm_buddy_free_block(struct drm_buddy *mm, struct drm_buddy_block *block);
-
-void drm_buddy_free_list(struct drm_buddy *mm,
-                        struct list_head *objects,
-                        unsigned int flags);
-
-void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p);
-void drm_buddy_block_print(struct drm_buddy *mm,
-                          struct drm_buddy_block *block,
-                          struct drm_printer *p);
-#endif
diff --git a/include/linux/gpu_buddy.h b/include/linux/gpu_buddy.h
new file mode 100644 (file)
index 0000000..b909fa8
--- /dev/null
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2021 Intel Corporation
+ */
+
+#ifndef __DRM_BUDDY_H__
+#define __DRM_BUDDY_H__
+
+#include <linux/bitops.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/rbtree.h>
+
+struct drm_printer;
+
+#define DRM_BUDDY_RANGE_ALLOCATION             BIT(0)
+#define DRM_BUDDY_TOPDOWN_ALLOCATION           BIT(1)
+#define DRM_BUDDY_CONTIGUOUS_ALLOCATION                BIT(2)
+#define DRM_BUDDY_CLEAR_ALLOCATION             BIT(3)
+#define DRM_BUDDY_CLEARED                      BIT(4)
+#define DRM_BUDDY_TRIM_DISABLE                 BIT(5)
+
+struct drm_buddy_block {
+#define DRM_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12)
+#define DRM_BUDDY_HEADER_STATE  GENMASK_ULL(11, 10)
+#define   DRM_BUDDY_ALLOCATED     (1 << 10)
+#define   DRM_BUDDY_FREE          (2 << 10)
+#define   DRM_BUDDY_SPLIT         (3 << 10)
+#define DRM_BUDDY_HEADER_CLEAR  GENMASK_ULL(9, 9)
+/* Free to be used, if needed in the future */
+#define DRM_BUDDY_HEADER_UNUSED GENMASK_ULL(8, 6)
+#define DRM_BUDDY_HEADER_ORDER  GENMASK_ULL(5, 0)
+       u64 header;
+
+       struct drm_buddy_block *left;
+       struct drm_buddy_block *right;
+       struct drm_buddy_block *parent;
+
+       void *private; /* owned by creator */
+
+       /*
+        * While the block is allocated by the user through drm_buddy_alloc*,
+        * the user has ownership of the link, for example to maintain within
+        * a list, if so desired. As soon as the block is freed with
+        * drm_buddy_free* ownership is given back to the mm.
+        */
+       union {
+               struct rb_node rb;
+               struct list_head link;
+       };
+
+       struct list_head tmp_link;
+};
+
+/* Order-zero must be at least SZ_4K */
+#define DRM_BUDDY_MAX_ORDER (63 - 12)
+
+/*
+ * Binary Buddy System.
+ *
+ * Locking should be handled by the user, a simple mutex around
+ * drm_buddy_alloc* and drm_buddy_free* should suffice.
+ */
+struct drm_buddy {
+       /* Maintain a free list for each order. */
+       struct rb_root **free_trees;
+
+       /*
+        * Maintain explicit binary tree(s) to track the allocation of the
+        * address space. This gives us a simple way of finding a buddy block
+        * and performing the potentially recursive merge step when freeing a
+        * block.  Nodes are either allocated or free, in which case they will
+        * also exist on the respective free list.
+        */
+       struct drm_buddy_block **roots;
+
+       /*
+        * Anything from here is public, and remains static for the lifetime of
+        * the mm. Everything above is considered do-not-touch.
+        */
+       unsigned int n_roots;
+       unsigned int max_order;
+
+       /* Must be at least SZ_4K */
+       u64 chunk_size;
+       u64 size;
+       u64 avail;
+       u64 clear_avail;
+};
+
+static inline u64
+drm_buddy_block_offset(const struct drm_buddy_block *block)
+{
+       return block->header & DRM_BUDDY_HEADER_OFFSET;
+}
+
+static inline unsigned int
+drm_buddy_block_order(struct drm_buddy_block *block)
+{
+       return block->header & DRM_BUDDY_HEADER_ORDER;
+}
+
+static inline unsigned int
+drm_buddy_block_state(struct drm_buddy_block *block)
+{
+       return block->header & DRM_BUDDY_HEADER_STATE;
+}
+
+static inline bool
+drm_buddy_block_is_allocated(struct drm_buddy_block *block)
+{
+       return drm_buddy_block_state(block) == DRM_BUDDY_ALLOCATED;
+}
+
+static inline bool
+drm_buddy_block_is_clear(struct drm_buddy_block *block)
+{
+       return block->header & DRM_BUDDY_HEADER_CLEAR;
+}
+
+static inline bool
+drm_buddy_block_is_free(struct drm_buddy_block *block)
+{
+       return drm_buddy_block_state(block) == DRM_BUDDY_FREE;
+}
+
+static inline bool
+drm_buddy_block_is_split(struct drm_buddy_block *block)
+{
+       return drm_buddy_block_state(block) == DRM_BUDDY_SPLIT;
+}
+
+static inline u64
+drm_buddy_block_size(struct drm_buddy *mm,
+                    struct drm_buddy_block *block)
+{
+       return mm->chunk_size << drm_buddy_block_order(block);
+}
+
+int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size);
+
+void drm_buddy_fini(struct drm_buddy *mm);
+
+struct drm_buddy_block *
+drm_get_buddy(struct drm_buddy_block *block);
+
+int 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);
+
+int drm_buddy_block_trim(struct drm_buddy *mm,
+                        u64 *start,
+                        u64 new_size,
+                        struct list_head *blocks);
+
+void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear);
+
+void drm_buddy_free_block(struct drm_buddy *mm, struct drm_buddy_block *block);
+
+void drm_buddy_free_list(struct drm_buddy *mm,
+                        struct list_head *objects,
+                        unsigned int flags);
+
+void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p);
+void drm_buddy_block_print(struct drm_buddy *mm,
+                          struct drm_buddy_block *block,
+                          struct drm_printer *p);
+#endif