[PATCH 1/1] xe: Add xe_bo_alloc test

Matthew Brost matthew.brost at intel.com
Wed Apr 10 07:12:09 UTC 2024


Add xe_bo_alloc test which allocates BOs of various sizes, binds them,
and does an exec with a dword on every single page. Varies sections
exist to few or many BOs, leak binds, leak BO mappings, trigger
evictions run with threads, and run with multiple processes.

Signed-off-by: Matthew Brost <matthew.brost at intel.com>
---
 lib/xe/xe_ioctl.c         |  12 +
 lib/xe/xe_ioctl.h         |   1 +
 tests/intel/xe_bo_alloc.c | 568 ++++++++++++++++++++++++++++++++++++++
 tests/meson.build         |   1 +
 4 files changed, 582 insertions(+)
 create mode 100644 tests/intel/xe_bo_alloc.c

diff --git a/lib/xe/xe_ioctl.c b/lib/xe/xe_ioctl.c
index 934c877ebc..d2eaa8ecf2 100644
--- a/lib/xe/xe_ioctl.c
+++ b/lib/xe/xe_ioctl.c
@@ -424,6 +424,18 @@ void *xe_bo_map(int fd, uint32_t bo, size_t size)
 	return __xe_bo_map(fd, bo, size, PROT_WRITE);
 }
 
+void *xe_bo_map_fixed(int fd, uint32_t bo, size_t size, uint64_t addr)
+{
+	uint64_t mmo;
+	void *map;
+
+	mmo = xe_bo_mmap_offset(fd, bo);
+	map = mmap((void *)addr, size, PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, mmo);
+	igt_assert(map != MAP_FAILED);
+
+	return map;
+}
+
 void *xe_bo_mmap_ext(int fd, uint32_t bo, size_t size, int prot)
 {
 	return __xe_bo_map(fd, bo, size, prot);
diff --git a/lib/xe/xe_ioctl.h b/lib/xe/xe_ioctl.h
index 4d08402e0b..44657acd65 100644
--- a/lib/xe/xe_ioctl.h
+++ b/lib/xe/xe_ioctl.h
@@ -81,6 +81,7 @@ uint32_t xe_exec_queue_create_class(int fd, uint32_t vm, uint16_t class);
 void xe_exec_queue_destroy(int fd, uint32_t exec_queue);
 uint64_t xe_bo_mmap_offset(int fd, uint32_t bo);
 void *xe_bo_map(int fd, uint32_t bo, size_t size);
+void *xe_bo_map_fixed(int fd, uint32_t bo, size_t size, uint64_t addr);
 void *xe_bo_mmap_ext(int fd, uint32_t bo, size_t size, int prot);
 int __xe_exec(int fd, struct drm_xe_exec *exec);
 void xe_exec(int fd, struct drm_xe_exec *exec);
diff --git a/tests/intel/xe_bo_alloc.c b/tests/intel/xe_bo_alloc.c
new file mode 100644
index 0000000000..68c9494fc2
--- /dev/null
+++ b/tests/intel/xe_bo_alloc.c
@@ -0,0 +1,568 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2024 Intel Corporation
+ */
+
+/**
+ * TEST: Check if BO allocation functionality is working
+ * Category: Software building block
+ * Sub-category: BO
+ * Functionality: BO allocation, BO eviction, VNA binding
+ */
+
+#include <fcntl.h>
+
+#include "igt.h"
+
+#include "lib/igt_syncobj.h"
+#include "lib/intel_reg.h"
+
+#include "xe/xe_ioctl.h"
+#include "xe/xe_query.h"
+
+#include "xe_drm.h"
+
+/**
+ * SUBTEST: all-sizes-once
+ * Description: Test all BO allocations sizes in test table
+ * Test category: functionality test
+ *
+ * SUBTEST: rand-sizes-10
+ * Description: Test 10 random BO allocation sizes in test table
+ * Test category: functionality test
+ *
+ * SUBTEST: rand-sizes-100
+ * Description: Test 100 random BO allocation sizes in test table
+ * Test category: functionality test
+ *
+ * SUBTEST: rand-sizes-100-unaligned
+ * Description: Test 100 random BO allocation sizes in test table, unaligned bind addresses
+ * Test category: functionality test
+ *
+ * SUBTEST: threads-rand-sizes-50
+ * description: test 50 random bo allocation sizes in test table with a thread per engine
+ * test category: stress test
+ *
+ * SUBTEST: threads-rand-sizes-50-unaligned
+ * description: test 50 random bo allocation sizes in test table with a thread per engine, unaligned bind addresses
+ * test category: stress test
+ *
+ * SUBTEST: threads-leak-binding-rand-sizes-50
+ * Description: Test 50 random BO allocation sizes in test table with a thread per engine, leak the binding
+ * Test category: stress test
+ *
+ * SUBTEST: threads-leak-binding-rand-sizes-50-unaligned
+ * Description: Test 50 random BO allocation sizes in test table with a thread per engine, leak the binding, unaligned bind addresses
+ * Test category: stress test
+ *
+ * SUBTEST: processes-rand-sizes-50
+ * Description: Test 50 random BO allocation sizes in test table with 2 process per engine
+ * Test category: stress test
+ *
+ * SUBTEST: processes-rand-sizes-50-unaligned
+ * Description: Test 50 random BO allocation sizes in test table with 2 process per engine, unaligned bind addresses
+ * Test category: stress test
+ *
+ * SUBTEST: processes-leak-binding-rand-sizes-50
+ * Description: Test 50 random BO allocation sizes in test table with 2 process per engine, leak the binding
+ * Test category: stress test
+ *
+ * SUBTEST: processes-leak-binding-rand-sizes-50-unaligned
+ * Description: Test 50 random BO allocation sizes in test table with 2 process per engine, leak the binding, unaligned bind addresses
+ * Test category: stress test
+ *
+ * SUBTEST: processes-leak-bo-rand-sizes-50
+ * Description: Test 50 random BO allocation sizes in test table with 2 process per engine, leak the BO munmap / gem close
+ * Test category: stress test
+ *
+ * SUBTEST: processes-leak-bo-rand-sizes-50-unaligned
+ * Description: Test 50 random BO allocation sizes in test table with 2 process per engine, leak the BO munmap / gem close, unaligned bind addresses
+ * Test category: stress test
+ *
+ * SUBTEST: processes-leak-bo-rand-sizes-evict
+ * Description: Test enough random BO allocation sizes in test table to trigger evictions with 2 process per engine, leak the BO munmap / gem close
+ * Test category: stress test
+ *
+ * SUBTEST: processes-leak-bo-rand-sizes-evict-unaligned
+ * Description: Test enough random BO allocation sizes in test table to trigger evictions with 2 process per engine, leak the BO munmap / gem close, unaligned bind addresses
+ * Test category: stress test
+ */
+
+#define SZ_4K_SHIFT	12
+
+#define N_ALLOC_SIZES	256
+static uint64_t *alloc_sizes = NULL;
+
+static void alloc_sizes_init(void)
+{
+	int i;
+
+	alloc_sizes = malloc(sizeof(*alloc_sizes) * N_ALLOC_SIZES);
+
+	/* For now just do incremnts of 64k */
+	for (i = 0; i < N_ALLOC_SIZES; ++i)
+		alloc_sizes[i] = 0x10000ull * (i + 1);
+}
+
+static void alloc_sizes_fini(void)
+{
+	free(alloc_sizes);
+}
+
+struct batch_data {
+	uint32_t batch[16];
+	uint64_t pad;
+	uint32_t data;
+};
+
+static uint32_t wkey = 0xc0ffeeull;
+#define WRITE_VALUE(page)	(((wkey) << 8) | (page))
+
+static void check_exec_data(void *ptr, int n_pages)
+{
+	int i;
+
+	for (i = 0; i < n_pages; ++i) {
+		struct batch_data *data = ptr + i * SZ_4K;
+
+		igt_assert_eq(data->data, WRITE_VALUE(i));
+	}
+}
+
+static void touch_all_pages(int fd, uint32_t q, void *ptr, uint64_t bo_size)
+{
+	struct drm_xe_sync sync = {
+	    .type = DRM_XE_SYNC_TYPE_SYNCOBJ, .flags = DRM_XE_SYNC_FLAG_SIGNAL,
+	    .handle = syncobj_create(fd, 0),
+	};
+	struct drm_xe_exec exec = {
+		.num_batch_buffer = 1,
+		.num_syncs = 0,
+		.exec_queue_id = q,
+		.syncs = to_user_pointer(&sync),
+	};
+	int i, n_pages = bo_size >> SZ_4K_SHIFT;
+	struct batch_data *data = NULL;
+	uint64_t addr = to_user_pointer(ptr);
+
+	for (i = 0; i < n_pages; ++i, addr += SZ_4K) {
+		uint64_t batch_offset = (char *)&data->batch - (char *)data;
+		uint64_t batch_addr = addr + batch_offset;
+		uint64_t sdi_offset = (char *)&data->data - (char *)data;
+		uint64_t sdi_addr = addr + sdi_offset;
+		int b = 0;
+
+		data = ptr + i * SZ_4K;
+		data->batch[b++] = MI_STORE_DWORD_IMM_GEN4;
+		data->batch[b++] = sdi_addr;
+		data->batch[b++] = sdi_addr >> 32;
+		data->batch[b++] = WRITE_VALUE(i);
+		data->batch[b++] = MI_BATCH_BUFFER_END;
+		igt_assert(b <= ARRAY_SIZE(data->batch));
+
+		exec.address = batch_addr;
+		if (i + 1 == n_pages)
+			exec.num_syncs = 1;
+		xe_exec(fd, &exec);
+	}
+
+	igt_assert(syncobj_wait(fd, &sync.handle, 1, INT64_MAX, 0, NULL));
+	check_exec_data(ptr, n_pages);
+
+	syncobj_destroy(fd, sync.handle);
+}
+
+#define LEAK_BINDING		(0x1 << 0)
+#define LEAK_BO			(0x1 << 1)
+#define EVICT			(0x1 << 2)
+#define UNALIGNED		(0x1 << 3)
+
+struct leak {
+	void *ptr;
+	uint64_t bo_size;
+	uint32_t bo;
+};
+
+static struct leak *test_alloc_size(int fd, uint32_t vm, uint32_t q,
+				    uint16_t gt_id, uint64_t bo_size,
+				    uint32_t flags)
+{
+	uint32_t bo;
+	void *ptr;
+
+	if (flags & UNALIGNED)
+		ptr = aligned_alloc(0x10000, bo_size);
+	else
+		ptr = aligned_alloc(bo_size, bo_size);
+	igt_assert(ptr);
+
+	bo = xe_bo_create(fd, !(flags & EVICT) ? vm : 0, bo_size,
+			  vram_if_possible(fd, gt_id),
+			  DRM_XE_GEM_CREATE_FLAG_NEEDS_VISIBLE_VRAM);
+	xe_vm_bind_sync(fd, vm, bo, 0, to_user_pointer(ptr), bo_size);
+	ptr = xe_bo_map_fixed(fd, bo, bo_size, to_user_pointer(ptr));
+
+	touch_all_pages(fd, q, ptr, bo_size);
+
+	if (!(flags & LEAK_BINDING))
+		xe_vm_unbind_sync(fd, vm, 0, to_user_pointer(ptr), bo_size);
+
+	if (!(flags & LEAK_BO)) {
+		munmap(ptr, bo_size);
+		gem_close(fd, bo);
+	} else {
+		struct leak *leak;
+
+		leak = malloc(sizeof(*leak));
+		igt_assert(leak);
+
+		leak->ptr = ptr;
+		leak->bo_size = bo_size;
+		leak->bo = bo;
+
+		return leak;
+	}
+
+	return NULL;
+}
+
+static void all_sizes_once(int fd, struct drm_xe_engine_class_instance *hwe)
+{
+	uint32_t vm, q;
+	int i;
+
+	vm = xe_vm_create(fd, 0, 0);
+	q =  xe_exec_queue_create(fd, vm, hwe, 0);
+
+	for (i = 0; i < N_ALLOC_SIZES; ++i)
+		test_alloc_size(fd, vm, q, hwe->gt_id, alloc_sizes[i], 0);
+
+	xe_exec_queue_destroy(fd, q);
+	xe_vm_destroy(fd, vm);
+}
+
+static void check_leak(int fd, uint32_t vm, struct leak *leak, uint32_t flags)
+{
+	check_exec_data(leak->ptr, leak->bo_size >> SZ_4K_SHIFT);
+
+	/* Migrate buffer back into VRAM, recheck */
+	if (flags & EVICT) {
+		xe_vm_bind_sync(fd, vm, leak->bo, 0,
+				to_user_pointer(leak->ptr), leak->bo_size);
+
+		check_exec_data(leak->ptr, leak->bo_size >> SZ_4K_SHIFT);
+
+		xe_vm_unbind_sync(fd, vm, 0, to_user_pointer(leak->ptr),
+				  leak->bo_size);
+	}
+
+	munmap(leak->ptr, leak->bo_size);
+	gem_close(fd, leak->bo);
+}
+
+static void check_leaks(int fd, uint32_t vm, struct leak **leaks, int count,
+			uint32_t flags)
+{
+	int i;
+
+	for (i = 0; i < count; ++i) {
+		if (!leaks[i])
+			continue;
+
+		check_leak(fd, vm, leaks[i], flags);
+		free(leaks[i]);
+	}
+}
+
+static void rand_sizes(int fd, struct drm_xe_engine_class_instance *hwe,
+		       int count, uint64_t vram_per_process,
+		       pthread_barrier_t *barrier, uint32_t flags)
+{
+	struct leak **leaks = NULL;
+	uint32_t vm, q;
+	uint64_t vram_used = 0;
+	int i, alloc = (count == -1) ? 50000 : count;
+
+	igt_assert(count > 0 || (flags & EVICT && flags & LEAK_BO));
+
+	leaks = malloc(sizeof(leaks) * alloc);
+	igt_assert(leaks);
+
+	vm = xe_vm_create(fd, 0, 0);
+	q =  xe_exec_queue_create(fd, vm, hwe, 0);
+
+	for (i = 0; i < count || vram_used < vram_per_process; ++i) {
+		uint32_t bo_size = alloc_sizes[rand() % N_ALLOC_SIZES];
+
+		igt_assert(i < alloc);
+		leaks[i] = test_alloc_size(fd, vm, q, hwe->gt_id, bo_size, flags);
+		if (leaks[i])
+			vram_used += leaks[i]->bo_size;
+	}
+
+	if (barrier)
+		pthread_barrier_wait(barrier);
+	check_leaks(fd, vm, leaks, i, flags);
+
+	xe_exec_queue_destroy(fd, q);
+	xe_vm_destroy(fd, vm);
+	free(leaks);
+}
+
+struct thread_data {
+	pthread_t thread;
+	pthread_mutex_t *mutex;
+	pthread_cond_t *cond;
+	struct drm_xe_engine_class_instance *hwe;
+	int fd;
+	int count;
+	uint32_t vm;
+	uint32_t flags;
+	bool *go;
+};
+
+static void *thread(void *data)
+{
+	struct thread_data *t = data;
+	uint32_t q;
+	int i;
+
+	igt_assert(!(t->flags & LEAK_BO));
+
+	pthread_mutex_lock(t->mutex);
+	while (!*t->go)
+		pthread_cond_wait(t->cond, t->mutex);
+	pthread_mutex_unlock(t->mutex);
+
+	q =  xe_exec_queue_create(t->fd, t->vm, t->hwe, 0);
+
+	for (i = 0; i < t->count; ++i)
+		test_alloc_size(t->fd, t->vm, q, t->hwe->gt_id,
+				alloc_sizes[rand() % N_ALLOC_SIZES],
+				t->flags);
+
+	xe_exec_queue_destroy(t->fd, q);
+
+	return NULL;
+}
+
+static void threads(int fd, int count, uint32_t flags)
+{
+	struct drm_xe_engine_class_instance *hwe;
+	struct thread_data *threads_data;
+	pthread_mutex_t mutex;
+	pthread_cond_t cond;
+	int n_engines = 0, i = 0;
+	uint32_t vm = 0;
+	bool go = false;
+
+	vm = xe_vm_create(fd, 0, 0);
+
+	xe_for_each_engine(fd, hwe)
+		++n_engines;
+
+	threads_data = calloc(n_engines, sizeof(*threads_data));
+	igt_assert(threads_data);
+
+	pthread_mutex_init(&mutex, 0);
+	pthread_cond_init(&cond, 0);
+
+	xe_for_each_engine(fd, hwe) {
+		threads_data[i].mutex = &mutex;
+		threads_data[i].cond = &cond;
+		threads_data[i].hwe = hwe;
+		threads_data[i].fd = fd;
+		threads_data[i].count = count;
+		threads_data[i].vm = vm;
+		threads_data[i].flags = flags;
+		threads_data[i].go = &go;
+		pthread_create(&threads_data[i].thread, 0, thread,
+			       &threads_data[i]);
+		++i;
+	}
+
+	pthread_mutex_lock(&mutex);
+	go = true;
+	pthread_cond_broadcast(&cond);
+	pthread_mutex_unlock(&mutex);
+
+	for (i = 0; i < n_engines; ++i)
+		pthread_join(threads_data[i].thread, NULL);
+
+	xe_vm_destroy(fd, vm);
+}
+
+#define SYNC_FILE	"/tmp/xe_bo_alloc_sync"
+
+struct process_data {
+	pthread_mutex_t mutex;
+	pthread_cond_t cond;
+	pthread_barrier_t barrier;
+	bool go;
+};
+
+static void process(struct drm_xe_engine_class_instance *hwe,
+		    int count, uint64_t vram_per_process, uint32_t flags)
+{
+	struct process_data *pdata;
+	int map_fd;
+	int fd;
+
+	wkey = rand() & 0xffffffull;
+
+	map_fd = open(SYNC_FILE, O_RDWR, 0x666);
+	pdata = mmap(NULL, sizeof(*pdata), PROT_READ |
+		     PROT_WRITE, MAP_SHARED, map_fd, 0);
+
+	pthread_mutex_lock(&pdata->mutex);
+	while (!pdata->go)
+		pthread_cond_wait(&pdata->cond, &pdata->mutex);
+	pthread_mutex_unlock(&pdata->mutex);
+
+	fd = drm_open_driver(DRIVER_XE);
+	rand_sizes(fd, hwe, count, vram_per_process, &pdata->barrier, flags);
+	drm_close_driver(fd);
+
+	close(map_fd);
+	munmap(pdata, sizeof(*pdata));
+}
+
+static void processes(int fd, int count, uint32_t flags)
+{
+	struct drm_xe_engine_class_instance *hwe;
+	struct process_data *pdata;
+	pthread_mutexattr_t mutex_attr;
+	pthread_condattr_t cond_attr;
+	pthread_barrierattr_t barrier_attr;
+	uint64_t vram_per_process = 0;
+	int map_fd, n_engines = 0;
+
+	igt_assert(count > 0 || (flags & EVICT && flags & LEAK_BO));
+
+	xe_for_each_engine(fd, hwe)
+		++n_engines;
+
+	if (flags & EVICT)
+		vram_per_process = (xe_visible_vram_size(fd, 0) * 3) /
+			(n_engines * 4);
+
+	map_fd = open(SYNC_FILE, O_RDWR | O_CREAT, 0x666);
+	posix_fallocate(map_fd, 0, sizeof(*pdata));
+	pdata = mmap(NULL, sizeof(*pdata), PROT_READ |
+		     PROT_WRITE, MAP_SHARED, map_fd, 0);
+
+	pthread_mutexattr_init(&mutex_attr);
+	pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(&pdata->mutex, &mutex_attr);
+
+	pthread_condattr_init(&cond_attr);
+	pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
+	pthread_cond_init(&pdata->cond, &cond_attr);
+
+	pthread_barrierattr_init(&barrier_attr);
+	pthread_barrierattr_setpshared(&barrier_attr, PTHREAD_PROCESS_SHARED);
+	pthread_barrier_init(&pdata->barrier, &barrier_attr, n_engines * 2);
+
+	pdata->go = false;
+
+	xe_for_each_engine(fd, hwe) {
+		igt_fork(child, 2)
+			process(hwe, count, vram_per_process, flags);
+	}
+
+	pthread_mutex_lock(&pdata->mutex);
+	pdata->go = true;
+	pthread_cond_broadcast(&pdata->cond);
+	pthread_mutex_unlock(&pdata->mutex);
+
+	igt_waitchildren();
+
+	close(map_fd);
+	munmap(pdata, sizeof(*pdata));
+}
+
+igt_main
+{
+	struct drm_xe_engine_class_instance *hwe;
+	int fd;
+
+	igt_fixture {
+		fd = drm_open_driver(DRIVER_XE);
+		alloc_sizes_init();
+	}
+
+	igt_subtest_f("all-sizes-once")
+		xe_for_each_engine(fd, hwe) {
+			all_sizes_once(fd, hwe);
+			break;
+		}
+
+	igt_subtest_f("rand-sizes-10")
+		xe_for_each_engine(fd, hwe) {
+			rand_sizes(fd, hwe, 10, 0, NULL, 0);
+			break;
+		}
+
+	igt_subtest_f("rand-sizes-100")
+		xe_for_each_engine(fd, hwe) {
+			rand_sizes(fd, hwe, 100, 0, NULL, 0);
+			break;
+		}
+
+	igt_subtest_f("rand-sizes-100-unaligned")
+		xe_for_each_engine(fd, hwe) {
+			rand_sizes(fd, hwe, 100, 0, NULL, UNALIGNED);
+			break;
+		}
+
+	igt_subtest_f("threads-rand-sizes-50")
+		threads(fd, 50, 0);
+
+	igt_subtest_f("threads-rand-sizes-50-unaligned")
+		threads(fd, 50, UNALIGNED);
+
+	igt_subtest_f("threads-leak-binding-rand-sizes-50")
+		threads(fd, 50, LEAK_BINDING);
+
+	igt_subtest_f("threads-leak-binding-rand-sizes-50-unaligned")
+		threads(fd, 50, LEAK_BINDING | UNALIGNED);
+
+	igt_subtest_f("processes-rand-sizes-50")
+		processes(fd, 50, 0);
+
+	igt_subtest_f("processes-rand-sizes-50-unaligned")
+		processes(fd, 50, UNALIGNED);
+
+	igt_subtest_f("processes-leak-binding-rand-sizes-50")
+		processes(fd, 50, LEAK_BINDING);
+
+	igt_subtest_f("processes-leak-binding-rand-sizes-50-unaligned")
+		processes(fd, 50, LEAK_BINDING | UNALIGNED);
+
+	igt_subtest_f("processes-leak-bo-rand-sizes-50")
+		processes(fd, 50, LEAK_BO);
+
+	igt_subtest_f("processes-leak-bo-rand-sizes-50-unaligned")
+		processes(fd, 50, LEAK_BO | UNALIGNED);
+
+	igt_subtest_f("processes-leak-bo-rand-sizes-evict") {
+		igt_require(xe_has_vram(fd));
+		igt_require(igt_get_avail_ram_mb() >=
+			    (xe_visible_vram_size(fd, 0) >> 20) / 2);
+
+		processes(fd, -1, LEAK_BO | EVICT);
+	}
+
+	igt_subtest_f("processes-leak-bo-rand-sizes-evict-unaligned") {
+		igt_require(xe_has_vram(fd));
+		igt_require(igt_get_avail_ram_mb() >=
+			    (xe_visible_vram_size(fd, 0) >> 20) / 2);
+
+		processes(fd, -1, LEAK_BO | EVICT | UNALIGNED);
+	}
+
+	igt_fixture {
+		alloc_sizes_fini();
+		drm_close_driver(fd);
+	}
+}
diff --git a/tests/meson.build b/tests/meson.build
index a856510fce..52ec4de433 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -274,6 +274,7 @@ intel_kms_progs = [
 ]
 
 intel_xe_progs = [
+	'xe_bo_alloc',
 	'xe_ccs',
 	'xe_create',
 	'xe_compute',
-- 
2.34.1



More information about the igt-dev mailing list