diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/pci/quirks.c | 75 | ||||
-rw-r--r-- | drivers/pci/search.c | 87 |
2 files changed, 162 insertions, 0 deletions
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 92e68c7747f7..d3f29dd29876 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3342,6 +3342,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)) diff --git a/drivers/pci/search.c b/drivers/pci/search.c index 8e495bda678f..0e9a00e5ca60 100644 --- a/drivers/pci/search.c +++ b/drivers/pci/search.c @@ -17,6 +17,93 @@ DECLARE_RWSEM(pci_bus_sem); EXPORT_SYMBOL_GPL(pci_bus_sem); /* + * pci_for_each_dma_alias - Iterate over DMA aliases for a device + * @pdev: starting downstream device + * @fn: function to call for each alias + * @data: opaque data to pass to @fn + * + * Starting @pdev, walk up the bus calling @fn for each possible alias + * of @pdev at the root bus. + */ +int pci_for_each_dma_alias(struct pci_dev *pdev, + int (*fn)(struct pci_dev *pdev, + u16 alias, void *data), void *data) +{ + struct pci_bus *bus; + int ret; + + ret = fn(pdev, PCI_DEVID(pdev->bus->number, pdev->devfn), data); + if (ret) + return ret; + + /* + * If the device is broken and uses an alias requester ID for + * DMA, iterate over that too. + */ + if (unlikely(pdev->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN)) { + ret = fn(pdev, PCI_DEVID(pdev->bus->number, + pdev->dma_alias_devfn), data); + if (ret) + return ret; + } + + for (bus = pdev->bus; !pci_is_root_bus(bus); bus = bus->parent) { + struct pci_dev *tmp; + + /* Skip virtual buses */ + if (!bus->self) + continue; + + tmp = bus->self; + + /* + * PCIe-to-PCI/X bridges alias transactions from downstream + * devices using the subordinate bus number (PCI Express to + * PCI/PCI-X Bridge Spec, rev 1.0, sec 2.3). For all cases + * where the upstream bus is PCI/X we alias to the bridge + * (there are various conditions in the previous reference + * where the bridge may take ownership of transactions, even + * when the secondary interface is PCI-X). + */ + if (pci_is_pcie(tmp)) { + switch (pci_pcie_type(tmp)) { + case PCI_EXP_TYPE_ROOT_PORT: + case PCI_EXP_TYPE_UPSTREAM: + case PCI_EXP_TYPE_DOWNSTREAM: + continue; + case PCI_EXP_TYPE_PCI_BRIDGE: + ret = fn(tmp, + PCI_DEVID(tmp->subordinate->number, + PCI_DEVFN(0, 0)), data); + if (ret) + return ret; + continue; + case PCI_EXP_TYPE_PCIE_BRIDGE: + ret = fn(tmp, + PCI_DEVID(tmp->bus->number, + tmp->devfn), data); + if (ret) + return ret; + continue; + } + } else { + if (tmp->dev_flags & PCI_DEV_FLAG_PCIE_BRIDGE_ALIAS) + ret = fn(tmp, + PCI_DEVID(tmp->subordinate->number, + PCI_DEVFN(0, 0)), data); + else + ret = fn(tmp, + PCI_DEVID(tmp->bus->number, + tmp->devfn), data); + if (ret) + return ret; + } + } + + return ret; +} + +/* * find the upstream PCIe-to-PCI bridge of a PCI device * if the device is PCIE, return NULL * if the device isn't connected to a PCIe bridge (that is its parent is a |