Re: [PATCH i-g-t v6 8/9] tests/intel/xe_madvise: Add per-vma-tracking subtest

Yadav, Arvind
2026-04-07T08:50:21Z

On 07-04-2026 12:50, Sharma, Nishit wrote:
>
> On 3/25/2026 6:14 PM, Arvind Yadav wrote:
>> This test validates that purgeable state is tracked per-VMA when a
>> single BO is bound in multiple VMs. The test creates one BO shared
>> across two VMs at different virtual addresses. It verifies that marking
>> only one VMA as DONTNEED does not make the BO purgeable, but marking
>> both VMAs as DONTNEED allows the kernel to purge the shared BO. This
>> ensures proper per-VMA tracking for shared memory.
>>
>> v4:
>>    -  The comment now clarifies that triggering pressure. (Nishit)
>>
>> v6:
>>    - Move resource cleanup before igt_skip() to avoid leaking VM and BO
>>      handles on platforms where memory pressure cannot be induced;
>> replace
>>      igt_assert_eq(retained, 0) with a graceful skip. (Nishit)
>>
>> Cc: Matthew Brost <matthew.brost@intel.com>
>> Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com>
>> Cc: Himal Prasad Ghimiray <himal.prasad.ghimiray@intel.com>
>> Cc: Nishit Sharma <nishit.sharma@intel.com>
>> Cc: Pravalika Gurram <pravalika.gurram@intel.com>
>> Signed-off-by: Arvind Yadav <arvind.yadav@intel.com>
>> ---
>>   tests/intel/xe_madvise.c | 114 +++++++++++++++++++++++++++++++++++++++
>>   1 file changed, 114 insertions(+)
>>
>> diff --git a/tests/intel/xe_madvise.c b/tests/intel/xe_madvise.c
>> index 28ea81442..6c0a563d8 100644
>> --- a/tests/intel/xe_madvise.c
>> +++ b/tests/intel/xe_madvise.c
>> @@ -28,6 +28,7 @@ static bool xe_has_purgeable_support(int fd)
>>     /* Purgeable test constants */
>>   #define PURGEABLE_ADDR        0x1a0000
>> +#define PURGEABLE_ADDR2        0x2b0000
>>   #define PURGEABLE_BATCH_ADDR    0x3c0000
>>   #define PURGEABLE_BO_SIZE    4096
>>   #define PURGEABLE_FENCE_VAL    0xbeef
>> @@ -267,6 +268,58 @@ static void purgeable_setup_batch_and_data(int
>> fd, uint32_t *vm,
>>       xe_wait_ufence(fd, &vm_sync, PURGEABLE_FENCE_VAL, 0,
>> NSEC_PER_SEC);
>>   }
>>   +/**
>> + * purgeable_setup_two_vms_shared_bo - Setup two VMs with one shared BO
>> + * @fd: DRM file descriptor
>> + * @vm1: Output first VM handle
>> + * @vm2: Output second VM handle
>> + * @bo: Output shared BO handle
>> + * @addr1: Virtual address in VM1
>> + * @addr2: Virtual address in VM2
>> + * @size: Size of the BO
>> + * @use_scratch: Whether to use scratch page flag for VMs
>> + *
>> + * Helper to create two VMs and bind one shared BO in both VMs.
>> + * Returns mapped pointer to the BO.
>> + */
>> +static void *purgeable_setup_two_vms_shared_bo(int fd, uint32_t
>> *vm1, uint32_t *vm2,
>> +                           uint32_t *bo, uint64_t addr1,
>> +                           uint64_t addr2, size_t size,
>> +                           bool use_scratch)
>> +{
>> +    struct drm_xe_sync sync = {
>> +        .type = DRM_XE_SYNC_TYPE_USER_FENCE,
>> +        .flags = DRM_XE_SYNC_FLAG_SIGNAL,
>> +        .timeline_value = 1,
>> +    };
>> +    uint64_t sync_val = 0;
>> +    void *map;
>> +
>> +    /* Create two VMs */
>> +    *vm1 = xe_vm_create(fd, use_scratch ?
>> DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE : 0, 0);
>> +    *vm2 = xe_vm_create(fd, use_scratch ?
>> DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE : 0, 0);
>> +
>> +    /* Create shared BO */
>> +    *bo = xe_bo_create(fd, 0, size, vram_if_possible(fd, 0),
>> +               DRM_XE_GEM_CREATE_FLAG_NEEDS_VISIBLE_VRAM);
>> +
>> +    map = xe_bo_map(fd, *bo, size);
>> +    memset(map, 0xAB, size);
>> +
>> +    /* Bind BO in VM1 */
>> +    sync.addr = to_user_pointer(&sync_val);
>> +    sync_val = 0;
>> +    xe_vm_bind_async(fd, *vm1, 0, *bo, 0, addr1, size, &sync, 1);
>> +    xe_wait_ufence(fd, &sync_val, 1, 0, NSEC_PER_SEC);
>> +
>> +    /* Bind BO in VM2 */
>> +    sync_val = 0;
>> +    xe_vm_bind_async(fd, *vm2, 0, *bo, 0, addr2, size, &sync, 1);
>> +    xe_wait_ufence(fd, &sync_val, 1, 0, NSEC_PER_SEC);
>> +
>> +    return map;
>> +}
>> +
>>   /**
>>    * SUBTEST: dontneed-before-mmap
>>    * Description: Mark BO as DONTNEED before mmap, verify mmap()
>> fails with -EBUSY
>> @@ -546,6 +599,61 @@ static void test_dontneed_after_exec(int fd,
>> struct drm_xe_engine_class_instance
>>       xe_vm_destroy(fd, vm);
>>   }
>>   +/**
>> + * SUBTEST: per-vma-tracking
>> + * Description: One BO in two VMs becomes purgeable only when both
>> VMAs are DONTNEED
>> + * Test category: functionality test
>> + */
>> +static void test_per_vma_tracking(int fd, struct
>> drm_xe_engine_class_instance *hwe)
>> +{
>> +    uint32_t bo, vm1, vm2;
>> +    uint64_t addr1 = PURGEABLE_ADDR;
>> +    uint64_t addr2 = PURGEABLE_ADDR2;
>> +    size_t bo_size = PURGEABLE_BO_SIZE;
>> +    uint32_t retained;
>> +    void *map;
>> +
>> +    map = purgeable_setup_two_vms_shared_bo(fd, &vm1, &vm2, &bo,
>> +                        addr1, addr2,
>> +                        bo_size, false);
>> +
>> +    /* Mark VMA1 as DONTNEED */
>> +    retained = xe_vm_madvise_purgeable(fd, vm1, addr1, bo_size,
>> +                       DRM_XE_VMA_PURGEABLE_STATE_DONTNEED);
>> +    igt_assert_eq(retained, 1);
>
> As per description  if VM1 is set to DONTNEED it'll not purge untill
> VM2 also set to DONTNEED. So retained = 1 is expected which also verifies
>
> BO is purged or not, if returned 0, So below VM1 WILLNEED check can be
> dropped.

The WILLNEED round‑trip is intentional. It checks that the state change
from WILLNEED (default) → DONTNEED → WILLNEED works correctly and that
the BO is retained during this process. This ensures the DONTNEED to
WILLNEED transition happens smoothly.
Only after that do we mark both VMAs as DONTNEED and confirm that
purging occurs when all VMAs agree. Removing the WILLNEED check would
miss coverage of this state‑transition path.
It will never return 0 because both VMAs must first be set to DONTNEED.

>
>> +
>> +    /* Verify BO NOT purgeable (VMA2 still WILLNEED) */
>> +    retained = xe_vm_madvise_purgeable(fd, vm1, addr1, bo_size,
>> +                       DRM_XE_VMA_PURGEABLE_STATE_WILLNEED);
>> +    igt_assert_eq(retained, 1);
>> +
>> +    /* Mark both VMAs as DONTNEED */
>> +    retained = xe_vm_madvise_purgeable(fd, vm1, addr1, bo_size,
>> +                       DRM_XE_VMA_PURGEABLE_STATE_DONTNEED);
>> +    igt_assert_eq(retained, 1);
>> +
>> +    retained = xe_vm_madvise_purgeable(fd, vm2, addr2, bo_size,
>> +                       DRM_XE_VMA_PURGEABLE_STATE_DONTNEED);
>> +    igt_assert_eq(retained, 1);
>> +
>> +    /*
>> +     * Trigger pressure and verify BO was purged.
>> +     * Using vm1 is sufficient since both VMAs are DONTNEED - kernel
>> can purge the BO.
>> +     */
>> +    trigger_memory_pressure(fd, vm1);
>> +
>> +    retained = xe_vm_madvise_purgeable(fd, vm1, addr1, bo_size,
>> +                       DRM_XE_VMA_PURGEABLE_STATE_WILLNEED);
> Expecting 0 (for purge) here, right? Can we put a check over here?

Yes, we are checking this below. After cleanup, we verify if (retained
!= 0) and skip.

Thanks,
Arvind

>> +    munmap(map, bo_size);
>> +    gem_close(fd, bo);
>> +    xe_vm_destroy(fd, vm1);
>> +    xe_vm_destroy(fd, vm2);
>> +
>> +    if (retained != 0)
>> +        igt_skip("Unable to induce purge on this platform/config");
>> +
>> +}
>> +
>>   int igt_main()
>>   {
>>       struct drm_xe_engine_class_instance *hwe;
>> @@ -588,6 +696,12 @@ int igt_main()
>>               break;
>>           }
>>   +    igt_subtest("per-vma-tracking")
>> +        xe_for_each_engine(fd, hwe) {
>> +            test_per_vma_tracking(fd, hwe);
>> +            break;
>> +        }
>> +
>>       igt_fixture() {
>>           xe_device_put(fd);
>>           drm_close_driver(fd);