[RFC PATCH v2 15/16] vfio/pci: Add VFIO_DEVICE_FEATURE_PCI_ERROR_RECOVERY
From: Shameer Kolothum
Date: Tue Sep 29 2026 - 13:44:09 EST
Add a device feature to enable host PCI error recovery and query its
status. SET registers an eventfd for start and completion notifications;
eventfd -1 disables recovery. GET returns the status flags and sequence
number, including while recovery is active.
Reject SET while access is blocked or recovery has failed. Reset the
per-open status and sequence when updating the eventfd. Existing
VFIO_PCI_ERR_IRQ_INDEX notifications are unchanged.
Assisted-by: LLM
Signed-off-by: Shameer Kolothum <skolothumtho@xxxxxxxxxx>
---
include/uapi/linux/vfio.h | 44 +++++++++++++
drivers/vfio/pci/vfio_pci_core.c | 108 +++++++++++++++++++++++++++++++
2 files changed, 152 insertions(+)
diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h
index e41437fa17ad..b755849fd39b 100644
--- a/include/uapi/linux/vfio.h
+++ b/include/uapi/linux/vfio.h
@@ -1555,6 +1555,50 @@ struct vfio_device_feature_zpci_err {
#define VFIO_DEVICE_FEATURE_ZPCI_ERROR 13
+/*
+ * Report host PCI error recovery state for this device.
+ *
+ * VFIO_DEVICE_FEATURE_SET with a valid eventfd enables recovery for this
+ * open and registers notifications for the start and end of each event.
+ * SET with eventfd -1 disables recovery. flags and sequence must be zero.
+ * SET returns -EBUSY during recovery, after recovery failure, or while
+ * device access is blocked, and -ENODEV after close has started.
+ * VFIO_PCI_ERR_IRQ_INDEX notifications are unchanged.
+ *
+ * VFIO_DEVICE_FEATURE_GET returns the current state and eventfd = -1.
+ * GET is allowed during recovery, but may wait for a running callback.
+ *
+ * The sequence number increments for each event and resets to zero when
+ * recovery is enabled. Use it to detect coalesced notifications. Device
+ * accesses return -EIO while IN_PROGRESS is set. Recovery may complete
+ * before userspace reads the notification, so userspace must check the
+ * sequence and status rather than wait to observe IN_PROGRESS.
+ *
+ * CHANNEL_FROZEN records a frozen channel. DEVICE_RESET records a host
+ * reset; userspace must reconfigure interrupts with VFIO_DEVICE_SET_IRQS.
+ * FAILED indicates that recovery failed and access remains blocked until
+ * close and reopen. Status bits persist until the next event or SET.
+ * ENABLED indicates that recovery is enabled.
+ *
+ * Enabling recovery during an existing event does not provide status or
+ * notifications for that event.
+ *
+ * Open returns -EBUSY while a previously recorded host recovery is active,
+ * including events reported while recovery was disabled.
+ */
+struct vfio_device_pci_error_recovery {
+ __u32 flags;
+#define VFIO_PCI_ERROR_RECOVERY_IN_PROGRESS (1U << 0)
+#define VFIO_PCI_ERROR_RECOVERY_CHANNEL_FROZEN (1U << 1)
+#define VFIO_PCI_ERROR_RECOVERY_DEVICE_RESET (1U << 2)
+#define VFIO_PCI_ERROR_RECOVERY_FAILED (1U << 3)
+#define VFIO_PCI_ERROR_RECOVERY_ENABLED (1U << 4)
+ __s32 eventfd;
+ __aligned_u64 sequence;
+};
+
+#define VFIO_DEVICE_FEATURE_PCI_ERROR_RECOVERY 14
+
/* -------- API for Type1 VFIO IOMMU -------- */
/**
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index 2ec8022e4a69..314d77868803 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -1752,6 +1752,111 @@ static int vfio_pci_core_feature_token(struct vfio_pci_core_device *vdev,
return 0;
}
+/* Consumes the eventfd reference on both success and failure. */
+static int vfio_pci_recovery_set(struct vfio_pci_core_device *vdev,
+ struct eventfd_ctx *ctx, bool enable)
+{
+ int ret;
+
+ guard(mutex)(&vdev->access_lock);
+ if (!vdev->device_open) {
+ ret = -ENODEV;
+ goto out_put;
+ }
+ if (vdev->access_blocked) {
+ ret = -EBUSY;
+ goto out_put;
+ }
+ if (!enable &&
+ (vdev->pci_recovery_flags &
+ (VFIO_PCI_RECOVERY_IN_PROGRESS | VFIO_PCI_RECOVERY_FAILED))) {
+ ret = -EBUSY;
+ goto out_put;
+ }
+
+ mutex_lock(&vdev->igate);
+ ret = vfio_pci_eventfd_replace_locked(vdev, &vdev->pci_recovery_trigger,
+ ctx);
+ mutex_unlock(&vdev->igate);
+ if (ret)
+ goto out_put;
+
+ WRITE_ONCE(vdev->pci_recovery_enabled, enable);
+ /*
+ * Reset the per-open status when updating the recovery eventfd.
+ * The access block and host transaction state are unchanged.
+ */
+ WRITE_ONCE(vdev->pci_recovery_flags, 0);
+ vdev->pci_recovery_sequence = 0;
+ vdev->pci_recovery_command_valid = false;
+
+ return 0;
+
+out_put:
+ if (ctx)
+ eventfd_ctx_put(ctx);
+ return ret;
+}
+
+static int
+vfio_pci_core_feature_error_recovery(struct vfio_pci_core_device *vdev, u32 flags,
+ struct vfio_device_pci_error_recovery __user *arg,
+ size_t argsz)
+{
+ struct vfio_device_pci_error_recovery state = { .eventfd = -1 };
+ struct eventfd_ctx *ctx = NULL;
+ bool enable;
+ int ret;
+
+ if (!vdev->pci_recovery_supported)
+ return -ENOTTY;
+
+ ret = vfio_check_feature(flags, argsz,
+ VFIO_DEVICE_FEATURE_GET |
+ VFIO_DEVICE_FEATURE_SET, sizeof(state));
+ if (ret != 1)
+ return ret;
+
+ if (flags & VFIO_DEVICE_FEATURE_GET) {
+ scoped_guard(mutex, &vdev->access_lock) {
+ u32 rflags = vdev->pci_recovery_flags;
+
+ if (vdev->pci_recovery_enabled)
+ state.flags |= VFIO_PCI_ERROR_RECOVERY_ENABLED;
+ if (rflags & VFIO_PCI_RECOVERY_IN_PROGRESS)
+ state.flags |=
+ VFIO_PCI_ERROR_RECOVERY_IN_PROGRESS;
+ if (rflags & VFIO_PCI_RECOVERY_FROZEN)
+ state.flags |=
+ VFIO_PCI_ERROR_RECOVERY_CHANNEL_FROZEN;
+ if (rflags & VFIO_PCI_RECOVERY_RESET)
+ state.flags |=
+ VFIO_PCI_ERROR_RECOVERY_DEVICE_RESET;
+ if (rflags & VFIO_PCI_RECOVERY_FAILED)
+ state.flags |= VFIO_PCI_ERROR_RECOVERY_FAILED;
+ state.sequence = vdev->pci_recovery_sequence;
+ }
+
+ if (copy_to_user(arg, &state, sizeof(state)))
+ return -EFAULT;
+ return 0;
+ }
+
+ if (copy_from_user(&state, arg, sizeof(state)))
+ return -EFAULT;
+ if (state.flags || state.sequence || state.eventfd < -1)
+ return -EINVAL;
+
+ enable = state.eventfd >= 0;
+ if (enable) {
+ ctx = eventfd_ctx_fdget(state.eventfd);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+ }
+
+ return vfio_pci_recovery_set(vdev, ctx, enable);
+}
+
int vfio_pci_core_ioctl_feature(struct vfio_device *device, u32 flags,
void __user *arg, size_t argsz)
{
@@ -1772,6 +1877,9 @@ int vfio_pci_core_ioctl_feature(struct vfio_device *device, u32 flags,
return vfio_pci_core_feature_dma_buf(vdev, flags, arg, argsz);
case VFIO_DEVICE_FEATURE_ZPCI_ERROR:
return vfio_pci_zdev_feature_err(device, flags, arg, argsz);
+
+ case VFIO_DEVICE_FEATURE_PCI_ERROR_RECOVERY:
+ return vfio_pci_core_feature_error_recovery(vdev, flags, arg, argsz);
default:
return -ENOTTY;
}
--
2.43.0