]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/pci/pci.c
Merge tag 'pci-v4.8-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci
[karo-tx-linux.git] / drivers / pci / pci.c
index badbddc683f006fc0510c6e609a01772fc0ba4f8..aab9d5115a5f65ab4e945e5a29c00c91745a13e3 100644 (file)
@@ -7,8 +7,10 @@
  *     Copyright 1997 -- 2000 Martin Mares <mj@ucw.cz>
  */
 
+#include <linux/acpi.h>
 #include <linux/kernel.h>
 #include <linux/delay.h>
+#include <linux/dmi.h>
 #include <linux/init.h>
 #include <linux/of.h>
 #include <linux/of_pci.h>
@@ -25,7 +27,9 @@
 #include <linux/device.h>
 #include <linux/pm_runtime.h>
 #include <linux/pci_hotplug.h>
+#include <linux/vmalloc.h>
 #include <asm/setup.h>
+#include <asm/dma.h>
 #include <linux/aer.h>
 #include "pci.h"
 
@@ -81,6 +85,9 @@ unsigned long pci_cardbus_mem_size = DEFAULT_CARDBUS_MEM_SIZE;
 unsigned long pci_hotplug_io_size  = DEFAULT_HOTPLUG_IO_SIZE;
 unsigned long pci_hotplug_mem_size = DEFAULT_HOTPLUG_MEM_SIZE;
 
+#define DEFAULT_HOTPLUG_BUS_SIZE       1
+unsigned long pci_hotplug_bus_size = DEFAULT_HOTPLUG_BUS_SIZE;
+
 enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_DEFAULT;
 
 /*
@@ -101,6 +108,21 @@ unsigned int pcibios_max_latency = 255;
 /* If set, the PCIe ARI capability will not be used. */
 static bool pcie_ari_disabled;
 
+/* Disable bridge_d3 for all PCIe ports */
+static bool pci_bridge_d3_disable;
+/* Force bridge_d3 for all PCIe ports */
+static bool pci_bridge_d3_force;
+
+static int __init pcie_port_pm_setup(char *str)
+{
+       if (!strcmp(str, "off"))
+               pci_bridge_d3_disable = true;
+       else if (!strcmp(str, "force"))
+               pci_bridge_d3_force = true;
+       return 1;
+}
+__setup("pcie_port_pm=", pcie_port_pm_setup);
+
 /**
  * pci_bus_max_busnr - returns maximum PCI bus number of given bus' children
  * @bus: pointer to PCI bus structure to search
@@ -2155,6 +2177,164 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev)
                pm_runtime_put_sync(parent);
 }
 
+/**
+ * pci_bridge_d3_possible - Is it possible to put the bridge into D3
+ * @bridge: Bridge to check
+ *
+ * This function checks if it is possible to move the bridge to D3.
+ * Currently we only allow D3 for recent enough PCIe ports.
+ */
+static bool pci_bridge_d3_possible(struct pci_dev *bridge)
+{
+       unsigned int year;
+
+       if (!pci_is_pcie(bridge))
+               return false;
+
+       switch (pci_pcie_type(bridge)) {
+       case PCI_EXP_TYPE_ROOT_PORT:
+       case PCI_EXP_TYPE_UPSTREAM:
+       case PCI_EXP_TYPE_DOWNSTREAM:
+               if (pci_bridge_d3_disable)
+                       return false;
+               if (pci_bridge_d3_force)
+                       return true;
+
+               /*
+                * It should be safe to put PCIe ports from 2015 or newer
+                * to D3.
+                */
+               if (dmi_get_date(DMI_BIOS_DATE, &year, NULL, NULL) &&
+                   year >= 2015) {
+                       return true;
+               }
+               break;
+       }
+
+       return false;
+}
+
+static int pci_dev_check_d3cold(struct pci_dev *dev, void *data)
+{
+       bool *d3cold_ok = data;
+       bool no_d3cold;
+
+       /*
+        * The device needs to be allowed to go D3cold and if it is wake
+        * capable to do so from D3cold.
+        */
+       no_d3cold = dev->no_d3cold || !dev->d3cold_allowed ||
+               (device_may_wakeup(&dev->dev) && !pci_pme_capable(dev, PCI_D3cold)) ||
+               !pci_power_manageable(dev);
+
+       *d3cold_ok = !no_d3cold;
+
+       return no_d3cold;
+}
+
+/*
+ * pci_bridge_d3_update - Update bridge D3 capabilities
+ * @dev: PCI device which is changed
+ * @remove: Is the device being removed
+ *
+ * Update upstream bridge PM capabilities accordingly depending on if the
+ * device PM configuration was changed or the device is being removed.  The
+ * change is also propagated upstream.
+ */
+static void pci_bridge_d3_update(struct pci_dev *dev, bool remove)
+{
+       struct pci_dev *bridge;
+       bool d3cold_ok = true;
+
+       bridge = pci_upstream_bridge(dev);
+       if (!bridge || !pci_bridge_d3_possible(bridge))
+               return;
+
+       pci_dev_get(bridge);
+       /*
+        * If the device is removed we do not care about its D3cold
+        * capabilities.
+        */
+       if (!remove)
+               pci_dev_check_d3cold(dev, &d3cold_ok);
+
+       if (d3cold_ok) {
+               /*
+                * We need to go through all children to find out if all of
+                * them can still go to D3cold.
+                */
+               pci_walk_bus(bridge->subordinate, pci_dev_check_d3cold,
+                            &d3cold_ok);
+       }
+
+       if (bridge->bridge_d3 != d3cold_ok) {
+               bridge->bridge_d3 = d3cold_ok;
+               /* Propagate change to upstream bridges */
+               pci_bridge_d3_update(bridge, false);
+       }
+
+       pci_dev_put(bridge);
+}
+
+/**
+ * pci_bridge_d3_device_changed - Update bridge D3 capabilities on change
+ * @dev: PCI device that was changed
+ *
+ * If a device is added or its PM configuration, such as is it allowed to
+ * enter D3cold, is changed this function updates upstream bridge PM
+ * capabilities accordingly.
+ */
+void pci_bridge_d3_device_changed(struct pci_dev *dev)
+{
+       pci_bridge_d3_update(dev, false);
+}
+
+/**
+ * pci_bridge_d3_device_removed - Update bridge D3 capabilities on remove
+ * @dev: PCI device being removed
+ *
+ * Function updates upstream bridge PM capabilities based on other devices
+ * still left on the bus.
+ */
+void pci_bridge_d3_device_removed(struct pci_dev *dev)
+{
+       pci_bridge_d3_update(dev, true);
+}
+
+/**
+ * pci_d3cold_enable - Enable D3cold for device
+ * @dev: PCI device to handle
+ *
+ * This function can be used in drivers to enable D3cold from the device
+ * they handle.  It also updates upstream PCI bridge PM capabilities
+ * accordingly.
+ */
+void pci_d3cold_enable(struct pci_dev *dev)
+{
+       if (dev->no_d3cold) {
+               dev->no_d3cold = false;
+               pci_bridge_d3_device_changed(dev);
+       }
+}
+EXPORT_SYMBOL_GPL(pci_d3cold_enable);
+
+/**
+ * pci_d3cold_disable - Disable D3cold for device
+ * @dev: PCI device to handle
+ *
+ * This function can be used in drivers to disable D3cold from the device
+ * they handle.  It also updates upstream PCI bridge PM capabilities
+ * accordingly.
+ */
+void pci_d3cold_disable(struct pci_dev *dev)
+{
+       if (!dev->no_d3cold) {
+               dev->no_d3cold = true;
+               pci_bridge_d3_device_changed(dev);
+       }
+}
+EXPORT_SYMBOL_GPL(pci_d3cold_disable);
+
 /**
  * pci_pm_init - Initialize PM functions of given PCI device
  * @dev: PCI device to handle.
@@ -2189,6 +2369,7 @@ void pci_pm_init(struct pci_dev *dev)
        dev->pm_cap = pm;
        dev->d3_delay = PCI_PM_D3_WAIT;
        dev->d3cold_delay = PCI_PM_D3COLD_WAIT;
+       dev->bridge_d3 = pci_bridge_d3_possible(dev);
        dev->d3cold_allowed = true;
 
        dev->d1_support = false;
@@ -3165,6 +3346,23 @@ int __weak pci_remap_iospace(const struct resource *res, phys_addr_t phys_addr)
 #endif
 }
 
+/**
+ *     pci_unmap_iospace - Unmap the memory mapped I/O space
+ *     @res: resource to be unmapped
+ *
+ *     Unmap the CPU virtual address @res from virtual address space.
+ *     Only architectures that have memory mapped IO functions defined
+ *     (and the PCI_IOBASE value defined) should call this function.
+ */
+void pci_unmap_iospace(struct resource *res)
+{
+#if defined(PCI_IOBASE) && defined(CONFIG_MMU)
+       unsigned long vaddr = (unsigned long)PCI_IOBASE + res->start;
+
+       unmap_kernel_range(vaddr, resource_size(res));
+#endif
+}
+
 static void __pci_set_master(struct pci_dev *dev, bool enable)
 {
        u16 old_cmd, cmd;
@@ -4755,6 +4953,7 @@ static DEFINE_SPINLOCK(resource_alignment_lock);
 static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev)
 {
        int seg, bus, slot, func, align_order, count;
+       unsigned short vendor, device, subsystem_vendor, subsystem_device;
        resource_size_t align = 0;
        char *p;
 
@@ -4768,28 +4967,55 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev)
                } else {
                        align_order = -1;
                }
-               if (sscanf(p, "%x:%x:%x.%x%n",
-                       &seg, &bus, &slot, &func, &count) != 4) {
-                       seg = 0;
-                       if (sscanf(p, "%x:%x.%x%n",
-                                       &bus, &slot, &func, &count) != 3) {
-                               /* Invalid format */
-                               printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: %s\n",
-                                       p);
+               if (strncmp(p, "pci:", 4) == 0) {
+                       /* PCI vendor/device (subvendor/subdevice) ids are specified */
+                       p += 4;
+                       if (sscanf(p, "%hx:%hx:%hx:%hx%n",
+                               &vendor, &device, &subsystem_vendor, &subsystem_device, &count) != 4) {
+                               if (sscanf(p, "%hx:%hx%n", &vendor, &device, &count) != 2) {
+                                       printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: pci:%s\n",
+                                               p);
+                                       break;
+                               }
+                               subsystem_vendor = subsystem_device = 0;
+                       }
+                       p += count;
+                       if ((!vendor || (vendor == dev->vendor)) &&
+                               (!device || (device == dev->device)) &&
+                               (!subsystem_vendor || (subsystem_vendor == dev->subsystem_vendor)) &&
+                               (!subsystem_device || (subsystem_device == dev->subsystem_device))) {
+                               if (align_order == -1)
+                                       align = PAGE_SIZE;
+                               else
+                                       align = 1 << align_order;
+                               /* Found */
                                break;
                        }
                }
-               p += count;
-               if (seg == pci_domain_nr(dev->bus) &&
-                       bus == dev->bus->number &&
-                       slot == PCI_SLOT(dev->devfn) &&
-                       func == PCI_FUNC(dev->devfn)) {
-                       if (align_order == -1)
-                               align = PAGE_SIZE;
-                       else
-                               align = 1 << align_order;
-                       /* Found */
-                       break;
+               else {
+                       if (sscanf(p, "%x:%x:%x.%x%n",
+                               &seg, &bus, &slot, &func, &count) != 4) {
+                               seg = 0;
+                               if (sscanf(p, "%x:%x.%x%n",
+                                               &bus, &slot, &func, &count) != 3) {
+                                       /* Invalid format */
+                                       printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: %s\n",
+                                               p);
+                                       break;
+                               }
+                       }
+                       p += count;
+                       if (seg == pci_domain_nr(dev->bus) &&
+                               bus == dev->bus->number &&
+                               slot == PCI_SLOT(dev->devfn) &&
+                               func == PCI_FUNC(dev->devfn)) {
+                               if (align_order == -1)
+                                       align = PAGE_SIZE;
+                               else
+                                       align = 1 << align_order;
+                               /* Found */
+                               break;
+                       }
                }
                if (*p != ';' && *p != ',') {
                        /* End of param or invalid format */
@@ -4897,7 +5123,7 @@ static ssize_t pci_resource_alignment_store(struct bus_type *bus,
        return pci_set_resource_alignment_param(buf, count);
 }
 
-BUS_ATTR(resource_alignment, 0644, pci_resource_alignment_show,
+static BUS_ATTR(resource_alignment, 0644, pci_resource_alignment_show,
                                        pci_resource_alignment_store);
 
 static int __init pci_resource_alignment_sysfs_init(void)
@@ -4923,7 +5149,7 @@ int pci_get_new_domain_nr(void)
 }
 
 #ifdef CONFIG_PCI_DOMAINS_GENERIC
-void pci_bus_assign_domain_nr(struct pci_bus *bus, struct device *parent)
+static int of_pci_bus_find_domain_nr(struct device *parent)
 {
        static int use_dt_domains = -1;
        int domain = -1;
@@ -4967,7 +5193,13 @@ void pci_bus_assign_domain_nr(struct pci_bus *bus, struct device *parent)
                domain = -1;
        }
 
-       bus->domain_nr = domain;
+       return domain;
+}
+
+int pci_bus_find_domain_nr(struct pci_bus *bus, struct device *parent)
+{
+       return acpi_disabled ? of_pci_bus_find_domain_nr(parent) :
+                              acpi_pci_bus_find_domain_nr(bus);
 }
 #endif
 #endif
@@ -5021,6 +5253,11 @@ static int __init pci_setup(char *str)
                                pci_hotplug_io_size = memparse(str + 9, &str);
                        } else if (!strncmp(str, "hpmemsize=", 10)) {
                                pci_hotplug_mem_size = memparse(str + 10, &str);
+                       } else if (!strncmp(str, "hpbussize=", 10)) {
+                               pci_hotplug_bus_size =
+                                       simple_strtoul(str + 10, &str, 0);
+                               if (pci_hotplug_bus_size > 0xff)
+                                       pci_hotplug_bus_size = DEFAULT_HOTPLUG_BUS_SIZE;
                        } else if (!strncmp(str, "pcie_bus_tune_off", 17)) {
                                pcie_bus_config = PCIE_BUS_TUNE_OFF;
                        } else if (!strncmp(str, "pcie_bus_safe", 13)) {