aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci/pci.c
diff options
context:
space:
mode:
authorAlex Williamson <alex.williamson@redhat.com>2012-06-11 01:27:07 -0400
committerBjorn Helgaas <bhelgaas@google.com>2012-06-12 11:21:42 -0400
commitad805758c0eb25bce7b2e3b298d63dc62a1bc71c (patch)
tree031c42d1537e9ee25b7392a652a7799c3914c972 /drivers/pci/pci.c
parent12ea6cad1c7d046e21decc18b0e2170c6794dc51 (diff)
PCI: add ACS validation utility
In a PCI environment, transactions aren't always required to reach the root bus before being re-routed. Intermediate switches between an endpoint and the root bus can redirect DMA back downstream before things like IOMMUs have a chance to intervene. Legacy PCI is always susceptible to this as it operates on a shared bus. PCIe added a new capability to describe and control this behavior, Access Control Services, or ACS. The utility function pci_acs_enabled() allows us to test the ACS capabilities of an individual devices against a set of flags while pci_acs_path_enabled() tests a complete path from a given downstream device up to the specified upstream device. We also include the ability to add device specific tests as it's likely we'll see devices that do not implement ACS, but want to indicate support for various capabilities in this space. Signed-off-by: Alex Williamson <alex.williamson@redhat.com> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
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 447e83472c01..1ccf7d49f522 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -2360,6 +2360,75 @@ void pci_enable_acs(struct pci_dev *dev)
2360} 2360}
2361 2361
2362/** 2362/**
2363 * pci_acs_enabled - test ACS against required flags for a given device
2364 * @pdev: device to test
2365 * @acs_flags: required PCI ACS flags
2366 *
2367 * Return true if the device supports the provided flags. Automatically
2368 * filters out flags that are not implemented on multifunction devices.
2369 */
2370bool pci_acs_enabled(struct pci_dev *pdev, u16 acs_flags)
2371{
2372 int pos, ret;
2373 u16 ctrl;
2374
2375 ret = pci_dev_specific_acs_enabled(pdev, acs_flags);
2376 if (ret >= 0)
2377 return ret > 0;
2378
2379 if (!pci_is_pcie(pdev))
2380 return false;
2381
2382 /* Filter out flags not applicable to multifunction */
2383 if (pdev->multifunction)
2384 acs_flags &= (PCI_ACS_RR | PCI_ACS_CR |
2385 PCI_ACS_EC | PCI_ACS_DT);
2386
2387 if (pdev->pcie_type == PCI_EXP_TYPE_DOWNSTREAM ||
2388 pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT ||
2389 pdev->multifunction) {
2390 pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ACS);
2391 if (!pos)
2392 return false;
2393
2394 pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl);
2395 if ((ctrl & acs_flags) != acs_flags)
2396 return false;
2397 }
2398
2399 return true;
2400}
2401
2402/**
2403 * pci_acs_path_enable - test ACS flags from start to end in a hierarchy
2404 * @start: starting downstream device
2405 * @end: ending upstream device or NULL to search to the root bus
2406 * @acs_flags: required flags
2407 *
2408 * Walk up a device tree from start to end testing PCI ACS support. If
2409 * any step along the way does not support the required flags, return false.
2410 */
2411bool pci_acs_path_enabled(struct pci_dev *start,
2412 struct pci_dev *end, u16 acs_flags)
2413{
2414 struct pci_dev *pdev, *parent = start;
2415
2416 do {
2417 pdev = parent;
2418
2419 if (!pci_acs_enabled(pdev, acs_flags))
2420 return false;
2421
2422 if (pci_is_root_bus(pdev->bus))
2423 return (end == NULL);
2424
2425 parent = pdev->bus->self;
2426 } while (pdev != end);
2427
2428 return true;
2429}
2430
2431/**
2363 * pci_swizzle_interrupt_pin - swizzle INTx for device behind bridge 2432 * pci_swizzle_interrupt_pin - swizzle INTx for device behind bridge
2364 * @dev: the PCI device 2433 * @dev: the PCI device
2365 * @pin: the INTx pin (1=INTA, 2=INTB, 3=INTD, 4=INTD) 2434 * @pin: the INTx pin (1=INTA, 2=INTB, 3=INTD, 4=INTD)