[PATCH i-g-t v3 0/7] Madvise feature in SVM for Multi-GPU configs

STALE339d

Revision v3 of 18 in this series.

9 messages, 2 authors, 2025-11-05 · open the first message on its own page

[PATCH i-g-t v3 0/7] Madvise feature in SVM for Multi-GPU configs

From: <hidden>
Date: 2025-11-04 15:50:15

From: Nishit Sharma <redacted>

This patch series adds comprehensive SVM multi-GPU IGT test coverage for
madvise and prefetch functionality.

ver2:
- Test name changed in commits
- In patchwork v1 patch is missing due to last patch was not sent

ver3:
- In patch-7 tags were added and it was not sent on patchwork

Nishit Sharma (7):
  tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU cross-GPU memory
    access test
  tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU atomic operations
  tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU coherency test
  tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU performance test
  tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU fault handling test
  tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU simultaneous access
    test
  tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU conflicting madvise
    test

 lib/xe/xe_ioctl.c             |   40 +
 lib/xe/xe_ioctl.h             |    5 +
 tests/intel/xe_multi_gpusvm.c | 1379 +++++++++++++++++++++++++++++++++
 tests/intel/xe_multisvm.c     |   41 +-
 tests/meson.build             |    1 +
 5 files changed, 1428 insertions(+), 38 deletions(-)
 create mode 100644 tests/intel/xe_multi_gpusvm.c

-- 
2.48.1

[PATCH i-g-t v3 1/7] tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU cross-GPU memory access test

From: <hidden>
Date: 2025-11-04 15:50:14

From: Nishit Sharma <redacted>

his test allocates a buffer in SVM, writes data to it from GPU 1, and reads/verifies
the data from GPU 2. Optionally, the CPU also reads or modifies the buffer and both
GPUs verify the results, ensuring correct cross-GPU and CPU memory access in a
multi-GPU environment.

Signed-off-by: Nishit Sharma <redacted>
Acked-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
---
 lib/xe/xe_ioctl.c             |  40 +++++
 lib/xe/xe_ioctl.h             |   5 +
 tests/intel/xe_multi_gpusvm.c | 265 ++++++++++++++++++++++++++++++++++
 tests/intel/xe_multisvm.c     |  41 +-----
 tests/meson.build             |   1 +
 5 files changed, 314 insertions(+), 38 deletions(-)
 create mode 100644 tests/intel/xe_multi_gpusvm.c
diff --git a/lib/xe/xe_ioctl.c b/lib/xe/xe_ioctl.c
index 60c972407..52ac6f1b7 100644
--- a/lib/xe/xe_ioctl.c
+++ b/lib/xe/xe_ioctl.c
@@ -746,3 +746,43 @@ void xe_vm_madvise(int fd, uint32_t vm, uint64_t addr, uint64_t range,
 	igt_assert_eq(__xe_vm_madvise(fd, vm, addr, range, ext, type, op_val, policy,
 				      instance), 0);
 }
+
+#define	BIND_SYNC_VAL	0x686868
+void xe_vm_bind_lr_sync(int fd, uint32_t vm, uint32_t bo, uint64_t offset,
+			uint64_t addr, uint64_t size, uint32_t flags)
+{
+	volatile uint64_t *sync_addr = malloc(sizeof(*sync_addr));
+	struct drm_xe_sync sync = {
+		.flags = DRM_XE_SYNC_FLAG_SIGNAL,
+		.type = DRM_XE_SYNC_TYPE_USER_FENCE,
+		.addr = to_user_pointer((uint64_t *)sync_addr),
+		.timeline_value = BIND_SYNC_VAL,
+	};
+
+	igt_assert(!!sync_addr);
+	xe_vm_bind_async_flags(fd, vm, 0, bo, 0, addr, size, &sync, 1, flags);
+	if (*sync_addr != BIND_SYNC_VAL)
+		xe_wait_ufence(fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, 0, NSEC_PER_SEC * 10);
+	/* Only free if the wait succeeds */
+	free((void *)sync_addr);
+}
+
+void xe_vm_unbind_lr_sync(int fd, uint32_t vm, uint64_t offset,
+			  uint64_t addr, uint64_t size)
+{
+	volatile uint64_t *sync_addr = malloc(sizeof(*sync_addr));
+	struct drm_xe_sync sync = {
+		.flags = DRM_XE_SYNC_FLAG_SIGNAL,
+		.type = DRM_XE_SYNC_TYPE_USER_FENCE,
+		.addr = to_user_pointer((uint64_t *)sync_addr),
+		.timeline_value = BIND_SYNC_VAL,
+	};
+
+	igt_assert(!!sync_addr);
+	*sync_addr = 0;
+	xe_vm_unbind_async(fd, vm, 0, 0, addr, size, &sync, 1);
+	if (*sync_addr != BIND_SYNC_VAL)
+		xe_wait_ufence(fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, 0, NSEC_PER_SEC * 10);
+	free((void *)sync_addr);
+}
+
diff --git a/lib/xe/xe_ioctl.h b/lib/xe/xe_ioctl.h
index 5413b504e..4c90c5165 100644
--- a/lib/xe/xe_ioctl.h
+++ b/lib/xe/xe_ioctl.h
@@ -109,4 +109,9 @@ int xe_vm_vma_attrs(int fd, struct drm_xe_vm_query_mem_range_attr *vmas_attr,
 struct drm_xe_mem_range_attr
 *xe_vm_get_mem_attr_values_in_range(int fd, uint32_t vm, uint64_t start,
 				    uint64_t range, uint32_t *num_ranges);
+void xe_vm_bind_lr_sync(int fd, uint32_t vm, uint32_t bo,
+			uint64_t offset, uint64_t addr,
+			uint64_t size, uint32_t flags);
+void xe_vm_unbind_lr_sync(int fd, uint32_t vm, uint64_t offset,
+			  uint64_t addr, uint64_t size);
 #endif /* XE_IOCTL_H */
diff --git a/tests/intel/xe_multi_gpusvm.c b/tests/intel/xe_multi_gpusvm.c
new file mode 100644
index 000000000..a88b46323
--- /dev/null
+++ b/tests/intel/xe_multi_gpusvm.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2023 Intel Corporation
+ */
+
+#include <unistd.h>
+
+#include "drmtest.h"
+#include "igt.h"
+#include "igt_multigpu.h"
+
+#include "intel_blt.h"
+#include "intel_mocs.h"
+#include "intel_reg.h"
+
+#include "xe/xe_ioctl.h"
+#include "xe/xe_query.h"
+#include "xe/xe_util.h"
+
+/**
+ * TEST: Basic multi-gpu SVM testing
+ * Category: SVM
+ * Mega feature: Compute
+ * Sub-category: Compute tests
+ * Functionality: SVM p2p access, madvise and prefetch.
+ * Test category: functionality test
+ *
+ * SUBTEST: cross-gpu-mem-access
+ * Description:
+ *      This test creates two malloced regions, places the destination
+ *      region both remotely and locally and copies to it. Reads back to
+ *      system memory and checks the result.
+ *
+ */
+
+#define MAX_XE_REGIONS	8
+#define MAX_XE_GPUS 8
+#define NUM_LOOPS 1
+#define BATCH_SIZE(_fd) ALIGN(SZ_8K, xe_get_default_alignment(_fd))
+#define BIND_SYNC_VAL 0x686868
+#define EXEC_SYNC_VAL 0x676767
+#define COPY_SIZE SZ_64M
+
+struct xe_svm_gpu_info {
+        bool supports_faults;
+	int vram_regions[MAX_XE_REGIONS];
+	unsigned int num_regions;
+	unsigned int va_bits;
+	int fd;
+};
+
+static void open_pagemaps(int fd, struct xe_svm_gpu_info *info);
+
+static void batch_init(int fd, uint32_t vm, uint64_t src_addr,
+		       uint64_t dst_addr, uint64_t copy_size,
+		       uint32_t *bo, uint64_t *addr)
+{
+	uint32_t width = copy_size / 256;
+	uint32_t height = 1;
+	uint32_t batch_bo_size = BATCH_SIZE(fd);
+	uint32_t batch_bo;
+	uint64_t batch_addr;
+	void *batch;
+	uint32_t *cmd;
+	uint32_t mocs_index = intel_get_uc_mocs_index(fd);
+	int i = 0;
+
+	batch_bo = xe_bo_create(fd, vm, batch_bo_size, vram_if_possible(fd, 0), 0);
+	batch = xe_bo_map(fd, batch_bo, batch_bo_size);
+	cmd = (uint32_t *) batch;
+	cmd[i++] = MEM_COPY_CMD | (1 << 19);
+	cmd[i++] = width - 1;
+	cmd[i++] = height - 1;
+	cmd[i++] = width - 1;
+	cmd[i++] = width - 1;
+	cmd[i++] = src_addr & ((1UL << 32) - 1);
+	cmd[i++] = src_addr >> 32;
+	cmd[i++] = dst_addr & ((1UL << 32) - 1);
+	cmd[i++] = dst_addr >> 32;
+	cmd[i++] = mocs_index << XE2_MEM_COPY_MOCS_SHIFT | mocs_index;
+	cmd[i++] = MI_BATCH_BUFFER_END;
+	cmd[i++] = MI_BATCH_BUFFER_END;
+
+	batch_addr = to_user_pointer(batch);
+	/* Punch a gap in the SVM map where we map the batch_bo */
+	xe_vm_bind_lr_sync(fd, vm, batch_bo, 0, batch_addr, batch_bo_size, 0);
+	*bo = batch_bo;
+	*addr = batch_addr;
+}
+
+static void batch_fini(int fd, uint32_t vm, uint32_t bo, uint64_t addr)
+{
+        /* Unmap the batch bo by re-instating the SVM binding. */
+        xe_vm_bind_lr_sync(fd, vm, 0, 0, addr, BATCH_SIZE(fd),
+                           DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+        gem_close(fd, bo);
+}
+
+
+static void open_pagemaps(int fd, struct xe_svm_gpu_info *info)
+{
+	unsigned int count = 0;
+	uint64_t regions = all_memory_regions(fd);
+	uint32_t region;
+
+	xe_for_each_mem_region(fd, regions, region) {
+		if (XE_IS_VRAM_MEMORY_REGION(fd, region)) {
+			struct drm_xe_mem_region *mem_region =
+				xe_mem_region(fd, 1ull << (region - 1));
+			igt_assert(count < MAX_XE_REGIONS);
+			info->vram_regions[count++] = mem_region->instance;
+		}
+	}
+
+	info->num_regions = count;
+}
+
+static int get_device_info(struct xe_svm_gpu_info gpus[], int num_gpus)
+{
+	int cnt;
+	int xe;
+	int i;
+
+	for (i = 0, cnt = 0 && i < 128; cnt < num_gpus; i++) {
+		xe = __drm_open_driver_another(i, DRIVER_XE);
+		if (xe < 0)
+			break;
+
+		gpus[cnt].fd = xe;
+		cnt++;
+	}
+
+	return cnt;
+}
+
+static void
+copy_src_dst(struct xe_svm_gpu_info *gpu0,
+	     struct xe_svm_gpu_info *gpu1,
+	     struct drm_xe_engine_class_instance *eci)
+{
+	uint32_t vm[1];
+	uint32_t exec_queue[2];
+	uint32_t batch_bo;
+	void *copy_src, *copy_dst;
+	uint64_t batch_addr;
+	struct drm_xe_sync sync = {};
+	volatile uint64_t *sync_addr;
+
+	vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+	exec_queue[0] = xe_exec_queue_create(gpu0->fd, vm[0], eci, 0);
+	xe_vm_bind_lr_sync(gpu0->fd, vm[0], 0, 0, 0, 1ull <<  gpu0->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+	/* Allocate source and destination buffers */
+	copy_src = aligned_alloc(xe_get_default_alignment(gpu0->fd), SZ_64M);
+	igt_assert(copy_src);
+	copy_dst = aligned_alloc(xe_get_default_alignment(gpu1->fd), SZ_64M);
+	igt_assert(copy_dst);
+
+	/*
+	 * Initialize, map and bind the batch bo. Note that Xe doesn't seem to enjoy
+	 * batch buffer memory accessed over PCIe p2p.
+	 */
+	batch_init(gpu0->fd, vm[0], to_user_pointer(copy_src), to_user_pointer(copy_dst),
+			COPY_SIZE, &batch_bo, &batch_addr);
+
+	/* Fill the source with a pattern, clear the destination. */
+	memset(copy_src, 0x67, COPY_SIZE);
+	memset(copy_dst, 0x0, COPY_SIZE);
+
+	/* Place destination in an optionally remote location to test */
+	xe_vm_madvise(gpu0->fd, vm[0], to_user_pointer(copy_dst), COPY_SIZE, 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu1->fd, 0, gpu1->vram_regions[0]);
+
+	sync_addr = malloc(sizeof(*sync_addr));
+	igt_assert(!!sync_addr);
+	sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+	sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+	sync.addr = to_user_pointer((uint64_t *)sync_addr);
+	sync.timeline_value = BIND_SYNC_VAL;
+	*sync_addr = 0;
+
+	/* Prefetch half of destination */
+	xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, to_user_pointer(copy_dst),
+			     COPY_SIZE / 2, &sync, 1,
+			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+	if (*sync_addr != BIND_SYNC_VAL)
+		xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, exec_queue[0],
+				NSEC_PER_SEC * 10);
+	free((void *)sync_addr);
+
+	sync_addr = (void *)((char *)batch_addr + SZ_4K);
+	sync.addr = to_user_pointer((uint64_t *)sync_addr);
+	sync.timeline_value = EXEC_SYNC_VAL;
+	*sync_addr = 0;
+
+	/* Execute a GPU copy. */
+	xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr, &sync, 1);
+	if (*sync_addr != EXEC_SYNC_VAL)
+		xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[0],
+			       NSEC_PER_SEC * 10);
+
+	igt_assert(memcmp(copy_src, copy_dst, COPY_SIZE) == 0);
+
+	free(copy_dst);
+	free(copy_src);
+	munmap((void *)batch_addr, BATCH_SIZE(gpu0->fd));
+	batch_fini(gpu0->fd, vm[0], batch_bo, batch_addr);
+	xe_vm_unbind_lr_sync(gpu0->fd, vm[0], 0, 0, 1ull << gpu0->va_bits);
+	xe_exec_queue_destroy(gpu0->fd, exec_queue[0]);
+	xe_vm_destroy(gpu0->fd, vm[0]);
+}
+
+static void
+gpu_mem_access(struct xe_svm_gpu_info *src_gpu,
+	       struct xe_svm_gpu_info *dst_gpu,
+	       struct drm_xe_engine_class_instance *eci)
+{
+	igt_assert(src_gpu);
+	igt_assert(dst_gpu);
+
+	copy_src_dst(src_gpu, dst_gpu, eci);
+}
+
+igt_main
+{
+	struct xe_svm_gpu_info gpus[MAX_XE_GPUS];
+	struct xe_device *xe;
+	int gpu, gpu_cnt;
+
+	struct drm_xe_engine_class_instance eci = {
+                .engine_class = DRM_XE_ENGINE_CLASS_COPY,
+        };
+
+	igt_fixture {
+		gpu_cnt = get_device_info(gpus, ARRAY_SIZE(gpus));
+		igt_skip_on(gpu_cnt < 2);
+		
+		 for (gpu = 0; gpu < gpu_cnt; ++gpu) {
+			 igt_assert(gpu < MAX_XE_GPUS);
+
+			 open_pagemaps(gpus[gpu].fd, &gpus[gpu]);
+			 /* NOTE! inverted return value. */
+			 gpus[gpu].supports_faults = !xe_supports_faults(gpus[gpu].fd);
+			 fprintf(stderr, "GPU %u has %u VRAM regions%s, and %s SVM VMs.\n",
+				 gpu, gpus[gpu].num_regions,
+				 gpus[gpu].num_regions != 1 ? "s" : "",
+				 gpus[gpu].supports_faults ? "supports" : "doesn't support");
+
+			 xe = xe_device_get(gpus[gpu].fd);
+			 gpus[gpu].va_bits = xe->va_bits;
+		 }
+	}
+
+	igt_describe("gpu-gpu write-read");
+	igt_subtest("cross-gpu-mem-access")
+		gpu_mem_access(&gpus[0], &gpus[1], &eci);
+
+	igt_fixture {
+		int cnt;
+
+		for (cnt = 0; cnt < gpu_cnt; cnt++)
+			drm_close_driver(gpus[cnt].fd);
+	}
+}
diff --git a/tests/intel/xe_multisvm.c b/tests/intel/xe_multisvm.c
index a57b3d62a..d865a39f0 100644
--- a/tests/intel/xe_multisvm.c
+++ b/tests/intel/xe_multisvm.c
@@ -47,44 +47,6 @@ struct xe_svm_gpu_info {
 	int fd;
 };
 
-static void xe_vm_bind_lr_sync(int fd, uint32_t vm, uint32_t bo, uint64_t offset,
-			       uint64_t addr, uint64_t size, uint32_t flags)
-{
-	volatile uint64_t *sync_addr = malloc(sizeof(*sync_addr));
-	struct drm_xe_sync sync = {		
-		.flags = DRM_XE_SYNC_FLAG_SIGNAL,
-		.type = DRM_XE_SYNC_TYPE_USER_FENCE,
-		.addr = to_user_pointer((uint64_t *)sync_addr),
-		.timeline_value = BIND_SYNC_VAL,
-	};
-
-	igt_assert(!!sync_addr);
-	xe_vm_bind_async_flags(fd, vm, 0, bo, 0, addr, size, &sync, 1, flags);
-	if (*sync_addr != BIND_SYNC_VAL)
-		xe_wait_ufence(fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, 0, NSEC_PER_SEC * 10);
-	/* Only free if the wait succeeds */
-	free((void *)sync_addr);
-}
-
-static void xe_vm_unbind_lr_sync(int fd, uint32_t vm, uint64_t offset,
-				  uint64_t addr, uint64_t size)
-{
-	volatile uint64_t *sync_addr = malloc(sizeof(*sync_addr));
-	struct drm_xe_sync sync = {		
-		.flags = DRM_XE_SYNC_FLAG_SIGNAL,
-		.type = DRM_XE_SYNC_TYPE_USER_FENCE,
-		.addr = to_user_pointer((uint64_t *)sync_addr),
-		.timeline_value = BIND_SYNC_VAL,
-	};
-
-	igt_assert(!!sync_addr);
-	*sync_addr = 0;
-	xe_vm_unbind_async(fd, vm, 0, 0, addr, size, &sync, 1);
-	if (*sync_addr != BIND_SYNC_VAL)
-		xe_wait_ufence(fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, 0, NSEC_PER_SEC * 10);
-	free((void *)sync_addr);
-}
-
 static void batch_init(int fd, uint32_t vm, uint64_t src_addr, uint64_t dst_addr,
 		       uint64_t copy_size, uint32_t *bo, uint64_t *addr)
 {
@@ -217,9 +179,11 @@ static void open_pagemaps(int fd, struct xe_svm_gpu_info *info)
 				xe_mem_region(fd, 1ull << (region - 1));
 			igt_assert(count < MAX_XE_REGIONS);
 			info->vram_regions[count++] = mem_region->instance;
+			printf("mem_region->instance :%d\n", mem_region->instance);
 		}
 	}
 
+	printf("Total count :%d\n", count);
 	info->num_regions = count;
 }
 
@@ -282,6 +246,7 @@ igt_simple_main
 			struct xe_svm_gpu_info *peer_info = &infos[peer];
 			int region;
 
+			printf("fd :%d peer_info->fd :%d\n", fd, peer_info->fd);
 			for (region = 0; region < peer_info->num_regions; region++) {
 				test_copy(fd, &eci, peer_info->fd,
 					  peer_info->vram_regions[region]);
diff --git a/tests/meson.build b/tests/meson.build
index 530d33d05..fc386c3be 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -313,6 +313,7 @@ intel_xe_progs = [
 	'xe_mmap',
 	'xe_module_load',
 	'xe_multisvm',
+        'xe_multi_gpusvm',
 	'xe_noexec_ping_pong',
 	'xe_oa',
 	'xe_pat',
-- 
2.48.1

[PATCH i-g-t v3 3/7] tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU coherency test

From: <hidden>
Date: 2025-11-04 15:50:14

From: Nishit Sharma <redacted>

This test verifies memory coherency in a multi-GPU environment using SVM.
GPU 1 writes to a shared buffer, GPU 2 reads and checks for correct data
without explicit synchronization, and the test is repeated with CPU and
both GPUs to ensure consistent memory visibility across agents.

Signed-off-by: Nishit Sharma <redacted>
---
 tests/intel/xe_multi_gpusvm.c | 222 +++++++++++++++++++++++++++++++++-
 1 file changed, 220 insertions(+), 2 deletions(-)
diff --git a/tests/intel/xe_multi_gpusvm.c b/tests/intel/xe_multi_gpusvm.c
index 71bf01ba8..2c64de209 100644
--- a/tests/intel/xe_multi_gpusvm.c
+++ b/tests/intel/xe_multi_gpusvm.c
@@ -34,8 +34,13 @@
  * SUBTEST: atomic-inc-gpu-op
  * Description:
  * 	This test does atomic operation in multi-gpu by executing atomic
- *	operation on GPU1 and then atomic operation on GPU2 using same
- *	adress
+ * 	operation on GPU1 and then atomic operation on GPU2 using same
+ * 	adress
+ *
+ * SUBTEST: coherency-multi-gpu
+ * Description:
+ * 	This test checks coherency in multi-gpu by writing from GPU0
+ * 	reading from GPU1 and verify and repeating with CPU and both GPUs
  */
 
 #define MAX_XE_REGIONS	8
@@ -94,6 +99,35 @@ atomic_batch_init(int fd, uint32_t vm, uint64_t src_addr,
 	*addr = batch_addr;
 }
 
+static void
+store_dword_batch_init(int fd, uint32_t vm, uint64_t src_addr,
+                       uint32_t *bo, uint64_t *addr, int value)
+{
+        uint32_t batch_bo_size = BATCH_SIZE(fd);
+        uint32_t batch_bo;
+        uint64_t batch_addr;
+        void *batch;
+        uint32_t *cmd;
+        int i = 0;
+
+        batch_bo = xe_bo_create(fd, vm, batch_bo_size, vram_if_possible(fd, 0), 0);
+        batch = xe_bo_map(fd, batch_bo, batch_bo_size);
+        cmd = (uint32_t *) batch;
+
+        cmd[i++] = MI_STORE_DWORD_IMM_GEN4;
+        cmd[i++] = src_addr;
+        cmd[i++] = src_addr >> 32;
+        cmd[i++] = value;
+        cmd[i++] = MI_BATCH_BUFFER_END;
+
+        batch_addr = to_user_pointer(batch);
+
+        /* Punch a gap in the SVM map where we map the batch_bo */
+        xe_vm_bind_lr_sync(fd, vm, batch_bo, 0, batch_addr, batch_bo_size, 0);
+        *bo = batch_bo;
+        *addr = batch_addr;
+}
+
 static void batch_init(int fd, uint32_t vm, uint64_t src_addr,
 		       uint64_t dst_addr, uint64_t copy_size,
 		       uint32_t *bo, uint64_t *addr)
@@ -265,6 +299,172 @@ gpu_mem_access(struct xe_svm_gpu_info *src_gpu,
 	copy_src_dst(src_gpu, dst_gpu, eci);
 }
 
+static void
+coherency_test_multigpu(struct xe_svm_gpu_info *gpu0,
+			struct xe_svm_gpu_info *gpu1,
+			struct drm_xe_engine_class_instance *eci,
+			bool coh_fail_set,
+			bool prefetch_req)
+{
+        uint64_t addr;
+        uint32_t vm[2];
+        uint32_t exec_queue[2];
+        uint32_t batch_bo, batch1_bo[2];
+        uint64_t batch_addr, batch1_addr[2];
+        struct drm_xe_sync sync = {};
+        volatile uint64_t *sync_addr;
+        int value = 60;
+	uint64_t *data1;
+	void *copy_dst;
+
+        vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+        exec_queue[0] = xe_exec_queue_create(gpu0->fd, vm[0], eci, 0);
+        xe_vm_bind_lr_sync(gpu0->fd, vm[0], 0, 0, 0, 1ull <<  gpu0->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+        vm[1] = xe_vm_create(gpu1->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+        exec_queue[1] = xe_exec_queue_create(gpu1->fd, vm[1], eci, 0);
+        xe_vm_bind_lr_sync(gpu1->fd, vm[1], 0, 0, 0, 1ull <<  gpu1->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+        data1 = aligned_alloc(SZ_2M, SZ_4K);
+       	igt_assert(data1);
+	addr = to_user_pointer(data1);
+
+	copy_dst = aligned_alloc(SZ_2M, SZ_4K);
+	igt_assert(copy_dst);
+
+        store_dword_batch_init(gpu0->fd, vm[0], addr, &batch_bo, &batch_addr, value);
+
+        /* Place destination in GPU0 local memory location to test */
+        xe_vm_madvise(gpu0->fd, vm[0], addr, SZ_4K, 0,
+                      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+                      gpu0->fd, 0, gpu0->vram_regions[0]);
+
+        sync_addr = malloc(sizeof(*sync_addr));
+        igt_assert(!!sync_addr);
+        sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+        sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = BIND_SYNC_VAL;
+        *sync_addr = 0;
+
+	/* prefetch full buffer for GPU0 */
+	if (prefetch_req) {
+		xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, addr, SZ_4K, &sync, 1,
+				     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+		if (*sync_addr != BIND_SYNC_VAL)
+			xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, exec_queue[0],
+				       NSEC_PER_SEC * 10);
+	}
+        free((void *)sync_addr);
+
+        sync_addr = (void *)((char *)batch_addr + SZ_4K);
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = EXEC_SYNC_VAL;
+        *sync_addr = 0;
+
+        /* Execute STORE command on GPU0 */
+        xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr, &sync, 1);
+        if (*sync_addr != EXEC_SYNC_VAL)
+                xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[0],
+			       NSEC_PER_SEC * 10);
+
+        igt_assert_eq(*(uint64_t *)addr, value);
+
+	/* Creating batch for GPU1 using addr as Src which have value from GPU0 */
+	batch_init(gpu1->fd, vm[1], addr, to_user_pointer(copy_dst),
+			SZ_4K, &batch_bo, &batch_addr);
+
+        /* Place destination in GPU1 local memory location to test */
+        xe_vm_madvise(gpu1->fd, vm[1], addr, SZ_4K, 0,
+                      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+                      gpu1->fd, 0, gpu1->vram_regions[0]);
+
+        sync_addr = malloc(sizeof(*sync_addr));
+        igt_assert(!!sync_addr);
+        sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+        sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = BIND_SYNC_VAL;
+        *sync_addr = 0;
+
+	/* prefetch full buffer for GPU1 */
+	if (prefetch_req) {
+		xe_vm_prefetch_async(gpu1->fd, vm[1], 0, 0, addr,
+				     SZ_4K, &sync, 1,
+				     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+		if (*sync_addr != BIND_SYNC_VAL)
+			xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, exec_queue[1],
+				       NSEC_PER_SEC * 10);
+	}
+        free((void *)sync_addr);
+
+        sync_addr = (void *)((char *)batch_addr + SZ_4K);
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = EXEC_SYNC_VAL;
+        *sync_addr = 0;
+
+        /* Execute COPY command on GPU1 */
+        xe_exec_sync(gpu1->fd, exec_queue[1], batch_addr, &sync, 1);
+        if (*sync_addr != EXEC_SYNC_VAL)
+                xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[1],
+			       NSEC_PER_SEC * 10);
+
+        igt_assert_eq(*(uint64_t *)copy_dst, value);
+
+        /* CPU writes 10, memset set bytes no integer hence memset fills 4 bytes with 0x0A */
+        memset((void *)(uintptr_t)addr, 10, sizeof(int));
+        igt_assert_eq(*(uint64_t *)addr, 0x0A0A0A0A);
+
+	/* Coherency fail scenario */
+        store_dword_batch_init(gpu0->fd, vm[0], addr, &batch1_bo[0], &batch1_addr[0], value + 10);
+        store_dword_batch_init(gpu1->fd, vm[1], addr, &batch1_bo[1], &batch1_addr[1], value + 20);
+
+	if (coh_fail_set) {
+		igt_info("coherency fail impl\n");
+
+		sync_addr = (void *)((char *)batch1_addr[0] + SZ_4K);
+		sync.addr = to_user_pointer((uint64_t *)sync_addr);
+		sync.timeline_value = EXEC_SYNC_VAL;
+		*sync_addr = 0;
+
+		/* Execute STORE command on GPU1 */
+		xe_exec_sync(gpu0->fd, exec_queue[0], batch1_addr[0], &sync, 1);
+		if (*sync_addr != EXEC_SYNC_VAL)
+			xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[0],
+					NSEC_PER_SEC * 10);
+
+		sync_addr = (void *)((char *)batch1_addr[1] + SZ_4K);
+		sync.addr = to_user_pointer((uint64_t *)sync_addr);
+		sync.timeline_value = EXEC_SYNC_VAL;
+		*sync_addr = 0;
+
+		/* Execute STORE command on GPU2 */
+		xe_exec_sync(gpu1->fd, exec_queue[1], batch1_addr[1], &sync, 1);
+		if (*sync_addr != EXEC_SYNC_VAL)
+			xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[1],
+					NSEC_PER_SEC * 10);
+
+		igt_assert_f(*(uint64_t *)addr == (value + 10),
+				"GPU2 has overwritten value at addr\n");
+	}
+
+        /* CPU writes 11, memset set bytes no integer hence memset fills 4 bytes with 0x0B */
+        memset((void *)(uintptr_t)addr, 11, sizeof(int));
+        igt_assert_eq(*(uint64_t *)addr, 0x0B0B0B0B);
+
+        munmap((void *)batch_addr, BATCH_SIZE(gpu0->fd));
+        batch_fini(gpu0->fd, vm[0], batch_bo, batch_addr);
+        free(data1);
+
+        xe_vm_unbind_lr_sync(gpu0->fd, vm[0], 0, 0, 1ull << gpu0->va_bits);
+        xe_exec_queue_destroy(gpu0->fd, exec_queue[0]);
+        xe_vm_destroy(gpu0->fd, vm[0]);
+
+        xe_vm_unbind_lr_sync(gpu1->fd, vm[1], 0, 0, 1ull << gpu1->va_bits);
+        xe_exec_queue_destroy(gpu1->fd, exec_queue[1]);
+        xe_vm_destroy(gpu1->fd, vm[1]);
+}
+
 static void
 atomic_inc_op(struct xe_svm_gpu_info *gpu0,
 	      struct xe_svm_gpu_info *gpu1,
@@ -403,6 +603,19 @@ gpu_atomic_inc(struct xe_svm_gpu_info *src_gpu,
 	atomic_inc_op(src_gpu, dst_gpu, eci, prefetch_req);
 }
 
+static void
+gpu_coherecy_test(struct xe_svm_gpu_info *src_gpu,
+		  struct xe_svm_gpu_info *dst_gpu,
+		  struct drm_xe_engine_class_instance *eci,
+		  bool coh_fail,
+		  bool prefetch_req)
+{
+	igt_assert(src_gpu);
+	igt_assert(dst_gpu);
+
+        coherency_test_multigpu(src_gpu, dst_gpu, eci, coh_fail, prefetch_req);
+}
+
 igt_main
 {
 	struct xe_svm_gpu_info gpus[MAX_XE_GPUS];
@@ -442,6 +655,11 @@ igt_main
 		gpu_atomic_inc(&gpus[0], &gpus[1], &eci, 0);
 	}
 
+	igt_subtest("coherency-multi-gpu") {
+		gpu_coherecy_test(&gpus[0], &gpus[1], &eci, 0, 1);
+		gpu_coherecy_test(&gpus[0], &gpus[1], &eci, 1, 0);
+	}
+
 	igt_fixture {
 		int cnt;
 
-- 
2.48.1

[PATCH i-g-t v3 5/7] tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU fault handling test

From: <hidden>
Date: 2025-11-04 15:50:15

From: Nishit Sharma <redacted>

This test intentionally triggers page faults by accessing regions without
prefetch for both GPUs in a multi-GPU environment.

Signed-off-by: Nishit Sharma <redacted>
---
 tests/intel/xe_multi_gpusvm.c | 226 ++++++++++++++++++++++++++++++++++
 1 file changed, 226 insertions(+)
diff --git a/tests/intel/xe_multi_gpusvm.c b/tests/intel/xe_multi_gpusvm.c
index aa79c71bd..22b8ad95e 100644
--- a/tests/intel/xe_multi_gpusvm.c
+++ b/tests/intel/xe_multi_gpusvm.c
@@ -15,6 +15,7 @@
 
 #include "time.h"
 
+#include "xe/xe_gt.h"
 #include "xe/xe_ioctl.h"
 #include "xe/xe_query.h"
 #include "xe/xe_util.h"
@@ -54,6 +55,11 @@
  * Description:
  * 	This test measures and compares latency and bandwidth for buffer access
  * 	from CPU, local GPU, and remote GPU
+ *
+ * SUBTEST: pagefault-multi-gpu
+ * Description:
+ * 	This test intentionally triggers page faults by accessing unmapped SVM
+ * 	regions from both GPUs
  */
 
 #define MAX_XE_REGIONS	8
@@ -661,6 +667,212 @@ latency_test_multigpu(struct xe_svm_gpu_info *gpu0,
         xe_vm_destroy(gpu1->fd, vm[1]);
 }
 
+static void
+pagefault_test_multigpu(struct xe_svm_gpu_info *gpu0,
+			struct xe_svm_gpu_info *gpu1,
+			struct drm_xe_engine_class_instance *eci)
+{
+        uint64_t addr;
+        uint32_t vm[2];
+        uint32_t exec_queue[2];
+        uint32_t batch_bo;
+        uint8_t *copy_dst;
+        uint64_t batch_addr;
+        struct drm_xe_sync sync = {};
+        volatile uint64_t *sync_addr;
+        int value = 60, pf_count_1, pf_count_2;
+	int pf_count_madvise_1, pf_count_madvise_2;
+        int shared_val[4];
+        struct test_exec_data *data;
+	const char *pf_count_stat = "svm_pagefault_count";
+
+        vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+        exec_queue[0] = xe_exec_queue_create(gpu0->fd, vm[0], eci, 0);
+        xe_vm_bind_lr_sync(gpu0->fd, vm[0], 0, 0, 0, 1ull <<  gpu0->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+        vm[1] = xe_vm_create(gpu1->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+        exec_queue[1] = xe_exec_queue_create(gpu1->fd, vm[1], eci, 0);
+        xe_vm_bind_lr_sync(gpu1->fd, vm[1], 0, 0, 0, 1ull <<  gpu1->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+        data = aligned_alloc(xe_get_default_alignment(gpu0->fd), SZ_64M);
+        igt_assert(data);
+        data[0].vm_sync = 0;
+        addr = to_user_pointer(data);
+
+        copy_dst = aligned_alloc(xe_get_default_alignment(gpu1->fd), SZ_64M);
+        igt_assert(copy_dst);
+
+	pf_count_1 = xe_gt_stats_get_count(gpu0->fd, eci->gt_id, pf_count_stat);
+	igt_info("pf_count before trigerring xe_exec on gpu1 without madvise :%d\n",
+		 pf_count_1);
+
+	/* checking pagefault count for gpu1 */
+        store_dword_batch_init(gpu0->fd, vm[0], addr, &batch_bo, &batch_addr, value);
+
+        sync_addr = malloc(sizeof(*sync_addr));
+        igt_assert(!!sync_addr);
+        sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+        sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = BIND_SYNC_VAL;
+        *sync_addr = 0;
+        sync_addr = (void *)((char *)batch_addr + SZ_4K);
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = EXEC_SYNC_VAL;
+        *sync_addr = 0;
+
+	/* Execute STORE command on GPU1 */
+        xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr, &sync, 1);
+        if (*sync_addr != EXEC_SYNC_VAL)
+                xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[0],
+			       NSEC_PER_SEC * 10);
+
+	pf_count_2 = xe_gt_stats_get_count(gpu0->fd, eci->gt_id, pf_count_stat);
+	igt_info("pf_count after trigerring xe_exec on gpu1 without madvise :%d\n",
+		 pf_count_2 - pf_count_1);
+
+	memcpy(shared_val, (void *)addr, 4);
+        igt_assert_eq(shared_val[0], value);
+
+        /* CPU writes 10, memset set bytes no integer hence memset fills 4 bytes with 0x0A */
+        memset((void *)(uintptr_t)addr, 10, sizeof(int));
+        memcpy(shared_val, (void *)(uintptr_t)addr, sizeof(shared_val));
+        igt_assert_eq(shared_val[0], 0x0A0A0A0A);
+
+	pf_count_1 = xe_gt_stats_get_count(gpu1->fd, eci->gt_id, pf_count_stat);
+	igt_info("pf_count before trigerring xe_exec on gpu2 without madvise :%d\n",
+		 pf_count_1);
+
+	/* checking pagefault count for gpu2 */
+        store_dword_batch_init(gpu1->fd, vm[1], addr, &batch_bo, &batch_addr, value + 10);
+
+        sync_addr = malloc(sizeof(*sync_addr));
+        igt_assert(!!sync_addr);
+        sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+        sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = BIND_SYNC_VAL;
+        *sync_addr = 0;
+        sync_addr = (void *)((char *)batch_addr + SZ_4K);
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = EXEC_SYNC_VAL;
+        *sync_addr = 0;
+
+        /* Execute STORE command on GPU2 */
+        xe_exec_sync(gpu1->fd, exec_queue[1], batch_addr, &sync, 1);
+        if (*sync_addr != EXEC_SYNC_VAL)
+                xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[1],
+			       NSEC_PER_SEC * 10);
+
+	pf_count_2 = xe_gt_stats_get_count(gpu1->fd, eci->gt_id, pf_count_stat);
+	igt_info("pf_count after trigerring xe_exec on gpu2 without madvise :%d\n",
+		 pf_count_2 - pf_count_1);
+
+        memcpy(shared_val, (void *)addr, 4);
+        igt_assert_eq(shared_val[0], value + 10);
+
+	pf_count_madvise_1 = xe_gt_stats_get_count(gpu0->fd, eci->gt_id, pf_count_stat);
+	igt_info("pf_count before madvise on gpu1 :%d\n", pf_count_madvise_1);
+
+        store_dword_batch_init(gpu0->fd, vm[0], addr, &batch_bo, &batch_addr, value + 10);
+
+        /* Place destination in an optionally remote location to test */
+        xe_vm_madvise(gpu0->fd, vm[0], addr, COPY_SIZE, 0,
+                      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+                      gpu0->fd, 0, gpu0->vram_regions[0]);
+
+        sync_addr = malloc(sizeof(*sync_addr));
+        igt_assert(!!sync_addr);
+        sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+        sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = BIND_SYNC_VAL;
+        *sync_addr = 0;
+
+        /* Prefetch half of destination */
+        xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, addr, COPY_SIZE / 2, &sync, 1,
+                             DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+
+        if (*sync_addr != BIND_SYNC_VAL)
+                xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, exec_queue[0],
+			       NSEC_PER_SEC * 10);
+        free((void *)sync_addr);
+
+        sync_addr = (void *)((char *)batch_addr + SZ_4K);
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = EXEC_SYNC_VAL;
+        *sync_addr = 0;
+
+        /* Execute STORE command on GPU1 */
+        xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr, &sync, 1);
+        if (*sync_addr != EXEC_SYNC_VAL)
+                xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[0],
+			       NSEC_PER_SEC * 10);
+
+	pf_count_madvise_2 = xe_gt_stats_get_count(gpu0->fd, eci->gt_id, pf_count_stat);
+	if (pf_count_madvise_2 != pf_count_madvise_1)
+		igt_warn("pf_count_madvise_2(%d) != pf_count_madvise_1(%d)\n",
+			 pf_count_madvise_2, pf_count_madvise_1);
+
+        memcpy(shared_val, (void *)addr, 4);
+        igt_assert_eq(shared_val[0], value + 10);
+
+        /* CPU writes 10, memset set bytes no integer hence memset fills 4 bytes with 0x0A */
+        memset((void *)(uintptr_t)addr, 10, sizeof(int));
+        memcpy(shared_val, (void *)(uintptr_t)addr, sizeof(shared_val));
+        igt_assert_eq(shared_val[0], 0x0A0A0A0A);
+
+        store_dword_batch_init(gpu1->fd, vm[1], addr, &batch_bo, &batch_addr, value + 10);
+
+        sync_addr = malloc(sizeof(*sync_addr));
+        igt_assert(!!sync_addr);
+        sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+        sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = BIND_SYNC_VAL;
+        *sync_addr = 0;
+
+        /* Prefetch half of destination */
+        xe_vm_prefetch_async(gpu1->fd, vm[1], 0, 0, addr,
+			     COPY_SIZE / 2, &sync, 1,
+			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+        if (*sync_addr != BIND_SYNC_VAL)
+                xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, exec_queue[1],
+			       NSEC_PER_SEC * 10);
+        free((void *)sync_addr);
+
+        sync_addr = (void *)((char *)batch_addr + SZ_4K);
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = EXEC_SYNC_VAL;
+        *sync_addr = 0;
+
+        /* Execute STORE command on GPU2 */
+        xe_exec_sync(gpu1->fd, exec_queue[1], batch_addr, &sync, 1);
+        if (*sync_addr != EXEC_SYNC_VAL)
+                xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[1],
+			       NSEC_PER_SEC * 10);
+
+        memcpy(shared_val, (void *)addr, 4);
+        igt_assert_eq(shared_val[0], value + 10);
+
+        /* CPU writes 11, memset set bytes no integer hence memset fills 4 bytes with 0x0B */
+        memset((void *)(uintptr_t)addr, 11, sizeof(int));
+        memcpy(shared_val, (void *)(uintptr_t)addr, sizeof(shared_val));
+        igt_assert_eq(shared_val[0], 0x0B0B0B0B);
+
+        munmap((void *)batch_addr, BATCH_SIZE(gpu0->fd));
+        batch_fini(gpu0->fd, vm[0], batch_bo, batch_addr);
+        free(data);
+
+        xe_vm_unbind_lr_sync(gpu0->fd, vm[0], 0, 0, 1ull << gpu0->va_bits);
+        xe_exec_queue_destroy(gpu0->fd, exec_queue[0]);
+        xe_vm_destroy(gpu0->fd, vm[0]);
+
+        xe_vm_unbind_lr_sync(gpu1->fd, vm[1], 0, 0, 1ull << gpu1->va_bits);
+        xe_exec_queue_destroy(gpu1->fd, exec_queue[1]);
+        xe_vm_destroy(gpu1->fd, vm[1]);
+}
+
 int mem_region(int fd)
 {
 	uint64_t regions = all_memory_regions(fd);
@@ -1052,6 +1264,17 @@ gpu_latency_test(struct xe_svm_gpu_info *src_gpu,
         latency_test_multigpu(src_gpu, dst_gpu, eci, remote_copy, prefetch_req);
 }
 
+static void
+gpu_pagefault_test(struct xe_svm_gpu_info *src_gpu,
+		   struct xe_svm_gpu_info *dst_gpu,
+		   struct drm_xe_engine_class_instance *eci)
+{
+	igt_assert(src_gpu);
+	igt_assert(dst_gpu);
+
+        pagefault_test_multigpu(src_gpu, dst_gpu, eci);
+}
+
 igt_main
 {
 	struct xe_svm_gpu_info gpus[MAX_XE_GPUS];
@@ -1104,6 +1327,9 @@ igt_main
 		gpu_latency_test(&gpus[0], &gpus[1], &eci, 0, 0);
 	}
 
+	igt_subtest("pagefault-multi-gpu")
+		gpu_pagefault_test(&gpus[0], &gpus[1], &eci);
+
 	igt_fixture {
 		int cnt;
 
-- 
2.48.1

[PATCH i-g-t v3 2/7] tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU atomic operations

From: <hidden>
Date: 2025-11-04 15:50:16

From: Nishit Sharma <redacted>

This test performs atomic increment operation on a shared SVM buffer
from both GPUs and the CPU in a multi-GPU environment. It uses madvise
and prefetch to control buffer placement and verifies correctness and
ordering of atomic updates across agents.

Signed-off-by: Nishit Sharma <redacted>
---
 tests/intel/xe_multi_gpusvm.c | 186 ++++++++++++++++++++++++++++++++++
 1 file changed, 186 insertions(+)
diff --git a/tests/intel/xe_multi_gpusvm.c b/tests/intel/xe_multi_gpusvm.c
index a88b46323..71bf01ba8 100644
--- a/tests/intel/xe_multi_gpusvm.c
+++ b/tests/intel/xe_multi_gpusvm.c
@@ -31,6 +31,11 @@
  *      region both remotely and locally and copies to it. Reads back to
  *      system memory and checks the result.
  *
+ * SUBTEST: atomic-inc-gpu-op
+ * Description:
+ * 	This test does atomic operation in multi-gpu by executing atomic
+ *	operation on GPU1 and then atomic operation on GPU2 using same
+ *	adress
  */
 
 #define MAX_XE_REGIONS	8
@@ -40,6 +45,7 @@
 #define BIND_SYNC_VAL 0x686868
 #define EXEC_SYNC_VAL 0x676767
 #define COPY_SIZE SZ_64M
+#define	ATOMIC_OP_VAL	56
 
 struct xe_svm_gpu_info {
         bool supports_faults;
@@ -49,8 +55,45 @@ struct xe_svm_gpu_info {
 	int fd;
 };
 
+struct test_exec_data {
+	uint32_t batch[32];
+	uint64_t pad;
+	uint64_t vm_sync;
+	uint64_t exec_sync;
+	uint32_t data;
+	uint32_t expected_data;
+	uint64_t batch_addr;
+};
+
 static void open_pagemaps(int fd, struct xe_svm_gpu_info *info);
 
+static void
+atomic_batch_init(int fd, uint32_t vm, uint64_t src_addr,
+		  uint32_t *bo, uint64_t *addr)
+{
+	uint32_t batch_bo_size = BATCH_SIZE(fd);
+	uint32_t batch_bo;
+	uint64_t batch_addr;
+	void *batch;
+	uint32_t *cmd;
+	int i = 0;
+
+	batch_bo = xe_bo_create(fd, vm, batch_bo_size, vram_if_possible(fd, 0), 0);
+	batch = xe_bo_map(fd, batch_bo, batch_bo_size);
+	cmd = (uint32_t *)batch;
+
+	cmd[i++] = MI_ATOMIC | MI_ATOMIC_INC;
+	cmd[i++] = src_addr;
+	cmd[i++] = src_addr >> 32;
+	cmd[i++] = MI_BATCH_BUFFER_END;
+
+	batch_addr = to_user_pointer(batch);
+	/* Punch a gap in the SVM map where we map the batch_bo */
+	xe_vm_bind_lr_sync(fd, vm, batch_bo, 0, batch_addr, batch_bo_size, 0);
+	*bo = batch_bo;
+	*addr = batch_addr;
+}
+
 static void batch_init(int fd, uint32_t vm, uint64_t src_addr,
 		       uint64_t dst_addr, uint64_t copy_size,
 		       uint32_t *bo, uint64_t *addr)
@@ -222,6 +265,144 @@ gpu_mem_access(struct xe_svm_gpu_info *src_gpu,
 	copy_src_dst(src_gpu, dst_gpu, eci);
 }
 
+static void
+atomic_inc_op(struct xe_svm_gpu_info *gpu0,
+	      struct xe_svm_gpu_info *gpu1,
+	      struct drm_xe_engine_class_instance *eci,
+	      bool prefetch_req)
+{
+	uint64_t addr;
+	uint32_t vm[2];
+	uint32_t exec_queue[2];
+	uint32_t batch_bo;
+	struct test_exec_data *data;
+	uint64_t batch_addr;
+	struct drm_xe_sync sync = {};
+	volatile uint64_t *sync_addr;
+	volatile uint32_t *shared_val;
+
+	vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE |
+			     DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+	exec_queue[0] = xe_exec_queue_create(gpu0->fd, vm[0], eci, 0);
+	xe_vm_bind_lr_sync(gpu0->fd, vm[0], 0, 0, 0, 1ull <<  gpu0->va_bits,
+			   DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+	vm[1] = xe_vm_create(gpu1->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE |
+			     DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+	exec_queue[1] = xe_exec_queue_create(gpu1->fd, vm[1], eci, 0);
+	xe_vm_bind_lr_sync(gpu1->fd, vm[1], 0, 0, 0, 1ull <<  gpu1->va_bits,
+			   DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+	data = aligned_alloc(SZ_2M, SZ_4K);
+	igt_assert(data);
+	data[0].vm_sync = 0;
+	addr = to_user_pointer(data);
+
+	shared_val = (volatile uint32_t *)addr;
+	*shared_val = ATOMIC_OP_VAL - 1;
+
+	atomic_batch_init(gpu0->fd, vm[0], addr, &batch_bo, &batch_addr);
+
+	/* Place destination in an optionally remote location to test */
+	xe_vm_madvise(gpu0->fd, vm[0], addr, SZ_4K, 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu0->fd, 0, gpu0->vram_regions[0]);
+
+	sync_addr = malloc(sizeof(*sync_addr));
+	igt_assert(!!sync_addr);
+	sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+	sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+	sync.addr = to_user_pointer((uint64_t *)sync_addr);
+	sync.timeline_value = BIND_SYNC_VAL;
+	*sync_addr = 0;
+
+	if (prefetch_req) {
+		xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, addr,
+				     SZ_4K, &sync, 1,
+				     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+		if (*sync_addr != BIND_SYNC_VAL)
+			xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, BIND_SYNC_VAL,
+				       exec_queue[0], NSEC_PER_SEC * 10);
+	}
+	free((void *)sync_addr);
+
+	sync_addr = (void *)((char *)batch_addr + SZ_4K);
+	sync.addr = to_user_pointer((uint64_t *)sync_addr);
+	sync.timeline_value = EXEC_SYNC_VAL;
+	*sync_addr = 0;
+
+	/* Executing ATOMIC_INC on GPU0. */
+	xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr, &sync, 1);
+	if (*sync_addr != EXEC_SYNC_VAL)
+		xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[0],
+			       NSEC_PER_SEC * 10);
+
+	igt_assert_eq(*shared_val, ATOMIC_OP_VAL);
+
+	atomic_batch_init(gpu1->fd, vm[1], addr, &batch_bo, &batch_addr);
+
+	/* Place destination in an optionally remote location to test */
+	xe_vm_madvise(gpu1->fd, vm[1], addr, SZ_4K, 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu1->fd, 0, gpu1->vram_regions[0]);
+
+	sync_addr = malloc(sizeof(*sync_addr));
+	igt_assert(!!sync_addr);
+	sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+	sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+	sync.addr = to_user_pointer((uint64_t *)sync_addr);
+	sync.timeline_value = BIND_SYNC_VAL;
+	*sync_addr = 0;
+
+	if (prefetch_req) {
+		xe_vm_prefetch_async(gpu1->fd, vm[1], 0, 0, addr,
+				     SZ_4K, &sync, 1,
+				     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+		if (*sync_addr != BIND_SYNC_VAL)
+			xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, BIND_SYNC_VAL,
+				       exec_queue[1], NSEC_PER_SEC * 10);
+	}
+	free((void *)sync_addr);
+
+	sync_addr = (void *)((char *)batch_addr + SZ_4K);
+	sync.addr = to_user_pointer((uint64_t *)sync_addr);
+	sync.timeline_value = EXEC_SYNC_VAL;
+	*sync_addr = 0;
+
+	/* Execute ATOMIC_INC on GPU1 */
+	xe_exec_sync(gpu1->fd, exec_queue[1], batch_addr, &sync, 1);
+	if (*sync_addr != EXEC_SYNC_VAL)
+		xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[1],
+			       NSEC_PER_SEC * 10);
+
+	igt_assert_eq(*shared_val, ATOMIC_OP_VAL + 1);
+
+	munmap((void *)batch_addr, BATCH_SIZE(gpu0->fd));
+	batch_fini(gpu0->fd, vm[0], batch_bo, batch_addr);
+	batch_fini(gpu1->fd, vm[1], batch_bo, batch_addr);
+	free(data);
+
+	xe_vm_unbind_lr_sync(gpu0->fd, vm[0], 0, 0, 1ull << gpu0->va_bits);
+	xe_exec_queue_destroy(gpu0->fd, exec_queue[0]);
+	xe_vm_destroy(gpu0->fd, vm[0]);
+
+	xe_vm_unbind_lr_sync(gpu1->fd, vm[1], 0, 0, 1ull << gpu1->va_bits);
+	xe_exec_queue_destroy(gpu1->fd, exec_queue[1]);
+	xe_vm_destroy(gpu1->fd, vm[1]);
+}
+
+static void
+gpu_atomic_inc(struct xe_svm_gpu_info *src_gpu,
+	       struct xe_svm_gpu_info *dst_gpu,
+	       struct drm_xe_engine_class_instance *eci,
+	       bool prefetch_req)
+{
+	igt_assert(src_gpu);
+	igt_assert(dst_gpu);
+
+	atomic_inc_op(src_gpu, dst_gpu, eci, prefetch_req);
+}
+
 igt_main
 {
 	struct xe_svm_gpu_info gpus[MAX_XE_GPUS];
@@ -256,6 +437,11 @@ igt_main
 	igt_subtest("cross-gpu-mem-access")
 		gpu_mem_access(&gpus[0], &gpus[1], &eci);
 
+	igt_subtest("atomic-inc-gpu-op") {
+		gpu_atomic_inc(&gpus[0], &gpus[1], &eci, 1);
+		gpu_atomic_inc(&gpus[0], &gpus[1], &eci, 0);
+	}
+
 	igt_fixture {
 		int cnt;
 
-- 
2.48.1

[PATCH i-g-t v3 4/7] tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU performance test

From: <hidden>
Date: 2025-11-04 15:50:16

From: Nishit Sharma <redacted>

This test measures latency and bandwidth for buffer access from each GPU
and the CPU in a multi-GPU SVM environment. It compares performance for
local versus remote access using madvise and prefetch to control buffer
placement

Signed-off-by: Nishit Sharma <redacted>
---
 tests/intel/xe_multi_gpusvm.c | 444 ++++++++++++++++++++++++++++++++++
 1 file changed, 444 insertions(+)
diff --git a/tests/intel/xe_multi_gpusvm.c b/tests/intel/xe_multi_gpusvm.c
index 2c64de209..aa79c71bd 100644
--- a/tests/intel/xe_multi_gpusvm.c
+++ b/tests/intel/xe_multi_gpusvm.c
@@ -13,6 +13,8 @@
 #include "intel_mocs.h"
 #include "intel_reg.h"
 
+#include "time.h"
+
 #include "xe/xe_ioctl.h"
 #include "xe/xe_query.h"
 #include "xe/xe_util.h"
@@ -41,6 +43,17 @@
  * Description:
  * 	This test checks coherency in multi-gpu by writing from GPU0
  * 	reading from GPU1 and verify and repeating with CPU and both GPUs
+ *
+ * SUBTEST: conflicting-madvise-gpu
+ * Description:
+ * 	This test checks conflicting madvise by allocating shared buffer
+ * 	prefetches from both and checks for migration conflicts
+ * 	This test checks conflicting madvise
+ *
+ * SUBTEST: latency-multi-gpu
+ * Description:
+ * 	This test measures and compares latency and bandwidth for buffer access
+ * 	from CPU, local GPU, and remote GPU
  */
 
 #define MAX_XE_REGIONS	8
@@ -72,6 +85,11 @@ struct test_exec_data {
 
 static void open_pagemaps(int fd, struct xe_svm_gpu_info *info);
 
+static double time_diff(struct timespec *start, struct timespec *end)
+{
+    return (end->tv_sec - start->tv_sec) + (end->tv_nsec - start->tv_nsec) / 1e9;
+}
+
 static void
 atomic_batch_init(int fd, uint32_t vm, uint64_t src_addr,
 		  uint32_t *bo, uint64_t *addr)
@@ -465,6 +483,399 @@ coherency_test_multigpu(struct xe_svm_gpu_info *gpu0,
         xe_vm_destroy(gpu1->fd, vm[1]);
 }
 
+static void
+latency_test_multigpu(struct xe_svm_gpu_info *gpu0,
+		      struct xe_svm_gpu_info *gpu1,
+		      struct drm_xe_engine_class_instance *eci,
+		      bool remote_copy,
+		      bool prefetch_req)
+{
+        uint64_t addr;
+        uint32_t vm[2];
+        uint32_t exec_queue[2];
+        uint32_t batch_bo;
+        uint8_t *copy_dst;
+        uint64_t batch_addr;
+        struct drm_xe_sync sync = {};
+        volatile uint64_t *sync_addr;
+        int value = 60;
+        int shared_val[4];
+        struct test_exec_data *data;
+	struct timespec t_start, t_end;
+	double cpu_latency, gpu1_latency, gpu2_latency;
+	double cpu_bw, gpu1_bw, gpu2_bw;
+
+
+        vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+        exec_queue[0] = xe_exec_queue_create(gpu0->fd, vm[0], eci, 0);
+        xe_vm_bind_lr_sync(gpu0->fd, vm[0], 0, 0, 0, 1ull <<  gpu0->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+        vm[1] = xe_vm_create(gpu1->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+        exec_queue[1] = xe_exec_queue_create(gpu1->fd, vm[1], eci, 0);
+        xe_vm_bind_lr_sync(gpu1->fd, vm[1], 0, 0, 0, 1ull <<  gpu1->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+        data = aligned_alloc(SZ_2M, SZ_4K);
+        igt_assert(data);
+        data[0].vm_sync = 0;
+        addr = to_user_pointer(data);
+
+        copy_dst = aligned_alloc(SZ_2M, SZ_4K);
+        igt_assert(copy_dst);
+
+        store_dword_batch_init(gpu0->fd, vm[0], addr, &batch_bo, &batch_addr, value);
+
+	/* Measure GPU0 access latency/bandwidth */
+	clock_gettime(CLOCK_MONOTONIC, &t_start);
+
+        /* GPU0 access */
+        xe_vm_madvise(gpu0->fd, vm[0], addr, SZ_4K, 0,
+                      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+                      gpu0->fd, 0, gpu0->vram_regions[0]);
+
+        sync_addr = malloc(sizeof(*sync_addr));
+        igt_assert(!!sync_addr);
+        sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+        sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = BIND_SYNC_VAL;
+        *sync_addr = 0;
+
+	if (prefetch_req) {
+		xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, addr, SZ_4K, &sync, 1,
+				     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+		if (*sync_addr != BIND_SYNC_VAL)
+			xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, exec_queue[0],
+				       NSEC_PER_SEC * 10);
+	}
+        free((void *)sync_addr);
+
+	clock_gettime(CLOCK_MONOTONIC, &t_end);
+	gpu1_latency = time_diff(&t_start, &t_end);
+	gpu1_bw = COPY_SIZE / gpu1_latency / (1024 * 1024); // MB/s
+
+        sync_addr = (void *)((char *)batch_addr + SZ_4K);
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = EXEC_SYNC_VAL;
+        *sync_addr = 0;
+
+        /* Execute STORE command on GPU0 */
+        xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr, &sync, 1);
+        if (*sync_addr != EXEC_SYNC_VAL)
+                xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[0],
+			       NSEC_PER_SEC * 10);
+								 
+        memcpy(shared_val, (void *)addr, 4);
+        igt_assert_eq(shared_val[0], value);
+
+        /* CPU writes 10, memset set bytes no integer hence memset fills 4 bytes with 0x0A */
+        memset((void *)(uintptr_t)addr, 10, sizeof(int));
+        memcpy(shared_val, (void *)(uintptr_t)addr, sizeof(shared_val));
+        igt_assert_eq(shared_val[0], 0x0A0A0A0A);
+
+	*(uint64_t *)addr = 50;
+
+	if(remote_copy) {
+		igt_info("creating batch for COPY_CMD on GPU1\n");
+		batch_init(gpu1->fd, vm[1], addr, to_user_pointer(copy_dst),
+			   SZ_4K, &batch_bo, &batch_addr);
+	}
+	else {
+		igt_info("creating batch for STORE_CMD on GPU1\n");
+		store_dword_batch_init(gpu1->fd, vm[1], addr, &batch_bo, &batch_addr, value + 10);
+	}
+
+	/* Measure GPU1 access latency/bandwidth */
+	clock_gettime(CLOCK_MONOTONIC, &t_start);
+
+        /* GPU1 access */
+        xe_vm_madvise(gpu1->fd, vm[1], addr, SZ_4K, 0,
+                      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+                      gpu1->fd, 0, gpu1->vram_regions[0]);
+
+        sync_addr = malloc(sizeof(*sync_addr));
+        igt_assert(!!sync_addr);
+        sync.flags = DRM_XE_SYNC_FLAG_SIGNAL;
+        sync.type = DRM_XE_SYNC_TYPE_USER_FENCE;
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = BIND_SYNC_VAL;
+        *sync_addr = 0;
+
+	if (prefetch_req) {
+		xe_vm_prefetch_async(gpu1->fd, vm[1], 0, 0, addr,
+				     SZ_4K, &sync, 1,
+				     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+		if (*sync_addr != BIND_SYNC_VAL)
+			xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, BIND_SYNC_VAL, exec_queue[1],
+				       NSEC_PER_SEC * 10);
+	}
+        free((void *)sync_addr);
+
+	clock_gettime(CLOCK_MONOTONIC, &t_end);
+	gpu2_latency = time_diff(&t_start, &t_end);
+	gpu2_bw = COPY_SIZE / gpu2_latency / (1024 * 1024); // MB/s
+
+        sync_addr = (void *)((char *)batch_addr + SZ_4K);
+        sync.addr = to_user_pointer((uint64_t *)sync_addr);
+        sync.timeline_value = EXEC_SYNC_VAL;
+        *sync_addr = 0;
+
+        /* Execute COPY/STORE command on GPU1 */
+        xe_exec_sync(gpu1->fd, exec_queue[1], batch_addr, &sync, 1);
+        if (*sync_addr != EXEC_SYNC_VAL)
+                xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[1],
+			       NSEC_PER_SEC * 10);
+	
+	if (!remote_copy)
+        	igt_assert_eq(*(uint64_t *)addr, value + 10);
+	else
+		igt_assert_eq(*(uint64_t *)copy_dst, 50);
+
+        /* CPU writes 11, memset set bytes no integer hence memset fills 4 bytes with 0x0B */
+	/* Measure CPU access latency/bandwidth */
+	clock_gettime(CLOCK_MONOTONIC, &t_start);
+        memset((void *)(uintptr_t)addr, 11, sizeof(int));
+        memcpy(shared_val, (void *)(uintptr_t)addr, sizeof(shared_val));
+	clock_gettime(CLOCK_MONOTONIC, &t_end);
+	cpu_latency = time_diff(&t_start, &t_end);
+	cpu_bw = COPY_SIZE / cpu_latency / (1024 * 1024); // MB/s
+
+        igt_assert_eq(shared_val[0], 0x0B0B0B0B);
+
+	/* Print results */
+	igt_info("CPU: Latency %.6f s, Bandwidth %.2f MB/s\n", cpu_latency, cpu_bw);
+	igt_info("GPU1: Latency %.6f s, Bandwidth %.2f MB/s\n", gpu1_latency, gpu1_bw);
+	igt_info("GPU2: Latency %.6f s, Bandwidth %.2f MB/s\n", gpu2_latency, gpu2_bw);
+
+        munmap((void *)batch_addr, BATCH_SIZE(gpu0->fd));
+        batch_fini(gpu0->fd, vm[0], batch_bo, batch_addr);
+        batch_fini(gpu1->fd, vm[1], batch_bo, batch_addr);
+        free(data);
+        free(copy_dst);
+
+        xe_vm_unbind_lr_sync(gpu0->fd, vm[0], 0, 0, 1ull << gpu0->va_bits);
+        xe_exec_queue_destroy(gpu0->fd, exec_queue[0]);
+        xe_vm_destroy(gpu0->fd, vm[0]);
+
+        xe_vm_unbind_lr_sync(gpu1->fd, vm[1], 0, 0, 1ull << gpu1->va_bits);
+        xe_exec_queue_destroy(gpu1->fd, exec_queue[1]);
+        xe_vm_destroy(gpu1->fd, vm[1]);
+}
+
+int mem_region(int fd)
+{
+	uint64_t regions = all_memory_regions(fd);
+        uint64_t region = -1;
+
+	xe_for_each_mem_region(fd, regions, region) {
+                if (XE_IS_SYSMEM_MEMORY_REGION(fd, region)) {
+                        struct drm_xe_mem_region *mem_region =
+                                xe_mem_region(fd, 1ull << (region));
+                        region = mem_region->instance;
+			printf("region instance :%d region name :%s\n", region, xe_region_name(region));
+			break;
+                }
+        }
+
+	printf("returning region :%d\n", region);
+	return region;
+}
+
+#define	XE_BO_FLAG_SYSTEM	BIT(1)
+#define XE_BO_FLAG_CPU_ADDR_MIRROR      BIT(24)
+
+static void
+conflicting_madvise(struct xe_svm_gpu_info *gpu0,
+		    struct xe_svm_gpu_info *gpu1,
+		    struct drm_xe_engine_class_instance *eci)
+{
+
+	uint32_t vm[3];
+	uint32_t exec_queue[3];
+	uint32_t batch_bo[3];
+	uint64_t batch_addr[3], svm_addr;
+	struct test_exec_data *data;
+	volatile uint64_t sync_val1 = 0;
+	volatile uint64_t sync_val2 = 0;
+
+	// Define sync structures
+	struct drm_xe_sync sync1 = {
+		.type = DRM_XE_SYNC_TYPE_USER_FENCE,
+		.flags = DRM_XE_SYNC_FLAG_SIGNAL,
+		.addr = (uint64_t)(uintptr_t)&sync_val1, // user-space address
+		.timeline_value = USER_FENCE_VALUE, // or desired fence value
+	};
+
+	struct drm_xe_sync sync2 = {
+		.type = DRM_XE_SYNC_TYPE_USER_FENCE,
+		.flags = DRM_XE_SYNC_FLAG_SIGNAL,
+		.addr = (uint64_t)(uintptr_t)&sync_val2,
+		.timeline_value = USER_FENCE_VALUE,
+	};
+
+#define QUARTER_SEC             (NSEC_PER_SEC / 4)
+	int64_t timeout = QUARTER_SEC;
+	data = aligned_alloc(SZ_64M, SZ_64M);
+	igt_assert(data);
+
+	vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+	exec_queue[0] = xe_exec_queue_create(gpu0->fd, vm[0], eci, 0);
+	xe_vm_bind_lr_sync(gpu0->fd, vm[0], 0, 0, 0, 1ull <<  gpu0->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+	vm[1] = xe_vm_create(gpu1->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+	exec_queue[1] = xe_exec_queue_create(gpu1->fd, vm[1], eci, 0);
+	xe_vm_bind_lr_sync(gpu1->fd, vm[0], 0, 0, 0, 1ull <<  gpu1->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+	svm_addr = to_user_pointer(data);
+
+	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, COPY_SIZE, 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      DRM_XE_PREFERRED_LOC_DEFAULT_SYSTEM, 0, 0);
+
+	*(uint64_t *)svm_addr = 50;
+
+	atomic_batch_init(gpu0->fd, vm[0], svm_addr, &batch_bo[0], &batch_addr[0]);
+	atomic_batch_init(gpu1->fd, vm[1], svm_addr, &batch_bo[1], &batch_addr[1]);
+
+#if 0
+	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, COPY_SIZE, 0,
+			DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+			DRM_XE_PREFERRED_LOC_DEFAULT_SYSTEM, 0, 0);
+	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, COPY_SIZE, 0,
+			DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+			DRM_XE_PREFERRED_LOC_DEFAULT_SYSTEM, 0, 0);
+#endif
+
+#if 1
+	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, BATCH_SIZE(gpu0->fd), 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu1->fd, 0, gpu1->vram_regions[0]);
+
+	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu0->fd, 0, gpu0->vram_regions[0]);
+#endif
+
+	xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, svm_addr,
+			     BATCH_SIZE(gpu0->fd), &sync1, 1,
+			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+
+	xe_vm_prefetch_async(gpu1->fd, vm[0], 0, 0, svm_addr,
+			     BATCH_SIZE(gpu1->fd), &sync2, 1,
+			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+	if (sync_val1 != BIND_SYNC_VAL)
+		__xe_wait_ufence(gpu0->fd, (uint64_t *)&sync1, BIND_SYNC_VAL, exec_queue[0],
+				&timeout);
+	if (sync_val2 != BIND_SYNC_VAL)
+		__xe_wait_ufence(gpu1->fd, (uint64_t *)&sync2, BIND_SYNC_VAL, exec_queue[1],
+				&timeout);
+
+        /* Executing ATOMIC_INC on GPU0. */
+        xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr[0], &sync1, 1);
+        if (sync_val1 != EXEC_SYNC_VAL)
+                __xe_wait_ufence(gpu0->fd, (uint64_t *)sync_val1, EXEC_SYNC_VAL, exec_queue[0],
+                               &timeout);
+
+
+}
+
+static void
+conflict_test_multigpu(struct xe_svm_gpu_info *gpu0,
+		       struct xe_svm_gpu_info *gpu1,
+		       struct drm_xe_engine_class_instance *eci)
+{
+	uint32_t vm[2];
+	uint32_t exec_queue[2];
+	uint32_t batch_bo, batch1_bo;
+	void *copy_src, *copy_dst;
+	uint64_t batch_addr, svm_addr;
+	int region;
+	struct drm_xe_sync sync = {};
+	volatile uint64_t *sync_addr;
+	void *batch, *cpu_ptr;
+	struct xe_svm_gpu_info gpu_mem[1];
+
+#if 1
+	vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+	exec_queue[0] = xe_exec_queue_create(gpu0->fd, vm[0], eci, 0);
+	xe_vm_bind_lr_sync(gpu0->fd, vm[0], 0, 0, 0, 1ull <<  gpu0->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+	vm[1] = xe_vm_create(gpu1->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+	exec_queue[1] = xe_exec_queue_create(gpu1->fd, vm[1], eci, 0);
+	xe_vm_bind_lr_sync(gpu1->fd, vm[1], 0, 0, 0, 1ull <<  gpu1->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+	region = mem_region(gpu0->fd);
+	int render_fd;
+	render_fd = drm_open_driver(DRIVER_XE);
+	printf("render_fd :%d gpu0->fd :%d gpu1->fd :%d\n",
+			render_fd, gpu0->fd, gpu0->fd);
+//	printf("region is :%d\n", region);
+	//batch_bo = xe_bo_create(render_fd, 0, BATCH_SIZE(render_fd), system_memory(render_fd), XE_BO_FLAG_SYSTEM);
+	batch_bo = xe_bo_create(gpu0->fd, vm[0], BATCH_SIZE(gpu0->fd), region, XE_BO_FLAG_SYSTEM);
+	batch = xe_bo_map(gpu0->fd, batch_bo, BATCH_SIZE(gpu0->fd));
+	//batch = xe_bo_map(render_fd, batch_bo, BATCH_SIZE(render_fd));
+	render_fd = open("/dev/dri/card0", O_RDWR);
+	if (render_fd <= 0) {
+	    perror("open error for card0");
+    // handle error
+	}
+	//render_fd = drm_open_driver(DRIVER_XE);
+	printf("render_fd :%d gpu0->fd :%d gpu1->fd :%d\n",
+			render_fd, gpu0->fd, gpu1->fd);
+	printf("region is :%d\n", region);
+	//batch_bo = xe_bo_create(render_fd, 0, COPY_SIZE, region, XE_BO_FLAG_SYSTEM);
+	batch_bo = xe_bo_create(gpu0->fd, 0, BATCH_SIZE(gpu0->fd), region, XE_BO_FLAG_SYSTEM);
+	batch = xe_bo_map(gpu0->fd, batch_bo, BATCH_SIZE(gpu0->fd));
+	//batch = xe_bo_map(render_fd, batch_bo, COPY_SIZE);
+	igt_assert(batch);
+
+	svm_addr = to_user_pointer(batch);
+
+	igt_info("Calling first bind\n");
+	xe_vm_bind_lr_sync(gpu0->fd, vm[0], batch_bo, 0, svm_addr, BATCH_SIZE(gpu0->fd), 0);
+	igt_info("Calling second bind\n");
+	//xe_vm_bind_lr_sync(gpu1->fd, vm[1], batch_bo, 0, svm_addr, BATCH_SIZE(gpu1->fd), 0);
+	//xe_vm_bind_lr_sync(gpu1->fd, vm[0], batch_bo, 0, svm_addr, BATCH_SIZE(gpu1->fd), 0);
+
+	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, BATCH_SIZE(gpu0->fd), 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu0->fd, 0, gpu0->vram_regions[0]);
+
+	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu1->fd, 0, gpu1->vram_regions[0]);
+	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu1->fd, 0, gpu1->vram_regions[0]);
+#if 0
+	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu1->fd, 0, gpu1->vram_regions[0]);
+#endif
+
+	printf("1st Prefetch calling\n");
+	xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, svm_addr,
+			     BATCH_SIZE(gpu0->fd), &sync, 1,
+			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+
+	printf("2nd Prefetch calling\n");
+	xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, svm_addr,
+			     BATCH_SIZE(gpu1->fd), &sync, 1,
+			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+
+	munmap((void *)batch_addr, BATCH_SIZE(gpu0->fd));
+	batch_fini(gpu0->fd, vm[0], batch_bo, batch_addr);
+	xe_vm_unbind_lr_sync(gpu0->fd, vm[0], 0, 0, 1ull << gpu0->va_bits);
+	xe_exec_queue_destroy(gpu0->fd, exec_queue[0]);
+	xe_vm_destroy(gpu0->fd, vm[0]);
+
+	batch_fini(gpu1->fd, vm[1], batch_bo, batch_addr);
+	xe_vm_unbind_lr_sync(gpu1->fd, vm[1], 0, 0, 1ull << gpu1->va_bits);
+	xe_exec_queue_destroy(gpu1->fd, exec_queue[1]);
+	xe_vm_destroy(gpu1->fd, vm[1]);
+	drm_close_driver(render_fd);
+#endif
+}
+
 static void
 atomic_inc_op(struct xe_svm_gpu_info *gpu0,
 	      struct xe_svm_gpu_info *gpu1,
@@ -616,6 +1027,31 @@ gpu_coherecy_test(struct xe_svm_gpu_info *src_gpu,
         coherency_test_multigpu(src_gpu, dst_gpu, eci, coh_fail, prefetch_req);
 }
 
+static void
+gpu_conflict_madvise_test(struct xe_svm_gpu_info *src_gpu,
+                          struct xe_svm_gpu_info *dst_gpu,
+			  struct drm_xe_engine_class_instance *eci)
+{
+	igt_assert(src_gpu);
+	igt_assert(dst_gpu);
+
+        //conflict_test_multigpu(src_gpu, dst_gpu, eci);
+        conflicting_madvise(src_gpu, dst_gpu, eci);
+}
+
+static void
+gpu_latency_test(struct xe_svm_gpu_info *src_gpu,
+		 struct xe_svm_gpu_info *dst_gpu,
+		 struct drm_xe_engine_class_instance *eci,
+		 bool remote_copy,
+		 bool prefetch_req)
+{
+	igt_assert(src_gpu);
+	igt_assert(dst_gpu);
+
+        latency_test_multigpu(src_gpu, dst_gpu, eci, remote_copy, prefetch_req);
+}
+
 igt_main
 {
 	struct xe_svm_gpu_info gpus[MAX_XE_GPUS];
@@ -660,6 +1096,14 @@ igt_main
 		gpu_coherecy_test(&gpus[0], &gpus[1], &eci, 1, 0);
 	}
 
+	igt_subtest("conflicting-madvise-gpu")
+		gpu_conflict_madvise_test(&gpus[0], &gpus[1], &eci);
+
+	igt_subtest("latency-multi-gpu") {
+		gpu_latency_test(&gpus[0], &gpus[1], &eci, 1, 1);
+		gpu_latency_test(&gpus[0], &gpus[1], &eci, 0, 0);
+	}
+
 	igt_fixture {
 		int cnt;
 
-- 
2.48.1

[PATCH i-g-t v3 7/7] tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU conflicting madvise test

From: <hidden>
Date: 2025-11-04 15:50:17

From: Nishit Sharma <redacted>

This test calls madvise operations on GPU0 with the preferred location set
to GPU1 and vice versa. It reports conflicts when conflicting memory advice
is given for shared SVM buffers in a multi-GPU environment.

Signed-off-by: Nishit Sharma <redacted>
---
 tests/intel/xe_multi_gpusvm.c | 206 ++++++----------------------------
 1 file changed, 36 insertions(+), 170 deletions(-)
diff --git a/tests/intel/xe_multi_gpusvm.c b/tests/intel/xe_multi_gpusvm.c
index df4f972b2..5b2ee7afe 100644
--- a/tests/intel/xe_multi_gpusvm.c
+++ b/tests/intel/xe_multi_gpusvm.c
@@ -74,6 +74,8 @@
 #define EXEC_SYNC_VAL 0x676767
 #define COPY_SIZE SZ_64M
 #define	ATOMIC_OP_VAL	56
+#define USER_FENCE_VALUE        0xdeadbeefdeadbeefull
+#define FIVE_SEC                (5LL * NSEC_PER_SEC)
 
 struct xe_svm_gpu_info {
         bool supports_faults;
@@ -877,32 +879,14 @@ pagefault_test_multigpu(struct xe_svm_gpu_info *gpu0,
         xe_vm_destroy(gpu1->fd, vm[1]);
 }
 
-int mem_region(int fd)
-{
-	uint64_t regions = all_memory_regions(fd);
-        uint64_t region = -1;
-
-	xe_for_each_mem_region(fd, regions, region) {
-                if (XE_IS_SYSMEM_MEMORY_REGION(fd, region)) {
-                        struct drm_xe_mem_region *mem_region =
-                                xe_mem_region(fd, 1ull << (region));
-                        region = mem_region->instance;
-			printf("region instance :%d region name :%s\n", region, xe_region_name(region));
-			break;
-                }
-        }
-
-	printf("returning region :%d\n", region);
-	return region;
-}
-
 #define	XE_BO_FLAG_SYSTEM	BIT(1)
 #define XE_BO_FLAG_CPU_ADDR_MIRROR      BIT(24)
 
 static void
 conflicting_madvise(struct xe_svm_gpu_info *gpu0,
 		    struct xe_svm_gpu_info *gpu1,
-		    struct drm_xe_engine_class_instance *eci)
+		    struct drm_xe_engine_class_instance *eci,
+		    bool prefetch_req)
 {
 
 	uint32_t vm[3];
@@ -914,12 +898,11 @@ conflicting_madvise(struct xe_svm_gpu_info *gpu0,
 	volatile uint64_t sync_val2 = 0;
 	volatile uint64_t *sync_addr;
 
-	// Define sync structures
 	struct drm_xe_sync sync1 = {
 		.type = DRM_XE_SYNC_TYPE_USER_FENCE,
 		.flags = DRM_XE_SYNC_FLAG_SIGNAL,
-		.addr = (uint64_t)(uintptr_t)&sync_val1, // user-space address
-		.timeline_value = USER_FENCE_VALUE, // or desired fence value
+		.addr = (uint64_t)(uintptr_t)&sync_val1,
+		.timeline_value = USER_FENCE_VALUE,
 	};
 
 	struct drm_xe_sync sync2 = {
@@ -931,7 +914,7 @@ conflicting_madvise(struct xe_svm_gpu_info *gpu0,
 
 #define QUARTER_SEC             (NSEC_PER_SEC / 4)
 	int64_t timeout = QUARTER_SEC;
-	data = aligned_alloc(SZ_64M, SZ_64M);
+	data = aligned_alloc(SZ_2M, SZ_4K);
 	igt_assert(data);
 
 	vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
@@ -944,69 +927,48 @@ conflicting_madvise(struct xe_svm_gpu_info *gpu0,
 
 	svm_addr = to_user_pointer(data);
 
-	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, COPY_SIZE, 0,
+	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, SZ_4K, 0,
 		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
 		      DRM_XE_PREFERRED_LOC_DEFAULT_SYSTEM, 0, 0);
 
-	*(uint64_t *)svm_addr = 50;
-
-	atomic_batch_init(gpu0->fd, vm[0], svm_addr, &batch_bo[0], &batch_addr[0]);
-	atomic_batch_init(gpu1->fd, vm[1], svm_addr, &batch_bo[1], &batch_addr[1]);
-
-#if 0
-	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, COPY_SIZE, 0,
-			DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
-			DRM_XE_PREFERRED_LOC_DEFAULT_SYSTEM, 0, 0);
-	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, COPY_SIZE, 0,
-			DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
-			DRM_XE_PREFERRED_LOC_DEFAULT_SYSTEM, 0, 0);
-#endif
+	store_dword_batch_init(gpu0->fd, vm[0], svm_addr, &batch_bo[0], &batch_addr[0], 10);
+	store_dword_batch_init(gpu1->fd, vm[0], svm_addr, &batch_bo[1], &batch_addr[1], 10);
 
-#if 1
-	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, BATCH_SIZE(gpu0->fd), 0,
+	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, SZ_4K, 0,
 		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
 		      gpu1->fd, 0, gpu1->vram_regions[0]);
 
-	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
-		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
-		      gpu0->fd, 0, gpu0->vram_regions[0]);
-#endif
-#if 0
-	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, BATCH_SIZE(gpu0->fd), 0,
+	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, SZ_4K, 0,
 		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
 		      gpu0->fd, 0, gpu0->vram_regions[0]);
 
-	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
-		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
-		      gpu1->fd, 0, gpu1->vram_regions[0]);
-#endif
-
-	xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, svm_addr,
-			     BATCH_SIZE(gpu0->fd), &sync1, 1,
-			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
-
-	xe_vm_prefetch_async(gpu1->fd, vm[0], 0, 0, svm_addr,
-			     BATCH_SIZE(gpu1->fd), &sync2, 1,
-			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
-	if (sync_val1 != BIND_SYNC_VAL)
-		__xe_wait_ufence(gpu0->fd, (uint64_t *)&sync1, BIND_SYNC_VAL, exec_queue[0],
-				&timeout);
-	if (sync_val2 != BIND_SYNC_VAL)
-		__xe_wait_ufence(gpu1->fd, (uint64_t *)&sync2, BIND_SYNC_VAL, exec_queue[1],
-				&timeout);
+	if (prefetch_req) {
+		xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, svm_addr,
+				     SZ_4K, &sync1, 1,
+				     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+		xe_vm_prefetch_async(gpu1->fd, vm[0], 0, 0, svm_addr,
+				     SZ_4K, &sync2, 1,
+				     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+		if (sync_val1 != BIND_SYNC_VAL)
+			__xe_wait_ufence(gpu0->fd, (uint64_t *)&sync1, BIND_SYNC_VAL, exec_queue[0],
+					 &timeout);
+		if (sync_val2 != BIND_SYNC_VAL)
+			__xe_wait_ufence(gpu1->fd, (uint64_t *)&sync2, BIND_SYNC_VAL, exec_queue[1],
+					 &timeout);
+	}
 
 	sync_addr = (void *)((char *)batch_addr[0] + SZ_4K);
 	sync1.addr = to_user_pointer((uint64_t *)sync_addr);
-	//sync.timeline_value = EXEC_SYNC_VAL;
 	*sync_addr = 0;
 
-        /* Executing ATOMIC_INC on GPU0. */
+        /* Executing STORE on GPU0. */
         xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr[0], &sync1, 1);
         if (*sync_addr != EXEC_SYNC_VAL)
                 __xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[0],
                                &timeout);
 
-	igt_assert_eq(*(uint64_t *)svm_addr, 51);
+	igt_assert_eq(*(uint64_t *)svm_addr, 10);
+
 	munmap((void *)batch_addr[0], BATCH_SIZE(gpu0->fd));
 	munmap((void *)batch_addr[1], BATCH_SIZE(gpu1->fd));
 	batch_fini(gpu0->fd, vm[0], batch_bo[0], batch_addr[0]);
@@ -1022,104 +984,6 @@ conflicting_madvise(struct xe_svm_gpu_info *gpu0,
 	xe_vm_destroy(gpu1->fd, vm[1]);
 }
 
-static void
-conflict_test_multigpu(struct xe_svm_gpu_info *gpu0,
-		       struct xe_svm_gpu_info *gpu1,
-		       struct drm_xe_engine_class_instance *eci)
-{
-	uint32_t vm[2];
-	uint32_t exec_queue[2];
-	uint32_t batch_bo, batch1_bo;
-	void *copy_src, *copy_dst;
-	uint64_t batch_addr, svm_addr;
-	int region;
-	struct drm_xe_sync sync = {};
-	volatile uint64_t *sync_addr;
-	void *batch, *cpu_ptr;
-	struct xe_svm_gpu_info gpu_mem[1];
-
-#if 1
-	vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
-	exec_queue[0] = xe_exec_queue_create(gpu0->fd, vm[0], eci, 0);
-	xe_vm_bind_lr_sync(gpu0->fd, vm[0], 0, 0, 0, 1ull <<  gpu0->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
-
-	vm[1] = xe_vm_create(gpu1->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
-	exec_queue[1] = xe_exec_queue_create(gpu1->fd, vm[1], eci, 0);
-	xe_vm_bind_lr_sync(gpu1->fd, vm[1], 0, 0, 0, 1ull <<  gpu1->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
-
-	region = mem_region(gpu0->fd);
-	int render_fd;
-	render_fd = drm_open_driver(DRIVER_XE);
-	printf("render_fd :%d gpu0->fd :%d gpu1->fd :%d\n",
-			render_fd, gpu0->fd, gpu0->fd);
-//	printf("region is :%d\n", region);
-	//batch_bo = xe_bo_create(render_fd, 0, BATCH_SIZE(render_fd), system_memory(render_fd), XE_BO_FLAG_SYSTEM);
-	batch_bo = xe_bo_create(gpu0->fd, vm[0], BATCH_SIZE(gpu0->fd), region, XE_BO_FLAG_SYSTEM);
-	batch = xe_bo_map(gpu0->fd, batch_bo, BATCH_SIZE(gpu0->fd));
-	//batch = xe_bo_map(render_fd, batch_bo, BATCH_SIZE(render_fd));
-	render_fd = open("/dev/dri/card0", O_RDWR);
-	if (render_fd <= 0) {
-	    perror("open error for card0");
-    // handle error
-	}
-	//render_fd = drm_open_driver(DRIVER_XE);
-	printf("render_fd :%d gpu0->fd :%d gpu1->fd :%d\n",
-			render_fd, gpu0->fd, gpu1->fd);
-	printf("region is :%d\n", region);
-	//batch_bo = xe_bo_create(render_fd, 0, COPY_SIZE, region, XE_BO_FLAG_SYSTEM);
-	batch_bo = xe_bo_create(gpu0->fd, 0, BATCH_SIZE(gpu0->fd), region, XE_BO_FLAG_SYSTEM);
-	batch = xe_bo_map(gpu0->fd, batch_bo, BATCH_SIZE(gpu0->fd));
-	//batch = xe_bo_map(render_fd, batch_bo, COPY_SIZE);
-	igt_assert(batch);
-
-	svm_addr = to_user_pointer(batch);
-
-	igt_info("Calling first bind\n");
-	xe_vm_bind_lr_sync(gpu0->fd, vm[0], batch_bo, 0, svm_addr, BATCH_SIZE(gpu0->fd), 0);
-	igt_info("Calling second bind\n");
-	//xe_vm_bind_lr_sync(gpu1->fd, vm[1], batch_bo, 0, svm_addr, BATCH_SIZE(gpu1->fd), 0);
-	//xe_vm_bind_lr_sync(gpu1->fd, vm[0], batch_bo, 0, svm_addr, BATCH_SIZE(gpu1->fd), 0);
-
-	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, BATCH_SIZE(gpu0->fd), 0,
-		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
-		      gpu0->fd, 0, gpu0->vram_regions[0]);
-
-	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
-		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
-		      gpu1->fd, 0, gpu1->vram_regions[0]);
-	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
-		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
-		      gpu1->fd, 0, gpu1->vram_regions[0]);
-#if 0
-	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
-		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
-		      gpu1->fd, 0, gpu1->vram_regions[0]);
-#endif
-
-	printf("1st Prefetch calling\n");
-	xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, svm_addr,
-			     BATCH_SIZE(gpu0->fd), &sync, 1,
-			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
-
-	printf("2nd Prefetch calling\n");
-	xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, svm_addr,
-			     BATCH_SIZE(gpu1->fd), &sync, 1,
-			     DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
-
-	munmap((void *)batch_addr, BATCH_SIZE(gpu0->fd));
-	batch_fini(gpu0->fd, vm[0], batch_bo, batch_addr);
-	xe_vm_unbind_lr_sync(gpu0->fd, vm[0], 0, 0, 1ull << gpu0->va_bits);
-	xe_exec_queue_destroy(gpu0->fd, exec_queue[0]);
-	xe_vm_destroy(gpu0->fd, vm[0]);
-
-	batch_fini(gpu1->fd, vm[1], batch_bo, batch_addr);
-	xe_vm_unbind_lr_sync(gpu1->fd, vm[1], 0, 0, 1ull << gpu1->va_bits);
-	xe_exec_queue_destroy(gpu1->fd, exec_queue[1]);
-	xe_vm_destroy(gpu1->fd, vm[1]);
-	drm_close_driver(render_fd);
-#endif
-}
-
 static void
 atomic_inc_op(struct xe_svm_gpu_info *gpu0,
 	      struct xe_svm_gpu_info *gpu1,
@@ -1399,13 +1263,13 @@ gpu_coherecy_test(struct xe_svm_gpu_info *src_gpu,
 static void
 gpu_conflict_madvise_test(struct xe_svm_gpu_info *src_gpu,
                           struct xe_svm_gpu_info *dst_gpu,
-			  struct drm_xe_engine_class_instance *eci)
+			  struct drm_xe_engine_class_instance *eci,
+			  bool prefetch_req)
 {
 	igt_assert(src_gpu);
 	igt_assert(dst_gpu);
 
-        //conflict_test_multigpu(src_gpu, dst_gpu, eci);
-        conflicting_madvise(src_gpu, dst_gpu, eci);
+        conflicting_madvise(src_gpu, dst_gpu, eci, prefetch_req);
 }
 
 static void
@@ -1488,8 +1352,10 @@ igt_main
 		gpu_coherecy_test(&gpus[0], &gpus[1], &eci, 1, 0);
 	}
 
-	igt_subtest("conflicting-madvise-gpu")
-		gpu_conflict_madvise_test(&gpus[0], &gpus[1], &eci);
+	igt_subtest("conflicting-madvise-gpu") {
+		gpu_conflict_madvise_test(&gpus[0], &gpus[1], &eci, 1);
+		gpu_conflict_madvise_test(&gpus[0], &gpus[1], &eci, 0);
+	}
 
 	igt_subtest("latency-multi-gpu") {
 		gpu_latency_test(&gpus[0], &gpus[1], &eci, 1, 1);
-- 
2.48.1

[PATCH i-g-t v3 6/7] tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU simultaneous access test

From: <hidden>
Date: 2025-11-04 15:50:18

From: Nishit Sharma <redacted>

This test launches compute or copy workloads on both GPUs that access the same
SVM buffer, using synchronization primitives (fences/semaphores) to coordinate
access. It verifies data integrity and checks for the absence of race conditions
in a multi-GPU SVM environment.

Signed-off-by: Nishit Sharma <redacted>
---
 tests/intel/xe_multi_gpusvm.c | 180 +++++++++++++++++++++++++++++++++-
 1 file changed, 177 insertions(+), 3 deletions(-)
diff --git a/tests/intel/xe_multi_gpusvm.c b/tests/intel/xe_multi_gpusvm.c
index 22b8ad95e..df4f972b2 100644
--- a/tests/intel/xe_multi_gpusvm.c
+++ b/tests/intel/xe_multi_gpusvm.c
@@ -49,7 +49,6 @@
  * Description:
  * 	This test checks conflicting madvise by allocating shared buffer
  * 	prefetches from both and checks for migration conflicts
- * 	This test checks conflicting madvise
  *
  * SUBTEST: latency-multi-gpu
  * Description:
@@ -60,6 +59,11 @@
  * Description:
  * 	This test intentionally triggers page faults by accessing unmapped SVM
  * 	regions from both GPUs
+ *
+ * SUBTEST: concurrent-access-multi-gpu
+ * Description:
+ * 	This tests aunches simultaneous workloads on both GPUs accessing the
+ * 	same SVM buffer synchronizes with fences, and verifies data integrity
  */
 
 #define MAX_XE_REGIONS	8
@@ -908,6 +912,7 @@ conflicting_madvise(struct xe_svm_gpu_info *gpu0,
 	struct test_exec_data *data;
 	volatile uint64_t sync_val1 = 0;
 	volatile uint64_t sync_val2 = 0;
+	volatile uint64_t *sync_addr;
 
 	// Define sync structures
 	struct drm_xe_sync sync1 = {
@@ -966,6 +971,15 @@ conflicting_madvise(struct xe_svm_gpu_info *gpu0,
 		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
 		      gpu0->fd, 0, gpu0->vram_regions[0]);
 #endif
+#if 0
+	xe_vm_madvise(gpu0->fd, vm[0], svm_addr, BATCH_SIZE(gpu0->fd), 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu0->fd, 0, gpu0->vram_regions[0]);
+
+	xe_vm_madvise(gpu1->fd, vm[0], svm_addr, BATCH_SIZE(gpu1->fd), 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu1->fd, 0, gpu1->vram_regions[0]);
+#endif
 
 	xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, svm_addr,
 			     BATCH_SIZE(gpu0->fd), &sync1, 1,
@@ -981,13 +995,31 @@ conflicting_madvise(struct xe_svm_gpu_info *gpu0,
 		__xe_wait_ufence(gpu1->fd, (uint64_t *)&sync2, BIND_SYNC_VAL, exec_queue[1],
 				&timeout);
 
+	sync_addr = (void *)((char *)batch_addr[0] + SZ_4K);
+	sync1.addr = to_user_pointer((uint64_t *)sync_addr);
+	//sync.timeline_value = EXEC_SYNC_VAL;
+	*sync_addr = 0;
+
         /* Executing ATOMIC_INC on GPU0. */
         xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr[0], &sync1, 1);
-        if (sync_val1 != EXEC_SYNC_VAL)
-                __xe_wait_ufence(gpu0->fd, (uint64_t *)sync_val1, EXEC_SYNC_VAL, exec_queue[0],
+        if (*sync_addr != EXEC_SYNC_VAL)
+                __xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr, EXEC_SYNC_VAL, exec_queue[0],
                                &timeout);
 
+	igt_assert_eq(*(uint64_t *)svm_addr, 51);
+	munmap((void *)batch_addr[0], BATCH_SIZE(gpu0->fd));
+	munmap((void *)batch_addr[1], BATCH_SIZE(gpu1->fd));
+	batch_fini(gpu0->fd, vm[0], batch_bo[0], batch_addr[0]);
+	batch_fini(gpu1->fd, vm[1], batch_bo[1], batch_addr[1]);
+	free(data);
 
+	xe_vm_unbind_lr_sync(gpu0->fd, vm[0], 0, 0, 1ull << gpu0->va_bits);
+	xe_exec_queue_destroy(gpu0->fd, exec_queue[0]);
+	xe_vm_destroy(gpu0->fd, vm[0]);
+
+	xe_vm_unbind_lr_sync(gpu1->fd, vm[1], 0, 0, 1ull << gpu1->va_bits);
+	xe_exec_queue_destroy(gpu1->fd, exec_queue[1]);
+	xe_vm_destroy(gpu1->fd, vm[1]);
 }
 
 static void
@@ -1214,6 +1246,131 @@ atomic_inc_op(struct xe_svm_gpu_info *gpu0,
 	xe_vm_destroy(gpu1->fd, vm[1]);
 }
 
+static void
+multigpu_access_test(struct xe_svm_gpu_info *gpu0,
+		struct xe_svm_gpu_info *gpu1,
+		struct drm_xe_engine_class_instance *eci,
+		bool no_prefetch)
+{
+	uint64_t addr;
+	uint32_t vm[2];
+	uint32_t exec_queue[2];
+	uint32_t batch_bo[2];
+	struct test_exec_data *data;
+	uint64_t batch_addr[2];
+	struct drm_xe_sync sync[2] = {};
+	volatile uint64_t *sync_addr[2];
+	volatile uint32_t *shared_val;
+#define QUARTER_SEC             (NSEC_PER_SEC / 4)
+        int64_t timeout = QUARTER_SEC;
+
+	vm[0] = xe_vm_create(gpu0->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+	exec_queue[0] = xe_exec_queue_create(gpu0->fd, vm[0], eci, 0);
+	xe_vm_bind_lr_sync(gpu0->fd, vm[0], 0, 0, 0, 1ull <<  gpu0->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+	vm[1] = xe_vm_create(gpu1->fd, DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
+	exec_queue[1] = xe_exec_queue_create(gpu1->fd, vm[1], eci, 0);
+	xe_vm_bind_lr_sync(gpu1->fd, vm[1], 0, 0, 0, 1ull <<  gpu1->va_bits, DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);
+
+	data = aligned_alloc(xe_get_default_alignment(gpu0->fd), SZ_64M);
+	igt_assert(data);
+	data[0].vm_sync = 0;
+	addr = to_user_pointer(data);
+
+	shared_val = (volatile uint32_t *)addr;
+	*shared_val = ATOMIC_OP_VAL - 1;
+
+	atomic_batch_init(gpu0->fd, vm[0], addr, &batch_bo[0], &batch_addr[0]);
+	*shared_val = ATOMIC_OP_VAL - 2;
+	atomic_batch_init(gpu1->fd, vm[1], addr, &batch_bo[1], &batch_addr[1]);
+
+	/* Place destination in an optionally remote location to test */
+	xe_vm_madvise(gpu0->fd, vm[0], addr, COPY_SIZE, 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu0->fd, 0, gpu0->vram_regions[0]);
+
+	xe_vm_madvise(gpu1->fd, vm[1], addr, COPY_SIZE, 0,
+		      DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
+		      gpu1->fd, 0, gpu1->vram_regions[0]);
+
+	sync_addr[0] = malloc(sizeof(*sync_addr));
+	sync_addr[1] = malloc(sizeof(*sync_addr));
+	igt_assert(!!sync_addr[0]);
+	igt_assert(!!sync_addr[1]);
+
+	sync[0].flags = DRM_XE_SYNC_FLAG_SIGNAL;
+	sync[0].type = DRM_XE_SYNC_TYPE_USER_FENCE;
+	sync[0].addr = to_user_pointer((uint64_t *)sync_addr[0]);
+	sync[0].timeline_value = BIND_SYNC_VAL;
+	sync[1].flags = DRM_XE_SYNC_FLAG_SIGNAL;
+	sync[1].type = DRM_XE_SYNC_TYPE_USER_FENCE;
+	sync[1].addr = to_user_pointer((uint64_t *)sync_addr[1]);
+	sync[1].timeline_value = BIND_SYNC_VAL;
+	*sync_addr[0] = 0;
+	*sync_addr[1] = 0;
+
+	if(!no_prefetch) {
+		xe_vm_prefetch_async(gpu0->fd, vm[0], 0, 0, addr,
+				COPY_SIZE / 2, &sync[0], 1,
+				DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+
+		xe_vm_prefetch_async(gpu1->fd, vm[1], 0, 0, addr,
+				COPY_SIZE / 2, &sync[1], 1,
+				DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
+
+		if (*sync_addr[0] != BIND_SYNC_VAL)
+			xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr[0], BIND_SYNC_VAL, exec_queue[0],
+					NSEC_PER_SEC * 10);
+		free((void *)sync_addr[0]);
+		if (*sync_addr[1] != BIND_SYNC_VAL)
+			xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr[1], BIND_SYNC_VAL, exec_queue[1],
+					NSEC_PER_SEC * 10);
+		free((void *)sync_addr[1]);
+	}
+
+	if (no_prefetch) {
+		free((void *)sync_addr[0]);
+		free((void *)sync_addr[1]);
+	}
+
+	for (int i = 0; i < 100; i++) {
+		sync_addr[0] = (void *)((char *)batch_addr[0] + SZ_4K);
+		sync[0].addr = to_user_pointer((uint64_t *)sync_addr[0]);
+		sync[0].timeline_value = EXEC_SYNC_VAL;
+
+		sync_addr[1] = (void *)((char *)batch_addr[1] + SZ_4K);
+		sync[1].addr = to_user_pointer((uint64_t *)sync_addr[1]);
+		sync[1].timeline_value = EXEC_SYNC_VAL;
+		*sync_addr[0] = 0;
+		*sync_addr[1] = 0;
+
+		xe_exec_sync(gpu0->fd, exec_queue[0], batch_addr[0], &sync[0], 1);
+		if (*sync_addr[0] != EXEC_SYNC_VAL)
+			xe_wait_ufence(gpu0->fd, (uint64_t *)sync_addr[0], EXEC_SYNC_VAL, exec_queue[0],
+					NSEC_PER_SEC * 10);
+		xe_exec_sync(gpu1->fd, exec_queue[1], batch_addr[1], &sync[1], 1);
+		if (*sync_addr[1] != EXEC_SYNC_VAL)
+			xe_wait_ufence(gpu1->fd, (uint64_t *)sync_addr[1], EXEC_SYNC_VAL, exec_queue[1],
+					NSEC_PER_SEC * 10);
+	}
+
+	igt_assert_eq(*(uint64_t *)addr, 254);
+
+	munmap((void *)batch_addr[0], BATCH_SIZE(gpu0->fd));
+	munmap((void *)batch_addr[1], BATCH_SIZE(gpu0->fd));
+	batch_fini(gpu0->fd, vm[0], batch_bo[0], batch_addr[0]);
+	batch_fini(gpu1->fd, vm[1], batch_bo[1], batch_addr[1]);
+	free(data);
+
+	xe_vm_unbind_lr_sync(gpu0->fd, vm[0], 0, 0, 1ull << gpu0->va_bits);
+	xe_exec_queue_destroy(gpu0->fd, exec_queue[0]);
+	xe_vm_destroy(gpu0->fd, vm[0]);
+
+	xe_vm_unbind_lr_sync(gpu1->fd, vm[1], 0, 0, 1ull << gpu1->va_bits);
+	xe_exec_queue_destroy(gpu1->fd, exec_queue[1]);
+	xe_vm_destroy(gpu1->fd, vm[1]);
+}
+
 static void
 gpu_atomic_inc(struct xe_svm_gpu_info *src_gpu,
 	       struct xe_svm_gpu_info *dst_gpu,
@@ -1275,6 +1432,18 @@ gpu_pagefault_test(struct xe_svm_gpu_info *src_gpu,
         pagefault_test_multigpu(src_gpu, dst_gpu, eci);
 }
 
+static void
+gpu_access_test(struct xe_svm_gpu_info *src_gpu,
+		struct xe_svm_gpu_info *dst_gpu,
+		struct drm_xe_engine_class_instance *eci,
+		bool no_prefetch)
+{
+	igt_assert(src_gpu);
+	igt_assert(dst_gpu);
+
+	multigpu_access_test(src_gpu, dst_gpu, eci, no_prefetch);
+}
+
 igt_main
 {
 	struct xe_svm_gpu_info gpus[MAX_XE_GPUS];
@@ -1330,6 +1499,11 @@ igt_main
 	igt_subtest("pagefault-multi-gpu")
 		gpu_pagefault_test(&gpus[0], &gpus[1], &eci);
 
+	igt_subtest("concurrent-access-multi-gpu") {
+		gpu_access_test(&gpus[0], &gpus[1], &eci, 0);
+		gpu_access_test(&gpus[0], &gpus[1], &eci, 1);
+	}
+
 	igt_fixture {
 		int cnt;
 
-- 
2.48.1

✗ Fi.CI.BUILD: failure for Madvise feature in SVM for Multi-GPU configs (rev2)

From: Patchwork <hidden>
Date: 2025-11-05 13:29:01

== Series Details ==

Series: Madvise feature in SVM for Multi-GPU configs (rev2)
URL   : https://patchwork.freedesktop.org/series/157019/
State : failure

== Summary ==

Applying: tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU cross-GPU memory access test
Patch failed at 0001 tests/intel/xe_multi_gpusvm.c: Add SVM multi-GPU cross-GPU memory access test
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help