]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/pci/probe.c
PCI: Disable PCIe Relaxed Ordering if unsupported
[karo-tx-linux.git] / drivers / pci / probe.c
index 19c8950c6c382f3c56c9337565483fd2a74419b4..e6a917b4acd3f3c63dff5d57b905ab093d7d0487 100644 (file)
@@ -510,16 +510,18 @@ static struct pci_bus *pci_alloc_bus(struct pci_bus *parent)
        return b;
 }
 
-static void pci_release_host_bridge_dev(struct device *dev)
+static void devm_pci_release_host_bridge_dev(struct device *dev)
 {
        struct pci_host_bridge *bridge = to_pci_host_bridge(dev);
 
        if (bridge->release_fn)
                bridge->release_fn(bridge);
+}
 
-       pci_free_resource_list(&bridge->windows);
-
-       kfree(bridge);
+static void pci_release_host_bridge_dev(struct device *dev)
+{
+       devm_pci_release_host_bridge_dev(dev);
+       pci_free_host_bridge(to_pci_host_bridge(dev));
 }
 
 struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
@@ -531,11 +533,36 @@ struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
                return NULL;
 
        INIT_LIST_HEAD(&bridge->windows);
+       bridge->dev.release = pci_release_host_bridge_dev;
 
        return bridge;
 }
 EXPORT_SYMBOL(pci_alloc_host_bridge);
 
+struct pci_host_bridge *devm_pci_alloc_host_bridge(struct device *dev,
+                                                  size_t priv)
+{
+       struct pci_host_bridge *bridge;
+
+       bridge = devm_kzalloc(dev, sizeof(*bridge) + priv, GFP_KERNEL);
+       if (!bridge)
+               return NULL;
+
+       INIT_LIST_HEAD(&bridge->windows);
+       bridge->dev.release = devm_pci_release_host_bridge_dev;
+
+       return bridge;
+}
+EXPORT_SYMBOL(devm_pci_alloc_host_bridge);
+
+void pci_free_host_bridge(struct pci_host_bridge *bridge)
+{
+       pci_free_resource_list(&bridge->windows);
+
+       kfree(bridge);
+}
+EXPORT_SYMBOL(pci_free_host_bridge);
+
 static const unsigned char pcix_bus_speed[] = {
        PCI_SPEED_UNKNOWN,              /* 0 */
        PCI_SPEED_66MHz_PCIX,           /* 1 */
@@ -719,7 +746,7 @@ static void pci_set_bus_msi_domain(struct pci_bus *bus)
        dev_set_msi_domain(&bus->dev, d);
 }
 
-int pci_register_host_bridge(struct pci_host_bridge *bridge)
+static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 {
        struct device *parent = bridge->dev.parent;
        struct resource_entry *window, *n;
@@ -834,7 +861,6 @@ free:
        kfree(bus);
        return err;
 }
-EXPORT_SYMBOL(pci_register_host_bridge);
 
 static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
                                           struct pci_dev *bridge, int busnr)
@@ -1329,6 +1355,34 @@ static void pci_msi_setup_pci_dev(struct pci_dev *dev)
                pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_ENABLE, 0);
 }
 
+/**
+ * pci_intx_mask_broken - test PCI_COMMAND_INTX_DISABLE writability
+ * @dev: PCI device
+ *
+ * Test whether PCI_COMMAND_INTX_DISABLE is writable for @dev.  Check this
+ * at enumeration-time to avoid modifying PCI_COMMAND at run-time.
+ */
+static int pci_intx_mask_broken(struct pci_dev *dev)
+{
+       u16 orig, toggle, new;
+
+       pci_read_config_word(dev, PCI_COMMAND, &orig);
+       toggle = orig ^ PCI_COMMAND_INTX_DISABLE;
+       pci_write_config_word(dev, PCI_COMMAND, toggle);
+       pci_read_config_word(dev, PCI_COMMAND, &new);
+
+       pci_write_config_word(dev, PCI_COMMAND, orig);
+
+       /*
+        * PCI_COMMAND_INTX_DISABLE was reserved and read-only prior to PCI
+        * r2.3, so strictly speaking, a device is not *broken* if it's not
+        * writable.  But we'll live with the misnomer for now.
+        */
+       if (new != toggle)
+               return 1;
+       return 0;
+}
+
 /**
  * pci_setup_device - fill in class and map information of a device
  * @dev: the device structure to fill
@@ -1399,6 +1453,8 @@ int pci_setup_device(struct pci_dev *dev)
                }
        }
 
+       dev->broken_intx_masking = pci_intx_mask_broken(dev);
+
        switch (dev->hdr_type) {                    /* header type */
        case PCI_HEADER_TYPE_NORMAL:                /* standard header */
                if (class == PCI_CLASS_BRIDGE_PCI)
@@ -1674,6 +1730,11 @@ static void program_hpp_type2(struct pci_dev *dev, struct hpp_type2 *hpp)
        /* Initialize Advanced Error Capabilities and Control Register */
        pci_read_config_dword(dev, pos + PCI_ERR_CAP, &reg32);
        reg32 = (reg32 & hpp->adv_err_cap_and) | hpp->adv_err_cap_or;
+       /* Don't enable ECRC generation or checking if unsupported */
+       if (!(reg32 & PCI_ERR_CAP_ECRC_GENC))
+               reg32 &= ~PCI_ERR_CAP_ECRC_GENE;
+       if (!(reg32 & PCI_ERR_CAP_ECRC_CHKC))
+               reg32 &= ~PCI_ERR_CAP_ECRC_CHKE;
        pci_write_config_dword(dev, pos + PCI_ERR_CAP, reg32);
 
        /*
@@ -1701,6 +1762,48 @@ static void pci_configure_extended_tags(struct pci_dev *dev)
                                         PCI_EXP_DEVCTL_EXT_TAG);
 }
 
+/**
+ * pcie_relaxed_ordering_enabled - Probe for PCIe relaxed ordering enable
+ * @dev: PCI device to query
+ *
+ * Returns true if the device has enabled relaxed ordering attribute.
+ */
+bool pcie_relaxed_ordering_enabled(struct pci_dev *dev)
+{
+       u16 v;
+
+       pcie_capability_read_word(dev, PCI_EXP_DEVCTL, &v);
+
+       return !!(v & PCI_EXP_DEVCTL_RELAX_EN);
+}
+EXPORT_SYMBOL(pcie_relaxed_ordering_enabled);
+
+static void pci_configure_relaxed_ordering(struct pci_dev *dev)
+{
+       struct pci_dev *root;
+
+       /* PCI_EXP_DEVICE_RELAX_EN is RsvdP in VFs */
+       if (dev->is_virtfn)
+               return;
+
+       if (!pcie_relaxed_ordering_enabled(dev))
+               return;
+
+       /*
+        * For now, we only deal with Relaxed Ordering issues with Root
+        * Ports. Peer-to-Peer DMA is another can of worms.
+        */
+       root = pci_find_pcie_root_port(dev);
+       if (!root)
+               return;
+
+       if (root->dev_flags & PCI_DEV_FLAGS_NO_RELAXED_ORDERING) {
+               pcie_capability_clear_word(dev, PCI_EXP_DEVCTL,
+                                          PCI_EXP_DEVCTL_RELAX_EN);
+               dev_info(&dev->dev, "Disable Relaxed Ordering because the Root Port didn't support it\n");
+       }
+}
+
 static void pci_configure_device(struct pci_dev *dev)
 {
        struct hotplug_params hpp;
@@ -1708,6 +1811,7 @@ static void pci_configure_device(struct pci_dev *dev)
 
        pci_configure_mps(dev);
        pci_configure_extended_tags(dev);
+       pci_configure_relaxed_ordering(dev);
 
        memset(&hpp, 0, sizeof(hpp));
        ret = pci_get_hp_params(dev, &hpp);
@@ -2298,9 +2402,8 @@ void __weak pcibios_remove_bus(struct pci_bus *bus)
 {
 }
 
-static struct pci_bus *pci_create_root_bus_msi(struct device *parent,
-               int bus, struct pci_ops *ops, void *sysdata,
-               struct list_head *resources, struct msi_controller *msi)
+struct pci_bus *pci_create_root_bus(struct device *parent, int bus,
+               struct pci_ops *ops, void *sysdata, struct list_head *resources)
 {
        int error;
        struct pci_host_bridge *bridge;
@@ -2310,13 +2413,11 @@ static struct pci_bus *pci_create_root_bus_msi(struct device *parent,
                return NULL;
 
        bridge->dev.parent = parent;
-       bridge->dev.release = pci_release_host_bridge_dev;
 
        list_splice_init(resources, &bridge->windows);
        bridge->sysdata = sysdata;
        bridge->busnr = bus;
        bridge->ops = ops;
-       bridge->msi = msi;
 
        error = pci_register_host_bridge(bridge);
        if (error < 0)
@@ -2328,13 +2429,6 @@ err_out:
        kfree(bridge);
        return NULL;
 }
-
-struct pci_bus *pci_create_root_bus(struct device *parent, int bus,
-               struct pci_ops *ops, void *sysdata, struct list_head *resources)
-{
-       return pci_create_root_bus_msi(parent, bus, ops, sysdata, resources,
-                                      NULL);
-}
 EXPORT_SYMBOL_GPL(pci_create_root_bus);
 
 int pci_bus_insert_busn_res(struct pci_bus *b, int bus, int bus_max)
@@ -2400,24 +2494,28 @@ void pci_bus_release_busn_res(struct pci_bus *b)
                        res, ret ? "can not be" : "is");
 }
 
-struct pci_bus *pci_scan_root_bus_msi(struct device *parent, int bus,
-               struct pci_ops *ops, void *sysdata,
-               struct list_head *resources, struct msi_controller *msi)
+int pci_scan_root_bus_bridge(struct pci_host_bridge *bridge)
 {
        struct resource_entry *window;
        bool found = false;
        struct pci_bus *b;
-       int max;
+       int max, bus, ret;
 
-       resource_list_for_each_entry(window, resources)
+       if (!bridge)
+               return -EINVAL;
+
+       resource_list_for_each_entry(window, &bridge->windows)
                if (window->res->flags & IORESOURCE_BUS) {
                        found = true;
                        break;
                }
 
-       b = pci_create_root_bus_msi(parent, bus, ops, sysdata, resources, msi);
-       if (!b)
-               return NULL;
+       ret = pci_register_host_bridge(bridge);
+       if (ret < 0)
+               return ret;
+
+       b = bridge->bus;
+       bus = bridge->busnr;
 
        if (!found) {
                dev_info(&b->dev,
@@ -2431,14 +2529,41 @@ struct pci_bus *pci_scan_root_bus_msi(struct device *parent, int bus,
        if (!found)
                pci_bus_update_busn_res_end(b, max);
 
-       return b;
+       return 0;
 }
+EXPORT_SYMBOL(pci_scan_root_bus_bridge);
 
 struct pci_bus *pci_scan_root_bus(struct device *parent, int bus,
                struct pci_ops *ops, void *sysdata, struct list_head *resources)
 {
-       return pci_scan_root_bus_msi(parent, bus, ops, sysdata, resources,
-                                    NULL);
+       struct resource_entry *window;
+       bool found = false;
+       struct pci_bus *b;
+       int max;
+
+       resource_list_for_each_entry(window, resources)
+               if (window->res->flags & IORESOURCE_BUS) {
+                       found = true;
+                       break;
+               }
+
+       b = pci_create_root_bus(parent, bus, ops, sysdata, resources);
+       if (!b)
+               return NULL;
+
+       if (!found) {
+               dev_info(&b->dev,
+                "No busn resource found for root bus, will use [bus %02x-ff]\n",
+                       bus);
+               pci_bus_insert_busn_res(b, bus, 255);
+       }
+
+       max = pci_scan_child_bus(b);
+
+       if (!found)
+               pci_bus_update_busn_res_end(b, max);
+
+       return b;
 }
 EXPORT_SYMBOL(pci_scan_root_bus);