Add a function to support defragmentation. v5: Defragment the freelist order array beginning from min_order. Signed-off-by: Arunpravin Paneer Selvam <Arunpravin.PaneerSelvam@xxxxxxx> Suggested-by: Matthew Auld <matthew.auld@xxxxxxxxx> --- drivers/gpu/drm/drm_buddy.c | 70 ++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/drm_buddy.c b/drivers/gpu/drm/drm_buddy.c index d44172f23f05..8aa6d31cb826 100644 --- a/drivers/gpu/drm/drm_buddy.c +++ b/drivers/gpu/drm/drm_buddy.c @@ -276,10 +276,12 @@ drm_get_buddy(struct drm_buddy_block *block) } EXPORT_SYMBOL(drm_get_buddy); -static void __drm_buddy_free(struct drm_buddy *mm, - struct drm_buddy_block *block) +static unsigned int __drm_buddy_free(struct drm_buddy *mm, + struct drm_buddy_block *block, + bool defrag) { struct drm_buddy_block *parent; + unsigned int order; while ((parent = block->parent)) { struct drm_buddy_block *buddy; @@ -289,12 +291,14 @@ static void __drm_buddy_free(struct drm_buddy *mm, if (!drm_buddy_block_is_free(buddy)) break; - if (drm_buddy_block_is_clear(block) != - drm_buddy_block_is_clear(buddy)) - break; + if (!defrag) { + if (drm_buddy_block_is_clear(block) != + drm_buddy_block_is_clear(buddy)) + break; - if (drm_buddy_block_is_clear(block)) - mark_cleared(parent); + if (drm_buddy_block_is_clear(block)) + mark_cleared(parent); + } list_del(&buddy->link); @@ -304,7 +308,37 @@ static void __drm_buddy_free(struct drm_buddy *mm, block = parent; } + order = drm_buddy_block_order(block); mark_free(mm, block); + + return order; +} + +static void drm_buddy_defrag(struct drm_buddy *mm, + unsigned int min_order) +{ + struct drm_buddy_block *block; + struct list_head *list; + unsigned int order; + int i; + + if (min_order > mm->max_order) + return; + + for (i = min_order - 1; i >= 0; i--) { + list = &mm->free_list[i]; + if (list_empty(list)) + continue; + + list_for_each_entry_reverse(block, list, link) { + if (!block->parent) + continue; + + order = __drm_buddy_free(mm, block, 1); + if (order >= min_order) + return; + } + } } /** @@ -321,7 +355,7 @@ void drm_buddy_free_block(struct drm_buddy *mm, if (drm_buddy_block_is_clear(block)) mm->clear_avail += drm_buddy_block_size(mm, block); - __drm_buddy_free(mm, block); + __drm_buddy_free(mm, block, 0); } EXPORT_SYMBOL(drm_buddy_free_block); @@ -447,7 +481,7 @@ __alloc_range_bias(struct drm_buddy *mm, if (buddy && (drm_buddy_block_is_free(block) && drm_buddy_block_is_free(buddy))) - __drm_buddy_free(mm, block); + __drm_buddy_free(mm, block, 0); return ERR_PTR(err); } @@ -577,7 +611,7 @@ alloc_from_freelist(struct drm_buddy *mm, err_undo: if (tmp != order) - __drm_buddy_free(mm, block); + __drm_buddy_free(mm, block, 0); return ERR_PTR(err); } @@ -657,7 +691,7 @@ static int __alloc_range(struct drm_buddy *mm, if (buddy && (drm_buddy_block_is_free(block) && drm_buddy_block_is_free(buddy))) - __drm_buddy_free(mm, block); + __drm_buddy_free(mm, block, 0); err_free: if (err == -ENOSPC && total_allocated_on_err) { @@ -903,7 +937,17 @@ int drm_buddy_alloc_blocks(struct drm_buddy *mm, if (order-- == min_order) { if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION && - !(flags & DRM_BUDDY_RANGE_ALLOCATION)) + !(flags & DRM_BUDDY_RANGE_ALLOCATION)) { + /* + * Defragment the freelist + */ + drm_buddy_defrag(mm, min_order); + /* + * Try contiguous block allocation again! + */ + block = alloc_from_freelist(mm, min_order, flags); + if (!IS_ERR(block)) + break; /* * Try contiguous block allocation through * try harder method @@ -912,6 +956,8 @@ int drm_buddy_alloc_blocks(struct drm_buddy *mm, original_size, original_min_size, blocks); + } + err = -ENOSPC; goto err_free; } -- 2.25.1