PCI: Add pci_mmap_resource_range() and use it for ARM64
authorDavid Woodhouse <dwmw@amazon.co.uk>
Wed, 12 Apr 2017 12:25:59 +0000 (13:25 +0100)
committerBjorn Helgaas <bhelgaas@google.com>
Thu, 20 Apr 2017 13:47:47 +0000 (08:47 -0500)
Starting to leave behind the legacy of the pci_mmap_page_range() interface
which takes "user-visible" BAR addresses.  This takes just the resource and
offset.

For now, both APIs coexist and depending on the platform, one is
implemented as a wrapper around the other.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Documentation/filesystems/sysfs-pci.txt
arch/arm64/include/asm/pci.h
drivers/pci/Makefile
drivers/pci/mmap.c [new file with mode: 0644]
drivers/pci/pci-sysfs.c
drivers/pci/pci.h
include/linux/pci.h

index 46b95d82c4fd4a7696a31001e6bd3f0db835ec59..06f1d64c6f702fa2507b748b6feb5a6bddf20975 100644 (file)
@@ -113,9 +113,13 @@ Supporting PCI access on new platforms
 --------------------------------------
 
 In order to support PCI resource mapping as described above, Linux platform
-code must define HAVE_PCI_MMAP and provide a pci_mmap_page_range function.
-Platforms are free to only support subsets of the mmap functionality, but
-useful return codes should be provided.
+code should ideally define ARCH_GENERIC_PCI_MMAP_RESOURCE and use the generic
+implementation of that functionality. To support the historical interface of
+mmap() through files in /proc/bus/pci, platforms may also set HAVE_PCI_MMAP.
+
+Alternatively, platforms which set HAVE_PCI_MMAP may provide their own
+implementation of pci_mmap_page_range() instead of defining
+ARCH_GENERIC_PCI_MMAP_RESOURCE.
 
 Platforms which support write-combining maps of PCI resources must define
 arch_can_pci_mmap_wc() which shall evaluate to non-zero at runtime when
index b9a7ba9ca44c42aef55bf77f8c0df533b9a0ab3b..1fc19744ffe995f01f85a7cbc95c32410f763e94 100644 (file)
@@ -22,6 +22,8 @@
  */
 #define PCI_DMA_BUS_IS_PHYS    (0)
 
+#define ARCH_GENERIC_PCI_MMAP_RESOURCE 1
+
 extern int isa_dma_bridge_buggy;
 
 #ifdef CONFIG_PCI
index 8db5079f09a7826bdf66dc94f02c19e902f99e6e..3d40e415a1715606e222ac6b5e99897351c6bc0f 100644 (file)
@@ -4,7 +4,7 @@
 
 obj-y          += access.o bus.o probe.o host-bridge.o remove.o pci.o \
                        pci-driver.o search.o pci-sysfs.o rom.o setup-res.o \
-                       irq.o vpd.o setup-bus.o vc.o
+                       irq.o vpd.o setup-bus.o vc.o mmap.o
 obj-$(CONFIG_PROC_FS) += proc.o
 obj-$(CONFIG_SYSFS) += slot.o
 
diff --git a/drivers/pci/mmap.c b/drivers/pci/mmap.c
new file mode 100644 (file)
index 0000000..b7aca9c
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * mmap.c — generic PCI resource mmap helper
+ *
+ * Copyright © 2017 Amazon.com, Inc. or its affiliates.
+ *
+ * Author: David Woodhouse <dwmw2@infradead.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+
+#ifdef ARCH_GENERIC_PCI_MMAP_RESOURCE
+
+/*
+ * Modern setup: generic pci_mmap_resource_range(), and implement the legacy
+ * pci_mmap_page_range() (if needed) as a wrapper round it.
+ */
+
+#ifdef HAVE_PCI_MMAP
+int pci_mmap_page_range(struct pci_dev *pdev, int bar,
+                       struct vm_area_struct *vma,
+                       enum pci_mmap_state mmap_state, int write_combine)
+{
+       resource_size_t start, end;
+
+       pci_resource_to_user(pdev, bar, &pdev->resource[bar], &start, &end);
+
+       /* Adjust vm_pgoff to be the offset within the resource */
+       vma->vm_pgoff -= start >> PAGE_SHIFT;
+       return pci_mmap_resource_range(pdev, bar, vma, mmap_state,
+                                      write_combine);
+}
+#endif
+
+static const struct vm_operations_struct pci_phys_vm_ops = {
+#ifdef CONFIG_HAVE_IOREMAP_PROT
+       .access = generic_access_phys,
+#endif
+};
+
+int pci_mmap_resource_range(struct pci_dev *pdev, int bar,
+                           struct vm_area_struct *vma,
+                           enum pci_mmap_state mmap_state, int write_combine)
+{
+       unsigned long size;
+
+       if (mmap_state == pci_mmap_io)
+               return -EINVAL;
+
+       size = ((pci_resource_len(pdev, bar) - 1) >> PAGE_SHIFT) + 1;
+       if (vma->vm_pgoff + vma_pages(vma) > size)
+               return -EINVAL;
+
+       if (write_combine)
+               vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+       else
+               vma->vm_page_prot = pgprot_device(vma->vm_page_prot);
+
+       vma->vm_pgoff += (pci_resource_start(pdev, bar) >> PAGE_SHIFT);
+       vma->vm_ops = &pci_phys_vm_ops;
+
+       return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+                                 vma->vm_end - vma->vm_start,
+                                 vma->vm_page_prot);
+}
+
+#elif defined(HAVE_PCI_MMAP) /* && !ARCH_GENERIC_PCI_MMAP_RESOURCE */
+
+/*
+ * Legacy setup: Impement pci_mmap_resource_range() as a wrapper around
+ * the architecture's pci_mmap_page_range(), converting to "user visible"
+ * addresses as necessary.
+ */
+
+int pci_mmap_resource_range(struct pci_dev *pdev, int bar,
+                           struct vm_area_struct *vma,
+                           enum pci_mmap_state mmap_state, int write_combine)
+{
+       resource_size_t start, end;
+
+       /*
+        * pci_mmap_page_range() expects the same kind of entry as coming
+        * from /proc/bus/pci/ which is a "user visible" value. If this is
+        * different from the resource itself, arch will do necessary fixup.
+        */
+       pci_resource_to_user(pdev, bar, &pdev->resource[bar], &start, &end);
+       vma->vm_pgoff += start >> PAGE_SHIFT;
+       return pci_mmap_page_range(pdev, bar, vma, mmap_state, write_combine);
+}
+#endif
index bfd9efe637c4def6179f79eb1e8d9d62540312a4..10feb98a2b1d19095c52059a56c80b9f4df974e9 100644 (file)
@@ -980,7 +980,7 @@ void pci_remove_legacy_files(struct pci_bus *b)
 }
 #endif /* HAVE_PCI_LEGACY */
 
-#ifdef HAVE_PCI_MMAP
+#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE)
 
 int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vma,
                  enum pci_mmap_api mmap_api)
@@ -1019,7 +1019,6 @@ static int pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr,
        struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
        int bar = (unsigned long)attr->private;
        enum pci_mmap_state mmap_type;
-       resource_size_t start, end;
        struct resource *res = &pdev->resource[bar];
 
        if (res->flags & IORESOURCE_MEM && iomem_is_exclusive(res->start))
@@ -1033,15 +1032,9 @@ static int pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr,
                        (u64)pci_resource_len(pdev, bar));
                return -EINVAL;
        }
-
-       /* pci_mmap_page_range() expects the same kind of entry as coming
-        * from /proc/bus/pci/ which is a "user visible" value. If this is
-        * different from the resource itself, arch will do necessary fixup.
-        */
-       pci_resource_to_user(pdev, bar, res, &start, &end);
-       vma->vm_pgoff += start >> PAGE_SHIFT;
        mmap_type = res->flags & IORESOURCE_MEM ? pci_mmap_mem : pci_mmap_io;
-       return pci_mmap_page_range(pdev, bar, vma, mmap_type, write_combine);
+
+       return pci_mmap_resource_range(pdev, bar, vma, mmap_type, write_combine);
 }
 
 static int pci_mmap_resource_uc(struct file *filp, struct kobject *kobj,
index 8dd38e69d6f2a8ae4f4b7d6801f3f321abb5ac1a..8e5ca2dec7e7b05874298c065769765f7b919e46 100644 (file)
@@ -21,14 +21,14 @@ void pci_create_firmware_label_files(struct pci_dev *pdev);
 void pci_remove_firmware_label_files(struct pci_dev *pdev);
 #endif
 void pci_cleanup_rom(struct pci_dev *dev);
-#ifdef HAVE_PCI_MMAP
+
 enum pci_mmap_api {
        PCI_MMAP_SYSFS, /* mmap on /sys/bus/pci/devices/<BDF>/resource<N> */
        PCI_MMAP_PROCFS /* mmap on /proc/bus/pci/<BDF> */
 };
 int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vmai,
                  enum pci_mmap_api mmap_api);
-#endif
+
 int pci_probe_reset_function(struct pci_dev *dev);
 
 /**
index 7173a677d6ddbb716f9d43d2a4fab3b525400a0b..98a72abcf3611619140ef97bb2ce14b938777006 100644 (file)
@@ -1626,10 +1626,21 @@ static inline int pci_get_new_domain_nr(void) { return -ENOSYS; }
 
 #include <asm/pci.h>
 
-/* Map a range of PCI memory or I/O space for a device into user space.
- * Architectures provide this function if they set HAVE_PCI_MMAP, and
- * it accepts the 'write_combine' argument when arch_can_pci_mmap_wc()
- * evaluates to nonzero. */
+/* These two functions provide almost identical functionality. Depennding
+ * on the architecture, one will be implemented as a wrapper around the
+ * other (in drivers/pci/mmap.c).
+ *
+ * pci_mmap_resource_range() maps a specific BAR, and vm->vm_pgoff
+ * is expected to be an offset within that region.
+ *
+ * pci_mmap_page_range() is the legacy architecture-specific interface,
+ * which accepts a "user visible" resource address converted by
+ * pci_resource_to_user(), as used in the legacy mmap() interface in
+ * /proc/bus/pci/.
+ */
+int pci_mmap_resource_range(struct pci_dev *dev, int bar,
+                           struct vm_area_struct *vma,
+                           enum pci_mmap_state mmap_state, int write_combine);
 int pci_mmap_page_range(struct pci_dev *pdev, int bar,
                        struct vm_area_struct *vma,
                        enum pci_mmap_state mmap_state, int write_combine);