summaryrefslogtreecommitdiffstats
path: root/drivers/pci/pci.c
diff options
context:
space:
mode:
authorBjorn Helgaas <bhelgaas@google.com>2012-06-13 17:04:54 -0600
committerBjorn Helgaas <bhelgaas@google.com>2012-06-13 17:04:54 -0600
commitcc2fa3fa320d5f40a12713c104bbe5d3da4636e4 (patch)
tree342445a784c566116505ab8c9e7a24803a6e70c4 /drivers/pci/pci.c
parent10c480933d0ad2ea27630cbaa723a5d33dbece00 (diff)
parenta0dee2ed0cdc666b5622f1fc74979355a6b36850 (diff)
downloadlinux-cc2fa3fa320d5f40a12713c104bbe5d3da4636e4.tar.gz
linux-cc2fa3fa320d5f40a12713c104bbe5d3da4636e4.tar.bz2
linux-cc2fa3fa320d5f40a12713c104bbe5d3da4636e4.zip
Merge branch 'topic/alex-vfio-prep' into next
* topic/alex-vfio-prep: PCI: misc pci_reg additions PCI: create common pcibios_err_to_errno PCI: export pci_user functions for use by other drivers PCI: add ACS validation utility PCI: add PCI DMA source ID quirk
Diffstat (limited to 'drivers/pci/pci.c')
-rw-r--r--drivers/pci/pci.c69
1 files changed, 69 insertions, 0 deletions
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index a23b071798f6..b743a9afb4dd 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -2293,6 +2293,75 @@ void pci_enable_acs(struct pci_dev *dev)
}
/**
+ * pci_acs_enabled - test ACS against required flags for a given device
+ * @pdev: device to test
+ * @acs_flags: required PCI ACS flags
+ *
+ * Return true if the device supports the provided flags. Automatically
+ * filters out flags that are not implemented on multifunction devices.
+ */
+bool pci_acs_enabled(struct pci_dev *pdev, u16 acs_flags)
+{
+ int pos, ret;
+ u16 ctrl;
+
+ ret = pci_dev_specific_acs_enabled(pdev, acs_flags);
+ if (ret >= 0)
+ return ret > 0;
+
+ if (!pci_is_pcie(pdev))
+ return false;
+
+ /* Filter out flags not applicable to multifunction */
+ if (pdev->multifunction)
+ acs_flags &= (PCI_ACS_RR | PCI_ACS_CR |
+ PCI_ACS_EC | PCI_ACS_DT);
+
+ if (pdev->pcie_type == PCI_EXP_TYPE_DOWNSTREAM ||
+ pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT ||
+ pdev->multifunction) {
+ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ACS);
+ if (!pos)
+ return false;
+
+ pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl);
+ if ((ctrl & acs_flags) != acs_flags)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * pci_acs_path_enable - test ACS flags from start to end in a hierarchy
+ * @start: starting downstream device
+ * @end: ending upstream device or NULL to search to the root bus
+ * @acs_flags: required flags
+ *
+ * Walk up a device tree from start to end testing PCI ACS support. If
+ * any step along the way does not support the required flags, return false.
+ */
+bool pci_acs_path_enabled(struct pci_dev *start,
+ struct pci_dev *end, u16 acs_flags)
+{
+ struct pci_dev *pdev, *parent = start;
+
+ do {
+ pdev = parent;
+
+ if (!pci_acs_enabled(pdev, acs_flags))
+ return false;
+
+ if (pci_is_root_bus(pdev->bus))
+ return (end == NULL);
+
+ parent = pdev->bus->self;
+ } while (pdev != end);
+
+ return true;
+}
+
+/**
* pci_swizzle_interrupt_pin - swizzle INTx for device behind bridge
* @dev: the PCI device
* @pin: the INTx pin (1=INTA, 2=INTB, 3=INTD, 4=INTD)