[PATCH i-g-t, v2] tests/intel/xe_exec_mix_modes: Add new tests for parallel execution

Francois Dugast francois.dugast at intel.com
Wed Jul 17 16:35:19 UTC 2024


Test parallel execution of LR and dma fence jobs on the same device.

Add the following tests:
* "exec-simple-batch-store-lr"
* "exec-simple-batch-store-dma-fence"
* "exec-spinner-interrupted-lr"
* "exec-spinner-interrupted-dma-fence"

v2: Remove useless exec queue priority, multiple job submissions
    even when running without preemption, nits (Matt Brost)

Signed-off-by: Francois Dugast <francois.dugast at intel.com>
---
 tests/intel/xe_exec_mix_modes.c | 268 ++++++++++++++++++++++++++++++++
 tests/meson.build               |   1 +
 2 files changed, 269 insertions(+)
 create mode 100644 tests/intel/xe_exec_mix_modes.c

diff --git a/tests/intel/xe_exec_mix_modes.c b/tests/intel/xe_exec_mix_modes.c
new file mode 100644
index 000000000..f7bb96255
--- /dev/null
+++ b/tests/intel/xe_exec_mix_modes.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2024 Intel Corporation
+ */
+
+/**
+ * TEST: Test the parallel submission of jobs in LR and dma fence modes
+ * Category: Core
+ * Mega feature: General Core features
+ * Sub-category: CMD submission
+ * Functionality: fault mode
+ * GPU requirements: GPU needs support for DRM_XE_VM_CREATE_FLAG_FAULT_MODE
+ */
+
+#include <fcntl.h>
+
+#include "igt.h"
+#include "lib/igt_syncobj.h"
+#include "lib/intel_reg.h"
+#include "xe_drm.h"
+
+#include "xe/xe_ioctl.h"
+#include "xe/xe_query.h"
+#include "xe/xe_spin.h"
+#include <string.h>
+
+#define FLAG_EXEC_MODE_LR	(0x1 << 0)
+#define FLAG_JOB_TYPE_SIMPLE	(0x1 << 1)
+
+#define NUM_INTERRUPTING_JOBS	5
+#define USER_FENCE_VALUE	0xdeadbeefdeadbeefull
+#define ONE_SEC			MS_TO_NS(1000)
+#define VM_DATA			0
+#define SPIN_DATA		1
+#define EXEC_DATA		2
+#define DATA_COUNT		3
+
+struct data {
+	struct xe_spin spin;
+	uint32_t batch[16];
+	uint64_t vm_sync;
+	uint32_t data;
+	uint64_t exec_sync;
+	uint64_t addr;
+};
+
+static void store_dword_batch(struct data *data, uint64_t addr, int value)
+{
+	int b;
+	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;
+
+	b = 0;
+	data->batch[b++] = MI_STORE_DWORD_IMM_GEN4;
+	data->batch[b++] = sdi_addr;
+	data->batch[b++] = sdi_addr >> 32;
+	data->batch[b++] = value;
+	data->batch[b++] = MI_BATCH_BUFFER_END;
+	igt_assert(b <= ARRAY_SIZE(data->batch));
+
+	data->addr = batch_addr;
+}
+
+enum engine_execution_mode {
+	EXEC_MODE_LR,
+	EXEC_MODE_DMA_FENCE,
+};
+
+enum job_type {
+	SIMPLE_BATCH_STORE,
+	SPINNER_INTERRUPTED,
+};
+
+static void
+run_job(int fd, struct drm_xe_engine_class_instance *hwe,
+	enum engine_execution_mode engine_execution_mode,
+	enum job_type job_type, bool allow_recursion)
+{
+	struct drm_xe_sync sync[1] = {
+		{ .flags = DRM_XE_SYNC_FLAG_SIGNAL, },
+	};
+	struct drm_xe_exec exec = {
+		.num_batch_buffer = 1,
+		.num_syncs = 1,
+		.syncs = to_user_pointer(&sync),
+	};
+	struct data *data;
+	uint32_t vm;
+	uint32_t exec_queue;
+	size_t bo_size;
+	int value = 0x123456;
+	uint64_t addr = 0x100000;
+	uint32_t bo = 0;
+	unsigned int vm_flags = 0;
+	struct xe_spin_opts spin_opts = { .preempt = true };
+	const uint64_t duration_ns = NSEC_PER_SEC / 2; /* 500ms */
+	struct timespec tv;
+	enum engine_execution_mode interrupting_engine_execution_mode;
+
+	if (engine_execution_mode == EXEC_MODE_LR) {
+		sync[0].type = DRM_XE_SYNC_TYPE_USER_FENCE;
+		sync[0].timeline_value = USER_FENCE_VALUE;
+		vm_flags = DRM_XE_VM_CREATE_FLAG_LR_MODE | DRM_XE_VM_CREATE_FLAG_FAULT_MODE;
+	} else if (engine_execution_mode == EXEC_MODE_DMA_FENCE) {
+		sync[0].type = DRM_XE_SYNC_TYPE_SYNCOBJ;
+		sync[0].handle = syncobj_create(fd, 0);
+	}
+
+	vm = xe_vm_create(fd, vm_flags, 0);
+	bo_size = sizeof(*data) * DATA_COUNT;
+	bo_size = xe_bb_size(fd, bo_size);
+	bo = xe_bo_create(fd, vm, bo_size,
+			  vram_if_possible(fd, hwe->gt_id),
+			  DRM_XE_GEM_CREATE_FLAG_NEEDS_VISIBLE_VRAM);
+	data = xe_bo_map(fd, bo, bo_size);
+	if (engine_execution_mode == EXEC_MODE_LR)
+		sync[0].addr = to_user_pointer(&data[VM_DATA].vm_sync);
+	xe_vm_bind_async(fd, vm, 0, bo, 0, addr, bo_size, &sync[0], 1);
+
+	store_dword_batch(data, addr, value);
+	if (engine_execution_mode == EXEC_MODE_LR) {
+		xe_wait_ufence(fd, &data[VM_DATA].vm_sync, USER_FENCE_VALUE, 0, ONE_SEC);
+		sync[0].addr = addr + (char *)&data[EXEC_DATA].exec_sync - (char *)data;
+	} else if (engine_execution_mode == EXEC_MODE_DMA_FENCE) {
+		igt_assert(syncobj_wait(fd, &sync[0].handle, 1, INT64_MAX, 0, NULL));
+		syncobj_reset(fd, &sync[0].handle, 1);
+		sync[0].flags &= DRM_XE_SYNC_FLAG_SIGNAL;
+	}
+	exec_queue = xe_exec_queue_create(fd, vm, hwe, 0);
+	exec.exec_queue_id = exec_queue;
+
+	if (job_type == SPINNER_INTERRUPTED) {
+		spin_opts.addr = addr + (char *)&data[SPIN_DATA].spin - (char *)data;
+		spin_opts.ctx_ticks = duration_to_ctx_ticks(fd, 0, duration_ns);
+		xe_spin_init(&data[SPIN_DATA].spin, &spin_opts);
+		if (engine_execution_mode == EXEC_MODE_LR)
+			sync[0].addr = addr + (char *)&data[SPIN_DATA].exec_sync - (char *)data;
+		exec.address = spin_opts.addr;
+	} else if (job_type == SIMPLE_BATCH_STORE) {
+		exec.address = data->addr;
+	}
+	xe_exec(fd, &exec);
+
+	if (job_type == SPINNER_INTERRUPTED) {
+		if (engine_execution_mode == EXEC_MODE_LR)
+			interrupting_engine_execution_mode = EXEC_MODE_DMA_FENCE;
+		else if (engine_execution_mode == EXEC_MODE_DMA_FENCE)
+			interrupting_engine_execution_mode = EXEC_MODE_LR;
+		xe_spin_wait_started(&data[SPIN_DATA].spin);
+	} else if (job_type == SIMPLE_BATCH_STORE) {
+		interrupting_engine_execution_mode = engine_execution_mode;
+	}
+
+	if (allow_recursion) {
+		igt_gettime(&tv);
+		for (int i = 0; i < NUM_INTERRUPTING_JOBS; i++)
+		{
+			run_job(fd, hwe, interrupting_engine_execution_mode, SIMPLE_BATCH_STORE, false);
+			/**
+			 * Executing a SIMPLE_BATCH_STORE job takes significantly less time than
+			 * duration_ns.
+			 * When a spinner is running in LR mode, the interrupting job preempts it
+			 * in KMD and should complete fast, shortly after starting the spinner.
+			 * When a spinner is running in dma fence mode, the interrupting job waits
+			 * in KMD and should complete shortly after the spinner has ended.
+			 * The checks below are to verify preempting/waiting happens as expected
+			 * depending on the execution mode.
+			 */
+			if (engine_execution_mode == EXEC_MODE_LR)
+				igt_assert(igt_nsec_elapsed(&tv) < 0.5 * duration_ns);
+			else if (engine_execution_mode == EXEC_MODE_DMA_FENCE &&
+				 job_type == SPINNER_INTERRUPTED)
+				igt_assert(igt_nsec_elapsed(&tv) > duration_ns);
+		}
+	}
+
+	if (engine_execution_mode == EXEC_MODE_LR) {
+		if (job_type == SPINNER_INTERRUPTED)
+			xe_wait_ufence(fd, &data[SPIN_DATA].exec_sync, USER_FENCE_VALUE, 0, ONE_SEC);
+		else if (job_type == SIMPLE_BATCH_STORE)
+			xe_wait_ufence(fd, &data[EXEC_DATA].exec_sync, USER_FENCE_VALUE, 0, ONE_SEC);
+	} else if (engine_execution_mode == EXEC_MODE_DMA_FENCE) {
+		igt_assert(syncobj_wait(fd, &sync[0].handle, 1, INT64_MAX, 0, NULL));
+		syncobj_destroy(fd, sync[0].handle);
+	}
+
+	if (job_type == SIMPLE_BATCH_STORE)
+		igt_assert_eq(data->data, value);
+
+	munmap(data, bo_size);
+	gem_close(fd, bo);
+	xe_exec_queue_destroy(fd, exec_queue);
+	xe_vm_destroy(fd, vm);
+}
+
+/**
+ * SUBTEST: exec-simple-batch-store-lr
+ * Description: Execute a simple batch store job in long running mode
+ *
+ * SUBTEST: exec-simple-batch-store-dma-fence
+ * Description: Execute a simple batch store job in dma fence mode
+ *
+ * SUBTEST: exec-spinner-interrupted-lr
+ * Description: Spin in long running mode then get interrupted by a simple
+ *              batch store job in dma fence mode
+ *
+ * SUBTEST: exec-spinner-interrupted-dma-fence
+ * Description: Spin in dma fence mode then get interrupted by a simple
+ *              batch store job in long running mode
+ */
+static void
+test_exec(int fd, struct drm_xe_engine_class_instance *hwe,
+	  unsigned int flags)
+{
+	enum engine_execution_mode engine_execution_mode;
+	enum job_type job_type;
+
+	if (flags & FLAG_EXEC_MODE_LR)
+		engine_execution_mode = EXEC_MODE_LR;
+	else
+		engine_execution_mode = EXEC_MODE_DMA_FENCE;
+
+	if (flags & FLAG_JOB_TYPE_SIMPLE)
+		job_type = SIMPLE_BATCH_STORE;
+	else
+		job_type = SPINNER_INTERRUPTED;
+
+	run_job(fd, hwe, engine_execution_mode, job_type, true);
+}
+
+igt_main
+{
+	struct drm_xe_engine_class_instance *hwe;
+	const struct section {
+		const char *name;
+		unsigned int flags;
+	} sections[] = {
+		{ "simple-batch-store-lr", FLAG_JOB_TYPE_SIMPLE | FLAG_EXEC_MODE_LR },
+		{ "simple-batch-store-dma-fence", FLAG_JOB_TYPE_SIMPLE },
+		{ "spinner-interrupted-lr", FLAG_EXEC_MODE_LR },
+		{ "spinner-interrupted-dma-fence", 0 },
+		{ NULL },
+	};
+	int fd;
+
+	igt_fixture {
+		bool supports_faults;
+		int ret = 0;
+
+		fd = drm_open_driver(DRIVER_XE);
+		ret = xe_supports_faults(fd);
+		supports_faults = !ret;
+		igt_require(supports_faults);
+	}
+
+	for (const struct section *s = sections; s->name; s++) {
+		igt_subtest_f("exec-%s", s->name)
+			xe_for_each_engine(fd, hwe)
+				if (hwe->engine_class == DRM_XE_ENGINE_CLASS_COMPUTE)
+					test_exec(fd, hwe, s->flags);
+	}
+
+	igt_fixture {
+		drm_close_driver(fd);
+	}
+}
diff --git a/tests/meson.build b/tests/meson.build
index 357db2723..e649466be 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -286,6 +286,7 @@ intel_xe_progs = [
 	'xe_exec_basic',
 	'xe_exec_compute_mode',
 	'xe_exec_fault_mode',
+	'xe_exec_mix_modes',
 	'xe_exec_queue_property',
 	'xe_exec_reset',
 	'xe_exec_sip',
-- 
2.43.0



More information about the igt-dev mailing list