]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
PCI: Add managed partial-BAR request and map infrastructure
authorPhilipp Stanner <pstanner@redhat.com>
Thu, 13 Jun 2024 11:50:16 +0000 (13:50 +0200)
committerKrzysztof Wilczyński <kwilczynski@kernel.org>
Wed, 10 Jul 2024 04:19:35 +0000 (04:19 +0000)
The pcim_iomap_devres table tracks entire-BAR mappings, so we can't use it
to build a managed version of pci_iomap_range(), which maps partial BARs.

Add struct pcim_addr_devres, which can track request and mapping of both
entire BARs and partial BARs.

Add the following internal devres functions based on struct
pcim_addr_devres:

  pcim_iomap_region()               # request & map entire BAR
  pcim_iounmap_region()             # unmap & release entire BAR
  pcim_request_region()             # request entire BAR
  pcim_release_region()             # release entire BAR
  pcim_request_all_regions()        # request all entire BARs
  pcim_release_all_regions()        # release all entire BARs

Rework the following public interfaces using the new infrastructure
listed above:

  pcim_iomap()                      # map partial BAR
  pcim_iounmap()                    # unmap partial BAR
  pcim_iomap_regions()              # request & map specified BARs
  pcim_iomap_regions_request_all()  # request all BARs, map specified BARs
  pcim_iounmap_regions()            # unmap & release specified BARs

Link: https://lore.kernel.org/r/20240613115032.29098-4-pstanner@redhat.com
Signed-off-by: Philipp Stanner <pstanner@redhat.com>
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
[bhelgaas: commit log]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/devres.c
drivers/pci/pci.c
drivers/pci/pci.h

index 845d6fab0ce791453f9a3f3346fa375b6d769e5e..8388ca463a7a78e1185e72a5f905c14f127e9816 100644 (file)
 #include "pci.h"
 
 /*
- * PCI iomap devres
+ * On the state of PCI's devres implementation:
+ *
+ * The older devres API for PCI has two significant problems:
+ *
+ * 1. It is very strongly tied to the statically allocated mapping table in
+ *    struct pcim_iomap_devres below. This is mostly solved in the sense of the
+ *    pcim_ functions in this file providing things like ranged mapping by
+ *    bypassing this table, whereas the functions that were present in the old
+ *    API still enter the mapping addresses into the table for users of the old
+ *    API.
+ *
+ * 2. The region-request-functions in pci.c do become managed IF the device has
+ *    been enabled with pcim_enable_device() instead of pci_enable_device().
+ *    This resulted in the API becoming inconsistent: Some functions have an
+ *    obviously managed counter-part (e.g., pci_iomap() <-> pcim_iomap()),
+ *    whereas some don't and are never managed, while others don't and are
+ *    _sometimes_ managed (e.g. pci_request_region()).
+ *
+ *    Consequently, in the new API, region requests performed by the pcim_
+ *    functions are automatically cleaned up through the devres callback
+ *    pcim_addr_resource_release(), while requests performed by
+ *    pcim_enable_device() + pci_*region*() are automatically cleaned up
+ *    through the for-loop in pcim_release().
+ *
+ * TODO 1:
+ * Remove the legacy table entirely once all calls to pcim_iomap_table() in
+ * the kernel have been removed.
+ *
+ * TODO 2:
+ * Port everyone calling pcim_enable_device() + pci_*region*() to using the
+ * pcim_ functions. Then, remove all devres functionality from pci_*region*()
+ * functions and remove the associated cleanups described above in point #2.
  */
-#define PCIM_IOMAP_MAX PCI_STD_NUM_BARS
 
+/*
+ * Legacy struct storing addresses to whole mapped BARs.
+ */
 struct pcim_iomap_devres {
-       void __iomem *table[PCIM_IOMAP_MAX];
+       void __iomem *table[PCI_STD_NUM_BARS];
 };
 
+enum pcim_addr_devres_type {
+       /* Default initializer. */
+       PCIM_ADDR_DEVRES_TYPE_INVALID,
+
+       /* A requested region spanning an entire BAR. */
+       PCIM_ADDR_DEVRES_TYPE_REGION,
+
+       /*
+        * A requested region spanning an entire BAR, and a mapping for
+        * the entire BAR.
+        */
+       PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING,
+
+       /*
+        * A mapping within a BAR, either spanning the whole BAR or just a
+        * range.  Without a requested region.
+        */
+       PCIM_ADDR_DEVRES_TYPE_MAPPING,
+};
+
+/*
+ * This struct envelops IO or MEM addresses, i.e., mappings and region
+ * requests, because those are very frequently requested and released
+ * together.
+ */
+struct pcim_addr_devres {
+       enum pcim_addr_devres_type type;
+       void __iomem *baseaddr;
+       unsigned long offset;
+       unsigned long len;
+       int bar;
+};
+
+static inline void pcim_addr_devres_clear(struct pcim_addr_devres *res)
+{
+       memset(res, 0, sizeof(*res));
+       res->bar = -1;
+}
+
+/*
+ * The following functions, __pcim_*_region*, exist as counterparts to the
+ * versions from pci.c - which, unfortunately, can be in "hybrid mode", i.e.,
+ * sometimes managed, sometimes not.
+ *
+ * To separate the APIs cleanly, we define our own, simplified versions here.
+ */
+
+/**
+ * __pcim_request_region_range - Request a ranged region
+ * @pdev: PCI device the region belongs to
+ * @bar: BAR the range is within
+ * @offset: offset from the BAR's start address
+ * @maxlen: length in bytes, beginning at @offset
+ * @name: name associated with the request
+ * @req_flags: flags for the request, e.g., for kernel-exclusive requests
+ *
+ * Returns: 0 on success, a negative error code on failure.
+ *
+ * Request a range within a device's PCI BAR.  Sanity check the input.
+ */
+static int __pcim_request_region_range(struct pci_dev *pdev, int bar,
+                                      unsigned long offset,
+                                      unsigned long maxlen,
+                                      const char *name, int req_flags)
+{
+       resource_size_t start = pci_resource_start(pdev, bar);
+       resource_size_t len = pci_resource_len(pdev, bar);
+       unsigned long dev_flags = pci_resource_flags(pdev, bar);
+
+       if (start == 0 || len == 0) /* Unused BAR. */
+               return 0;
+       if (len <= offset)
+               return -EINVAL;
+
+       start += offset;
+       len -= offset;
+
+       if (len > maxlen && maxlen != 0)
+               len = maxlen;
+
+       if (dev_flags & IORESOURCE_IO) {
+               if (!request_region(start, len, name))
+                       return -EBUSY;
+       } else if (dev_flags & IORESOURCE_MEM) {
+               if (!__request_mem_region(start, len, name, req_flags))
+                       return -EBUSY;
+       } else {
+               /* That's not a device we can request anything on. */
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static void __pcim_release_region_range(struct pci_dev *pdev, int bar,
+                                       unsigned long offset,
+                                       unsigned long maxlen)
+{
+       resource_size_t start = pci_resource_start(pdev, bar);
+       resource_size_t len = pci_resource_len(pdev, bar);
+       unsigned long flags = pci_resource_flags(pdev, bar);
+
+       if (len <= offset || start == 0)
+               return;
+
+       if (len == 0 || maxlen == 0) /* This an unused BAR. Do nothing. */
+               return;
+
+       start += offset;
+       len -= offset;
+
+       if (len > maxlen)
+               len = maxlen;
+
+       if (flags & IORESOURCE_IO)
+               release_region(start, len);
+       else if (flags & IORESOURCE_MEM)
+               release_mem_region(start, len);
+}
+
+static int __pcim_request_region(struct pci_dev *pdev, int bar,
+                                const char *name, int flags)
+{
+       unsigned long offset = 0;
+       unsigned long len = pci_resource_len(pdev, bar);
+
+       return __pcim_request_region_range(pdev, bar, offset, len, name, flags);
+}
+
+static void __pcim_release_region(struct pci_dev *pdev, int bar)
+{
+       unsigned long offset = 0;
+       unsigned long len = pci_resource_len(pdev, bar);
+
+       __pcim_release_region_range(pdev, bar, offset, len);
+}
+
+static void pcim_addr_resource_release(struct device *dev, void *resource_raw)
+{
+       struct pci_dev *pdev = to_pci_dev(dev);
+       struct pcim_addr_devres *res = resource_raw;
+
+       switch (res->type) {
+       case PCIM_ADDR_DEVRES_TYPE_REGION:
+               __pcim_release_region(pdev, res->bar);
+               break;
+       case PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING:
+               pci_iounmap(pdev, res->baseaddr);
+               __pcim_release_region(pdev, res->bar);
+               break;
+       case PCIM_ADDR_DEVRES_TYPE_MAPPING:
+               pci_iounmap(pdev, res->baseaddr);
+               break;
+       default:
+               break;
+       }
+}
+
+static struct pcim_addr_devres *pcim_addr_devres_alloc(struct pci_dev *pdev)
+{
+       struct pcim_addr_devres *res;
+
+       res = devres_alloc_node(pcim_addr_resource_release, sizeof(*res),
+                               GFP_KERNEL, dev_to_node(&pdev->dev));
+       if (res)
+               pcim_addr_devres_clear(res);
+       return res;
+}
+
+/* Just for consistency and readability. */
+static inline void pcim_addr_devres_free(struct pcim_addr_devres *res)
+{
+       devres_free(res);
+}
+
+/*
+ * Used by devres to identify a pcim_addr_devres.
+ */
+static int pcim_addr_resources_match(struct device *dev,
+                                    void *a_raw, void *b_raw)
+{
+       struct pcim_addr_devres *a, *b;
+
+       a = a_raw;
+       b = b_raw;
+
+       if (a->type != b->type)
+               return 0;
+
+       switch (a->type) {
+       case PCIM_ADDR_DEVRES_TYPE_REGION:
+       case PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING:
+               return a->bar == b->bar;
+       case PCIM_ADDR_DEVRES_TYPE_MAPPING:
+               return a->baseaddr == b->baseaddr;
+       default:
+               return 0;
+       }
+}
 
 static void devm_pci_unmap_iospace(struct device *dev, void *ptr)
 {
@@ -92,8 +324,8 @@ EXPORT_SYMBOL(devm_pci_remap_cfgspace);
  *
  * All operations are managed and will be undone on driver detach.
  *
- * Returns a pointer to the remapped memory or an ERR_PTR() encoded error code
- * on failure. Usage example::
+ * Returns a pointer to the remapped memory or an IOMEM_ERR_PTR() encoded error
+ * code on failure. Usage example::
  *
  *     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  *     base = devm_pci_remap_cfg_resource(&pdev->dev, res);
@@ -172,6 +404,17 @@ static void pcim_release(struct device *gendev, void *res)
        struct pci_devres *this = res;
        int i;
 
+       /*
+        * This is legacy code.
+        *
+        * All regions requested by a pcim_ function do get released through
+        * pcim_addr_resource_release(). Thanks to the hybrid nature of the pci_
+        * region-request functions, this for-loop has to release the regions
+        * if they have been requested by such a function.
+        *
+        * TODO: Remove this once all users of pcim_enable_device() PLUS
+        * pci-region-request-functions have been ported to pcim_ functions.
+        */
        for (i = 0; i < DEVICE_COUNT_RESOURCE; i++)
                if (mask_contains_bar(this->region_mask, i))
                        pci_release_region(dev, i);
@@ -258,19 +501,21 @@ EXPORT_SYMBOL(pcim_pin_device);
 
 static void pcim_iomap_release(struct device *gendev, void *res)
 {
-       struct pci_dev *dev = to_pci_dev(gendev);
-       struct pcim_iomap_devres *this = res;
-       int i;
-
-       for (i = 0; i < PCIM_IOMAP_MAX; i++)
-               if (this->table[i])
-                       pci_iounmap(dev, this->table[i]);
+       /*
+        * Do nothing. This is legacy code.
+        *
+        * Cleanup of the mappings is now done directly through the callbacks
+        * registered when creating them.
+        */
 }
 
 /**
  * pcim_iomap_table - access iomap allocation table
  * @pdev: PCI device to access iomap table for
  *
+ * Returns:
+ * Const pointer to array of __iomem pointers on success, NULL on failure.
+ *
  * Access iomap allocation table for @dev.  If iomap table doesn't
  * exist and @pdev is managed, it will be allocated.  All iomaps
  * recorded in the iomap table are automatically unmapped on driver
@@ -343,30 +588,67 @@ static void pcim_remove_mapping_from_legacy_table(struct pci_dev *pdev,
        }
 }
 
+/*
+ * The same as pcim_remove_mapping_from_legacy_table(), but identifies the
+ * mapping by its BAR index.
+ */
+static void pcim_remove_bar_from_legacy_table(struct pci_dev *pdev, int bar)
+{
+       void __iomem **legacy_iomap_table;
+
+       if (bar >= PCI_STD_NUM_BARS)
+               return;
+
+       legacy_iomap_table = (void __iomem **)pcim_iomap_table(pdev);
+       if (!legacy_iomap_table)
+               return;
+
+       legacy_iomap_table[bar] = NULL;
+}
+
 /**
  * pcim_iomap - Managed pcim_iomap()
  * @pdev: PCI device to iomap for
  * @bar: BAR to iomap
  * @maxlen: Maximum length of iomap
  *
- * Managed pci_iomap().  Map is automatically unmapped on driver
- * detach.
+ * Returns: __iomem pointer on success, NULL on failure.
+ *
+ * Managed pci_iomap(). Map is automatically unmapped on driver detach. If
+ * desired, unmap manually only with pcim_iounmap().
+ *
+ * This SHOULD only be used once per BAR.
+ *
+ * NOTE:
+ * Contrary to the other pcim_* functions, this function does not return an
+ * IOMEM_ERR_PTR() on failure, but a simple NULL. This is done for backwards
+ * compatibility.
  */
 void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen)
 {
        void __iomem *mapping;
+       struct pcim_addr_devres *res;
+
+       res = pcim_addr_devres_alloc(pdev);
+       if (!res)
+               return NULL;
+       res->type = PCIM_ADDR_DEVRES_TYPE_MAPPING;
 
        mapping = pci_iomap(pdev, bar, maxlen);
        if (!mapping)
-               return NULL;
+               goto err_iomap;
+       res->baseaddr = mapping;
 
        if (pcim_add_mapping_to_legacy_table(pdev, mapping, bar) != 0)
                goto err_table;
 
+       devres_add(&pdev->dev, res);
        return mapping;
 
 err_table:
        pci_iounmap(pdev, mapping);
+err_iomap:
+       pcim_addr_devres_free(res);
        return NULL;
 }
 EXPORT_SYMBOL(pcim_iomap);
@@ -376,91 +658,291 @@ EXPORT_SYMBOL(pcim_iomap);
  * @pdev: PCI device to iounmap for
  * @addr: Address to unmap
  *
- * Managed pci_iounmap().  @addr must have been mapped using pcim_iomap().
+ * Managed pci_iounmap(). @addr must have been mapped using a pcim_* mapping
+ * function.
  */
 void pcim_iounmap(struct pci_dev *pdev, void __iomem *addr)
 {
-       pci_iounmap(pdev, addr);
+       struct pcim_addr_devres res_searched;
+
+       pcim_addr_devres_clear(&res_searched);
+       res_searched.type = PCIM_ADDR_DEVRES_TYPE_MAPPING;
+       res_searched.baseaddr = addr;
+
+       if (devres_release(&pdev->dev, pcim_addr_resource_release,
+                       pcim_addr_resources_match, &res_searched) != 0) {
+               /* Doesn't exist. User passed nonsense. */
+               return;
+       }
 
        pcim_remove_mapping_from_legacy_table(pdev, addr);
 }
 EXPORT_SYMBOL(pcim_iounmap);
 
+/**
+ * pcim_iomap_region - Request and iomap a PCI BAR
+ * @pdev: PCI device to map IO resources for
+ * @bar: Index of a BAR to map
+ * @name: Name associated with the request
+ *
+ * Returns: __iomem pointer on success, an IOMEM_ERR_PTR on failure.
+ *
+ * Mapping and region will get automatically released on driver detach. If
+ * desired, release manually only with pcim_iounmap_region().
+ */
+static void __iomem *pcim_iomap_region(struct pci_dev *pdev, int bar,
+                                      const char *name)
+{
+       int ret;
+       struct pcim_addr_devres *res;
+
+       res = pcim_addr_devres_alloc(pdev);
+       if (!res)
+               return IOMEM_ERR_PTR(-ENOMEM);
+
+       res->type = PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING;
+       res->bar = bar;
+
+       ret = __pcim_request_region(pdev, bar, name, 0);
+       if (ret != 0)
+               goto err_region;
+
+       res->baseaddr = pci_iomap(pdev, bar, 0);
+       if (!res->baseaddr) {
+               ret = -EINVAL;
+               goto err_iomap;
+       }
+
+       devres_add(&pdev->dev, res);
+       return res->baseaddr;
+
+err_iomap:
+       __pcim_release_region(pdev, bar);
+err_region:
+       pcim_addr_devres_free(res);
+
+       return IOMEM_ERR_PTR(ret);
+}
+
+/**
+ * pcim_iounmap_region - Unmap and release a PCI BAR
+ * @pdev: PCI device to operate on
+ * @bar: Index of BAR to unmap and release
+ *
+ * Unmap a BAR and release its region manually. Only pass BARs that were
+ * previously mapped by pcim_iomap_region().
+ */
+static void pcim_iounmap_region(struct pci_dev *pdev, int bar)
+{
+       struct pcim_addr_devres res_searched;
+
+       pcim_addr_devres_clear(&res_searched);
+       res_searched.type = PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING;
+       res_searched.bar = bar;
+
+       devres_release(&pdev->dev, pcim_addr_resource_release,
+                       pcim_addr_resources_match, &res_searched);
+}
+
 /**
  * pcim_iomap_regions - Request and iomap PCI BARs
  * @pdev: PCI device to map IO resources for
  * @mask: Mask of BARs to request and iomap
- * @name: Name used when requesting regions
+ * @name: Name associated with the requests
+ *
+ * Returns: 0 on success, negative error code on failure.
  *
  * Request and iomap regions specified by @mask.
  */
 int pcim_iomap_regions(struct pci_dev *pdev, int mask, const char *name)
 {
-       void __iomem * const *iomap;
-       int i, rc;
+       int ret;
+       int bar;
+       void __iomem *mapping;
 
-       iomap = pcim_iomap_table(pdev);
-       if (!iomap)
-               return -ENOMEM;
+       for (bar = 0; bar < DEVICE_COUNT_RESOURCE; bar++) {
+               if (!mask_contains_bar(mask, bar))
+                       continue;
 
-       for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
-               unsigned long len;
+               mapping = pcim_iomap_region(pdev, bar, name);
+               if (IS_ERR(mapping)) {
+                       ret = PTR_ERR(mapping);
+                       goto err;
+               }
+               ret = pcim_add_mapping_to_legacy_table(pdev, mapping, bar);
+               if (ret != 0)
+                       goto err;
+       }
 
-               if (!mask_contains_bar(mask, i))
-                       continue;
+       return 0;
+
+err:
+       while (--bar >= 0) {
+               pcim_iounmap_region(pdev, bar);
+               pcim_remove_bar_from_legacy_table(pdev, bar);
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL(pcim_iomap_regions);
 
-               rc = -EINVAL;
-               len = pci_resource_len(pdev, i);
-               if (!len)
-                       goto err_inval;
+static int _pcim_request_region(struct pci_dev *pdev, int bar, const char *name,
+                               int request_flags)
+{
+       int ret;
+       struct pcim_addr_devres *res;
 
-               rc = pci_request_region(pdev, i, name);
-               if (rc)
-                       goto err_inval;
+       res = pcim_addr_devres_alloc(pdev);
+       if (!res)
+               return -ENOMEM;
+       res->type = PCIM_ADDR_DEVRES_TYPE_REGION;
+       res->bar = bar;
 
-               rc = -ENOMEM;
-               if (!pcim_iomap(pdev, i, 0))
-                       goto err_region;
+       ret = __pcim_request_region(pdev, bar, name, request_flags);
+       if (ret != 0) {
+               pcim_addr_devres_free(res);
+               return ret;
        }
 
+       devres_add(&pdev->dev, res);
        return 0;
+}
 
- err_region:
-       pci_release_region(pdev, i);
- err_inval:
-       while (--i >= 0) {
-               if (!mask_contains_bar(mask, i))
-                       continue;
-               pcim_iounmap(pdev, iomap[i]);
-               pci_release_region(pdev, i);
+/**
+ * pcim_request_region - Request a PCI BAR
+ * @pdev: PCI device to requestion region for
+ * @bar: Index of BAR to request
+ * @name: Name associated with the request
+ *
+ * Returns: 0 on success, a negative error code on failure.
+ *
+ * Request region specified by @bar.
+ *
+ * The region will automatically be released on driver detach. If desired,
+ * release manually only with pcim_release_region().
+ */
+static int pcim_request_region(struct pci_dev *pdev, int bar, const char *name)
+{
+       return _pcim_request_region(pdev, bar, name, 0);
+}
+
+/**
+ * pcim_release_region - Release a PCI BAR
+ * @pdev: PCI device to operate on
+ * @bar: Index of BAR to release
+ *
+ * Release a region manually that was previously requested by
+ * pcim_request_region().
+ */
+static void pcim_release_region(struct pci_dev *pdev, int bar)
+{
+       struct pcim_addr_devres res_searched;
+
+       pcim_addr_devres_clear(&res_searched);
+       res_searched.type = PCIM_ADDR_DEVRES_TYPE_REGION;
+       res_searched.bar = bar;
+
+       devres_release(&pdev->dev, pcim_addr_resource_release,
+                       pcim_addr_resources_match, &res_searched);
+}
+
+
+/**
+ * pcim_release_all_regions - Release all regions of a PCI-device
+ * @pdev: the PCI device
+ *
+ * Release all regions previously requested through pcim_request_region()
+ * or pcim_request_all_regions().
+ *
+ * Can be called from any context, i.e., not necessarily as a counterpart to
+ * pcim_request_all_regions().
+ */
+static void pcim_release_all_regions(struct pci_dev *pdev)
+{
+       int bar;
+
+       for (bar = 0; bar < PCI_STD_NUM_BARS; bar++)
+               pcim_release_region(pdev, bar);
+}
+
+/**
+ * pcim_request_all_regions - Request all regions
+ * @pdev: PCI device to map IO resources for
+ * @name: name associated with the request
+ *
+ * Returns: 0 on success, negative error code on failure.
+ *
+ * Requested regions will automatically be released at driver detach. If
+ * desired, release individual regions with pcim_release_region() or all of
+ * them at once with pcim_release_all_regions().
+ */
+static int pcim_request_all_regions(struct pci_dev *pdev, const char *name)
+{
+       int ret;
+       int bar;
+
+       for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+               ret = pcim_request_region(pdev, bar, name);
+               if (ret != 0)
+                       goto err;
        }
 
-       return rc;
+       return 0;
+
+err:
+       pcim_release_all_regions(pdev);
+
+       return ret;
 }
-EXPORT_SYMBOL(pcim_iomap_regions);
 
 /**
  * pcim_iomap_regions_request_all - Request all BARs and iomap specified ones
  * @pdev: PCI device to map IO resources for
  * @mask: Mask of BARs to iomap
- * @name: Name used when requesting regions
+ * @name: Name associated with the requests
+ *
+ * Returns: 0 on success, negative error code on failure.
  *
  * Request all PCI BARs and iomap regions specified by @mask.
+ *
+ * To release these resources manually, call pcim_release_region() for the
+ * regions and pcim_iounmap() for the mappings.
  */
 int pcim_iomap_regions_request_all(struct pci_dev *pdev, int mask,
                                   const char *name)
 {
-       int request_mask = ((1 << 6) - 1) & ~mask;
-       int rc;
+       int bar;
+       int ret;
+       void __iomem **legacy_iomap_table;
 
-       rc = pci_request_selected_regions(pdev, request_mask, name);
-       if (rc)
-               return rc;
+       ret = pcim_request_all_regions(pdev, name);
+       if (ret != 0)
+               return ret;
 
-       rc = pcim_iomap_regions(pdev, mask, name);
-       if (rc)
-               pci_release_selected_regions(pdev, request_mask);
-       return rc;
+       for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+               if (!mask_contains_bar(mask, bar))
+                       continue;
+               if (!pcim_iomap(pdev, bar, 0))
+                       goto err;
+       }
+
+       return 0;
+
+err:
+       /*
+        * If bar is larger than 0, then pcim_iomap() above has most likely
+        * failed because of -EINVAL. If it is equal 0, most likely the table
+        * couldn't be created, indicating -ENOMEM.
+        */
+       ret = bar > 0 ? -EINVAL : -ENOMEM;
+       legacy_iomap_table = (void __iomem **)pcim_iomap_table(pdev);
+
+       while (--bar >= 0)
+               pcim_iounmap(pdev, legacy_iomap_table[bar]);
+
+       pcim_release_all_regions(pdev);
+
+       return ret;
 }
 EXPORT_SYMBOL(pcim_iomap_regions_request_all);
 
@@ -473,19 +955,14 @@ EXPORT_SYMBOL(pcim_iomap_regions_request_all);
  */
 void pcim_iounmap_regions(struct pci_dev *pdev, int mask)
 {
-       void __iomem * const *iomap;
        int i;
 
-       iomap = pcim_iomap_table(pdev);
-       if (!iomap)
-               return;
-
-       for (i = 0; i < PCIM_IOMAP_MAX; i++) {
+       for (i = 0; i < PCI_STD_NUM_BARS; i++) {
                if (!mask_contains_bar(mask, i))
                        continue;
 
-               pcim_iounmap(pdev, iomap[i]);
-               pci_release_region(pdev, i);
+               pcim_iounmap_region(pdev, i);
+               pcim_remove_bar_from_legacy_table(pdev, i);
        }
 }
 EXPORT_SYMBOL(pcim_iounmap_regions);
index 59e0949fb079d5f62edb5f93bd19a22f8b8d3d48..d94445f5f882e129c2f31543f25e670836896d07 100644 (file)
@@ -3883,6 +3883,17 @@ void pci_release_region(struct pci_dev *pdev, int bar)
                release_mem_region(pci_resource_start(pdev, bar),
                                pci_resource_len(pdev, bar));
 
+       /*
+        * This devres utility makes this function sometimes managed
+        * (when pcim_enable_device() has been called before).
+        *
+        * This is bad because it conflicts with the pcim_ functions being
+        * exclusively responsible for managed PCI. Its "sometimes yes,
+        * sometimes no" nature can cause bugs.
+        *
+        * TODO: Remove this once all users that use pcim_enable_device() PLUS
+        * a region request function have been ported to using pcim_ functions.
+        */
        dr = find_pci_dr(pdev);
        if (dr)
                dr->region_mask &= ~(1 << bar);
@@ -3927,6 +3938,17 @@ static int __pci_request_region(struct pci_dev *pdev, int bar,
                        goto err_out;
        }
 
+       /*
+        * This devres utility makes this function sometimes managed
+        * (when pcim_enable_device() has been called before).
+        *
+        * This is bad because it conflicts with the pcim_ functions being
+        * exclusively responsible for managed pci. Its "sometimes yes,
+        * sometimes no" nature can cause bugs.
+        *
+        * TODO: Remove this once all users that use pcim_enable_device() PLUS
+        * a region request function have been ported to using pcim_ functions.
+        */
        dr = find_pci_dr(pdev);
        if (dr)
                dr->region_mask |= 1 << bar;
index fd44565c47562868bacdad9b1bb2e017cae09795..c09487f5550c9299a10a535b7be70218a243c96d 100644 (file)
@@ -826,6 +826,11 @@ struct pci_devres {
        unsigned int orig_intx:1;
        unsigned int restore_intx:1;
        unsigned int mwi:1;
+
+       /*
+        * TODO: remove the region_mask once everyone calling
+        * pcim_enable_device() + pci_*region*() is ported to pcim_ functions.
+        */
        u32 region_mask;
 };