s390/pci: Fix s390_mmio_read/write syscall page fault handling
authorNiklas Schnelle <schnelle@linux.ibm.com>
Wed, 26 Feb 2025 12:07:45 +0000 (13:07 +0100)
committerBjorn Helgaas <bhelgaas@google.com>
Fri, 21 Mar 2025 19:54:16 +0000 (14:54 -0500)
The s390 MMIO syscalls when using the classic PCI instructions do not
cause a page fault when follow_pfnmap_start() fails due to the page not
being present. Besides being a general deficiency this breaks vfio-pci's
mmap() handling once VFIO_PCI_MMAP gets enabled as this lazily maps on
first access. Fix this by following a failed follow_pfnmap_start() with
fixup_user_page() and retrying the follow_pfnmap_start(). Also fix
a VM_READ vs VM_WRITE mixup in the read syscall.

Link: https://lore.kernel.org/r/20250226-vfio_pci_mmap-v7-1-c5c0f1d26efd@linux.ibm.com
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
arch/s390/pci/pci_mmio.c

index 46f99dc164ade4ca10f170cd66bdb648f92aa904..1997d9b7965df3b9b6019c7537028cd29d52fc13 100644 (file)
@@ -175,8 +175,12 @@ SYSCALL_DEFINE3(s390_pci_mmio_write, unsigned long, mmio_addr,
        args.address = mmio_addr;
        args.vma = vma;
        ret = follow_pfnmap_start(&args);
-       if (ret)
-               goto out_unlock_mmap;
+       if (ret) {
+               fixup_user_fault(current->mm, mmio_addr, FAULT_FLAG_WRITE, NULL);
+               ret = follow_pfnmap_start(&args);
+               if (ret)
+                       goto out_unlock_mmap;
+       }
 
        io_addr = (void __iomem *)((args.pfn << PAGE_SHIFT) |
                        (mmio_addr & ~PAGE_MASK));
@@ -315,14 +319,18 @@ SYSCALL_DEFINE3(s390_pci_mmio_read, unsigned long, mmio_addr,
        if (!(vma->vm_flags & (VM_IO | VM_PFNMAP)))
                goto out_unlock_mmap;
        ret = -EACCES;
-       if (!(vma->vm_flags & VM_WRITE))
+       if (!(vma->vm_flags & VM_READ))
                goto out_unlock_mmap;
 
        args.vma = vma;
        args.address = mmio_addr;
        ret = follow_pfnmap_start(&args);
-       if (ret)
-               goto out_unlock_mmap;
+       if (ret) {
+               fixup_user_fault(current->mm, mmio_addr, 0, NULL);
+               ret = follow_pfnmap_start(&args);
+               if (ret)
+                       goto out_unlock_mmap;
+       }
 
        io_addr = (void __iomem *)((args.pfn << PAGE_SHIFT) |
                        (mmio_addr & ~PAGE_MASK));