All of lore.kernel.org
 help / color / mirror / Atom feed
From: Zhanjun Dong <zhanjun.dong@intel.com>
To: igt-dev@lists.freedesktop.org
Cc: Zhanjun Dong <zhanjun.dong@intel.com>,
	Alan Previn <alan.previn.teres.alexis@intel.com>,
	Kamil Konieczny <kamil.konieczny@linux.intel.com>
Subject: [PATCH v9] tests/intel/xe_exec_capture: Add xe_exec_capture test
Date: Fri,  6 Dec 2024 14:59:11 -0800	[thread overview]
Message-ID: <20241206225911.465603-1-zhanjun.dong@intel.com> (raw)

Submit cmds to the GPU that result in a GuC engine reset and check that
devcoredump register dump is generated, by the GuC, and includes the
full register range.

Signed-off-by: Zhanjun Dong <zhanjun.dong@intel.com>
Cc: Alan Previn <alan.previn.teres.alexis@intel.com>
Cc: Kamil Konieczny <kamil.konieczny@linux.intel.com>
---
Changes from prior revs:
 v9:-  Reduced job timeout to 2 seconds to speedup test
       Add info print to show test is running on single/multiple GPU
 v8:-  Move change list below ---
 v7:-  Fix typo and removed unused macros
 v6:-  Adjust start_line to start from 0
       Use 7 bit engine_cid, start with random number
       Add ioerror detect on fgets
       Reorgnize the regular expression
       Remove unnecessary radom seed init
 v5:-  Detect devcoredump matches the testing engine
       Engine will run with random cid
 v4:-  Support runs on multiple GPU
       Load all devcoredump content to buffer
       Alloc line buffer dynamic vs static global memory
       Changed to igt_assert_f to provide more info if failed
 v3:-  Remove call to bash and awk
       Add regular express parse
       Detect devcoredump through card index
       Add devcoredump removal check
 v2:-  Fix CI.build error
       Add multiple GPU card support
 tests/intel/xe_exec_capture.c | 510 ++++++++++++++++++++++++++++++++++
 tests/meson.build             |   1 +
 2 files changed, 511 insertions(+)
 create mode 100644 tests/intel/xe_exec_capture.c

diff --git a/tests/intel/xe_exec_capture.c b/tests/intel/xe_exec_capture.c
new file mode 100644
index 000000000..6123df7b4
--- /dev/null
+++ b/tests/intel/xe_exec_capture.c
@@ -0,0 +1,510 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2024 Intel Corporation
+ */
+
+/**
+ * TEST: Basic tests for GuC based register capture
+ * Category: Core
+ * Mega feature: General Core features
+ * Sub-category: CMD submission
+ * Functionality: Debug
+ * Test category: functionality test
+ */
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "igt.h"
+#include "igt_device.h"
+#include "igt_sysfs.h"
+#include "lib/igt_syncobj.h"
+#include "lib/intel_reg.h"
+#include "linux_scaffold.h"
+#include "xe_drm.h"
+#include "xe/xe_ioctl.h"
+#include "xe/xe_query.h"
+#include "xe/xe_spin.h"
+
+#define MAX_N_EXECQUEUES		16
+#define CAPTURE_JOB_TIMEOUT		2000
+#define JOB_TIMOUT_ENTRY		"job_timeout_ms"
+
+#define BASE_ADDRESS			0x1a0000
+#define ADDRESS_SHIFT			39
+#define CID_ADDRESS_MASK		0x7F
+/* Batch buffer element count, in number of dwords(u32) */
+#define BATCH_DW_COUNT			16
+
+#define MAX_TEMP_LEN			80
+#define MAX_SYSFS_PATH_LEN		128
+#define MAX_LINES			4096
+/* Max line buffer size (includes last '\0') */
+#define MAX_LINE_LEN			1024
+#define MAIN_BUF_SIZE			(MAX_LINES * MAX_LINE_LEN * sizeof(char))
+/*
+ * Devcoredump might have long line this test don't care.
+ * This buffer size used when load dump content
+ */
+#define LINE_BUF_SIZE			(64 * 1024)
+
+#define DUMP_PATH			"/sys/class/drm/card%d/device/devcoredump/data"
+#define START_TAG			"**** Job ****"
+#define END_TAG				"**** VM state ****"
+
+/* Optional Space */
+#define SPC_O				"[ \t]*"
+/* Required Space */
+#define SPC				"[ \t]+"
+/* Optional Non-Space */
+#define NSPC_O				"([^ \t]*)"
+/* Required Non-Space */
+#define NSPC				"([^ \t]+)"
+#define BEG				"^" SPC_O
+#define REQ_FIELD			NSPC SPC
+#define REQ_FIELD_LAST			NSPC SPC_O
+#define OPT_FIELD			NSPC_O SPC_O
+#define END				SPC_O "$"
+
+#define REGEX_NON_SPACE_GROUPS	BEG REQ_FIELD REQ_FIELD_LAST OPT_FIELD OPT_FIELD OPT_FIELD END
+#define REGEX_NON_SPACE_GROUPS_COUNT	6
+
+#define INDEX_KEY			1
+#define INDEX_VALUE			2
+#define INDEX_ENGINE_PHYSICAL		2
+#define INDEX_ENGINE_NAME		1
+#define INDEX_ENGINE_INSTANCE		4
+
+static u64
+xe_sysfs_get_job_timeout_ms(int fd, struct drm_xe_engine_class_instance *eci)
+{
+	int engine_fd = -1;
+	u64 ret;
+
+	engine_fd = xe_sysfs_engine_open(fd, eci->gt_id, eci->engine_class);
+	ret = igt_sysfs_get_u64(engine_fd, JOB_TIMOUT_ENTRY);
+	close(engine_fd);
+
+	return ret;
+}
+
+static void xe_sysfs_set_job_timeout_ms(int fd, struct drm_xe_engine_class_instance *eci,
+					u64 timeout)
+{
+	int engine_fd = -1;
+
+	engine_fd = xe_sysfs_engine_open(fd, eci->gt_id, eci->engine_class);
+	igt_sysfs_set_u64(engine_fd, JOB_TIMOUT_ENTRY, CAPTURE_JOB_TIMEOUT);
+	close(engine_fd);
+}
+
+static char *safe_strncpy(char *dst, const char *src, int n)
+{
+	char *s;
+
+	igt_assert(n > 0);
+	igt_assert(dst && src);
+
+	s = strncpy(dst, src, n - 1);
+	s[n - 1] = '\0';
+
+	return s;
+}
+
+static const char *xe_engine_class_name(u32 engine_class)
+{
+	switch (engine_class) {
+	case DRM_XE_ENGINE_CLASS_RENDER:
+		return "rcs";
+	case DRM_XE_ENGINE_CLASS_COPY:
+		return "bcs";
+	case DRM_XE_ENGINE_CLASS_VIDEO_DECODE:
+		return "vcs";
+	case DRM_XE_ENGINE_CLASS_VIDEO_ENHANCE:
+		return "vecs";
+	case DRM_XE_ENGINE_CLASS_COMPUTE:
+		return "ccs";
+	default:
+		igt_warn("Engine class 0x%x unknown\n", engine_class);
+		return "unknown";
+	}
+}
+
+static void
+test_legacy_mode(int fd, struct drm_xe_engine_class_instance *eci, int n_exec_queues, int n_execs,
+		 unsigned int flags, u64 addr)
+{
+	u32 vm;
+	struct drm_xe_sync sync[2] = {
+		{ .type = DRM_XE_SYNC_TYPE_SYNCOBJ, .flags = DRM_XE_SYNC_FLAG_SIGNAL, },
+		{ .type = DRM_XE_SYNC_TYPE_SYNCOBJ, .flags = DRM_XE_SYNC_FLAG_SIGNAL, },
+	};
+	struct drm_xe_exec exec = {
+		.num_batch_buffer = 1,
+		.num_syncs = 2,
+		.syncs = to_user_pointer(sync),
+	};
+	u32 exec_queues[MAX_N_EXECQUEUES];
+	u32 syncobjs[MAX_N_EXECQUEUES];
+	size_t bo_size;
+	u32 bo = 0;
+	struct {
+		struct xe_spin spin;
+		u32 batch[BATCH_DW_COUNT];
+		u64 pad;
+		u32 data;
+	} *data;
+	struct xe_spin_opts spin_opts = { .preempt = false };
+	int i, b;
+
+	igt_assert_lte(n_exec_queues, MAX_N_EXECQUEUES);
+
+	vm = xe_vm_create(fd, 0, 0);
+	bo_size = sizeof(*data) * n_execs;
+	bo_size = xe_bb_size(fd, bo_size);
+
+	bo = xe_bo_create(fd, vm, bo_size,
+			  vram_if_possible(fd, eci->gt_id),
+			  DRM_XE_GEM_CREATE_FLAG_NEEDS_VISIBLE_VRAM);
+	data = xe_bo_map(fd, bo, bo_size);
+
+	for (i = 0; i < n_exec_queues; i++) {
+		exec_queues[i] = xe_exec_queue_create(fd, vm, eci, 0);
+		syncobjs[i] = syncobj_create(fd, 0);
+	};
+
+	sync[0].handle = syncobj_create(fd, 0);
+	xe_vm_bind_async(fd, vm, 0, bo, 0, addr, bo_size, sync, 1);
+
+	for (i = 0; i < n_execs; i++) {
+		u64 base_addr = addr;
+		u64 batch_offset = (char *)&data[i].batch - (char *)data;
+		u64 batch_addr = base_addr + batch_offset;
+		u64 spin_offset = (char *)&data[i].spin - (char *)data;
+		u64 sdi_offset = (char *)&data[i].data - (char *)data;
+		u64 sdi_addr = base_addr + sdi_offset;
+		u64 exec_addr;
+		int e = i % n_exec_queues;
+
+		if (!i) {
+			spin_opts.addr = base_addr + spin_offset;
+			xe_spin_init(&data[i].spin, &spin_opts);
+			exec_addr = spin_opts.addr;
+		} else {
+			b = 0;
+			data[i].batch[b++] = MI_STORE_DWORD_IMM_GEN4;
+			data[i].batch[b++] = sdi_addr;
+			data[i].batch[b++] = sdi_addr >> 32;
+			data[i].batch[b++] = 0xc0ffee;
+			data[i].batch[b++] = MI_BATCH_BUFFER_END;
+			igt_assert(b <= ARRAY_SIZE(data[i].batch));
+
+			exec_addr = batch_addr;
+		}
+
+		sync[0].flags &= ~DRM_XE_SYNC_FLAG_SIGNAL;
+		sync[1].flags |= DRM_XE_SYNC_FLAG_SIGNAL;
+		sync[1].handle = syncobjs[e];
+
+		exec.exec_queue_id = exec_queues[e];
+		exec.address = exec_addr;
+		if (e != i)
+			syncobj_reset(fd, &syncobjs[e], 1);
+		xe_exec(fd, &exec);
+	}
+
+	for (i = 0; i < n_exec_queues && n_execs; i++)
+		igt_assert(syncobj_wait(fd, &syncobjs[i], 1, INT64_MAX, 0,
+					NULL));
+	igt_assert(syncobj_wait(fd, &sync[0].handle, 1, INT64_MAX, 0, NULL));
+
+	sync[0].flags |= DRM_XE_SYNC_FLAG_SIGNAL;
+	xe_vm_unbind_async(fd, vm, 0, 0, addr, bo_size, sync, 1);
+	igt_assert(syncobj_wait(fd, &sync[0].handle, 1, INT64_MAX, 0, NULL));
+
+	syncobj_destroy(fd, sync[0].handle);
+	for (i = 0; i < n_exec_queues; i++) {
+		syncobj_destroy(fd, syncobjs[i]);
+		xe_exec_queue_destroy(fd, exec_queues[i]);
+	}
+
+	munmap(data, bo_size);
+	gem_close(fd, bo);
+	xe_vm_destroy(fd, vm);
+}
+
+static char **alloc_lines_buffer(void)
+{
+	int i;
+	char **lines = (char **)malloc(MAX_LINES * sizeof(char *));
+	char *main_buf =  (char *)malloc(MAIN_BUF_SIZE);
+
+	igt_assert_f(lines, "Out of memory.\n");
+	igt_assert_f(main_buf, "Out of memory.\n");
+
+	/* set string array pointers */
+	for (i = 0; i < MAX_LINES; i++)
+		lines[i] = main_buf + i * MAX_LINE_LEN;
+
+	return lines;
+}
+
+static char *get_devcoredump_path(int card_id, char *buf)
+{
+	sprintf(buf, DUMP_PATH, card_id);
+	return buf;
+}
+
+static int load_all(FILE *fd, char **lines, char *buf)
+{
+	int start_line = 0, i = 0;
+	bool skip = true;
+
+	memset(lines[0], 0, MAIN_BUF_SIZE);
+	while (!feof(fd) && i < MAX_LINES) {
+		/*
+		 * Devcoredump might have long lines, load up to
+		 * LINE_BUF_SIZE for a single line
+		 */
+		if (!fgets(buf, LINE_BUF_SIZE, fd))
+			if (ferror(fd) != 0) {
+				igt_warn("Failed to read devcoredump file, error: %d\n",
+					 ferror(fd));
+				break;
+			}
+
+		if (skip) {
+			start_line++;
+			/* Skip all lines before START_TAG */
+			if (strncmp(START_TAG, buf, strlen(START_TAG)))
+				continue;
+			else
+				skip = false;
+		}
+
+		/* Only save up to MAX_LINE_LEN to buffer */
+		safe_strncpy(lines[i++], buf, MAX_LINE_LEN);
+
+		/* Stop on END_TAG */
+		if (!strncmp(END_TAG, buf, strlen(END_TAG)))
+			break;
+	}
+	return start_line;
+}
+
+static int access_devcoredump(char *path, char **lines, char *line_buf)
+{
+	int start_line = -1;
+	FILE *fd = fopen(path, "r");
+
+	if (!fd)
+		return false;
+
+	igt_debug("Devcoredump found: %s\n", path);
+
+	/* Clear memory before load file */
+	if (lines)
+		start_line = load_all(fd, lines, line_buf);
+
+	fclose(fd);
+	return start_line;
+}
+
+static bool rm_devcoredump(char *path)
+{
+	int fd = open(path, O_WRONLY);
+
+	if (fd != -1) {
+		igt_debug("Clearing devcoredump.\n");
+		write(fd, "0", 1);
+		close(fd);
+		return true;
+	}
+
+	return false;
+}
+
+static char
+*get_coredump_item(regex_t *regex, char **lines, const char *tag, int tag_index, int target_index)
+{
+	int i;
+	regmatch_t match[REGEX_NON_SPACE_GROUPS_COUNT];
+
+	for (i = 0; i < MAX_LINES; i++) {
+		char *line = lines[i];
+
+		/* Skip lines without tag */
+		if (!strstr(line, tag))
+			continue;
+
+		if ((regexec(regex, line, REGEX_NON_SPACE_GROUPS_COUNT, match, 0)) == 0) {
+			char *key = NULL, *value = NULL;
+
+			if (match[tag_index].rm_so >= 0) {
+				key = &line[match[tag_index].rm_so];
+				line[match[tag_index].rm_eo] = '\0';
+			}
+			if (match[target_index].rm_so >= 0) {
+				value = &line[match[target_index].rm_so];
+				line[match[target_index].rm_eo] = '\0';
+			}
+
+			if (key && value && strcmp(tag, key) == 0)
+				return value;
+			/* if key != tag,  keep searching and loop to next line */
+		}
+	}
+
+	return NULL;
+}
+
+static void
+check_item_u64(regex_t *regex, char **lines, const char *tag, u64 addr_lo, u64 addr_hi)
+{
+	u64 result;
+	char *output;
+
+	igt_assert_f((output = get_coredump_item(regex, lines, tag, INDEX_KEY, INDEX_VALUE)),
+		     "Target not found:%s\n", tag);
+	result = strtoul(output, NULL, 16);
+	igt_debug("Compare %s %s vs [0x%lX-0x%lX]\n", tag, output, addr_lo, addr_hi);
+	igt_assert_f((addr_lo <= result) && (result <= addr_hi),
+		     "value %lX out of range[0x%lX-0x%lX]\n", result, addr_lo, addr_hi);
+}
+
+static void
+check_item_str(regex_t *regex, char **lines, const char *tag, int tag_index, int target_index,
+	       const char *target, bool up_to_target_len)
+{
+	char buf[MAX_TEMP_LEN] = {0};
+	char *output;
+	int code;
+
+	igt_assert_f(output = get_coredump_item(regex, lines, tag, tag_index, target_index),
+		     "Target not found:%s\n", tag);
+
+	if (up_to_target_len) {
+		igt_assert_f(strlen(target) < MAX_TEMP_LEN, "Target too long.\n");
+		safe_strncpy(buf, output, MAX_TEMP_LEN);
+		buf[strlen(target)] = 0;
+		output = buf;
+	}
+	code = strncmp(output, target, strlen(target));
+	igt_debug("From tag '%s' found %s vs %s\n", tag, output, target);
+	igt_assert_f(code == 0, "Expected value:%s, received:%s\n", target, output);
+}
+
+/**
+ * SUBTEST: reset
+ * Description: Reset GuC, check devcoredump output values
+ */
+static void test_card(int fd)
+{
+	struct drm_xe_engine_class_instance *hwe;
+	regex_t regex;
+	int start_line;
+	int engine_cid = rand();
+	char **lines;
+	char *single_line_buf =  (char *)malloc(LINE_BUF_SIZE);
+	char temp[MAX_TEMP_LEN];
+	char path[MAX_SYSFS_PATH_LEN];
+
+	igt_assert_f(single_line_buf, "Out of memory.\n");
+
+	regcomp(&regex, REGEX_NON_SPACE_GROUPS, REG_EXTENDED | REG_NEWLINE);
+	get_devcoredump_path(igt_device_get_card_index(fd), path);
+	lines = alloc_lines_buffer();
+
+	/* clear old devcoredump, if any */
+	rm_devcoredump(path);
+
+	xe_for_each_engine(fd, hwe) {
+		/*
+		 * To test devcoredump register data, the test batch address is
+		 * used to compare with the dump, address bit 40 to 46 act as
+		 * context id, which start with an random number, increased 1
+		 * per engine. By this way, the address is unique for each
+		 * engine, and start with an random number on each run.
+		 */
+		const u64 addr = BASE_ADDRESS | ((u64)(engine_cid++ % CID_ADDRESS_MASK) <<
+						 ADDRESS_SHIFT);
+		u64 job_timeout = xe_sysfs_get_job_timeout_ms(fd, hwe);
+
+		igt_debug("Running on engine class: %x instance: %x\n", hwe->engine_class,
+			  hwe->engine_instance);
+
+		/* Reduce timeout value to speedup test */
+		xe_sysfs_set_job_timeout_ms(fd, hwe, CAPTURE_JOB_TIMEOUT);
+
+		test_legacy_mode(fd, hwe, 1, 1, 0, addr);
+
+		/* Restore timeout value */
+		xe_sysfs_set_job_timeout_ms(fd, hwe, job_timeout);
+
+		/* assert devcoredump created */
+		igt_assert_f((start_line = access_devcoredump(path, lines, single_line_buf)) > 0,
+			     "Devcoredump not exist, errno=%d.\n", errno);
+
+		sprintf(temp, "instance=%d", hwe->engine_instance);
+		check_item_str(&regex, lines, "(physical),", INDEX_ENGINE_PHYSICAL,
+			       INDEX_ENGINE_INSTANCE, temp, false);
+		check_item_str(&regex, lines, "(physical),", INDEX_ENGINE_PHYSICAL,
+			       INDEX_ENGINE_NAME, xe_engine_class_name(hwe->engine_class), true);
+
+		check_item_str(&regex, lines, "Capture_source:", INDEX_KEY, INDEX_VALUE,
+			       "GuC", false);
+		check_item_u64(&regex, lines, "ACTHD:", addr,
+			       addr + BATCH_DW_COUNT * sizeof(u32));
+		check_item_u64(&regex, lines, "RING_BBADDR:", addr,
+			       addr + BATCH_DW_COUNT * sizeof(u32));
+
+		/* clear devcoredump */
+		rm_devcoredump(path);
+		sleep(1);
+		/* Assert devcoredump removed */
+		igt_assert_f(!access_devcoredump(path, NULL, NULL), "Devcoredump not removed\n");
+	}
+	/* Free lines buffer */
+	free(lines);
+	free(single_line_buf);
+	regfree(&regex);
+}
+
+igt_main
+{
+	int xe;
+
+	igt_fixture
+		xe = drm_open_driver(DRIVER_XE);
+
+	igt_subtest("reset") {
+		int gpu_count = drm_prepare_filtered_multigpu(DRIVER_XE);
+
+		igt_require(xe > 0);
+		if (gpu_count >= 2) {
+			igt_info("Running test on multiple GPU\n");
+
+			igt_multi_fork(child, gpu_count) {
+				int gpu_fd;
+
+				gpu_fd = drm_open_filtered_card(child);
+				igt_assert_f(gpu_fd > 0, "cannot open gpu-%d, errno=%d\n", child,
+					     errno);
+				igt_assert(is_xe_device(gpu_fd));
+
+				test_card(gpu_fd);
+				drm_close_driver(gpu_fd);
+			}
+			igt_waitchildren();
+		} else {
+			igt_info("Running test on single GPU\n");
+			test_card(xe);
+		}
+	}
+
+	igt_fixture
+		drm_close_driver(xe);
+}
diff --git a/tests/meson.build b/tests/meson.build
index 2724c7a9a..a6750d523 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -285,6 +285,7 @@ intel_xe_progs = [
 	'xe_exec_atomic',
 	'xe_exec_balancer',
 	'xe_exec_basic',
+	'xe_exec_capture',
 	'xe_exec_compute_mode',
 	'xe_exec_fault_mode',
 	'xe_exec_mix_modes',
-- 
2.34.1


             reply	other threads:[~2024-12-06 22:59 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-12-06 22:59 Zhanjun Dong [this message]
2024-12-06 23:38 ` ✓ Xe.CI.BAT: success for tests/intel/xe_exec_capture: Add xe_exec_capture test (rev8) Patchwork
2024-12-06 23:52 ` ✓ i915.CI.BAT: " Patchwork
2024-12-07  1:11 ` ✗ i915.CI.Full: failure " Patchwork
2024-12-07  4:42 ` ✗ Xe.CI.Full: " Patchwork
2024-12-11 22:08 ` [PATCH v9] tests/intel/xe_exec_capture: Add xe_exec_capture test Teres Alexis, Alan Previn
2024-12-12 19:54   ` Teres Alexis, Alan Previn
2025-01-07 15:41     ` Dong, Zhanjun
2024-12-12 21:41 ` Teres Alexis, Alan Previn

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20241206225911.465603-1-zhanjun.dong@intel.com \
    --to=zhanjun.dong@intel.com \
    --cc=alan.previn.teres.alexis@intel.com \
    --cc=igt-dev@lists.freedesktop.org \
    --cc=kamil.konieczny@linux.intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.