9ae3a8
From 742bae4cea60c8601bbb6e5ec643167d8ca664d7 Mon Sep 17 00:00:00 2001
9ae3a8
From: Nigel Croxon <ncroxon@redhat.com>
9ae3a8
Date: Tue, 6 Aug 2013 19:52:04 +0200
9ae3a8
Subject: vfio: QEMU-AER: Qemu changes to support AER for VFIO-PCI devices
9ae3a8
9ae3a8
RH-Author: Nigel Croxon <ncroxon@redhat.com>
9ae3a8
Message-id: <1375818724-41239-3-git-send-email-ncroxon@redhat.com>
9ae3a8
Patchwork-id: 53017
9ae3a8
O-Subject: [RHEL7.0 qemu-kvm PATCH v2 2/2] vfio: QEMU-AER: Qemu changes to support AER for VFIO-PCI devices
9ae3a8
Bugzilla: 984604
9ae3a8
RH-Acked-by: Laszlo Ersek <lersek@redhat.com>
9ae3a8
RH-Acked-by: Alex Williamson <alex.williamson@redhat.com>
9ae3a8
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
9ae3a8
9ae3a8
From: Vijay Mohan Pandarathil <vijaymohan.pandarathil@hp.com>
9ae3a8
9ae3a8
Add support for error containment when a VFIO device assigned to a KVM
9ae3a8
guest encounters an error. This is for PCIe devices/drivers that support AER
9ae3a8
functionality. When the host OS is notified of an error in a device either
9ae3a8
through the firmware first approach or through an interrupt handled by the AER
9ae3a8
root port driver, the error handler registered by the vfio-pci driver gets
9ae3a8
invoked. The qemu process is signaled through an eventfd registered per
9ae3a8
VFIO device by the qemu process. In the eventfd handler, qemu decides on
9ae3a8
what action to take. In this implementation, guest is brought down to
9ae3a8
contain the error.
9ae3a8
9ae3a8
The kernel patches for the above functionality has been already accepted.
9ae3a8
9ae3a8
This is a refresh of the QEMU patch which was reviewed earlier.
9ae3a8
http://marc.info/?l=linux-kernel&m=136281557608087&w=2
9ae3a8
This patch has the same contents and has been built after refreshing
9ae3a8
to latest upstream and after the linux headers have been updated in qemu.
9ae3a8
9ae3a8
	- Create eventfd per vfio device assigned to a guest and register an
9ae3a8
          event handler
9ae3a8
9ae3a8
	- This fd is passed to the vfio_pci driver through the SET_IRQ ioctl
9ae3a8
9ae3a8
	- When the device encounters an error, the eventfd is signalled
9ae3a8
          and the qemu eventfd handler gets invoked.
9ae3a8
9ae3a8
	- In the handler decide what action to take. Current action taken
9ae3a8
          is to stop the guest.
9ae3a8
9ae3a8
Signed-off-by: Vijay Mohan Pandarathil <vijaymohan.pandarathil@hp.com>
9ae3a8
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
9ae3a8
(cherry picked from commit 7b4b0e9eda51902b53bc1a2318df53cdb8b72eed)
9ae3a8
9ae3a8
diff --git a/hw/misc/vfio.c b/hw/misc/vfio.c
9ae3a8
index 693a9ff..f8fef8c 100644
9ae3a8
--- a/hw/misc/vfio.c
9ae3a8
+++ b/hw/misc/vfio.c
9ae3a8
@@ -158,6 +158,7 @@ typedef struct VFIODevice {
9ae3a8
     PCIHostDeviceAddress host;
9ae3a8
     QLIST_ENTRY(VFIODevice) next;
9ae3a8
     struct VFIOGroup *group;
9ae3a8
+    EventNotifier err_notifier;
9ae3a8
     uint32_t features;
9ae3a8
 #define VFIO_FEATURE_ENABLE_VGA_BIT 0
9ae3a8
 #define VFIO_FEATURE_ENABLE_VGA (1 << VFIO_FEATURE_ENABLE_VGA_BIT)
9ae3a8
@@ -165,6 +166,7 @@ typedef struct VFIODevice {
9ae3a8
     uint8_t pm_cap;
9ae3a8
     bool reset_works;
9ae3a8
     bool has_vga;
9ae3a8
+    bool pci_aer;
9ae3a8
 } VFIODevice;
9ae3a8
 
9ae3a8
 typedef struct VFIOGroup {
9ae3a8
@@ -2776,6 +2778,7 @@ static int vfio_get_device(VFIOGroup *group, const char *name, VFIODevice *vdev)
9ae3a8
 {
9ae3a8
     struct vfio_device_info dev_info = { .argsz = sizeof(dev_info) };
9ae3a8
     struct vfio_region_info reg_info = { .argsz = sizeof(reg_info) };
9ae3a8
+    struct vfio_irq_info irq_info = { .argsz = sizeof(irq_info) };
9ae3a8
     int ret, i;
9ae3a8
 
9ae3a8
     ret = ioctl(group->fd, VFIO_GROUP_GET_DEVICE_FD, name);
9ae3a8
@@ -2919,6 +2922,19 @@ static int vfio_get_device(VFIOGroup *group, const char *name, VFIODevice *vdev)
9ae3a8
 
9ae3a8
         vdev->has_vga = true;
9ae3a8
     }
9ae3a8
+    irq_info.index = VFIO_PCI_ERR_IRQ_INDEX;
9ae3a8
+
9ae3a8
+    ret = ioctl(vdev->fd, VFIO_DEVICE_GET_IRQ_INFO, &irq_info);
9ae3a8
+    if (ret) {
9ae3a8
+        /* This can fail for an old kernel or legacy PCI dev */
9ae3a8
+        DPRINTF("VFIO_DEVICE_GET_IRQ_INFO failure ret=%d\n", ret);
9ae3a8
+        ret = 0;
9ae3a8
+    } else if (irq_info.count == 1) {
9ae3a8
+        vdev->pci_aer = true;
9ae3a8
+    } else {
9ae3a8
+        error_report("vfio: Warning: "
9ae3a8
+                     "Could not enable error recovery for the device\n");
9ae3a8
+    }
9ae3a8
 
9ae3a8
 error:
9ae3a8
     if (ret) {
9ae3a8
@@ -2941,6 +2957,113 @@ static void vfio_put_device(VFIODevice *vdev)
9ae3a8
     }
9ae3a8
 }
9ae3a8
 
9ae3a8
+static void vfio_err_notifier_handler(void *opaque)
9ae3a8
+{
9ae3a8
+    VFIODevice *vdev = opaque;
9ae3a8
+
9ae3a8
+    if (!event_notifier_test_and_clear(&vdev->err_notifier)) {
9ae3a8
+        return;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    /*
9ae3a8
+     * TBD. Retrieve the error details and decide what action
9ae3a8
+     * needs to be taken. One of the actions could be to pass
9ae3a8
+     * the error to the guest and have the guest driver recover
9ae3a8
+     * from the error. This requires that PCIe capabilities be
9ae3a8
+     * exposed to the guest. For now, we just terminate the
9ae3a8
+     * guest to contain the error.
9ae3a8
+     */
9ae3a8
+
9ae3a8
+    error_report("%s (%04x:%02x:%02x.%x)"
9ae3a8
+        "Unrecoverable error detected...\n"
9ae3a8
+        "Please collect any data possible and then kill the guest",
9ae3a8
+        __func__, vdev->host.domain, vdev->host.bus,
9ae3a8
+        vdev->host.slot, vdev->host.function);
9ae3a8
+
9ae3a8
+    vm_stop(RUN_STATE_IO_ERROR);
9ae3a8
+}
9ae3a8
+
9ae3a8
+/*
9ae3a8
+ * Registers error notifier for devices supporting error recovery.
9ae3a8
+ * If we encounter a failure in this function, we report an error
9ae3a8
+ * and continue after disabling error recovery support for the
9ae3a8
+ * device.
9ae3a8
+ */
9ae3a8
+static void vfio_register_err_notifier(VFIODevice *vdev)
9ae3a8
+{
9ae3a8
+    int ret;
9ae3a8
+    int argsz;
9ae3a8
+    struct vfio_irq_set *irq_set;
9ae3a8
+    int32_t *pfd;
9ae3a8
+
9ae3a8
+    if (!vdev->pci_aer) {
9ae3a8
+        return;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    if (event_notifier_init(&vdev->err_notifier, 0)) {
9ae3a8
+        error_report("vfio: Warning: "
9ae3a8
+                     "Unable to init event notifier for error detection\n");
9ae3a8
+        vdev->pci_aer = false;
9ae3a8
+        return;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    argsz = sizeof(*irq_set) + sizeof(*pfd);
9ae3a8
+
9ae3a8
+    irq_set = g_malloc0(argsz);
9ae3a8
+    irq_set->argsz = argsz;
9ae3a8
+    irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
9ae3a8
+                     VFIO_IRQ_SET_ACTION_TRIGGER;
9ae3a8
+    irq_set->index = VFIO_PCI_ERR_IRQ_INDEX;
9ae3a8
+    irq_set->start = 0;
9ae3a8
+    irq_set->count = 1;
9ae3a8
+    pfd = (int32_t *)&irq_set->data;
9ae3a8
+
9ae3a8
+    *pfd = event_notifier_get_fd(&vdev->err_notifier);
9ae3a8
+    qemu_set_fd_handler(*pfd, vfio_err_notifier_handler, NULL, vdev);
9ae3a8
+
9ae3a8
+    ret = ioctl(vdev->fd, VFIO_DEVICE_SET_IRQS, irq_set);
9ae3a8
+    if (ret) {
9ae3a8
+        error_report("vfio: Failed to set up error notification\n");
9ae3a8
+        qemu_set_fd_handler(*pfd, NULL, NULL, vdev);
9ae3a8
+        event_notifier_cleanup(&vdev->err_notifier);
9ae3a8
+        vdev->pci_aer = false;
9ae3a8
+    }
9ae3a8
+    g_free(irq_set);
9ae3a8
+}
9ae3a8
+
9ae3a8
+static void vfio_unregister_err_notifier(VFIODevice *vdev)
9ae3a8
+{
9ae3a8
+    int argsz;
9ae3a8
+    struct vfio_irq_set *irq_set;
9ae3a8
+    int32_t *pfd;
9ae3a8
+    int ret;
9ae3a8
+
9ae3a8
+    if (!vdev->pci_aer) {
9ae3a8
+        return;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    argsz = sizeof(*irq_set) + sizeof(*pfd);
9ae3a8
+
9ae3a8
+    irq_set = g_malloc0(argsz);
9ae3a8
+    irq_set->argsz = argsz;
9ae3a8
+    irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
9ae3a8
+                     VFIO_IRQ_SET_ACTION_TRIGGER;
9ae3a8
+    irq_set->index = VFIO_PCI_ERR_IRQ_INDEX;
9ae3a8
+    irq_set->start = 0;
9ae3a8
+    irq_set->count = 1;
9ae3a8
+    pfd = (int32_t *)&irq_set->data;
9ae3a8
+    *pfd = -1;
9ae3a8
+
9ae3a8
+    ret = ioctl(vdev->fd, VFIO_DEVICE_SET_IRQS, irq_set);
9ae3a8
+    if (ret) {
9ae3a8
+        error_report("vfio: Failed to de-assign error fd: %d\n", ret);
9ae3a8
+    }
9ae3a8
+    g_free(irq_set);
9ae3a8
+    qemu_set_fd_handler(event_notifier_get_fd(&vdev->err_notifier),
9ae3a8
+                        NULL, NULL, vdev);
9ae3a8
+    event_notifier_cleanup(&vdev->err_notifier);
9ae3a8
+}
9ae3a8
+
9ae3a8
 static int vfio_initfn(PCIDevice *pdev)
9ae3a8
 {
9ae3a8
     VFIODevice *pvdev, *vdev = DO_UPCAST(VFIODevice, pdev, pdev);
9ae3a8
@@ -3073,6 +3196,7 @@ static int vfio_initfn(PCIDevice *pdev)
9ae3a8
     }
9ae3a8
 
9ae3a8
     add_boot_device_path(vdev->bootindex, &pdev->qdev, NULL);
9ae3a8
+    vfio_register_err_notifier(vdev);
9ae3a8
 
9ae3a8
     return 0;
9ae3a8
 
9ae3a8
@@ -3092,6 +3216,7 @@ static void vfio_exitfn(PCIDevice *pdev)
9ae3a8
     VFIODevice *vdev = DO_UPCAST(VFIODevice, pdev, pdev);
9ae3a8
     VFIOGroup *group = vdev->group;
9ae3a8
 
9ae3a8
+    vfio_unregister_err_notifier(vdev);
9ae3a8
     pci_device_set_intx_routing_notifier(&vdev->pdev, NULL);
9ae3a8
     vfio_disable_interrupts(vdev);
9ae3a8
     if (vdev->intx.mmap_timer) {