OSDN Git Service

PCI/P2PDMA: Allow P2P DMA between any devices under AMD ZEN Root Complex
authorChristian König <ckoenig.leichtzumerken@gmail.com>
Thu, 18 Apr 2019 11:58:59 +0000 (13:58 +0200)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 1 May 2019 21:53:39 +0000 (16:53 -0500)
The PCI specs say that peer-to-peer DMA should be supported between any two
devices that have a common upstream PCI-to-PCI bridge.  But devices under
different Root Ports don't share a common upstream bridge, and PCIe r4.0,
sec 1.3.1, says routing peer-to-peer transactions between Root Ports in the
same Root Complex is optional.

Many Root Complexes, including AMD ZEN, *do* support peer-to-peer DMA even
between Root Ports.  Add a whitelist and allow peer-to-peer DMA if both
participants are attached to a Root Complex known to support it.

Link: https://lore.kernel.org/linux-pci/20190418115859.2394-1-christian.koenig@amd.com
Signed-off-by: Christian König <christian.koenig@amd.com>
[bhelgaas: changelog]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
drivers/pci/p2pdma.c

index c52298d..742928d 100644 (file)
@@ -275,6 +275,30 @@ static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev)
 }
 
 /*
+ * If we can't find a common upstream bridge take a look at the root
+ * complex and compare it to a whitelist of known good hardware.
+ */
+static bool root_complex_whitelist(struct pci_dev *dev)
+{
+       struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);
+       struct pci_dev *root = pci_get_slot(host->bus, PCI_DEVFN(0, 0));
+       unsigned short vendor, device;
+
+       if (!root)
+               return false;
+
+       vendor = root->vendor;
+       device = root->device;
+       pci_dev_put(root);
+
+       /* AMD ZEN host bridges can do peer to peer */
+       if (vendor == PCI_VENDOR_ID_AMD && device == 0x1450)
+               return true;
+
+       return false;
+}
+
+/*
  * Find the distance through the nearest common upstream bridge between
  * two PCI devices.
  *
@@ -317,13 +341,13 @@ static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev)
  * In this case, a list of all infringing bridge addresses will be
  * populated in acs_list (assuming it's non-null) for printk purposes.
  */
-static int upstream_bridge_distance(struct pci_dev *a,
-                                   struct pci_dev *b,
+static int upstream_bridge_distance(struct pci_dev *provider,
+                                   struct pci_dev *client,
                                    struct seq_buf *acs_list)
 {
+       struct pci_dev *a = provider, *b = client, *bb;
        int dist_a = 0;
        int dist_b = 0;
-       struct pci_dev *bb = NULL;
        int acs_cnt = 0;
 
        /*
@@ -354,6 +378,14 @@ static int upstream_bridge_distance(struct pci_dev *a,
                dist_a++;
        }
 
+       /*
+        * Allow the connection if both devices are on a whitelisted root
+        * complex, but add an arbitary large value to the distance.
+        */
+       if (root_complex_whitelist(provider) &&
+           root_complex_whitelist(client))
+               return 0x1000 + dist_a + dist_b;
+
        return -1;
 
 check_b_path_acs: