PCI: Add bridge DMA alias quirk for ASMedia and Tundra bridges
[deliverable/linux.git] / drivers / pci / quirks.c
index 5cb726c193de824db34ece33610cc6b3bec86eaa..ab620aca632bf5499eaa830cc54eeb58b13bc215 100644 (file)
@@ -296,6 +296,7 @@ static void quirk_s3_64M(struct pci_dev *dev)
        struct resource *r = &dev->resource[0];
 
        if ((r->start & 0x3ffffff) || r->end != r->start + 0x3ffffff) {
+               r->flags |= IORESOURCE_UNSET;
                r->start = 0;
                r->end = 0x3ffffff;
        }
@@ -937,6 +938,8 @@ DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_AMD,   PCI_DEVICE_ID_AMD_FE_GATE_700C
 static void quirk_dunord(struct pci_dev *dev)
 {
        struct resource *r = &dev->resource [1];
+
+       r->flags |= IORESOURCE_UNSET;
        r->start = 0;
        r->end = 0xffffff;
 }
@@ -1740,6 +1743,7 @@ static void quirk_tc86c001_ide(struct pci_dev *dev)
        struct resource *r = &dev->resource[0];
 
        if (r->start & 0x8) {
+               r->flags |= IORESOURCE_UNSET;
                r->start = 0;
                r->end = 0xf;
        }
@@ -1769,6 +1773,7 @@ static void quirk_plx_pci9050(struct pci_dev *dev)
                        dev_info(&dev->dev,
                                 "Re-allocating PLX PCI 9050 BAR %u to length 256 to avoid bit 7 bug\n",
                                 bar);
+                       r->flags |= IORESOURCE_UNSET;
                        r->start = 0;
                        r->end = 0xff;
                }
@@ -3328,6 +3333,81 @@ int pci_dev_specific_reset(struct pci_dev *dev, int probe)
        return -ENOTTY;
 }
 
+static void quirk_dma_func0_alias(struct pci_dev *dev)
+{
+       if (PCI_FUNC(dev->devfn) != 0) {
+               dev->dma_alias_devfn = PCI_DEVFN(PCI_SLOT(dev->devfn), 0);
+               dev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVFN;
+       }
+}
+
+/*
+ * https://bugzilla.redhat.com/show_bug.cgi?id=605888
+ *
+ * Some Ricoh devices use function 0 as the PCIe requester ID for DMA.
+ */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_RICOH, 0xe832, quirk_dma_func0_alias);
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_RICOH, 0xe476, quirk_dma_func0_alias);
+
+static void quirk_dma_func1_alias(struct pci_dev *dev)
+{
+       if (PCI_FUNC(dev->devfn) != 1) {
+               dev->dma_alias_devfn = PCI_DEVFN(PCI_SLOT(dev->devfn), 1);
+               dev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVFN;
+       }
+}
+
+/*
+ * Marvell 88SE9123 uses function 1 as the requester ID for DMA.  In some
+ * SKUs function 1 is present and is a legacy IDE controller, in other
+ * SKUs this function is not present, making this a ghost requester.
+ * https://bugzilla.kernel.org/show_bug.cgi?id=42679
+ */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MARVELL_EXT, 0x9123,
+                        quirk_dma_func1_alias);
+/* https://bugzilla.kernel.org/show_bug.cgi?id=42679#c14 */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MARVELL_EXT, 0x9130,
+                        quirk_dma_func1_alias);
+/* https://bugzilla.kernel.org/show_bug.cgi?id=42679#c47 + c57 */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MARVELL_EXT, 0x9172,
+                        quirk_dma_func1_alias);
+/* https://bugzilla.kernel.org/show_bug.cgi?id=42679#c59 */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MARVELL_EXT, 0x917a,
+                        quirk_dma_func1_alias);
+/* https://bugzilla.kernel.org/show_bug.cgi?id=42679#c46 */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MARVELL_EXT, 0x91a0,
+                        quirk_dma_func1_alias);
+/* https://bugzilla.kernel.org/show_bug.cgi?id=42679#c49 */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MARVELL_EXT, 0x9230,
+                        quirk_dma_func1_alias);
+/* https://bugs.gentoo.org/show_bug.cgi?id=497630 */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_JMICRON,
+                        PCI_DEVICE_ID_JMICRON_JMB388_ESD,
+                        quirk_dma_func1_alias);
+
+/*
+ * A few PCIe-to-PCI bridges fail to expose a PCIe capability, resulting in
+ * using the wrong DMA alias for the device.  Some of these devices can be
+ * used as either forward or reverse bridges, so we need to test whether the
+ * device is operating in the correct mode.  We could probably apply this
+ * quirk to PCI_ANY_ID, but for now we'll just use known offenders.  The test
+ * is for a non-root, non-PCIe bridge where the upstream device is PCIe and
+ * is not a PCIe-to-PCI bridge, then @pdev is actually a PCIe-to-PCI bridge.
+ */
+static void quirk_use_pcie_bridge_dma_alias(struct pci_dev *pdev)
+{
+       if (!pci_is_root_bus(pdev->bus) &&
+           pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE &&
+           !pci_is_pcie(pdev) && pci_is_pcie(pdev->bus->self) &&
+           pci_pcie_type(pdev->bus->self) != PCI_EXP_TYPE_PCI_BRIDGE)
+               pdev->dev_flags |= PCI_DEV_FLAG_PCIE_BRIDGE_ALIAS;
+}
+/* ASM1083/1085, https://bugzilla.kernel.org/show_bug.cgi?id=44881#c46 */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ASMEDIA, 0x1080,
+                        quirk_use_pcie_bridge_dma_alias);
+/* Tundra 8113, https://bugzilla.kernel.org/show_bug.cgi?id=44881#c43 */
+DECLARE_PCI_FIXUP_HEADER(0x10e3, 0x8113, quirk_use_pcie_bridge_dma_alias);
+
 static struct pci_dev *pci_func_0_dma_source(struct pci_dev *dev)
 {
        if (!PCI_FUNC(dev->devfn))
@@ -3423,6 +3503,61 @@ static int pci_quirk_amd_sb_acs(struct pci_dev *dev, u16 acs_flags)
 #endif
 }
 
+/*
+ * Many Intel PCH root ports do provide ACS-like features to disable peer
+ * transactions and validate bus numbers in requests, but do not provide an
+ * actual PCIe ACS capability.  This is the list of device IDs known to fall
+ * into that category as provided by Intel in Red Hat bugzilla 1037684.
+ */
+static const u16 pci_quirk_intel_pch_acs_ids[] = {
+       /* Ibexpeak PCH */
+       0x3b42, 0x3b43, 0x3b44, 0x3b45, 0x3b46, 0x3b47, 0x3b48, 0x3b49,
+       0x3b4a, 0x3b4b, 0x3b4c, 0x3b4d, 0x3b4e, 0x3b4f, 0x3b50, 0x3b51,
+       /* Cougarpoint PCH */
+       0x1c10, 0x1c11, 0x1c12, 0x1c13, 0x1c14, 0x1c15, 0x1c16, 0x1c17,
+       0x1c18, 0x1c19, 0x1c1a, 0x1c1b, 0x1c1c, 0x1c1d, 0x1c1e, 0x1c1f,
+       /* Pantherpoint PCH */
+       0x1e10, 0x1e11, 0x1e12, 0x1e13, 0x1e14, 0x1e15, 0x1e16, 0x1e17,
+       0x1e18, 0x1e19, 0x1e1a, 0x1e1b, 0x1e1c, 0x1e1d, 0x1e1e, 0x1e1f,
+       /* Lynxpoint-H PCH */
+       0x8c10, 0x8c11, 0x8c12, 0x8c13, 0x8c14, 0x8c15, 0x8c16, 0x8c17,
+       0x8c18, 0x8c19, 0x8c1a, 0x8c1b, 0x8c1c, 0x8c1d, 0x8c1e, 0x8c1f,
+       /* Lynxpoint-LP PCH */
+       0x9c10, 0x9c11, 0x9c12, 0x9c13, 0x9c14, 0x9c15, 0x9c16, 0x9c17,
+       0x9c18, 0x9c19, 0x9c1a, 0x9c1b,
+       /* Wildcat PCH */
+       0x9c90, 0x9c91, 0x9c92, 0x9c93, 0x9c94, 0x9c95, 0x9c96, 0x9c97,
+       0x9c98, 0x9c99, 0x9c9a, 0x9c9b,
+};
+
+static bool pci_quirk_intel_pch_acs_match(struct pci_dev *dev)
+{
+       int i;
+
+       /* Filter out a few obvious non-matches first */
+       if (!pci_is_pcie(dev) || pci_pcie_type(dev) != PCI_EXP_TYPE_ROOT_PORT)
+               return false;
+
+       for (i = 0; i < ARRAY_SIZE(pci_quirk_intel_pch_acs_ids); i++)
+               if (pci_quirk_intel_pch_acs_ids[i] == dev->device)
+                       return true;
+
+       return false;
+}
+
+#define INTEL_PCH_ACS_FLAGS (PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF | PCI_ACS_SV)
+
+static int pci_quirk_intel_pch_acs(struct pci_dev *dev, u16 acs_flags)
+{
+       u16 flags = dev->dev_flags & PCI_DEV_FLAGS_ACS_ENABLED_QUIRK ?
+                   INTEL_PCH_ACS_FLAGS : 0;
+
+       if (!pci_quirk_intel_pch_acs_match(dev))
+               return -ENOTTY;
+
+       return acs_flags & ~flags ? 0 : 1;
+}
+
 static const struct pci_dev_acs_enabled {
        u16 vendor;
        u16 device;
@@ -3434,6 +3569,7 @@ static const struct pci_dev_acs_enabled {
        { PCI_VENDOR_ID_ATI, 0x439d, pci_quirk_amd_sb_acs },
        { PCI_VENDOR_ID_ATI, 0x4384, pci_quirk_amd_sb_acs },
        { PCI_VENDOR_ID_ATI, 0x4399, pci_quirk_amd_sb_acs },
+       { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_intel_pch_acs },
        { 0 }
 };
 
@@ -3461,3 +3597,132 @@ int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags)
 
        return -ENOTTY;
 }
+
+/* Config space offset of Root Complex Base Address register */
+#define INTEL_LPC_RCBA_REG 0xf0
+/* 31:14 RCBA address */
+#define INTEL_LPC_RCBA_MASK 0xffffc000
+/* RCBA Enable */
+#define INTEL_LPC_RCBA_ENABLE (1 << 0)
+
+/* Backbone Scratch Pad Register */
+#define INTEL_BSPR_REG 0x1104
+/* Backbone Peer Non-Posted Disable */
+#define INTEL_BSPR_REG_BPNPD (1 << 8)
+/* Backbone Peer Posted Disable */
+#define INTEL_BSPR_REG_BPPD  (1 << 9)
+
+/* Upstream Peer Decode Configuration Register */
+#define INTEL_UPDCR_REG 0x1114
+/* 5:0 Peer Decode Enable bits */
+#define INTEL_UPDCR_REG_MASK 0x3f
+
+static int pci_quirk_enable_intel_lpc_acs(struct pci_dev *dev)
+{
+       u32 rcba, bspr, updcr;
+       void __iomem *rcba_mem;
+
+       /*
+        * Read the RCBA register from the LPC (D31:F0).  PCH root ports
+        * are D28:F* and therefore get probed before LPC, thus we can't
+        * use pci_get_slot/pci_read_config_dword here.
+        */
+       pci_bus_read_config_dword(dev->bus, PCI_DEVFN(31, 0),
+                                 INTEL_LPC_RCBA_REG, &rcba);
+       if (!(rcba & INTEL_LPC_RCBA_ENABLE))
+               return -EINVAL;
+
+       rcba_mem = ioremap_nocache(rcba & INTEL_LPC_RCBA_MASK,
+                                  PAGE_ALIGN(INTEL_UPDCR_REG));
+       if (!rcba_mem)
+               return -ENOMEM;
+
+       /*
+        * The BSPR can disallow peer cycles, but it's set by soft strap and
+        * therefore read-only.  If both posted and non-posted peer cycles are
+        * disallowed, we're ok.  If either are allowed, then we need to use
+        * the UPDCR to disable peer decodes for each port.  This provides the
+        * PCIe ACS equivalent of PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF
+        */
+       bspr = readl(rcba_mem + INTEL_BSPR_REG);
+       bspr &= INTEL_BSPR_REG_BPNPD | INTEL_BSPR_REG_BPPD;
+       if (bspr != (INTEL_BSPR_REG_BPNPD | INTEL_BSPR_REG_BPPD)) {
+               updcr = readl(rcba_mem + INTEL_UPDCR_REG);
+               if (updcr & INTEL_UPDCR_REG_MASK) {
+                       dev_info(&dev->dev, "Disabling UPDCR peer decodes\n");
+                       updcr &= ~INTEL_UPDCR_REG_MASK;
+                       writel(updcr, rcba_mem + INTEL_UPDCR_REG);
+               }
+       }
+
+       iounmap(rcba_mem);
+       return 0;
+}
+
+/* Miscellaneous Port Configuration register */
+#define INTEL_MPC_REG 0xd8
+/* MPC: Invalid Receive Bus Number Check Enable */
+#define INTEL_MPC_REG_IRBNCE (1 << 26)
+
+static void pci_quirk_enable_intel_rp_mpc_acs(struct pci_dev *dev)
+{
+       u32 mpc;
+
+       /*
+        * When enabled, the IRBNCE bit of the MPC register enables the
+        * equivalent of PCI ACS Source Validation (PCI_ACS_SV), which
+        * ensures that requester IDs fall within the bus number range
+        * of the bridge.  Enable if not already.
+        */
+       pci_read_config_dword(dev, INTEL_MPC_REG, &mpc);
+       if (!(mpc & INTEL_MPC_REG_IRBNCE)) {
+               dev_info(&dev->dev, "Enabling MPC IRBNCE\n");
+               mpc |= INTEL_MPC_REG_IRBNCE;
+               pci_write_config_word(dev, INTEL_MPC_REG, mpc);
+       }
+}
+
+static int pci_quirk_enable_intel_pch_acs(struct pci_dev *dev)
+{
+       if (!pci_quirk_intel_pch_acs_match(dev))
+               return -ENOTTY;
+
+       if (pci_quirk_enable_intel_lpc_acs(dev)) {
+               dev_warn(&dev->dev, "Failed to enable Intel PCH ACS quirk\n");
+               return 0;
+       }
+
+       pci_quirk_enable_intel_rp_mpc_acs(dev);
+
+       dev->dev_flags |= PCI_DEV_FLAGS_ACS_ENABLED_QUIRK;
+
+       dev_info(&dev->dev, "Intel PCH root port ACS workaround enabled\n");
+
+       return 0;
+}
+
+static const struct pci_dev_enable_acs {
+       u16 vendor;
+       u16 device;
+       int (*enable_acs)(struct pci_dev *dev);
+} pci_dev_enable_acs[] = {
+       { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_enable_intel_pch_acs },
+       { 0 }
+};
+
+void pci_dev_specific_enable_acs(struct pci_dev *dev)
+{
+       const struct pci_dev_enable_acs *i;
+       int ret;
+
+       for (i = pci_dev_enable_acs; i->enable_acs; i++) {
+               if ((i->vendor == dev->vendor ||
+                    i->vendor == (u16)PCI_ANY_ID) &&
+                   (i->device == dev->device ||
+                    i->device == (u16)PCI_ANY_ID)) {
+                       ret = i->enable_acs(dev);
+                       if (ret >= 0)
+                               return;
+               }
+       }
+}
This page took 0.02782 seconds and 5 git commands to generate.