Commit 0f97da83 authored by Christian König's avatar Christian König Committed by Bjorn Helgaas

PCI/P2PDMA: Allow P2P DMA between any devices under AMD ZEN Root Complex

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.comSigned-off-by: default avatarChristian König <christian.koenig@amd.com>
[bhelgaas: changelog]
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
Reviewed-by: default avatarLogan Gunthorpe <logang@deltatee.com>
parent 9e98c678
...@@ -274,6 +274,30 @@ static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev) ...@@ -274,6 +274,30 @@ static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev)
seq_buf_printf(buf, "%s;", pci_name(pdev)); seq_buf_printf(buf, "%s;", pci_name(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 * Find the distance through the nearest common upstream bridge between
* two PCI devices. * two PCI devices.
...@@ -317,13 +341,13 @@ static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev) ...@@ -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 * In this case, a list of all infringing bridge addresses will be
* populated in acs_list (assuming it's non-null) for printk purposes. * populated in acs_list (assuming it's non-null) for printk purposes.
*/ */
static int upstream_bridge_distance(struct pci_dev *a, static int upstream_bridge_distance(struct pci_dev *provider,
struct pci_dev *b, struct pci_dev *client,
struct seq_buf *acs_list) struct seq_buf *acs_list)
{ {
struct pci_dev *a = provider, *b = client, *bb;
int dist_a = 0; int dist_a = 0;
int dist_b = 0; int dist_b = 0;
struct pci_dev *bb = NULL;
int acs_cnt = 0; int acs_cnt = 0;
/* /*
...@@ -354,6 +378,14 @@ static int upstream_bridge_distance(struct pci_dev *a, ...@@ -354,6 +378,14 @@ static int upstream_bridge_distance(struct pci_dev *a,
dist_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; return -1;
check_b_path_acs: check_b_path_acs:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment