* [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; 6+ 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] 6+ 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 14:38 ` Kamil Konieczny
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, 1 reply; 6+ 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] 6+ 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; 6+ 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] 6+ 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; 6+ 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] 6+ 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; 6+ 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] 6+ messages in thread
* Re: [PATCH i-g-t 1/4] lib/xe: add xe_vm_restart ioctl helper
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 14:38 ` Kamil Konieczny
0 siblings, 0 replies; 6+ messages in thread
From: Kamil Konieczny @ 2026-06-12 14:38 UTC (permalink / raw)
To: Thomas Hellström
Cc: igt-dev, Matthew Brost, Maarten Lankhorst, Michal Mrozek,
John Falkowski, Rodrigo Vivi, Lahtinen Joonas
Hi Thomas,
On 2026-06-12 at 13:06:16 +0200, Thomas Hellström wrote:
> 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
You s-o-b is missing here.
> ---
> include/drm-uapi/xe_drm.h | 40 +++++++++++++++++++++++++++++++++++
This change should be in separate commit, syncing with drm-next.
See git log.
Regards,
Kamil
> 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
> */
>
[cut]
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-12 14:39 UTC | newest]
Thread overview: 6+ 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 14:38 ` Kamil Konieczny
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 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.