From: Matthew Auld <matthew.auld@intel.com>
To: Arunpravin Paneer Selvam <arunpravin.paneerselvam@amd.com>,
dri-devel@lists.freedesktop.org, intel-gfx@lists.freedesktop.org,
intel-xe@lists.freedesktop.org, amd-gfx@lists.freedesktop.org
Cc: christian.koenig@amd.com, alexander.deucher@amd.com,
Anand.Raghavendra@amd.com
Subject: Re: [PATCH v2] gpu/buddy: add range-restricted contiguous allocation fallback
Date: Fri, 9 Oct 2026 11:17:37 +0100 [thread overview]
Message-ID: <e225564c-81ba-4ca3-8dbd-d848e02f4c6f@intel.com> (raw)
In-Reply-To: <b5f63c1e-589d-4f60-869e-bb85c9d2a554@amd.com>
On 09/10/2026 10:14, Arunpravin Paneer Selvam wrote:
>
>
> On 10/6/2026 3:27 PM, Matthew Auld wrote:
>> On 05/10/2026 15:14, Arunpravin Paneer Selvam wrote:
>>>
>>>
>>> On 10/1/2026 11:58 PM, Matthew Auld wrote:
>>>> On 30/09/2026 07:21, Arunpravin Paneer Selvam wrote:
>>>>> From: Arunpravin Paneer Selvam <Arunpravin.PaneerSelvam@amd.com>
>>>>>
>>>>> A range + contiguous allocation (e.g. a scanout FB confined to the
>>>>> CPU-visible VRAM aperture) rounds its size up to a power of two and
>>>>> requires a naturally aligned free block of that size; on a fragmented
>>>>> aperture no such aligned block may exist even though enough contiguous
>>>>> space is free, so the allocation fails with -ENOSPC.
>>>>>
>>>>> The non-range contiguous path already recovers from this via
>>>>> __alloc_contig_try_harder(), which stitches an exact-size span from
>>>>> smaller adjacent blocks, but that fallback was unreachable once
>>>>> GPU_BUDDY_RANGE_ALLOCATION was set. Give __alloc_contig_try_harder()
>>>>> a [range_start, range_end) window and route the range + contiguous
>>>>> case through it: each candidate placement is confined to the window
>>>>> and aligned to min_block_size. Non-range callers pass [0, mm->size),
>>>>> where the guards are no-ops, so the existing behaviour is unchanged.
>>>>> A KUnit regression test covers the fragmentation pattern.
>>>>>
>>>>> Resolves the igt@kms_plane@plane-panning-bottom-right@pipe-a/pipe-b
>>>>> regression.
>>>>>
>>>>> v2:
>>>>> - Drop the split-undo patch; the range-bias search always
>>>>> descends to
>>>>> an exact-order block or fails the split (already handled), so the
>>>>> extra undo was redundant. (Matthew)
>>>>> - Verified this fallback alone fixes the kms_plane regression.
>>>>>
>>>>> Fixes: 1ad5e807f716 ("gpu/buddy: replace dual-tree/force_merge with
>>>>> decoupled dirty tracker")
>>>>
>>>> Patch looks more like totally new functionally/feature, so the fixes
>>>> here is maybe unexpected? Did something in that fixes commit change
>>>> something such that the try_harder is now needed, but that needs
>>>> some expansion with bias + contig? Do we know exactly what changed
>>>> here?
>>> That commit removed __force_merge(), which previously helped recover
>>> larger contiguous allocations on demand. As a result, some range-
>>
>> __force_merge() got nuked, but IIRC I think that was essentially
>> because we now "force merge" on free, so shouldn't we get the ~same
>> result? Or is the fact that we force_merge() on every free giving a
>> different layout of pages, and with some very some specific allocation
>> pattern that difference subtly results in -ENOSPC, somehow?
> You are right - merge-on-free reclaims contiguity just like the old on-
> demand __force_merge() , so that is not the cause and the layout is
> effectively the same. The real change is where clear/dirty segregation
> lives: it used to be an inline guard inside __alloc_range_bias() , but
> the rework pulled it out into dirty_steer_window() , which only runs for
> non-range allocations. So range (aperture) requests now go through
> __alloc_range_bias() with no clear/dirty guard at all - clear and dirty
> allocations interleave and fragment the aperture until no large same-
> class run survives for a 4K scanout pin (-> -ENOSPC). Restoring that
> two-pass guard is the real fix.
Ahh, yeah that makes more sense now :)
>
> I have accordingly moved the Fixes: tag onto that guard-restore patch,
> and kept the range support in __alloc_contig_try_harder() as a new
> feature - it still helps, but it is a safety-net fallback that should
> land on top, not as the root-cause fix.
>
> Regards,
> Arun.
>>
>>> restricted contiguous allocation requests
>>> that used to succeed now fail with -ENOSPC (the kms_plane
>>> regression). This patch restores that capability through the
>>> try_harder() stitching logic, so the Fixes: tag reflects a
>>> regression introduced by that commit rather than new functionality.
>>>>
>>>>> Assisted-by: Claude:claude-opus-4-8
>>>>
>>>> Assisted-by: LLM
>>>>
>>>>> Cc: Matthew Auld <matthew.auld@intel.com>
>>>>> Cc: Christian König <christian.koenig@amd.com>
>>>>> Signed-off-by: Arunpravin Paneer Selvam
>>>>> <Arunpravin.PaneerSelvam@amd.com>
>>>>> ---
>>>>> drivers/gpu/buddy.c | 88 +++++++++++++++
>>>>> +--------------
>>>>> drivers/gpu/tests/gpu_buddy_test.c | 83 ++++++++++++++++++++++++++--
>>>>> 2 files changed, 126 insertions(+), 45 deletions(-)
>>>>>
>>>>> diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c
>>>>> index 2f2aaadafe35..5265e1f6a318 100644
>>>>> --- a/drivers/gpu/buddy.c
>>>>> +++ b/drivers/gpu/buddy.c
>>>>> @@ -1685,26 +1685,14 @@ 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;
>>>>> struct rb_root *root;
>>>>> struct rb_node *iter;
>>>>> @@ -1734,20 +1722,24 @@ static int __alloc_contig_try_harder(struct
>>>>> gpu_buddy *mm,
>>>>> flags, &filled, blocks);
>>>>> if (err && err != -ENOSPC)
>>>>> return err;
>>>>> - if (!err && IS_ALIGNED(rhs_offset, min_block_size))
>>>>> + if (!err && IS_ALIGNED(rhs_offset, min_block_size) &&
>>>>> + rhs_offset >= range_start && rhs_offset + size <=
>>>>> range_end)
>>>>> 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);
>>>>
>>>> Did you consider doing a bias-range for [start, end] using
>>>> min_block_size and using what it returns as the starting point,
>>>> extending left/right? If that fails advance start and try again?
>>>> Maybe what you have here is much better for the case you have in mind?
>>> I took a closer look at the alloc_range_bias approach. My
>>> understanding is that this would repeatedly bias within [start, end],
>>> use the returned min_block_size block as a starting point, and then
>>> extend left/right to build the requested run.
>>> While that should work, every failed attempt may require splitting
>>> higher-order blocks to obtain a min_block_size block, followed by a
>>> free/merge back when the extension fails.
>>> The free-tree descent evaluates the available starting points through
>>> a tree walk without any splitting or allocation just for enumeration,
>>> so I kept that approach.
>>> Please let me know if there is a case where alloc_range_bias would
>>> provide an advantage over the free-tree descent.
>>>
>>> While evaluating it, I found two issues in the current try_harder()
>>> implementation:
>>>
>>> 1. It only searches free_tree[size_order]. This is insufficient when
>>> min_block_size < size. For example, for a 128 KiB allocation
>>> (order-5) with min_block_size = 4 KiB, free_tree[5] may be empty
>>> while two adjacent order-4 (64 KiB) blocks covering [64 KiB, 128 KiB]
>>> and [128 KiB, 192 KiB] are free. These blocks still form a valid
>>> order-5-sized contiguous run, but they can never appear in
>>> free_tree[5] because they are not mergeable buddies. As a result, a
>>> search that only considers free_tree[5] will never find this
>>> placement. v3 addresses this by walking the free tree from the
>>> requested size order down to min_order, while reusing the existing
>>> extend/stitch logic and min_block_size alignment checks unchanged.
>>>
>>> 2. The search is not restricted to the requested range. v3 confines
>>> the walk to [range_start, range_end], skipping candidates at or above
>>> range_end and stopping once the walk falls below range_start.
>>> This ensures that range-constrained allocations only consider free
>>> blocks within the requested window and avoids performing allocation/
>>> free attempts on blocks outside the specified range.
>>> For non-range callers, the effective window remains [0, mm->size], so
>>> the behavior is unchanged.
>>>
>>> Regards,
>>> Arun.
>>>>
>>>>> + if (!err)
>>>>> + return 0;
>>>>> + if (err != -ENOSPC) {
>>>>> + gpu_buddy_free_list_internal(mm, blocks);
>>>>> + return err;
>>>>> + }
>>>>> }
>>>>> goto next;
>>>>> }
>>>>> @@ -1759,15 +1751,17 @@ 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);
>>>>> @@ -2021,11 +2015,18 @@ int gpu_buddy_alloc_blocks(struct gpu_buddy
>>>>> *mm,
>>>>> min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
>>>>> if (order > mm->max_order || size > mm->size) {
>>>>> - if ((flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION) &&
>>>>> - !(flags & GPU_BUDDY_RANGE_ALLOCATION))
>>>>> - return __alloc_contig_try_harder(mm, original_size,
>>>>> + if (flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION) {
>>>>> + u64 range_start, range_end;
>>>>> +
>>>>> + range_start = (flags & GPU_BUDDY_RANGE_ALLOCATION) ?
>>>>> start : 0;
>>>>> + range_end = (flags & GPU_BUDDY_RANGE_ALLOCATION) ?
>>>>> end : mm->size;
>>>>> +
>>>>> + return __alloc_contig_try_harder(mm, range_start,
>>>>> + range_end,
>>>>> + original_size,
>>>>> original_min_size,
>>>>> flags, blocks);
>>>>> + }
>>>>> return -EINVAL;
>>>>> }
>>>>> @@ -2058,9 +2059,14 @@ int gpu_buddy_alloc_blocks(struct gpu_buddy
>>>>> *mm,
>>>>> * Try contiguous block allocation through
>>>>> * try harder method.
>>>>> */
>>>>> - if (flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION &&
>>>>> - !(flags & GPU_BUDDY_RANGE_ALLOCATION)) {
>>>>> - err = __alloc_contig_try_harder(mm,
>>>>> + if (flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION) {
>>>>> + u64 range_start, range_end;
>>>>> +
>>>>> + range_start = (flags &
>>>>> GPU_BUDDY_RANGE_ALLOCATION) ? start : 0;
>>>>> + range_end = (flags & GPU_BUDDY_RANGE_ALLOCATION) ?
>>>>> end : mm->size;
>>>>> +
>>>>> + err = __alloc_contig_try_harder(mm, range_start,
>>>>> + range_end,
>>>>> original_size,
>>>>> original_min_size,
>>>>> flags,
>>>>> @@ -2068,9 +2074,9 @@ int gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
>>>>> if (!err)
>>>>> return 0;
>>>>> if (err != -ENOSPC)
>>>>> - return err;
>>>>> - goto err_free;
>>>>> + goto err_free;
>>>>> }
>>>>> +
>>>>> err = -ENOSPC;
>>>>> goto err_free;
>>>>> } while (1);
>>>>> diff --git a/drivers/gpu/tests/gpu_buddy_test.c b/drivers/gpu/
>>>>> tests/ gpu_buddy_test.c
>>>>> index b75d32ca6ca0..2c445870b808 100644
>>>>> --- a/drivers/gpu/tests/gpu_buddy_test.c
>>>>> +++ b/drivers/gpu/tests/gpu_buddy_test.c
>>>>> @@ -1251,6 +1251,77 @@ static void
>>>>> gpu_test_buddy_alloc_contiguous(struct kunit *test)
>>>>> gpu_buddy_fini(&mm);
>>>>> }
>>>>> +static void gpu_test_buddy_alloc_range_contiguous(struct kunit
>>>>> *test)
>>>>> +{
>>>>> + const unsigned long ps = SZ_4K, mm_size = 16 * ps;
>>>>> + const unsigned long range_end = 8 * ps;
>>>>> + struct gpu_buddy_block *block, *prev;
>>>>> + LIST_HEAD(allocated);
>>>>> + struct gpu_buddy mm;
>>>>> + LIST_HEAD(pin_lo);
>>>>> + LIST_HEAD(pin_hi);
>>>>> + u64 total;
>>>>> +
>>>>> + KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
>>>>> + "buddy_init failed\n");
>>>>> +
>>>>> + /*
>>>>> + * Idea is to confine the test to the sub-range [0, 32K),
>>>>> which a 12K
>>>>> + * contiguous request (rounded up to 16K) splits into two
>>>>> naturally
>>>>> + * aligned 16K slots: [0, 16K) and [16K, 32K). We pin the
>>>>> first 4K page
>>>>> + * of each slot ([0, 4K) and [16K, 20K)) so that neither
>>>>> aligned slot
>>>>> + * can satisfy the rounded-up 16K allocation, yet the freed
>>>>> remainder
>>>>> + * still leaves a contiguous 12K hole at offset 4K, which is
>>>>> page-aligned
>>>>> + * but not 16K-aligned. A 12K contiguous+range allocation must
>>>>> therefore
>>>>> + * fall back to stitching that span instead of returning -ENOSPC.
>>>>> + */
>>>>> + KUNIT_ASSERT_FALSE_MSG(test,
>>>>> + gpu_buddy_alloc_blocks(&mm, 0, ps, ps, ps,
>>>>> + &pin_lo, 0),
>>>>> + "failed to pin low page\n");
>>>>> + KUNIT_ASSERT_FALSE_MSG(test,
>>>>> + gpu_buddy_alloc_blocks(&mm, 4 * ps, 5 * ps, ps,
>>>>> + ps, &pin_hi, 0),
>>>>> + "failed to pin high page\n");
>>>>> +
>>>>> + /* No aligned 16K block is free; the range-aware fallback must
>>>>> stitch
>>>>> + * the unaligned [ps, 4*ps) hole instead of returning -ENOSPC.
>>>>> + */
>>>>> + KUNIT_ASSERT_FALSE_MSG(test,
>>>>> + gpu_buddy_alloc_blocks(&mm, 0, range_end, 3 * ps,
>>>>> + ps, &allocated,
>>>>> + GPU_BUDDY_CONTIGUOUS_ALLOCATION |
>>>>> + GPU_BUDDY_RANGE_ALLOCATION),
>>>>> + "range-restricted contiguous alloc failed\n");
>>>>> +
>>>>> + /* The result must be exactly 3*ps, contiguous, and inside the
>>>>> range. */
>>>>> + total = 0;
>>>>> + prev = NULL;
>>>>> + list_for_each_entry(block, &allocated, link) {
>>>>> + u64 offset = gpu_buddy_block_offset(block);
>>>>> + u64 bsize = gpu_buddy_block_size(&mm, block);
>>>>> +
>>>>> + KUNIT_EXPECT_TRUE_MSG(test, offset + bsize <= range_end,
>>>>> + "block [%llx, %llx) outside range\n",
>>>>> + offset, offset + bsize);
>>>>> + if (prev)
>>>>> + KUNIT_EXPECT_EQ_MSG(test,
>>>>> + gpu_buddy_block_offset(prev) +
>>>>> + gpu_buddy_block_size(&mm, prev),
>>>>> + offset,
>>>>> + "block at %llx not contiguous\n",
>>>>> + offset);
>>>>> + prev = block;
>>>>> + total += bsize;
>>>>> + }
>>>>> + KUNIT_EXPECT_EQ(test, total, 3 * ps);
>>>>> +
>>>>> + gpu_buddy_free_list(&mm, &allocated, 0);
>>>>> + gpu_buddy_free_list(&mm, &pin_lo, 0);
>>>>> + gpu_buddy_free_list(&mm, &pin_hi, 0);
>>>>> + gpu_buddy_fini(&mm);
>>>>> +}
>>>>> +
>>>>> static void gpu_test_buddy_alloc_pathological(struct kunit *test)
>>>>> {
>>>>> u64 mm_size, size, start = 0;
>>>>> @@ -1534,10 +1605,13 @@ static void
>>>>> gpu_test_buddy_alloc_exceeds_max_order(struct kunit *test)
>>>>> GPU_BUDDY_RANGE_ALLOCATION);
>>>>> KUNIT_EXPECT_EQ(test, err, -EINVAL);
>>>>> - /* CONTIGUOUS + RANGE should return -EINVAL (no try_harder
>>>>> for RANGE) */
>>>>> - err = gpu_buddy_alloc_blocks(&mm, 0, mm_size, size, SZ_4K,
>>>>> &blocks,
>>>>> - GPU_BUDDY_CONTIGUOUS_ALLOCATION |
>>>>> GPU_BUDDY_RANGE_ALLOCATION);
>>>>> - KUNIT_EXPECT_EQ(test, err, -EINVAL);
>>>>> + /* CONTIGUOUS + RANGE should succeed via the range-aware
>>>>> try_harder */
>>>>> + KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0,
>>>>> mm_size, size,
>>>>> + SZ_4K, &blocks,
>>>>> + GPU_BUDDY_CONTIGUOUS_ALLOCATION |
>>>>> + GPU_BUDDY_RANGE_ALLOCATION),
>>>>> + "range contiguous alloc hit an error
>>>>> size=%llu\n", size);
>>>>> + gpu_buddy_free_list(&mm, &blocks, 0);
>>>>> gpu_buddy_fini(&mm);
>>>>> }
>>>>> @@ -1603,6 +1677,7 @@ static struct kunit_case gpu_buddy_tests[] = {
>>>>> KUNIT_CASE(gpu_test_buddy_alloc_pessimistic),
>>>>> KUNIT_CASE(gpu_test_buddy_alloc_pathological),
>>>>> KUNIT_CASE(gpu_test_buddy_alloc_contiguous),
>>>>> + KUNIT_CASE(gpu_test_buddy_alloc_range_contiguous),
>>>>> KUNIT_CASE(gpu_test_buddy_alloc_clear),
>>>>> KUNIT_CASE(gpu_test_buddy_alloc_range),
>>>>> KUNIT_CASE(gpu_test_buddy_alloc_range_bias),
>>>>>
>>>>> base-commit: 744f262401cc2e1f3827496c72a47e072d31a852
>>>>
>>>
>>
>
prev parent reply other threads:[~2026-10-09 10:17 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-30 6:21 [PATCH v2] gpu/buddy: add range-restricted contiguous allocation fallback Arunpravin Paneer Selvam
2026-09-30 6:29 ` ✓ CI.KUnit: success for " Patchwork
2026-09-30 6:31 ` [PATCH v2] " sashiko-bot
2026-09-30 7:32 ` ✓ Xe.CI.BAT: success for " Patchwork
2026-09-30 8:00 ` ✓ i915.CI.BAT: " Patchwork
2026-09-30 11:53 ` ✗ Xe.CI.FULL: failure " Patchwork
2026-09-30 18:34 ` ✗ i915.CI.Full: " Patchwork
2026-10-01 7:06 ` [PATCH v2] " Arunpravin Paneer Selvam
2026-10-01 18:28 ` Matthew Auld
2026-10-05 14:14 ` Arunpravin Paneer Selvam
2026-10-06 9:57 ` Matthew Auld
2026-10-09 9:14 ` Arunpravin Paneer Selvam
2026-10-09 10:17 ` Matthew Auld [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=e225564c-81ba-4ca3-8dbd-d848e02f4c6f@intel.com \
--to=matthew.auld@intel.com \
--cc=Anand.Raghavendra@amd.com \
--cc=alexander.deucher@amd.com \
--cc=amd-gfx@lists.freedesktop.org \
--cc=arunpravin.paneerselvam@amd.com \
--cc=christian.koenig@amd.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=intel-gfx@lists.freedesktop.org \
--cc=intel-xe@lists.freedesktop.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.