Thank you for your contribution! Sashiko AI review found 1 potential issue(s) 
to consider:
- [Medium] gpu/buddy: spurious -ENOSPC in __alloc_contig_try_harder() for range 
allocs
--

commit 14acba1e552d60bba9eee2e544118f0040222647
Author: Arunpravin Paneer Selvam <[email protected]>

gpu/buddy: add range-restricted contiguous allocation fallback

This commit extends __alloc_contig_try_harder() with a [range_start,
range_end) window and routes range-restricted contiguous allocations through
it when no single naturally aligned power-of-two block is available. It also
adds KUnit tests covering fragmented range-restricted contiguous allocations.

> diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c
> index 8eb5835d6944c..693176ee3daf9 100644
> --- a/drivers/gpu/buddy.c
> +++ b/drivers/gpu/buddy.c
> @@ -1723,43 +1723,32 @@ static int __gpu_buddy_alloc_range(struct gpu_buddy 
> *mm,
>                            blocks, total_allocated_on_err);
>  }
>  
> -static int __alloc_contig_aligned_retry(struct gpu_buddy *mm,
> -                                     u64 unaligned_offset,
> -                                     u64 size,
> -                                     u64 min_block_size,
> -                                     unsigned long flags,
> -                                     struct list_head *blocks)
> -{
> -     u64 aligned_offset = round_down(unaligned_offset, min_block_size);
> -
> -     return __gpu_buddy_alloc_range(mm, aligned_offset, size, flags,
> -                                    NULL, blocks);
> -}
> -
>  static int __alloc_contig_try_harder(struct gpu_buddy *mm,
> +                                  u64 range_start, u64 range_end,
>                                    u64 size,
>                                    u64 min_block_size,
>                                    unsigned long flags,
>                                    struct list_head *blocks)
>  {
> -     u64 rhs_offset, lhs_offset, filled;
> +     u64 rhs_offset, lhs_offset, filled, aligned;
>       struct gpu_buddy_block *block;
> +     unsigned int min_order, order;
>       struct rb_root *root;
>       struct rb_node *iter;
>       unsigned long pages;
> -     unsigned int order;
>       u64 modify_size;
>       int err;
>  
> +     min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
> +
>       modify_size = rounddown_pow_of_two(size);
>       pages = modify_size >> ilog2(mm->chunk_size);
>       order = fls(pages) - 1;
> -     if (order == 0)
> -             return -ENOSPC;
>  
> +retry_order:
>       root = &mm->free_tree[order];
>       if (RB_EMPTY_ROOT(root))
> -             return -ENOSPC;
> +             goto next_order;
>  
>       iter = rb_last(root);
>       while (iter) {
> @@ -1767,25 +1756,48 @@ static int __alloc_contig_try_harder(struct gpu_buddy 
> *mm,
>  
>               rhs_offset = gpu_buddy_block_offset(block);
>  
> +             if (rhs_offset >= range_end) {
> +                     iter = rb_prev(iter);
> +                     continue;
> +             }
> +             if (rhs_offset < range_start)
> +                     break;
> +
>               /* Allocate blocks traversing RHS */
>               err =  __gpu_buddy_alloc_range(mm, rhs_offset, size,
>                                              flags, &filled, blocks);
>               if (err && err != -ENOSPC)
>                       return err;
> -             if (!err && IS_ALIGNED(rhs_offset, min_block_size))
> +
> +             if (!err)
> +                     filled = size;
> +
> +             /* Clamp a full or partial fill that crosses range_end. */
> +             if (rhs_offset + filled > range_end) {
> +                     gpu_buddy_free_list_internal(mm, blocks);
> +                     filled = range_end - rhs_offset;
> +                     err = -ENOSPC;
> +             }
> +
> +             if (!err && IS_ALIGNED(rhs_offset, min_block_size) &&
> +                 rhs_offset >= range_start)
>                       return 0;
> +
>               if (!err) {
>                       /* Allocate the unaligned RHS offset using round_down */
>                       gpu_buddy_free_list_internal(mm, blocks);
> -                     err = __alloc_contig_aligned_retry(mm, rhs_offset,
> -                                                        size,
> -                                                        min_block_size,
> -                                                        flags, blocks);
> -                     if (!err)
> -                             return 0;
> -                     if (err != -ENOSPC) {
> -                             gpu_buddy_free_list_internal(mm, blocks);
> -                             return err;
> +
> +                     aligned = round_down(rhs_offset, min_block_size);
> +                     if (aligned >= range_start &&
> +                         aligned + size <= range_end) {
> +                             err = __gpu_buddy_alloc_range(mm, aligned, size,
> +                                                           flags, NULL, 
> blocks);
> +                             if (!err)
> +                                     return 0;
> +                             if (err != -ENOSPC) {
> +                                     gpu_buddy_free_list_internal(mm, 
> blocks);
> +                                     return err;
> +                             }
>                       }
>                       goto next;
>               }
> @@ -1797,21 +1809,73 @@ static int __alloc_contig_try_harder(struct gpu_buddy 
> *mm,
>  
>               /* Allocate the unaligned LHS offset using round_down */
>               gpu_buddy_free_list_internal(mm, blocks);
> -             err = __alloc_contig_aligned_retry(mm, lhs_offset,
> -                                                size,
> -                                                min_block_size,
> -                                                flags, blocks);
> -             if (!err)
> -                     return 0;
> -             if (err != -ENOSPC) {
> -                     gpu_buddy_free_list_internal(mm, blocks);
> -                     return err;
> +
> +             aligned = round_down(lhs_offset, min_block_size);
> +             if (aligned >= range_start && aligned + size <= range_end) {
> +                     err = __gpu_buddy_alloc_range(mm, aligned, size,
> +                                                   flags, NULL, blocks);
> +                     if (!err)
> +                             return 0;
> +                     if (err != -ENOSPC) {
> +                             gpu_buddy_free_list_internal(mm, blocks);
> +                             return err;
> +                     }
>               }
>  next:
>               gpu_buddy_free_list_internal(mm, blocks);
>               iter = rb_prev(iter);
>       }
>  
> +next_order:
> +     if (order > min_order) {
> +             order--;
> +             goto retry_order;
> +     }
> +
> +     /* A non-range request would already have used a larger free block. */
> +     if (!(flags & GPU_BUDDY_RANGE_ALLOCATION))
> +             return -ENOSPC;
> +
> +     /*
> +      * Last resort: the span may sit inside a larger coalesced free block
> +      * that the anchored search above never visits. Walk the higher-order
> +      * free trees (bounded by the number of free blocks, not by the window
> +      * size) and carve the highest aligned span that fits inside each block
> +      * overlapping the window.
> +      */
> +     for (order = fls(modify_size >> ilog2(mm->chunk_size));
> +          order <= mm->max_order; order++) {
> +             for (iter = rb_last(&mm->free_tree[order]); iter;
> +                  iter = rb_prev(iter)) {
> +                     u64 low, high;
> +
> +                     block = rbtree_get_free_block(iter);
> +                     low = max(gpu_buddy_block_offset(block), range_start);
> +                     high = min(gpu_buddy_block_offset(block) +
> +                                gpu_buddy_block_size(mm, block), range_end);
> +
> +                     if (gpu_buddy_block_offset(block) >= range_end)
> +                             continue;
> +                     if (gpu_buddy_block_offset(block) +
> +                         gpu_buddy_block_size(mm, block) <= range_start)
> +                             break;
> +                     if (high < low + size)
> +                             continue;
> +
> +                     aligned = round_down(high - size, min_block_size);
> +                     if (aligned < low)
> +                             continue;
> +
> +                     err = __gpu_buddy_alloc_range(mm, aligned, size, flags,
> +                                                   NULL, blocks);
> +                     if (!err)
> +                             return 0;
> +                     gpu_buddy_free_list_internal(mm, blocks);
> +                     if (err != -ENOSPC)
> +                             return err;
> +             }
> +     }
> +
>       return -ENOSPC;
>  }

[Severity: Medium]
Can __alloc_contig_try_harder() in drivers/gpu/buddy.c still return a
spurious -ENOSPC when gpu_buddy_alloc_blocks() falls back to it for a
range-restricted or min_block_size-aligned contiguous allocation, if the
valid contiguous span straddles a higher-order block boundary, a block
starting below range_start, or a trailing block smaller than min_block_size?

In the first pass, the anchored search starts at
order = fls(pages) - 1, breaks out of the free_tree walk as soon as
rhs_offset < range_start:

                if (rhs_offset < range_start)
                        break;

and stops descending at min_order:

        if (order > min_order) {
                order--;
                goto retry_order;
        }

In the second pass, the higher-order free_tree walk clips each individual
free block to [range_start, range_end) via [low, high) and requires the
entire request to fit inside that single block's clipped window:

                        if (high < low + size)
                                continue;

                        aligned = round_down(high - size, min_block_size);
                        if (aligned < low)
                                continue;

Because the second pass only tests placements contained within a single
higher-order block and never stitches across adjacent free blocks, it looks
like valid contiguous placements are missed in three cases:

1. A span straddling two higher-order blocks. For example, with chunk_size
of 4K, if [16K, 32K) is free at order 2 and [32K, 64K) is free at order 3, a
request for size = 12K, min_block_size = 4K in [24K, 40K) finds free_tree[1]
and free_tree[0] empty in the first pass. The second pass clips [16K, 32K)
to [24K, 32K) (8K < 12K) and [32K, 64K) to [32K, 40K) (8K < 12K), skipping
both and returning -ENOSPC even though the entire [24K, 40K) window is free.

2. A span straddling a block that starts below range_start and an adjacent
higher-order block. With the same free blocks [16K, 32K) at order 2 and
[32K, 64K) at order 3, a request for size = 20K, min_block_size = 4K in
[20K, 44K) starts at order 2, sees rhs_offset = 16K < range_start (20K), and
breaks without probing from range_start. The second pass clips [32K, 64K) to
[32K, 44K) (12K < 20K) and skips it, returning -ENOSPC when [20K, 44K) is
completely free.

3. A span straddling a higher-order block and a trailing free block with
order < min_order. Since min_block_size constrains the start alignment of
the contiguous span rather than the size of trailing blocks stitched into
it, if [0, 16K) is free at order 2 and [16K, 20K) is free at order 0, a
request for size = 12K, min_block_size = 8K (min_order = 1) in [8K, 24K)
stops descending at min_order = 1 without visiting [16K, 20K) at order 0,
and the second pass skips [0, 16K) because [8K, 16K) is only 8K (< 12K),
missing the valid 8K-aligned placement at [8K, 20K).

Should the anchored search instead start from mm->max_order down to order 0,
and clamp blocks that start below range_start (with rhs_offset + block_size >
range_start) to start probing at range_start?

[ ... ]

-- 
Sashiko AI review ยท 
https://sashiko.dev/#/patchset/[email protected]?part=2

Reply via email to