Igt-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH i-g-t 0/4] xe: watch queue event support and VM restart recovery
@ 2026-06-12 11:06 Thomas Hellström
  2026-06-12 11:06 ` [PATCH i-g-t 1/4] lib/xe: add xe_vm_restart ioctl helper Thomas Hellström
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Thomas Hellström @ 2026-06-12 11:06 UTC (permalink / raw)
  To: igt-dev
  Cc: Thomas Hellström, Matthew Brost, Maarten Lankhorst,
	Michal Mrozek, John Falkowski, Rodrigo Vivi, Lahtinen Joonas

This series adds IGT support for two new Xe kernel interfaces: the watch
queue event notification mechanism and the VM restart IOCTL.

The watch queue allows a userspace process to subscribe to device-scoped
events delivered through a pipe — notably VM errors caused by memory
pressure.  The VM restart IOCTL lets userspace synchronously trigger a
re-run of the preempt-rebind worker to recover a VM after such an error.

Together they enable a fault-tolerance loop: a listener thread receives
VM error events carrying an error code and VM id; when the error is
-ENOMEM or -ENOSPC the test can immediately attempt to restart the
affected VM rather than letting it stay faulted.

The xe_exec_compute_mode test is extended to demonstrate this: it
subscribes to file events at fixture time, logs all notifications, and
automatically calls VM restart on memory-pressure errors.

Thomas Hellström (4):
  lib/xe: add xe_vm_restart ioctl helper
  lib/xe: add xe_watch listener for watch queue events
  tests/intel/xe_exec_compute_mode: Add a listener for file events
  tests/intel/xe_exec_compute_mode: Restart VM on ENOMEM/ENOSPC errors

 include/drm-uapi/xe_drm.h          |  84 +++++++++++
 include/drm-uapi/xe_drm_events.h   |  71 +++++++++
 lib/meson.build                    |   1 +
 lib/xe/xe_ioctl.c                  |  44 ++++++
 lib/xe/xe_ioctl.h                  |   2 +
 lib/xe/xe_watch.c                  | 221 +++++++++++++++++++++++++++++
 lib/xe/xe_watch.h                  |  80 +++++++++++
 tests/intel/xe_exec_compute_mode.c |  92 +++++++++++-
 8 files changed, 591 insertions(+), 4 deletions(-)
 create mode 100644 include/drm-uapi/xe_drm_events.h
 create mode 100644 lib/xe/xe_watch.c
 create mode 100644 lib/xe/xe_watch.h

-- 
2.54.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH i-g-t 1/4] lib/xe: add xe_vm_restart ioctl helper
  2026-06-12 11:06 [PATCH i-g-t 0/4] xe: watch queue event support and VM restart recovery Thomas Hellström
@ 2026-06-12 11:06 ` Thomas Hellström
  2026-06-12 11:06 ` [PATCH i-g-t 2/4] lib/xe: add xe_watch listener for watch queue events Thomas Hellström
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Thomas Hellström @ 2026-06-12 11:06 UTC (permalink / raw)
  To: igt-dev
  Cc: Thomas Hellström, Matthew Brost, Maarten Lankhorst,
	Michal Mrozek, John Falkowski, Rodrigo Vivi, Lahtinen Joonas

Add DRM_XE_VM_RESTART (ioctl 0x10) and struct drm_xe_vm_restart to the
Xe DRM UAPI header, taken from the xe_event kernel branch.

Add DRM_XE_VM_CREATE_FLAG_RESTARTABLE (bit 4) to allow VMs to opt in to
the restart mechanism.

Add __xe_vm_restart() (failable, with bounded EAGAIN retry) and
xe_vm_restart() (asserting wrapper) to lib/xe/xe_ioctl.  Both take an
optional CLOCK_MONOTONIC timestamp_ns which is forwarded to the IOCTL so
the driver can log event-to-restart latency.

Assisted-by: GitHub Copilot:claude-sonnet-4.6
---
 include/drm-uapi/xe_drm.h | 40 +++++++++++++++++++++++++++++++++++
 lib/xe/xe_ioctl.c         | 44 +++++++++++++++++++++++++++++++++++++++
 lib/xe/xe_ioctl.h         |  2 ++
 3 files changed, 86 insertions(+)

diff --git a/include/drm-uapi/xe_drm.h b/include/drm-uapi/xe_drm.h
index 5a96a7910..43b65b1d9 100644
--- a/include/drm-uapi/xe_drm.h
+++ b/include/drm-uapi/xe_drm.h
@@ -84,6 +84,7 @@ extern "C" {
  *  - &DRM_IOCTL_XE_MADVISE
  *  - &DRM_IOCTL_XE_VM_QUERY_MEM_RANGE_ATTRS
  *  - &DRM_IOCTL_XE_VM_GET_PROPERTY
+ *  - &DRM_IOCTL_XE_VM_RESTART
  */
 
 /*
@@ -109,6 +110,7 @@ extern "C" {
 #define DRM_XE_VM_QUERY_MEM_RANGE_ATTRS	0x0d
 #define DRM_XE_EXEC_QUEUE_SET_PROPERTY	0x0e
 #define DRM_XE_VM_GET_PROPERTY		0x0f
+#define DRM_XE_VM_RESTART		0x10
 
 /* Must be kept compact -- no holes */
 
@@ -128,6 +130,7 @@ extern "C" {
 #define DRM_IOCTL_XE_VM_QUERY_MEM_RANGE_ATTRS	DRM_IOWR(DRM_COMMAND_BASE + DRM_XE_VM_QUERY_MEM_RANGE_ATTRS, struct drm_xe_vm_query_mem_range_attr)
 #define DRM_IOCTL_XE_EXEC_QUEUE_SET_PROPERTY	DRM_IOW(DRM_COMMAND_BASE + DRM_XE_EXEC_QUEUE_SET_PROPERTY, struct drm_xe_exec_queue_set_property)
 #define DRM_IOCTL_XE_VM_GET_PROPERTY		DRM_IOWR(DRM_COMMAND_BASE + DRM_XE_VM_GET_PROPERTY, struct drm_xe_vm_get_property)
+#define DRM_IOCTL_XE_VM_RESTART			DRM_IOW(DRM_COMMAND_BASE + DRM_XE_VM_RESTART, struct drm_xe_vm_restart)
 
 /**
  * DOC: Xe IOCTL Extensions
@@ -991,6 +994,7 @@ struct drm_xe_vm_create {
 #define DRM_XE_VM_CREATE_FLAG_LR_MODE	        (1 << 1)
 #define DRM_XE_VM_CREATE_FLAG_FAULT_MODE	(1 << 2)
 #define DRM_XE_VM_CREATE_FLAG_NO_VM_OVERCOMMIT  (1 << 3)
+#define DRM_XE_VM_CREATE_FLAG_RESTARTABLE       (1 << 4)
 	/** @flags: Flags */
 	__u32 flags;
 
@@ -2609,6 +2613,42 @@ enum drm_xe_ras_error_component {
 	[DRM_XE_RAS_ERR_COMP_SOC_INTERNAL] = "soc-internal"		\
 }
 
+/**
+ * DOC: DRM_XE_VM_RESTART
+ *
+ * Synchronously restart a VM by running its preempt-rebind worker in the
+ * calling context.  The VM must be in preempt-fence mode (i.e. it must have
+ * been created with exec queues that use preempt fences).
+ *
+ * On return the rebind attempt has completed or a retriable error was
+ * encountered.  Any non-retriable error is surfaced through the event
+ * mechanism if the caller has subscribed to %DRM_XE_EVENT_MASK_VM_ERR.
+ * The IOCTL may return -EAGAIN if userptr memory needs to be repinned;
+ * callers should retry in that case.
+ */
+
+/**
+ * struct drm_xe_vm_restart - restart a VM's preempt-rebind worker
+ *
+ * Used with %DRM_IOCTL_XE_VM_RESTART.
+ */
+struct drm_xe_vm_restart {
+	/** @vm_id: ID of the VM to restart */
+	__u32 vm_id;
+	/** @pad: reserved, must be zero */
+	__u32 pad;
+	/**
+	 * @timestamp_ns: optional CLOCK_MONOTONIC timestamp in nanoseconds.
+	 * When non-zero, the driver logs the delay between this timestamp and
+	 * the point the rebind completes, which can be used to measure the
+	 * response latency from event delivery to VM restart.  Pass zero to
+	 * disable the logging.
+	 */
+	__u64 timestamp_ns;
+	/** @reserved: reserved, must be zero */
+	__u64 reserved;
+};
+
 #if defined(__cplusplus)
 }
 #endif
diff --git a/lib/xe/xe_ioctl.c b/lib/xe/xe_ioctl.c
index c8ed99182..f102fe34e 100644
--- a/lib/xe/xe_ioctl.c
+++ b/lib/xe/xe_ioctl.c
@@ -337,6 +337,50 @@ void xe_vm_get_property(int fd, uint32_t vm, struct drm_xe_vm_get_property *quer
 	igt_assert_eq(igt_ioctl(fd, DRM_IOCTL_XE_VM_GET_PROPERTY, query), 0);
 }
 
+/**
+ * __xe_vm_restart() - restart a VM's preempt-rebind worker (failable)
+ * @fd: open Xe DRM device file descriptor
+ * @vm: VM id to restart
+ * @timestamp_ns: CLOCK_MONOTONIC timestamp from the triggering event, or 0
+ *
+ * Calls %DRM_IOCTL_XE_VM_RESTART, retrying up to 10 times on -EAGAIN as
+ * required when userptr memory needs repinning.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int __xe_vm_restart(int fd, uint32_t vm, uint64_t timestamp_ns)
+{
+	struct drm_xe_vm_restart restart = {
+		.vm_id = vm,
+		.timestamp_ns = timestamp_ns,
+	};
+	int err, tries = 10;
+
+	do {
+		err = igt_ioctl(fd, DRM_IOCTL_XE_VM_RESTART, &restart);
+		if (err) {
+			err = -errno;
+			igt_assume(err);
+			errno = 0;
+		}
+	} while (err == -EAGAIN && --tries > 0);
+
+	return err;
+}
+
+/**
+ * xe_vm_restart() - restart a VM's preempt-rebind worker
+ * @fd: open Xe DRM device file descriptor
+ * @vm: VM id to restart
+ * @timestamp_ns: CLOCK_MONOTONIC timestamp from the triggering event, or 0
+ *
+ * Calls __xe_vm_restart() and asserts success.
+ */
+void xe_vm_restart(int fd, uint32_t vm, uint64_t timestamp_ns)
+{
+	igt_assert_eq(__xe_vm_restart(fd, vm, timestamp_ns), 0);
+}
+
 void xe_vm_destroy(int fd, uint32_t vm)
 {
 	struct drm_xe_vm_destroy destroy = {
diff --git a/lib/xe/xe_ioctl.h b/lib/xe/xe_ioctl.h
index bf40fb6bd..95f2ec3d8 100644
--- a/lib/xe/xe_ioctl.h
+++ b/lib/xe/xe_ioctl.h
@@ -66,6 +66,8 @@ void xe_vm_unbind_all_async(int fd, uint32_t vm, uint32_t exec_queue,
 			    uint32_t bo, struct drm_xe_sync *sync,
 			    uint32_t num_syncs);
 void xe_vm_get_property(int fd, uint32_t vm, struct drm_xe_vm_get_property *query);
+int __xe_vm_restart(int fd, uint32_t vm, uint64_t timestamp_ns);
+void xe_vm_restart(int fd, uint32_t vm, uint64_t timestamp_ns);
 void xe_vm_destroy(int fd, uint32_t vm);
 uint32_t __xe_bo_create(int fd, uint32_t vm, uint64_t size, uint32_t placement,
 			uint32_t flags, void *ext, uint32_t *handle);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH i-g-t 2/4] lib/xe: add xe_watch listener for watch queue events
  2026-06-12 11:06 [PATCH i-g-t 0/4] xe: watch queue event support and VM restart recovery Thomas Hellström
  2026-06-12 11:06 ` [PATCH i-g-t 1/4] lib/xe: add xe_vm_restart ioctl helper Thomas Hellström
@ 2026-06-12 11:06 ` Thomas Hellström
  2026-06-12 11:06 ` [PATCH i-g-t 3/4] tests/intel/xe_exec_compute_mode: Add a listener for file events Thomas Hellström
  2026-06-12 11:06 ` [PATCH i-g-t 4/4] tests/intel/xe_exec_compute_mode: Restart VM on ENOMEM/ENOSPC errors Thomas Hellström
  3 siblings, 0 replies; 5+ messages in thread
From: Thomas Hellström @ 2026-06-12 11:06 UTC (permalink / raw)
  To: igt-dev
  Cc: Thomas Hellström, Matthew Brost, Maarten Lankhorst,
	Michal Mrozek, John Falkowski, Rodrigo Vivi, Lahtinen Joonas

Add a new xe_watch library module that subscribes an Xe device file
descriptor to kernel watch queue notifications and dispatches them to a
caller-supplied handler from a background pthread.

The public API consists of two functions and three types:

  xe_watch_listener_create(xe_fd, event)
    Opens an O_NOTIFICATION_PIPE, sizes it, filters it to
    WATCH_TYPE_DRM_NOTIFY events, registers it with the Xe device via
    DRM_IOCTL_XE_WATCH_QUEUE, and starts a worker pthread.

  xe_watch_listener_destroy(listener)
    Signals the worker to exit via an internal stop-pipe, joins the
    thread, and frees all resources.

  struct xe_watch_event / xe_watch_event_ops / xe_watch_event_fn
    Caller embeds struct xe_watch_event in its own private state and
    points ops->event_deliver at its handler. The worker sets
    event->notif (a struct watch_notification *) before each call, and
    the handler uses container_of() to recover its private context.

The implementation relies on the userspace-safe guard added to
<linux/watch_queue.h> so it can be included from xe_drm.h directly.
A WATCH_TYPE_DRM_XE_NOTIFY fallback define is provided for build
environments whose system header predates that define.

xe_drm.h is updated to match the upstream watch_queue branch:
  - Add DRM_XE_WATCH_QUEUE ioctl number (0x0f) and
    DRM_IOCTL_XE_WATCH_QUEUE macro.
  - Add struct drm_xe_watch_queue, enum drm_xe_watch_event
    (DRM_XE_WATCH_EVENT_VM_ERR = 0), and
    struct drm_xe_watch_notification_vm_oom.

Assisted-by: GitHub Copilot:claude-sonnet-4.6
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
---
 include/drm-uapi/xe_drm.h        |  46 +++++++
 include/drm-uapi/xe_drm_events.h |  71 ++++++++++
 lib/meson.build                  |   1 +
 lib/xe/xe_watch.c                | 221 +++++++++++++++++++++++++++++++
 lib/xe/xe_watch.h                |  80 +++++++++++
 5 files changed, 419 insertions(+)
 create mode 100644 include/drm-uapi/xe_drm_events.h
 create mode 100644 lib/xe/xe_watch.c
 create mode 100644 lib/xe/xe_watch.h

diff --git a/include/drm-uapi/xe_drm.h b/include/drm-uapi/xe_drm.h
index 43b65b1d9..1b7857a9f 100644
--- a/include/drm-uapi/xe_drm.h
+++ b/include/drm-uapi/xe_drm.h
@@ -85,6 +85,7 @@ extern "C" {
  *  - &DRM_IOCTL_XE_VM_QUERY_MEM_RANGE_ATTRS
  *  - &DRM_IOCTL_XE_VM_GET_PROPERTY
  *  - &DRM_IOCTL_XE_VM_RESTART
+ *  - &DRM_IOCTL_XE_WATCH_QUEUE
  */
 
 /*
@@ -111,6 +112,7 @@ extern "C" {
 #define DRM_XE_EXEC_QUEUE_SET_PROPERTY	0x0e
 #define DRM_XE_VM_GET_PROPERTY		0x0f
 #define DRM_XE_VM_RESTART		0x10
+#define DRM_XE_WATCH_QUEUE		0x11
 
 /* Must be kept compact -- no holes */
 
@@ -131,6 +133,7 @@ extern "C" {
 #define DRM_IOCTL_XE_EXEC_QUEUE_SET_PROPERTY	DRM_IOW(DRM_COMMAND_BASE + DRM_XE_EXEC_QUEUE_SET_PROPERTY, struct drm_xe_exec_queue_set_property)
 #define DRM_IOCTL_XE_VM_GET_PROPERTY		DRM_IOWR(DRM_COMMAND_BASE + DRM_XE_VM_GET_PROPERTY, struct drm_xe_vm_get_property)
 #define DRM_IOCTL_XE_VM_RESTART			DRM_IOW(DRM_COMMAND_BASE + DRM_XE_VM_RESTART, struct drm_xe_vm_restart)
+#define DRM_IOCTL_XE_WATCH_QUEUE		DRM_IOW(DRM_COMMAND_BASE + DRM_XE_WATCH_QUEUE, struct drm_xe_watch_queue)
 
 /**
  * DOC: Xe IOCTL Extensions
@@ -2649,6 +2652,49 @@ struct drm_xe_vm_restart {
 	__u64 reserved;
 };
 
+/**
+ * DOC: DRM_XE_WATCH_QUEUE
+ *
+ * Subscribe a notification pipe to receive device events for the calling
+ * process's DRM file handle.  Events are scoped to the subscribing file:
+ * only events that belong to that file (for example, VM error on a VM created
+ * through the same file) are delivered, preventing information leaks between
+ * processes sharing the same GPU device.
+ *
+ * The pipe must first be opened with O_NOTIFICATION_PIPE (i.e. O_EXCL passed
+ * to pipe2()) and sized via %IOC_WATCH_QUEUE_SET_SIZE before subscribing.
+ *
+ * Events are delivered as notification records read from the pipe.  The
+ * @watch_id field is embedded in the notification info field and can be used
+ * to distinguish multiple watches sharing a pipe.
+ *
+ * Currently defined event subtypes:
+ *  - %DRM_XE_WATCH_EVENT_VM_ERR - a VM owned by this file has encountered an error
+ */
+
+/**
+ * struct drm_xe_watch_queue - subscribe to device event notifications
+ *
+ * Used with %DRM_IOCTL_XE_WATCH_QUEUE.  Notifications are scoped to the
+ * DRM file handle used to issue this IOCTL.
+ */
+struct drm_xe_watch_queue {
+	/** @fd: file descriptor of pipe opened with O_NOTIFICATION_PIPE */
+	__u32 fd;
+
+	/**
+	 * @watch_id: identifier (0–255) embedded in the watch notification
+	 * info field; allows multiplexing several watches on one pipe
+	 */
+	__u32 watch_id;
+
+	/** @flags: must be zero */
+	__u32 flags;
+
+	/** @pad: reserved, must be zero */
+	__u32 pad;
+};
+
 #if defined(__cplusplus)
 }
 #endif
diff --git a/include/drm-uapi/xe_drm_events.h b/include/drm-uapi/xe_drm_events.h
new file mode 100644
index 000000000..16f8f4558
--- /dev/null
+++ b/include/drm-uapi/xe_drm_events.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+#ifndef _UAPI_XE_DRM_EVENTS_H_
+#define _UAPI_XE_DRM_EVENTS_H_
+
+#include <linux/types.h>
+#include <linux/watch_queue.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+/*
+ * WATCH_TYPE_DRM_XE_NOTIFY is defined in the xe watch_queue branch; provide
+ * a fallback for system headers that do not yet carry it.
+ */
+#ifndef WATCH_TYPE_DRM_XE_NOTIFY
+#define WATCH_TYPE_DRM_XE_NOTIFY 2
+#endif
+
+/**
+ * enum drm_xe_watch_event - Xe device watch event subtypes
+ *
+ * Subtypes for notifications delivered via %WATCH_TYPE_DRM_XE_NOTIFY when
+ * reading from a pipe subscribed with %DRM_IOCTL_XE_WATCH_QUEUE.
+ */
+enum drm_xe_watch_event {
+	/**
+	 * @DRM_XE_WATCH_EVENT_VM_ERR: a VM has encountered an error.
+	 *
+	 * Indicates that a fatal or resource error occurred within the given
+	 * VM.  The vm_id of the affected VM is carried in the
+	 * @drm_xe_watch_notification_vm_err::vm_id field of the extended
+	 * notification record.
+	 */
+	DRM_XE_WATCH_EVENT_VM_ERR = 0,
+};
+
+/**
+ * struct drm_xe_watch_notification_vm_err - VM error event notification
+ *
+ * Notification record delivered for %DRM_XE_WATCH_EVENT_VM_ERR.
+ * The record type is always %WATCH_TYPE_DRM_XE_NOTIFY and the subtype is
+ * %DRM_XE_WATCH_EVENT_VM_ERR.
+ */
+struct drm_xe_watch_notification_vm_err {
+	/** @base: common watch notification header */
+	struct watch_notification base;
+
+	/** @vm_id: ID of the VM that encountered an error */
+	__u32 vm_id;
+
+	/** @error_code: error code describing the error condition (negative errno) */
+	__s32 error_code;
+
+	/**
+	 * @timestamp_ns: CLOCK_MONOTONIC timestamp in nanoseconds at the
+	 * point the error was detected
+	 */
+	__u64 timestamp_ns;
+};
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* _UAPI_XE_DRM_H_ */
diff --git a/lib/meson.build b/lib/meson.build
index f25ecd8b2..2f07ad0c2 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -134,6 +134,7 @@ lib_sources = [
 	'xe/xe_sriov_debugfs.c',
 	'xe/xe_sriov_provisioning.c',
 	'xe/xe_util.c',
+	'xe/xe_watch.c',
 
 	# Vendored libraries:
 	'vendor/uwildmat/uwildmat.c',
diff --git a/lib/xe/xe_watch.c b/lib/xe/xe_watch.c
new file mode 100644
index 000000000..46febb18a
--- /dev/null
+++ b/lib/xe/xe_watch.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+#include <errno.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <xf86drm.h>
+
+#include "igt_core.h"
+#include "xe_drm.h"
+#include "xe_drm_events.h"
+#include "xe/xe_watch.h"
+
+/* Pipe ring-buffer size in pages (one page holds multiple records). */
+#define XE_WATCH_PIPE_PAGES	1
+
+/* Read buffer – large enough for any single notification record. */
+#define XE_WATCH_BUF_SIZE	512
+
+/**
+ * struct xe_watch_listener - internal state for an Xe watch queue listener
+ * @xe_fd: xe DRM device file descriptor passed to xe_watch_listener_create()
+ * @pipe_fd: read end of the O_NOTIFICATION_PIPE used to receive kernel events
+ * @stop_pipe: internal pipe pair used to unblock the worker; write end is
+ *             written by xe_watch_listener_destroy() to signal the thread to exit
+ * @event: caller-supplied event object dispatched for each notification
+ * @thread: background pthread running xe_watch_worker()
+ * @running: set to %false by xe_watch_listener_destroy() to request exit
+ */
+struct xe_watch_listener {
+	int xe_fd;
+	int pipe_fd;
+	int stop_pipe[2];
+	struct xe_watch_event *event;
+	pthread_t thread;
+	bool running;
+};
+
+static void *xe_watch_worker(void *arg)
+{
+	struct xe_watch_listener *l = arg;
+	unsigned char buf[XE_WATCH_BUF_SIZE];
+	struct pollfd pfds[2] = {
+		{ .fd = l->pipe_fd,      .events = POLLIN },
+		{ .fd = l->stop_pipe[0], .events = POLLIN },
+	};
+
+	while (READ_ONCE(l->running)) {
+		unsigned char *p, *end;
+		ssize_t len;
+		int ret;
+
+		ret = poll(pfds, 2, 200/* ms */);
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+			igt_warn("xe_watch: poll failed: %s\n", strerror(errno));
+			break;
+		}
+
+		/* Stop signal received */
+		if (pfds[1].revents & POLLIN)
+			break;
+
+		if (!(pfds[0].revents & POLLIN))
+			continue;
+
+		len = read(l->pipe_fd, buf, sizeof(buf));
+
+		if (len < 0) {
+			if (errno == EINTR || errno == EAGAIN)
+				continue;
+			igt_warn("xe_watch: read failed: %s\n", strerror(errno));
+			break;
+		}
+
+		p = buf;
+		end = buf + len;
+
+		while (p + sizeof(struct watch_notification) <= end) {
+			const struct watch_notification *notif =
+				(const struct watch_notification *)p;
+			size_t rec_len = notif->info & WATCH_INFO_LENGTH;
+
+			if (rec_len < sizeof(struct watch_notification) ||
+			    p + rec_len > end)
+				break;
+
+			l->event->notif = notif;
+			(*l->event->ops->event_deliver)(l->event);
+
+			p += rec_len;
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * xe_watch_listener_create - create and start an Xe watch queue listener
+ * @xe_fd: open xe DRM device file descriptor
+ * @event: caller-initialised event object; its @ops->event_deliver function
+ *         is called from a background pthread for every received
+ *         %WATCH_TYPE_DRM_XE_NOTIFY record, with @event->notif pointing to the
+ *         notification.  The caller typically embeds @event in a larger
+ *         private structure and uses container_of() inside the callback.
+ *
+ * Opens an ``O_NOTIFICATION_PIPE``, registers it with the Xe device via
+ * %DRM_IOCTL_XE_WATCH_QUEUE, and starts a background pthread that reads
+ * events and dispatches them via @event->ops->event_deliver().
+ *
+ * Returns a pointer to the new listener on success, or asserts on failure.
+ * Free with xe_watch_listener_destroy().
+ */
+struct xe_watch_listener *xe_watch_listener_create(int xe_fd,
+						   struct xe_watch_event *event)
+{
+	struct xe_watch_listener *l;
+	struct {
+		struct watch_notification_filter filter;
+		struct watch_notification_type_filter slot[2];
+	} fbuf = {
+		.filter = { .nr_filters = 2 },
+		.slot = {
+			{
+				.type           = WATCH_TYPE_DRM_XE_NOTIFY,
+				.subtype_filter = { [0] = UINT32_MAX },
+			},
+			{
+				.type           = WATCH_TYPE_META,
+				.subtype_filter = { [0] = UINT32_MAX },
+			},
+		},
+	};
+	struct drm_xe_watch_queue args = {};
+	int pipefd[2];
+	int ret;
+
+	igt_assert(event && event->ops && event->ops->event_deliver);
+
+	l = calloc(1, sizeof(*l));
+	igt_assert(l);
+
+	l->xe_fd = xe_fd;
+	l->event = event;
+	WRITE_ONCE(l->running, true);
+
+	/* Create the notification pipe */
+	ret = pipe2(pipefd, O_NOTIFICATION_PIPE);
+	igt_assert_f(ret == 0, "xe_watch: pipe2(O_NOTIFICATION_PIPE) failed: %s\n",
+		     strerror(errno));
+	l->pipe_fd = pipefd[0];
+
+	/* Size the pipe ring buffer */
+	ret = ioctl(l->pipe_fd, IOC_WATCH_QUEUE_SET_SIZE, XE_WATCH_PIPE_PAGES);
+	igt_assert_f(ret == 0, "xe_watch: IOC_WATCH_QUEUE_SET_SIZE failed: %s\n",
+		     strerror(errno));
+
+	/* Filter to only DRM_XE and META notifications */
+	ret = ioctl(l->pipe_fd, IOC_WATCH_QUEUE_SET_FILTER, &fbuf.filter);
+	igt_assert_f(ret == 0, "xe_watch: IOC_WATCH_QUEUE_SET_FILTER failed: %s\n",
+		     strerror(errno));
+
+	/* Register the pipe with the Xe device */
+	args.fd       = pipefd[1];
+	args.watch_id = 0;
+	ret = drmIoctl(xe_fd, DRM_IOCTL_XE_WATCH_QUEUE, &args);
+	igt_assert_f(ret == 0, "xe_watch: DRM_IOCTL_XE_WATCH_QUEUE failed: %s\n",
+		     strerror(errno));
+
+	/*
+	 * The write end is owned by the kernel after subscription; close our
+	 * copy so that the pipe is torn down when the device closes it.
+	 */
+	close(pipefd[1]);
+
+	/* Internal stop-signal pipe */
+	ret = pipe(l->stop_pipe);
+	igt_assert_f(ret == 0, "xe_watch: pipe(stop) failed: %s\n",
+		     strerror(errno));
+
+	ret = pthread_create(&l->thread, NULL, xe_watch_worker, l);
+	igt_assert_f(ret == 0, "xe_watch: pthread_create failed: %s\n",
+		     strerror(ret));
+
+	return l;
+}
+
+/**
+ * xe_watch_listener_destroy - stop and free an Xe watch queue listener
+ * @listener: listener returned by xe_watch_listener_create()
+ *
+ * Signals the worker thread to exit, waits for it to finish, closes file
+ * descriptors and frees the listener.
+ */
+void xe_watch_listener_destroy(struct xe_watch_listener *listener)
+{
+	if (!listener)
+		return;
+
+	/* Signal the worker to exit */
+	WRITE_ONCE(listener->running, false);
+	write(listener->stop_pipe[1], "\0", 1);
+
+	pthread_join(listener->thread, NULL);
+
+	close(listener->stop_pipe[0]);
+	close(listener->stop_pipe[1]);
+	close(listener->pipe_fd);
+
+	free(listener);
+}
diff --git a/lib/xe/xe_watch.h b/lib/xe/xe_watch.h
new file mode 100644
index 000000000..135cfe941
--- /dev/null
+++ b/lib/xe/xe_watch.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2024 Intel Corporation
+ */
+
+#ifndef _XE_WATCH_H_
+#define _XE_WATCH_H_
+
+#include <stdint.h>
+
+#include "xe_drm.h"
+
+struct xe_watch_event;
+
+/**
+ * typedef xe_watch_event_fn - callback invoked for each watch queue event
+ * @event: the event object whose @ops->event_deliver was registered; the
+ *         @notif field is set to the received notification before the call.
+ *         The caller typically uses container_of() to recover its private
+ *         state from @event.
+ */
+typedef void (*xe_watch_event_fn)(struct xe_watch_event *event);
+
+/**
+ * struct xe_watch_event_ops - operations table for &struct xe_watch_event
+ * @event_deliver: called by the watch listener worker for every received
+ *                 %WATCH_TYPE_DRM_XE_NOTIFY record.  Must not be NULL.
+ */
+struct xe_watch_event_ops {
+	xe_watch_event_fn event_deliver;
+};
+
+/**
+ * struct xe_watch_event - base object embedded in caller-private event state
+ * @ops: pointer to the operations table; must be initialised before calling
+ *       xe_watch_listener_create()
+ * @notif: set by the worker to the current notification record immediately
+ *         before dispatching @ops->event_deliver(); valid only for the
+ *         duration of that call.  Cast to
+ *         &struct drm_xe_watch_notification_vm_err when
+ *         @notif->subtype == %DRM_XE_WATCH_EVENT_VM_ERR.
+ *
+ * Callers embed this struct in their own private structure and recover it
+ * inside the callback using container_of():
+ *
+ * .. code-block:: c
+ *
+ *   struct my_state {
+ *       struct xe_watch_event base;
+ *       int my_field;
+ *   };
+ *
+ *   static void on_event(struct xe_watch_event *e)
+ *   {
+ *       struct my_state *s = container_of(e, struct my_state, base);
+ *       // use s->base.notif and s->my_field
+ *   }
+ *
+ *   static const struct xe_watch_event_ops my_ops = {
+ *       .event_deliver = on_event,
+ *   };
+ */
+struct xe_watch_event {
+	const struct xe_watch_event_ops *ops;
+	const struct watch_notification *notif;
+};
+
+/**
+ * struct xe_watch_listener - listener state for Xe device watch queue events
+ *
+ * Opaque handle returned by xe_watch_listener_create().  Do not access
+ * members directly; use the xe_watch_listener_* API.
+ */
+struct xe_watch_listener;
+
+struct xe_watch_listener *xe_watch_listener_create(int xe_fd,
+						   struct xe_watch_event *event);
+void xe_watch_listener_destroy(struct xe_watch_listener *listener);
+
+#endif /* _XE_WATCH_H_ */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH i-g-t 3/4] tests/intel/xe_exec_compute_mode: Add a listener for file events
  2026-06-12 11:06 [PATCH i-g-t 0/4] xe: watch queue event support and VM restart recovery Thomas Hellström
  2026-06-12 11:06 ` [PATCH i-g-t 1/4] lib/xe: add xe_vm_restart ioctl helper Thomas Hellström
  2026-06-12 11:06 ` [PATCH i-g-t 2/4] lib/xe: add xe_watch listener for watch queue events Thomas Hellström
@ 2026-06-12 11:06 ` Thomas Hellström
  2026-06-12 11:06 ` [PATCH i-g-t 4/4] tests/intel/xe_exec_compute_mode: Restart VM on ENOMEM/ENOSPC errors Thomas Hellström
  3 siblings, 0 replies; 5+ messages in thread
From: Thomas Hellström @ 2026-06-12 11:06 UTC (permalink / raw)
  To: igt-dev
  Cc: Thomas Hellström, Matthew Brost, Maarten Lankhorst,
	Michal Mrozek, John Falkowski, Rodrigo Vivi, Lahtinen Joonas

Using the new xe_watch library functionality, add a listener
for file events. The listener gets notified on

* File close,
* Lost event,
* Rebind worker error.

But doesn't take any action.

Assisted-by: GitHub Copilot:claude-sonnet-4.6
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
---
 tests/intel/xe_exec_compute_mode.c | 63 +++++++++++++++++++++++++++++-
 1 file changed, 61 insertions(+), 2 deletions(-)

diff --git a/tests/intel/xe_exec_compute_mode.c b/tests/intel/xe_exec_compute_mode.c
index 438ea163f..5bab971b0 100644
--- a/tests/intel/xe_exec_compute_mode.c
+++ b/tests/intel/xe_exec_compute_mode.c
@@ -19,9 +19,14 @@
 #include <sys/ioctl.h>
 #include "xe_drm.h"
 
+/* Don't include linux/fcntl.h since we already included <fcntl.h> */
+#define _LINUX_FCNTL_H
+#include "xe_drm_events.h"
+
 #include "xe/xe_ioctl.h"
 #include "xe/xe_query.h"
 #include "xe/xe_spin.h"
+#include "xe/xe_watch.h"
 #include <string.h>
 
 #define MAX_N_EXECQUEUES 	16
@@ -35,6 +40,52 @@
 #define FREE_MAPPPING			(0x1 << 7)
 #define UNMAP_MAPPPING			(0x1 << 8)
 
+static void xe_event_fn(struct xe_watch_event *event)
+{
+	const struct watch_notification *notif = event->notif;
+	const struct drm_xe_watch_notification_vm_err *err_event =
+		igt_container_of(notif, err_event, base);
+
+	switch (notif->type) {
+	case WATCH_TYPE_META:
+		switch (notif->subtype) {
+		case WATCH_META_REMOVAL_NOTIFICATION:
+			igt_info("The device file was closed.\n");
+			break;
+		case WATCH_META_LOSS_NOTIFICATION:
+			igt_warn("The listener lost a message.\n");
+			break;
+		default:
+			igt_warn("Unknown META subtype.\n");
+			break;
+		}
+		break;
+
+	case WATCH_TYPE_DRM_XE_NOTIFY:
+		switch (notif->subtype) {
+		case DRM_XE_WATCH_EVENT_VM_ERR:
+			igt_info("VM with id %u saw an error: %d\n",
+				 (unsigned int) err_event->vm_id,
+				 (int) err_event->error_code);
+			break;
+		default:
+			igt_warn("Unknown XE watch subtype %u\n",
+				 (unsigned int) notif->subtype);
+			break;
+		}
+
+		break;
+
+	default:
+		igt_warn("Unknown watch type %u.\n", notif->type);
+		break;
+	}
+}
+
+const struct xe_watch_event_ops event_ops = {
+	.event_deliver = xe_event_fn,
+};
+
 /**
  * SUBTEST: twice-%s
  * Description: Run %arg[1] compute machine test twice
@@ -430,9 +481,15 @@ int igt_main()
 		{ NULL },
 	};
 	int fd;
+	struct xe_watch_event watch_event = {
+		.ops = &event_ops,
+	};
+	struct xe_watch_listener *listener;
 
-	igt_fixture()
+	igt_fixture() {
 		fd = drm_open_driver(DRIVER_XE);
+		listener = xe_watch_listener_create(fd, &watch_event);
+	}
 
 	for (const struct section *s = sections; s->name; s++) {
 		igt_subtest_f("once-%s", s->name)
@@ -465,6 +522,8 @@ int igt_main()
 		lr_mode_workload(fd);
 
 
-	igt_fixture()
+	igt_fixture() {
 		drm_close_driver(fd);
+		xe_watch_listener_destroy(listener);
+	}
 }
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH i-g-t 4/4] tests/intel/xe_exec_compute_mode: Restart VM on ENOMEM/ENOSPC errors
  2026-06-12 11:06 [PATCH i-g-t 0/4] xe: watch queue event support and VM restart recovery Thomas Hellström
                   ` (2 preceding siblings ...)
  2026-06-12 11:06 ` [PATCH i-g-t 3/4] tests/intel/xe_exec_compute_mode: Add a listener for file events Thomas Hellström
@ 2026-06-12 11:06 ` Thomas Hellström
  3 siblings, 0 replies; 5+ messages in thread
From: Thomas Hellström @ 2026-06-12 11:06 UTC (permalink / raw)
  To: igt-dev
  Cc: Thomas Hellström, Matthew Brost, Maarten Lankhorst,
	Michal Mrozek, John Falkowski, Rodrigo Vivi, Lahtinen Joonas

When a DRM_XE_EVENT_VM_ERR event is received with error code -ENOMEM or
-ENOSPC, call DRM_IOCTL_XE_VM_RESTART to attempt recovery via the
preempt-rebind worker.

To pass the file descriptor into the event callback, introduce struct
xe_watch_ctx embedding the existing struct xe_watch_event alongside an
fd field, following the container_of() pattern documented by the
xe_watch library.

The restart uses __xe_vm_restart() (failable) rather than the asserting
xe_vm_restart() since the callback runs on the background listener
thread.  -ENOENT is treated as non-fatal (event arrived after VM
destruction); other errors are logged as warnings.

Alongside the test change, add the __xe_vm_restart() and xe_vm_restart()
helpers to lib/xe/xe_ioctl and the DRM_XE_VM_RESTART UAPI to
include/drm-uapi/xe_drm.h, taken from the xe_event kernel branch.

Assisted-by: GitHub Copilot:claude-sonnet-4.6
---
 include/drm-uapi/xe_drm.h          |  6 ++---
 tests/intel/xe_exec_compute_mode.c | 37 +++++++++++++++++++++++++-----
 2 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/include/drm-uapi/xe_drm.h b/include/drm-uapi/xe_drm.h
index 1b7857a9f..f6ec85a02 100644
--- a/include/drm-uapi/xe_drm.h
+++ b/include/drm-uapi/xe_drm.h
@@ -2624,8 +2624,8 @@ enum drm_xe_ras_error_component {
  * been created with exec queues that use preempt fences).
  *
  * On return the rebind attempt has completed or a retriable error was
- * encountered.  Any non-retriable error is surfaced through the event
- * mechanism if the caller has subscribed to %DRM_XE_EVENT_MASK_VM_ERR.
+ * encountered.  Any non-retriable error is surfaced through the watch queue
+ * if the caller has subscribed via %DRM_IOCTL_XE_WATCH_QUEUE.
  * The IOCTL may return -EAGAIN if userptr memory needs to be repinned;
  * callers should retry in that case.
  */
@@ -2654,8 +2654,6 @@ struct drm_xe_vm_restart {
 
 /**
  * DOC: DRM_XE_WATCH_QUEUE
- *
- * Subscribe a notification pipe to receive device events for the calling
  * process's DRM file handle.  Events are scoped to the subscribing file:
  * only events that belong to that file (for example, VM error on a VM created
  * through the same file) are delivered, preventing information leaks between
diff --git a/tests/intel/xe_exec_compute_mode.c b/tests/intel/xe_exec_compute_mode.c
index 5bab971b0..71b6110e2 100644
--- a/tests/intel/xe_exec_compute_mode.c
+++ b/tests/intel/xe_exec_compute_mode.c
@@ -11,7 +11,9 @@
  * Functionality: compute test
  */
 
+#include <errno.h>
 #include <fcntl.h>
+#include <stdatomic.h>
 
 #include "igt.h"
 #include "lib/igt_syncobj.h"
@@ -40,11 +42,19 @@
 #define FREE_MAPPPING			(0x1 << 7)
 #define UNMAP_MAPPPING			(0x1 << 8)
 
+struct xe_watch_ctx {
+	struct xe_watch_event base;
+	int fd;
+	atomic_uint restart_count;
+};
+
 static void xe_event_fn(struct xe_watch_event *event)
 {
+	struct xe_watch_ctx *ctx = igt_container_of(event, ctx, base);
 	const struct watch_notification *notif = event->notif;
 	const struct drm_xe_watch_notification_vm_err *err_event =
 		igt_container_of(notif, err_event, base);
+	int err;
 
 	switch (notif->type) {
 	case WATCH_TYPE_META:
@@ -67,6 +77,16 @@ static void xe_event_fn(struct xe_watch_event *event)
 			igt_info("VM with id %u saw an error: %d\n",
 				 (unsigned int) err_event->vm_id,
 				 (int) err_event->error_code);
+			if (err_event->error_code == -ENOMEM ||
+			    err_event->error_code == -ENOSPC) {
+				err = __xe_vm_restart(ctx->fd, err_event->vm_id,
+					      err_event->timestamp_ns);
+				if (err && err != -ENOENT)
+					igt_warn("VM %u restart failed: %d\n",
+						 (unsigned int) err_event->vm_id, err);
+				else if (!err)
+					atomic_fetch_add(&ctx->restart_count, 1);
+			}
 			break;
 		default:
 			igt_warn("Unknown XE watch subtype %u\n",
@@ -176,7 +196,8 @@ test_exec(int fd, struct drm_xe_engine_class_instance *eci,
 	igt_debug("%s running on: %s\n", __func__, xe_engine_class_string(eci->engine_class));
 	igt_assert_lte(n_exec_queues, MAX_N_EXECQUEUES);
 
-	vm = xe_vm_create(fd, DRM_XE_VM_CREATE_FLAG_LR_MODE, 0);
+	vm = xe_vm_create(fd, DRM_XE_VM_CREATE_FLAG_LR_MODE |
+			  DRM_XE_VM_CREATE_FLAG_RESTARTABLE, 0);
 	bo_size = sizeof(*data) * n_execs;
 	bo_size = xe_bb_size(fd, bo_size);
 
@@ -401,7 +422,8 @@ static void lr_mode_workload(int fd)
 	uint32_t bo;
 	uint32_t ts_1, ts_2;
 
-	vm = xe_vm_create(fd, DRM_XE_VM_CREATE_FLAG_LR_MODE, 0);
+	vm = xe_vm_create(fd, DRM_XE_VM_CREATE_FLAG_LR_MODE |
+			  DRM_XE_VM_CREATE_FLAG_RESTARTABLE, 0);
 	ahnd = intel_allocator_open(fd, 0, INTEL_ALLOCATOR_RELOC);
 	bo_size = xe_bb_size(fd, sizeof(*spin));
 	engine = xe_find_engine_by_class(fd, DRM_XE_ENGINE_CLASS_COPY);
@@ -481,14 +503,15 @@ int igt_main()
 		{ NULL },
 	};
 	int fd;
-	struct xe_watch_event watch_event = {
-		.ops = &event_ops,
+	struct xe_watch_ctx watch_ctx = {
+		.base.ops = &event_ops,
 	};
 	struct xe_watch_listener *listener;
 
 	igt_fixture() {
 		fd = drm_open_driver(DRIVER_XE);
-		listener = xe_watch_listener_create(fd, &watch_event);
+		watch_ctx.fd = fd;
+		listener = xe_watch_listener_create(fd, &watch_ctx.base);
 	}
 
 	for (const struct section *s = sections; s->name; s++) {
@@ -523,7 +546,9 @@ int igt_main()
 
 
 	igt_fixture() {
-		drm_close_driver(fd);
 		xe_watch_listener_destroy(listener);
+		igt_info("VM restarts performed: %u\n",
+			 atomic_load(&watch_ctx.restart_count));
+		drm_close_driver(fd);
 	}
 }
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-06-12 11:08 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-12 11:06 [PATCH i-g-t 0/4] xe: watch queue event support and VM restart recovery Thomas Hellström
2026-06-12 11:06 ` [PATCH i-g-t 1/4] lib/xe: add xe_vm_restart ioctl helper Thomas Hellström
2026-06-12 11:06 ` [PATCH i-g-t 2/4] lib/xe: add xe_watch listener for watch queue events Thomas Hellström
2026-06-12 11:06 ` [PATCH i-g-t 3/4] tests/intel/xe_exec_compute_mode: Add a listener for file events Thomas Hellström
2026-06-12 11:06 ` [PATCH i-g-t 4/4] tests/intel/xe_exec_compute_mode: Restart VM on ENOMEM/ENOSPC errors Thomas Hellström

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox