LCOV - code coverage report
Current view: top level - drivers/gpu/drm - drm_buddy.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 182 285 63.9 %
Date: 2023-03-27 20:00:47 Functions: 14 19 73.7 %

          Line data    Source code
       1             : // SPDX-License-Identifier: MIT
       2             : /*
       3             :  * Copyright © 2021 Intel Corporation
       4             :  */
       5             : 
       6             : #include <linux/kmemleak.h>
       7             : #include <linux/module.h>
       8             : #include <linux/sizes.h>
       9             : 
      10             : #include <drm/drm_buddy.h>
      11             : 
      12             : static struct kmem_cache *slab_blocks;
      13             : 
      14       19729 : static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
      15             :                                                struct drm_buddy_block *parent,
      16             :                                                unsigned int order,
      17             :                                                u64 offset)
      18             : {
      19             :         struct drm_buddy_block *block;
      20             : 
      21       19729 :         BUG_ON(order > DRM_BUDDY_MAX_ORDER);
      22             : 
      23       39458 :         block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
      24       19729 :         if (!block)
      25             :                 return NULL;
      26             : 
      27             :         block->header = offset;
      28       19729 :         block->header |= order;
      29       19729 :         block->parent = parent;
      30             : 
      31       19729 :         BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
      32             :         return block;
      33             : }
      34             : 
      35             : static void drm_block_free(struct drm_buddy *mm,
      36             :                            struct drm_buddy_block *block)
      37             : {
      38       19729 :         kmem_cache_free(slab_blocks, block);
      39             : }
      40             : 
      41       29701 : static void list_insert_sorted(struct drm_buddy *mm,
      42             :                                struct drm_buddy_block *block)
      43             : {
      44             :         struct drm_buddy_block *node;
      45             :         struct list_head *head;
      46             : 
      47       59402 :         head = &mm->free_list[drm_buddy_block_order(block)];
      48       29701 :         if (list_empty(head)) {
      49       11282 :                 list_add(&block->link, head);
      50             :                 return;
      51             :         }
      52             : 
      53       28243 :         list_for_each_entry(node, head, link)
      54       55869 :                 if (drm_buddy_block_offset(block) < drm_buddy_block_offset(node))
      55             :                         break;
      56             : 
      57       18419 :         __list_add(&block->link, node->link.prev, &node->link);
      58             : }
      59             : 
      60             : static void mark_allocated(struct drm_buddy_block *block)
      61             : {
      62        9972 :         block->header &= ~DRM_BUDDY_HEADER_STATE;
      63        9972 :         block->header |= DRM_BUDDY_ALLOCATED;
      64             : 
      65       19944 :         list_del(&block->link);
      66             : }
      67             : 
      68             : static void mark_free(struct drm_buddy *mm,
      69             :                       struct drm_buddy_block *block)
      70             : {
      71       29701 :         block->header &= ~DRM_BUDDY_HEADER_STATE;
      72       29701 :         block->header |= DRM_BUDDY_FREE;
      73             : 
      74       29701 :         list_insert_sorted(mm, block);
      75             : }
      76             : 
      77             : static void mark_split(struct drm_buddy_block *block)
      78             : {
      79        9821 :         block->header &= ~DRM_BUDDY_HEADER_STATE;
      80        9821 :         block->header |= DRM_BUDDY_SPLIT;
      81             : 
      82       19642 :         list_del(&block->link);
      83             : }
      84             : 
      85             : /**
      86             :  * drm_buddy_init - init memory manager
      87             :  *
      88             :  * @mm: DRM buddy manager to initialize
      89             :  * @size: size in bytes to manage
      90             :  * @chunk_size: minimum page size in bytes for our allocations
      91             :  *
      92             :  * Initializes the memory manager and its resources.
      93             :  *
      94             :  * Returns:
      95             :  * 0 on success, error code on failure.
      96             :  */
      97           6 : int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
      98             : {
      99             :         unsigned int i;
     100             :         u64 offset;
     101             : 
     102           6 :         if (size < chunk_size)
     103             :                 return -EINVAL;
     104             : 
     105           6 :         if (chunk_size < PAGE_SIZE)
     106             :                 return -EINVAL;
     107             : 
     108           6 :         if (!is_power_of_2(chunk_size))
     109             :                 return -EINVAL;
     110             : 
     111           6 :         size = round_down(size, chunk_size);
     112             : 
     113           6 :         mm->size = size;
     114           6 :         mm->avail = size;
     115           6 :         mm->chunk_size = chunk_size;
     116          18 :         mm->max_order = ilog2(size) - ilog2(chunk_size);
     117             : 
     118           6 :         BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
     119             : 
     120           6 :         mm->free_list = kmalloc_array(mm->max_order + 1,
     121             :                                       sizeof(struct list_head),
     122             :                                       GFP_KERNEL);
     123           6 :         if (!mm->free_list)
     124             :                 return -ENOMEM;
     125             : 
     126         116 :         for (i = 0; i <= mm->max_order; ++i)
     127         232 :                 INIT_LIST_HEAD(&mm->free_list[i]);
     128             : 
     129          12 :         mm->n_roots = hweight64(size);
     130             : 
     131           6 :         mm->roots = kmalloc_array(mm->n_roots,
     132             :                                   sizeof(struct drm_buddy_block *),
     133             :                                   GFP_KERNEL);
     134           6 :         if (!mm->roots)
     135             :                 goto out_free_list;
     136             : 
     137             :         offset = 0;
     138             :         i = 0;
     139             : 
     140             :         /*
     141             :          * Split into power-of-two blocks, in case we are given a size that is
     142             :          * not itself a power-of-two.
     143             :          */
     144             :         do {
     145             :                 struct drm_buddy_block *root;
     146             :                 unsigned int order;
     147             :                 u64 root_size;
     148             : 
     149         174 :                 root_size = rounddown_pow_of_two(size);
     150         261 :                 order = ilog2(root_size) - ilog2(chunk_size);
     151             : 
     152          87 :                 root = drm_block_alloc(mm, NULL, order, offset);
     153          87 :                 if (!root)
     154             :                         goto out_free_roots;
     155             : 
     156          87 :                 mark_free(mm, root);
     157             : 
     158          87 :                 BUG_ON(i > mm->max_order);
     159         174 :                 BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
     160             : 
     161          87 :                 mm->roots[i] = root;
     162             : 
     163          87 :                 offset += root_size;
     164          87 :                 size -= root_size;
     165          87 :                 i++;
     166          87 :         } while (size);
     167             : 
     168             :         return 0;
     169             : 
     170             : out_free_roots:
     171           0 :         while (i--)
     172           0 :                 drm_block_free(mm, mm->roots[i]);
     173           0 :         kfree(mm->roots);
     174             : out_free_list:
     175           0 :         kfree(mm->free_list);
     176           0 :         return -ENOMEM;
     177             : }
     178             : EXPORT_SYMBOL(drm_buddy_init);
     179             : 
     180             : /**
     181             :  * drm_buddy_fini - tear down the memory manager
     182             :  *
     183             :  * @mm: DRM buddy manager to free
     184             :  *
     185             :  * Cleanup memory manager resources and the freelist
     186             :  */
     187           6 : void drm_buddy_fini(struct drm_buddy *mm)
     188             : {
     189             :         int i;
     190             : 
     191          93 :         for (i = 0; i < mm->n_roots; ++i) {
     192         174 :                 WARN_ON(!drm_buddy_block_is_free(mm->roots[i]));
     193         174 :                 drm_block_free(mm, mm->roots[i]);
     194             :         }
     195             : 
     196           6 :         WARN_ON(mm->avail != mm->size);
     197             : 
     198           6 :         kfree(mm->roots);
     199           6 :         kfree(mm->free_list);
     200           6 : }
     201             : EXPORT_SYMBOL(drm_buddy_fini);
     202             : 
     203        9821 : static int split_block(struct drm_buddy *mm,
     204             :                        struct drm_buddy_block *block)
     205             : {
     206       19642 :         unsigned int block_order = drm_buddy_block_order(block) - 1;
     207       19642 :         u64 offset = drm_buddy_block_offset(block);
     208             : 
     209        9821 :         BUG_ON(!drm_buddy_block_is_free(block));
     210        9821 :         BUG_ON(!drm_buddy_block_order(block));
     211             : 
     212        9821 :         block->left = drm_block_alloc(mm, block, block_order, offset);
     213        9821 :         if (!block->left)
     214             :                 return -ENOMEM;
     215             : 
     216        9821 :         block->right = drm_block_alloc(mm, block, block_order,
     217        9821 :                                        offset + (mm->chunk_size << block_order));
     218        9821 :         if (!block->right) {
     219           0 :                 drm_block_free(mm, block->left);
     220           0 :                 return -ENOMEM;
     221             :         }
     222             : 
     223       19642 :         mark_free(mm, block->left);
     224       19642 :         mark_free(mm, block->right);
     225             : 
     226        9821 :         mark_split(block);
     227             : 
     228        9821 :         return 0;
     229             : }
     230             : 
     231             : static struct drm_buddy_block *
     232             : __get_buddy(struct drm_buddy_block *block)
     233             : {
     234             :         struct drm_buddy_block *parent;
     235             : 
     236       29794 :         parent = block->parent;
     237       10135 :         if (!parent)
     238             :                 return NULL;
     239             : 
     240       29500 :         if (parent->left == block)
     241       14740 :                 return parent->right;
     242             : 
     243             :         return parent->left;
     244             : }
     245             : 
     246             : /**
     247             :  * drm_get_buddy - get buddy address
     248             :  *
     249             :  * @block: DRM buddy block
     250             :  *
     251             :  * Returns the corresponding buddy block for @block, or NULL
     252             :  * if this is a root block and can't be merged further.
     253             :  * Requires some kind of locking to protect against
     254             :  * any concurrent allocate and free operations.
     255             :  */
     256             : struct drm_buddy_block *
     257       10135 : drm_get_buddy(struct drm_buddy_block *block)
     258             : {
     259       10135 :         return __get_buddy(block);
     260             : }
     261             : EXPORT_SYMBOL(drm_get_buddy);
     262             : 
     263        9972 : static void __drm_buddy_free(struct drm_buddy *mm,
     264             :                              struct drm_buddy_block *block)
     265             : {
     266             :         struct drm_buddy_block *parent;
     267             : 
     268       29765 :         while ((parent = block->parent)) {
     269             :                 struct drm_buddy_block *buddy;
     270             : 
     271       19659 :                 buddy = __get_buddy(block);
     272             : 
     273       19659 :                 if (!drm_buddy_block_is_free(buddy))
     274             :                         break;
     275             : 
     276       19642 :                 list_del(&buddy->link);
     277             : 
     278       19642 :                 drm_block_free(mm, block);
     279       19642 :                 drm_block_free(mm, buddy);
     280             : 
     281        9821 :                 block = parent;
     282             :         }
     283             : 
     284        9972 :         mark_free(mm, block);
     285        9972 : }
     286             : 
     287             : /**
     288             :  * drm_buddy_free_block - free a block
     289             :  *
     290             :  * @mm: DRM buddy manager
     291             :  * @block: block to be freed
     292             :  */
     293        9972 : void drm_buddy_free_block(struct drm_buddy *mm,
     294             :                           struct drm_buddy_block *block)
     295             : {
     296        9972 :         BUG_ON(!drm_buddy_block_is_allocated(block));
     297       19944 :         mm->avail += drm_buddy_block_size(mm, block);
     298        9972 :         __drm_buddy_free(mm, block);
     299        9972 : }
     300             : EXPORT_SYMBOL(drm_buddy_free_block);
     301             : 
     302             : /**
     303             :  * drm_buddy_free_list - free blocks
     304             :  *
     305             :  * @mm: DRM buddy manager
     306             :  * @objects: input list head to free blocks
     307             :  */
     308         120 : void drm_buddy_free_list(struct drm_buddy *mm, struct list_head *objects)
     309             : {
     310             :         struct drm_buddy_block *block, *on;
     311             : 
     312       10056 :         list_for_each_entry_safe(block, on, objects, link) {
     313        9936 :                 drm_buddy_free_block(mm, block);
     314        9936 :                 cond_resched();
     315             :         }
     316         120 :         INIT_LIST_HEAD(objects);
     317         120 : }
     318             : EXPORT_SYMBOL(drm_buddy_free_list);
     319             : 
     320             : static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
     321             : {
     322        2090 :         return s1 <= e2 && e1 >= s2;
     323             : }
     324             : 
     325             : static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
     326             : {
     327        1118 :         return s1 <= s2 && e1 >= e2;
     328             : }
     329             : 
     330             : static struct drm_buddy_block *
     331           0 : alloc_range_bias(struct drm_buddy *mm,
     332             :                  u64 start, u64 end,
     333             :                  unsigned int order)
     334             : {
     335             :         struct drm_buddy_block *block;
     336             :         struct drm_buddy_block *buddy;
     337           0 :         LIST_HEAD(dfs);
     338             :         int err;
     339             :         int i;
     340             : 
     341           0 :         end = end - 1;
     342             : 
     343           0 :         for (i = 0; i < mm->n_roots; ++i)
     344           0 :                 list_add_tail(&mm->roots[i]->tmp_link, &dfs);
     345             : 
     346             :         do {
     347             :                 u64 block_start;
     348             :                 u64 block_end;
     349             : 
     350           0 :                 block = list_first_entry_or_null(&dfs,
     351             :                                                  struct drm_buddy_block,
     352             :                                                  tmp_link);
     353           0 :                 if (!block)
     354             :                         break;
     355             : 
     356           0 :                 list_del(&block->tmp_link);
     357             : 
     358           0 :                 if (drm_buddy_block_order(block) < order)
     359           0 :                         continue;
     360             : 
     361           0 :                 block_start = drm_buddy_block_offset(block);
     362           0 :                 block_end = block_start + drm_buddy_block_size(mm, block) - 1;
     363             : 
     364           0 :                 if (!overlaps(start, end, block_start, block_end))
     365           0 :                         continue;
     366             : 
     367           0 :                 if (drm_buddy_block_is_allocated(block))
     368           0 :                         continue;
     369             : 
     370           0 :                 if (contains(start, end, block_start, block_end) &&
     371           0 :                     order == drm_buddy_block_order(block)) {
     372             :                         /*
     373             :                          * Find the free block within the range.
     374             :                          */
     375           0 :                         if (drm_buddy_block_is_free(block))
     376             :                                 return block;
     377             : 
     378           0 :                         continue;
     379             :                 }
     380             : 
     381           0 :                 if (!drm_buddy_block_is_split(block)) {
     382           0 :                         err = split_block(mm, block);
     383           0 :                         if (unlikely(err))
     384             :                                 goto err_undo;
     385             :                 }
     386             : 
     387           0 :                 list_add(&block->right->tmp_link, &dfs);
     388           0 :                 list_add(&block->left->tmp_link, &dfs);
     389             :         } while (1);
     390             : 
     391             :         return ERR_PTR(-ENOSPC);
     392             : 
     393             : err_undo:
     394             :         /*
     395             :          * We really don't want to leave around a bunch of split blocks, since
     396             :          * bigger is better, so make sure we merge everything back before we
     397             :          * free the allocated blocks.
     398             :          */
     399           0 :         buddy = __get_buddy(block);
     400           0 :         if (buddy &&
     401           0 :             (drm_buddy_block_is_free(block) &&
     402           0 :              drm_buddy_block_is_free(buddy)))
     403           0 :                 __drm_buddy_free(mm, block);
     404           0 :         return ERR_PTR(err);
     405             : }
     406             : 
     407             : static struct drm_buddy_block *
     408             : get_maxblock(struct drm_buddy *mm, unsigned int order)
     409             : {
     410             :         struct drm_buddy_block *max_block = NULL, *node;
     411             :         unsigned int i;
     412             : 
     413           0 :         for (i = order; i <= mm->max_order; ++i) {
     414           0 :                 if (!list_empty(&mm->free_list[i])) {
     415           0 :                         node = list_last_entry(&mm->free_list[i],
     416             :                                                struct drm_buddy_block,
     417             :                                                link);
     418           0 :                         if (!max_block) {
     419           0 :                                 max_block = node;
     420           0 :                                 continue;
     421             :                         }
     422             : 
     423           0 :                         if (drm_buddy_block_offset(node) >
     424           0 :                             drm_buddy_block_offset(max_block)) {
     425           0 :                                 max_block = node;
     426             :                         }
     427             :                 }
     428             :         }
     429             : 
     430             :         return max_block;
     431             : }
     432             : 
     433             : static struct drm_buddy_block *
     434        9772 : alloc_from_freelist(struct drm_buddy *mm,
     435             :                     unsigned int order,
     436             :                     unsigned long flags)
     437             : {
     438        9772 :         struct drm_buddy_block *block = NULL;
     439             :         unsigned int tmp;
     440             :         int err;
     441             : 
     442        9772 :         if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
     443           0 :                 block = get_maxblock(mm, order);
     444           0 :                 if (block)
     445             :                         /* Store the obtained block order */
     446           0 :                         tmp = drm_buddy_block_order(block);
     447             :         } else {
     448       10362 :                 for (tmp = order; tmp <= mm->max_order; ++tmp) {
     449       40070 :                         if (!list_empty(&mm->free_list[tmp])) {
     450        9673 :                                 block = list_last_entry(&mm->free_list[tmp],
     451             :                                                         struct drm_buddy_block,
     452             :                                                         link);
     453        9673 :                                 if (block)
     454             :                                         break;
     455             :                         }
     456             :                 }
     457             :         }
     458             : 
     459        9772 :         if (!block)
     460             :                 return ERR_PTR(-ENOSPC);
     461             : 
     462        9673 :         BUG_ON(!drm_buddy_block_is_free(block));
     463             : 
     464       19206 :         while (tmp != order) {
     465        9533 :                 err = split_block(mm, block);
     466        9533 :                 if (unlikely(err))
     467             :                         goto err_undo;
     468             : 
     469        9533 :                 block = block->right;
     470        9533 :                 tmp--;
     471             :         }
     472             :         return block;
     473             : 
     474             : err_undo:
     475             :         if (tmp != order)
     476           0 :                 __drm_buddy_free(mm, block);
     477           0 :         return ERR_PTR(err);
     478             : }
     479             : 
     480          55 : static int __alloc_range(struct drm_buddy *mm,
     481             :                          struct list_head *dfs,
     482             :                          u64 start, u64 size,
     483             :                          struct list_head *blocks)
     484             : {
     485             :         struct drm_buddy_block *block;
     486             :         struct drm_buddy_block *buddy;
     487          55 :         LIST_HEAD(allocated);
     488             :         u64 end;
     489             :         int err;
     490             : 
     491          55 :         end = start + size - 1;
     492             : 
     493             :         do {
     494             :                 u64 block_start;
     495             :                 u64 block_end;
     496             : 
     497        2143 :                 block = list_first_entry_or_null(dfs,
     498             :                                                  struct drm_buddy_block,
     499             :                                                  tmp_link);
     500        2143 :                 if (!block)
     501             :                         break;
     502             : 
     503        4180 :                 list_del(&block->tmp_link);
     504             : 
     505        4180 :                 block_start = drm_buddy_block_offset(block);
     506        4180 :                 block_end = block_start + drm_buddy_block_size(mm, block) - 1;
     507             : 
     508        2090 :                 if (!overlaps(start, end, block_start, block_end))
     509         972 :                         continue;
     510             : 
     511        1118 :                 if (drm_buddy_block_is_allocated(block)) {
     512             :                         err = -ENOSPC;
     513             :                         goto err_free;
     514             :                 }
     515             : 
     516        1118 :                 if (contains(start, end, block_start, block_end)) {
     517         301 :                         if (!drm_buddy_block_is_free(block)) {
     518             :                                 err = -ENOSPC;
     519             :                                 goto err_free;
     520             :                         }
     521             : 
     522         299 :                         mark_allocated(block);
     523         598 :                         mm->avail -= drm_buddy_block_size(mm, block);
     524         598 :                         list_add_tail(&block->link, &allocated);
     525         299 :                         continue;
     526             :                 }
     527             : 
     528         817 :                 if (!drm_buddy_block_is_split(block)) {
     529         288 :                         err = split_block(mm, block);
     530         288 :                         if (unlikely(err))
     531             :                                 goto err_undo;
     532             :                 }
     533             : 
     534        1634 :                 list_add(&block->right->tmp_link, dfs);
     535         817 :                 list_add(&block->left->tmp_link, dfs);
     536             :         } while (1);
     537             : 
     538             :         list_splice_tail(&allocated, blocks);
     539             :         return 0;
     540             : 
     541             : err_undo:
     542             :         /*
     543             :          * We really don't want to leave around a bunch of split blocks, since
     544             :          * bigger is better, so make sure we merge everything back before we
     545             :          * free the allocated blocks.
     546             :          */
     547           0 :         buddy = __get_buddy(block);
     548           0 :         if (buddy &&
     549           0 :             (drm_buddy_block_is_free(block) &&
     550           0 :              drm_buddy_block_is_free(buddy)))
     551           0 :                 __drm_buddy_free(mm, block);
     552             : 
     553             : err_free:
     554           2 :         drm_buddy_free_list(mm, &allocated);
     555           2 :         return err;
     556             : }
     557             : 
     558          55 : static int __drm_buddy_alloc_range(struct drm_buddy *mm,
     559             :                                    u64 start,
     560             :                                    u64 size,
     561             :                                    struct list_head *blocks)
     562             : {
     563          55 :         LIST_HEAD(dfs);
     564             :         int i;
     565             : 
     566         511 :         for (i = 0; i < mm->n_roots; ++i)
     567         912 :                 list_add_tail(&mm->roots[i]->tmp_link, &dfs);
     568             : 
     569          55 :         return __alloc_range(mm, &dfs, start, size, blocks);
     570             : }
     571             : 
     572             : /**
     573             :  * drm_buddy_block_trim - free unused pages
     574             :  *
     575             :  * @mm: DRM buddy manager
     576             :  * @new_size: original size requested
     577             :  * @blocks: Input and output list of allocated blocks.
     578             :  * MUST contain single block as input to be trimmed.
     579             :  * On success will contain the newly allocated blocks
     580             :  * making up the @new_size. Blocks always appear in
     581             :  * ascending order
     582             :  *
     583             :  * For contiguous allocation, we round up the size to the nearest
     584             :  * power of two value, drivers consume *actual* size, so remaining
     585             :  * portions are unused and can be optionally freed with this function
     586             :  *
     587             :  * Returns:
     588             :  * 0 on success, error code on failure.
     589             :  */
     590           0 : int drm_buddy_block_trim(struct drm_buddy *mm,
     591             :                          u64 new_size,
     592             :                          struct list_head *blocks)
     593             : {
     594             :         struct drm_buddy_block *parent;
     595             :         struct drm_buddy_block *block;
     596           0 :         LIST_HEAD(dfs);
     597             :         u64 new_start;
     598             :         int err;
     599             : 
     600           0 :         if (!list_is_singular(blocks))
     601             :                 return -EINVAL;
     602             : 
     603           0 :         block = list_first_entry(blocks,
     604             :                                  struct drm_buddy_block,
     605             :                                  link);
     606             : 
     607           0 :         if (WARN_ON(!drm_buddy_block_is_allocated(block)))
     608             :                 return -EINVAL;
     609             : 
     610           0 :         if (new_size > drm_buddy_block_size(mm, block))
     611             :                 return -EINVAL;
     612             : 
     613           0 :         if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
     614             :                 return -EINVAL;
     615             : 
     616           0 :         if (new_size == drm_buddy_block_size(mm, block))
     617             :                 return 0;
     618             : 
     619           0 :         list_del(&block->link);
     620           0 :         mark_free(mm, block);
     621           0 :         mm->avail += drm_buddy_block_size(mm, block);
     622             : 
     623             :         /* Prevent recursively freeing this node */
     624           0 :         parent = block->parent;
     625           0 :         block->parent = NULL;
     626             : 
     627           0 :         new_start = drm_buddy_block_offset(block);
     628           0 :         list_add(&block->tmp_link, &dfs);
     629           0 :         err =  __alloc_range(mm, &dfs, new_start, new_size, blocks);
     630           0 :         if (err) {
     631           0 :                 mark_allocated(block);
     632           0 :                 mm->avail -= drm_buddy_block_size(mm, block);
     633           0 :                 list_add(&block->link, blocks);
     634             :         }
     635             : 
     636           0 :         block->parent = parent;
     637           0 :         return err;
     638             : }
     639             : EXPORT_SYMBOL(drm_buddy_block_trim);
     640             : 
     641             : /**
     642             :  * drm_buddy_alloc_blocks - allocate power-of-two blocks
     643             :  *
     644             :  * @mm: DRM buddy manager to allocate from
     645             :  * @start: start of the allowed range for this block
     646             :  * @end: end of the allowed range for this block
     647             :  * @size: size of the allocation
     648             :  * @min_page_size: alignment of the allocation
     649             :  * @blocks: output list head to add allocated blocks
     650             :  * @flags: DRM_BUDDY_*_ALLOCATION flags
     651             :  *
     652             :  * alloc_range_bias() called on range limitations, which traverses
     653             :  * the tree and returns the desired block.
     654             :  *
     655             :  * alloc_from_freelist() called when *no* range restrictions
     656             :  * are enforced, which picks the block from the freelist.
     657             :  *
     658             :  * Returns:
     659             :  * 0 on success, error code on failure.
     660             :  */
     661        9827 : int drm_buddy_alloc_blocks(struct drm_buddy *mm,
     662             :                            u64 start, u64 end, u64 size,
     663             :                            u64 min_page_size,
     664             :                            struct list_head *blocks,
     665             :                            unsigned long flags)
     666             : {
     667        9827 :         struct drm_buddy_block *block = NULL;
     668             :         unsigned int min_order, order;
     669             :         unsigned long pages;
     670        9827 :         LIST_HEAD(allocated);
     671             :         int err;
     672             : 
     673        9827 :         if (size < mm->chunk_size)
     674             :                 return -EINVAL;
     675             : 
     676        9827 :         if (min_page_size < mm->chunk_size)
     677             :                 return -EINVAL;
     678             : 
     679        9827 :         if (!is_power_of_2(min_page_size))
     680             :                 return -EINVAL;
     681             : 
     682        9827 :         if (!IS_ALIGNED(start | end | size, mm->chunk_size))
     683             :                 return -EINVAL;
     684             : 
     685        9827 :         if (end > mm->size)
     686             :                 return -EINVAL;
     687             : 
     688        9827 :         if (range_overflows(start, size, mm->size))
     689             :                 return -EINVAL;
     690             : 
     691             :         /* Actual range allocation */
     692        9827 :         if (start + size == end)
     693          55 :                 return __drm_buddy_alloc_range(mm, start, size, blocks);
     694             : 
     695        9772 :         if (!IS_ALIGNED(size, min_page_size))
     696             :                 return -EINVAL;
     697             : 
     698       19544 :         pages = size >> ilog2(mm->chunk_size);
     699       19544 :         order = fls(pages) - 1;
     700       29316 :         min_order = ilog2(min_page_size) - ilog2(mm->chunk_size);
     701             : 
     702             :         do {
     703       19544 :                 order = min(order, (unsigned int)fls(pages) - 1);
     704        9772 :                 BUG_ON(order > mm->max_order);
     705        9772 :                 BUG_ON(order < min_order);
     706             : 
     707             :                 do {
     708        9772 :                         if (flags & DRM_BUDDY_RANGE_ALLOCATION)
     709             :                                 /* Allocate traversing within the range */
     710           0 :                                 block = alloc_range_bias(mm, start, end, order);
     711             :                         else
     712             :                                 /* Allocate from freelist */
     713        9772 :                                 block = alloc_from_freelist(mm, order, flags);
     714             : 
     715        9772 :                         if (!IS_ERR(block))
     716             :                                 break;
     717             : 
     718          99 :                         if (order-- == min_order) {
     719          99 :                                 err = -ENOSPC;
     720             :                                 goto err_free;
     721             :                         }
     722             :                 } while (1);
     723             : 
     724        9673 :                 mark_allocated(block);
     725       19346 :                 mm->avail -= drm_buddy_block_size(mm, block);
     726        9673 :                 kmemleak_update_trace(block);
     727       19346 :                 list_add_tail(&block->link, &allocated);
     728             : 
     729        9673 :                 pages -= BIT(order);
     730             : 
     731        9673 :                 if (!pages)
     732             :                         break;
     733             :         } while (1);
     734             : 
     735             :         list_splice_tail(&allocated, blocks);
     736             :         return 0;
     737             : 
     738             : err_free:
     739          99 :         drm_buddy_free_list(mm, &allocated);
     740          99 :         return err;
     741             : }
     742             : EXPORT_SYMBOL(drm_buddy_alloc_blocks);
     743             : 
     744             : /**
     745             :  * drm_buddy_block_print - print block information
     746             :  *
     747             :  * @mm: DRM buddy manager
     748             :  * @block: DRM buddy block
     749             :  * @p: DRM printer to use
     750             :  */
     751           0 : void drm_buddy_block_print(struct drm_buddy *mm,
     752             :                            struct drm_buddy_block *block,
     753             :                            struct drm_printer *p)
     754             : {
     755           0 :         u64 start = drm_buddy_block_offset(block);
     756           0 :         u64 size = drm_buddy_block_size(mm, block);
     757             : 
     758           0 :         drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
     759           0 : }
     760             : EXPORT_SYMBOL(drm_buddy_block_print);
     761             : 
     762             : /**
     763             :  * drm_buddy_print - print allocator state
     764             :  *
     765             :  * @mm: DRM buddy manager
     766             :  * @p: DRM printer to use
     767             :  */
     768           0 : void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
     769             : {
     770             :         int order;
     771             : 
     772           0 :         drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB\n",
     773           0 :                    mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20);
     774             : 
     775           0 :         for (order = mm->max_order; order >= 0; order--) {
     776             :                 struct drm_buddy_block *block;
     777           0 :                 u64 count = 0, free;
     778             : 
     779           0 :                 list_for_each_entry(block, &mm->free_list[order], link) {
     780           0 :                         BUG_ON(!drm_buddy_block_is_free(block));
     781           0 :                         count++;
     782             :                 }
     783             : 
     784           0 :                 drm_printf(p, "order-%d ", order);
     785             : 
     786           0 :                 free = count * (mm->chunk_size << order);
     787           0 :                 if (free < SZ_1M)
     788           0 :                         drm_printf(p, "free: %lluKiB", free >> 10);
     789             :                 else
     790           0 :                         drm_printf(p, "free: %lluMiB", free >> 20);
     791             : 
     792           0 :                 drm_printf(p, ", pages: %llu\n", count);
     793             :         }
     794           0 : }
     795             : EXPORT_SYMBOL(drm_buddy_print);
     796             : 
     797           0 : static void drm_buddy_module_exit(void)
     798             : {
     799           0 :         kmem_cache_destroy(slab_blocks);
     800           0 : }
     801             : 
     802           1 : static int __init drm_buddy_module_init(void)
     803             : {
     804           1 :         slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
     805           1 :         if (!slab_blocks)
     806             :                 return -ENOMEM;
     807             : 
     808           1 :         return 0;
     809             : }
     810             : 
     811             : module_init(drm_buddy_module_init);
     812             : module_exit(drm_buddy_module_exit);
     813             : 
     814             : MODULE_DESCRIPTION("DRM Buddy Allocator");
     815             : MODULE_LICENSE("Dual MIT/GPL");

Generated by: LCOV version 1.14