aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
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
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')
-rw-r--r--drivers/pci/pci.c69
-rw-r--r--drivers/pci/quirks.c33
2 files changed, 102 insertions, 0 deletions
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 447e83472c0..1ccf7d49f52 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)
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index acd3956b44b..27e2c8f4ec7 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -3230,3 +3230,36 @@ struct pci_dev *pci_get_dma_source(struct pci_dev *dev)
3230 3230
3231 return pci_dev_get(dev); 3231 return pci_dev_get(dev);
3232} 3232}
3233
3234static const struct pci_dev_acs_enabled {
3235 u16 vendor;
3236 u16 device;
3237 int (*acs_enabled)(struct pci_dev *dev, u16 acs_flags);
3238} pci_dev_acs_enabled[] = {
3239 { 0 }
3240};
3241
3242int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags)
3243{
3244 const struct pci_dev_acs_enabled *i;
3245 int ret;
3246
3247 /*
3248 * Allow devices that do not expose standard PCIe ACS capabilities
3249 * or control to indicate their support here. Multi-function express
3250 * devices which do not allow internal peer-to-peer between functions,
3251 * but do not implement PCIe ACS may wish to return true here.
3252 */
3253 for (i = pci_dev_acs_enabled; i->acs_enabled; i++) {
3254 if ((i->vendor == dev->vendor ||
3255 i->vendor == (u16)PCI_ANY_ID) &&
3256 (i->device == dev->device ||
3257 i->device == (u16)PCI_ANY_ID)) {
3258 ret = i->acs_enabled(dev, acs_flags);
3259 if (ret >= 0)
3260 return ret;
3261 }
3262 }
3263
3264 return -ENOTTY;
3265}