vfio/pci: Implement ioeventfd thread handler for contended memory lock
authorAlex Williamson <alex.williamson@redhat.com>
Mon, 2 Nov 2020 22:02:00 +0000 (15:02 -0700)
committerAlex Williamson <alex.williamson@redhat.com>
Tue, 3 Nov 2020 18:07:40 +0000 (11:07 -0700)
The ioeventfd is called under spinlock with interrupts disabled,
therefore if the memory lock is contended defer code that might
sleep to a thread context.

Fixes: bc93b9ae0151 ("vfio-pci: Avoid recursive read-lock usage")
Link: https://bugzilla.kernel.org/show_bug.cgi?id=209253#c1
Reported-by: Ian Pilcher <arequipeno@gmail.com>
Tested-by: Ian Pilcher <arequipeno@gmail.com>
Tested-by: Justin Gatzen <justin.gatzen@gmail.com>
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
drivers/vfio/pci/vfio_pci_rdwr.c

index 9e353c4..a0b5fc8 100644 (file)
@@ -356,34 +356,60 @@ ssize_t vfio_pci_vga_rw(struct vfio_pci_device *vdev, char __user *buf,
        return done;
 }
 
-static int vfio_pci_ioeventfd_handler(void *opaque, void *unused)
+static void vfio_pci_ioeventfd_do_write(struct vfio_pci_ioeventfd *ioeventfd,
+                                       bool test_mem)
 {
-       struct vfio_pci_ioeventfd *ioeventfd = opaque;
-
        switch (ioeventfd->count) {
        case 1:
-               vfio_pci_iowrite8(ioeventfd->vdev, ioeventfd->test_mem,
+               vfio_pci_iowrite8(ioeventfd->vdev, test_mem,
                                  ioeventfd->data, ioeventfd->addr);
                break;
        case 2:
-               vfio_pci_iowrite16(ioeventfd->vdev, ioeventfd->test_mem,
+               vfio_pci_iowrite16(ioeventfd->vdev, test_mem,
                                   ioeventfd->data, ioeventfd->addr);
                break;
        case 4:
-               vfio_pci_iowrite32(ioeventfd->vdev, ioeventfd->test_mem,
+               vfio_pci_iowrite32(ioeventfd->vdev, test_mem,
                                   ioeventfd->data, ioeventfd->addr);
                break;
 #ifdef iowrite64
        case 8:
-               vfio_pci_iowrite64(ioeventfd->vdev, ioeventfd->test_mem,
+               vfio_pci_iowrite64(ioeventfd->vdev, test_mem,
                                   ioeventfd->data, ioeventfd->addr);
                break;
 #endif
        }
+}
+
+static int vfio_pci_ioeventfd_handler(void *opaque, void *unused)
+{
+       struct vfio_pci_ioeventfd *ioeventfd = opaque;
+       struct vfio_pci_device *vdev = ioeventfd->vdev;
+
+       if (ioeventfd->test_mem) {
+               if (!down_read_trylock(&vdev->memory_lock))
+                       return 1; /* Lock contended, use thread */
+               if (!__vfio_pci_memory_enabled(vdev)) {
+                       up_read(&vdev->memory_lock);
+                       return 0;
+               }
+       }
+
+       vfio_pci_ioeventfd_do_write(ioeventfd, false);
+
+       if (ioeventfd->test_mem)
+               up_read(&vdev->memory_lock);
 
        return 0;
 }
 
+static void vfio_pci_ioeventfd_thread(void *opaque, void *unused)
+{
+       struct vfio_pci_ioeventfd *ioeventfd = opaque;
+
+       vfio_pci_ioeventfd_do_write(ioeventfd, ioeventfd->test_mem);
+}
+
 long vfio_pci_ioeventfd(struct vfio_pci_device *vdev, loff_t offset,
                        uint64_t data, int count, int fd)
 {
@@ -457,7 +483,8 @@ long vfio_pci_ioeventfd(struct vfio_pci_device *vdev, loff_t offset,
        ioeventfd->test_mem = vdev->pdev->resource[bar].flags & IORESOURCE_MEM;
 
        ret = vfio_virqfd_enable(ioeventfd, vfio_pci_ioeventfd_handler,
-                                NULL, NULL, &ioeventfd->virqfd, fd);
+                                vfio_pci_ioeventfd_thread, NULL,
+                                &ioeventfd->virqfd, fd);
        if (ret) {
                kfree(ioeventfd);
                goto out_unlock;