* [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* 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
* [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