- Make drm_buddy_alloc a single function to handle
  range allocation and non-range allocation demands.

- Implemented a new function alloc_range() which allocates
  the requested order (in bytes) comply with range limitations

- Moved memory alignment logic from i915 driver

Signed-off-by: Arunpravin <arunpravin.paneersel...@amd.com>
---
 drivers/gpu/drm/drm_buddy.c | 208 +++++++++++++++++++++++++++++++-----
 include/drm/drm_buddy.h     |  18 +++-
 2 files changed, 194 insertions(+), 32 deletions(-)

diff --git a/drivers/gpu/drm/drm_buddy.c b/drivers/gpu/drm/drm_buddy.c
index 0398706cb7ae..f5f299dd9131 100644
--- a/drivers/gpu/drm/drm_buddy.c
+++ b/drivers/gpu/drm/drm_buddy.c
@@ -246,27 +246,112 @@ void drm_buddy_free_list(struct drm_buddy_mm *mm, struct 
list_head *objects)
        INIT_LIST_HEAD(objects);
 }
 
-/*
- * Allocate power-of-two block. The order value here translates to:
- *
- *   0 = 2^0 * mm->chunk_size
- *   1 = 2^1 * mm->chunk_size
- *   2 = 2^2 * mm->chunk_size
- *   ...
- */
-struct drm_buddy_block *
-drm_buddy_alloc(struct drm_buddy_mm *mm, unsigned int order)
+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 *
+alloc_range(struct drm_buddy_mm *mm,
+           u64 start, u64 end,
+           unsigned int 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 (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->left->tmp_link, &dfs);
+               list_add(&block->right->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);
+       return ERR_PTR(err);
+}
+
+static struct drm_buddy_block *
+alloc_from_freelist(struct drm_buddy_mm *mm,
+                   unsigned int order,
+                   unsigned long flags)
 {
        struct drm_buddy_block *block = NULL;
        unsigned int i;
        int err;
 
        for (i = order; i <= mm->max_order; ++i) {
-               block = list_first_entry_or_null(&mm->free_list[i],
-                                                struct drm_buddy_block,
-                                                link);
-               if (block)
-                       break;
+               if (!list_empty(&mm->free_list[i])) {
+                       block = list_first_entry_or_null(&mm->free_list[i],
+                                                        struct drm_buddy_block,
+                                                        link);
+
+                       if (block)
+                               break;
+               }
        }
 
        if (!block)
@@ -276,33 +361,100 @@ drm_buddy_alloc(struct drm_buddy_mm *mm, unsigned int 
order)
 
        while (i != order) {
                err = split_block(mm, block);
+
                if (unlikely(err))
-                       goto out_free;
+                       goto err_undo;
 
-               /* Go low */
-               block = block->left;
+               block = block->right;
                i--;
        }
 
-       mark_allocated(block);
-       mm->avail -= drm_buddy_block_size(mm, block);
-       kmemleak_update_trace(block);
        return block;
 
-out_free:
+err_undo:
        if (i != order)
                __drm_buddy_free(mm, block);
        return ERR_PTR(err);
 }
 
-static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
+/*
+ * Allocate power-of-two block. The order value here translates to:
+ *
+ *   0 = 2^0 * mm->chunk_size
+ *   1 = 2^1 * mm->chunk_size
+ *   2 = 2^2 * mm->chunk_size
+ *   ...
+ */
+int drm_buddy_alloc(struct drm_buddy_mm *mm,
+                   u64 start, u64 end, u64 size,
+                   u64 min_page_size,
+                   struct list_head *blocks,
+                   unsigned long flags)
 {
-       return s1 <= e2 && e1 >= s2;
-}
+       struct drm_buddy_block *block = NULL;
+       unsigned int min_order, order;
+       unsigned long pages;
+       LIST_HEAD(allocated);
+       int err;
 
-static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
-{
-       return s1 <= s2 && e1 >= e2;
+       if (size < mm->chunk_size)
+               return -EINVAL;
+
+       if (!IS_ALIGNED(start, mm->chunk_size))
+               return -EINVAL;
+
+       if (!IS_ALIGNED(end, mm->chunk_size))
+               return -EINVAL;
+
+       if (!IS_ALIGNED(size, mm->chunk_size))
+               return -EINVAL;
+
+       if (check_range_overflow(start, end, size, mm->size))
+               return -EINVAL;
+
+       pages = size >> ilog2(mm->chunk_size);
+       min_order = ilog2(min_page_size) - ilog2(mm->chunk_size);
+
+       do {
+               order = fls(pages) - 1;
+               BUG_ON(order > mm->max_order);
+               BUG_ON(order < min_order);
+
+               do {
+                       if (flags & DRM_BUDDY_RANGE_ALLOCATION)
+                               /* Allocate traversing within the range */
+                               block = alloc_range(mm, start, end, order);
+                       else
+                               /* Allocate from freelist */
+                               block = alloc_from_freelist(mm, order, flags);
+
+                       if (!IS_ERR(block))
+                               break;
+
+                       if (order-- == min_order) {
+                               err = -ENOSPC;
+                               goto err_free;
+                       }
+               } while (1);
+
+               mark_allocated(block);
+               mm->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);
+
+       list_splice_tail(&allocated, blocks);
+       return 0;
+
+err_free:
+       drm_buddy_free_list(mm, &allocated);
+       return err;
 }
 
 /*
diff --git a/include/drm/drm_buddy.h b/include/drm/drm_buddy.h
index 390b133fe342..c64fd4062cb6 100644
--- a/include/drm/drm_buddy.h
+++ b/include/drm/drm_buddy.h
@@ -13,15 +13,22 @@
 
 #include <drm/drm_print.h>
 
-#define range_overflows(start, size, max) ({ \
+#define check_range_overflow(start, end, size, max) ({ \
        typeof(start) start__ = (start); \
+       typeof(end) end__ = (end);\
        typeof(size) size__ = (size); \
        typeof(max) max__ = (max); \
        (void)(&start__ == &size__); \
        (void)(&start__ == &max__); \
-       start__ >= max__ || size__ > max__ - start__; \
+       (void)(&start__ == &end__); \
+       (void)(&end__ == &size__); \
+       (void)(&end__ == &max__); \
+       start__ >= max__ || end__ > max__ \
+               || size__ > end__ - start__; \
 })
 
+#define DRM_BUDDY_RANGE_ALLOCATION (1 << 1)
+
 struct drm_buddy_block {
 #define DRM_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12)
 #define DRM_BUDDY_HEADER_STATE  GENMASK_ULL(11, 10)
@@ -131,8 +138,11 @@ int drm_buddy_init(struct drm_buddy_mm *mm, u64 size, u64 
chunk_size);
 
 void drm_buddy_fini(struct drm_buddy_mm *mm);
 
-struct drm_buddy_block *
-drm_buddy_alloc(struct drm_buddy_mm *mm, unsigned int order);
+int drm_buddy_alloc(struct drm_buddy_mm *mm,
+                   u64 start, u64 end, u64 size,
+                   u64 min_page_size,
+                   struct list_head *blocks,
+                   unsigned long flags);
 
 int drm_buddy_alloc_range(struct drm_buddy_mm *mm,
                           struct list_head *blocks,
-- 
2.25.1

Reply via email to