[PATCH v4 12/18] PCI/P2PDMA: Honor ACS egress control vectors
Leon Romanovsky <[email protected]>
| Newsgroups | org.kernel.vger.linux-pci,dev.linux.lists.iommu,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Leon Romanovsky <[email protected]> An enabled Egress Control bit does not by itself redirect a peer Request. The result depends on the vector bit for the target port; a set bit without Request Redirect is an ACS Violation, while a clear bit permits direct routing. Evaluate controls only at the path divergence: Request Redirect and Egress Control on the client-side port, and Completion Redirect on the provider-side port. Below it, traffic toward the other branch already routes upstream. Reject unreadable vectors and ACS Violations. Neither condition redirects the transaction upstream, so a host-bridge mapping cannot make the path usable. Tested-by: Tushar Dave <[email protected]> Fixes: 52916982af48 ("PCI/P2PDMA: Support peer-to-peer memory") Signed-off-by: Leon Romanovsky <[email protected]> --- drivers/pci/p2pdma.c | 127 +++++++++++++++++++++++++++++++-------------- include/linux/pci-p2pdma.h | 8 +-- 2 files changed, 91 insertions(+), 44 deletions(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 1eda7b2135d7..bcbbdbd8ee47 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -541,6 +541,7 @@ static struct pci_dev *find_parent_pci_dev(struct device *dev) enum pci_acs_p2pdma_state { PCI_ACS_P2PDMA_DIRECT, PCI_ACS_P2PDMA_REDIRECT, + PCI_ACS_P2PDMA_NOT_SUPPORTED, }; enum pci_acs_p2pdma_tlp { @@ -549,9 +550,10 @@ enum pci_acs_p2pdma_tlp { }; static enum pci_acs_p2pdma_state -pci_acs_p2pdma_state(struct pci_dev *pdev, enum pci_acs_p2pdma_tlp tlp) +pci_acs_p2pdma_state(struct pci_dev *pdev, struct pci_dev *target, + enum pci_acs_p2pdma_tlp tlp) { - int pos; + int pos, ret; u16 ctrl; pos = pdev->acs_cap; @@ -559,14 +561,31 @@ pci_acs_p2pdma_state(struct pci_dev *pdev, enum pci_acs_p2pdma_tlp tlp) return PCI_ACS_P2PDMA_DIRECT; if (pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl)) - return PCI_ACS_P2PDMA_REDIRECT; + return PCI_ACS_P2PDMA_NOT_SUPPORTED; if (tlp == PCI_ACS_P2PDMA_TLP_COMPLETION) return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT; - return ctrl & (PCI_ACS_RR | PCI_ACS_EC) ? - PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT; + /* EC applies only to Requests where the target is known. */ + if (!target || !(ctrl & PCI_ACS_EC)) + return ctrl & PCI_ACS_RR ? + PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT; + + /* + * PCIe r7.0, sec 6.12.3, table 6-11: a set Egress Control Vector + * bit redirects the request only when Request Redirect is set. With + * Request Redirect clear, the request is handled as an ACS Violation. + * A clear vector bit permits the Request to route directly. + */ + ret = pci_acs_egress_ctrl_is_set(pdev, target); + if (ret < 0) + return PCI_ACS_P2PDMA_NOT_SUPPORTED; + if (ret) + return ctrl & PCI_ACS_RR ? PCI_ACS_P2PDMA_REDIRECT : + PCI_ACS_P2PDMA_NOT_SUPPORTED; + + return PCI_ACS_P2PDMA_DIRECT; } static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev) @@ -744,14 +763,14 @@ static unsigned long map_types_idx(struct pci_dev *client) * then to Device B. The mapping type returned depends on the ACS * redirection setting of the ports along the path. * - * The client initiates Requests to provider memory. Check Request Redirect - * on the client path and Completion Redirect for read Completions on the - * provider path. + * The client initiates Requests to provider memory. At the path divergence, + * check Request Redirect and Egress Control on the client-side port, and + * Completion Redirect for read Completions on the provider-side port. * - * If ACS redirect is set on any port in the path, traffic between the - * devices will go through the host bridge, so return - * PCI_P2PDMA_MAP_THRU_HOST_BRIDGE; otherwise return - * PCI_P2PDMA_MAP_BUS_ADDR. + * If ACS redirects traffic at either divergence port, return + * PCI_P2PDMA_MAP_THRU_HOST_BRIDGE. If ACS blocks the direct path or leaves + * its routing indeterminate, return PCI_P2PDMA_MAP_NOT_SUPPORTED. Otherwise, + * return PCI_P2PDMA_MAP_BUS_ADDR. * * Any two devices that have a data path that goes through the host bridge * will consult a whitelist. If the host bridge is in the whitelist, return @@ -765,9 +784,12 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, { enum pci_p2pdma_map_type map_type = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE; struct pci_dev *a = provider, *b = client, *bb; + struct pci_dev *a_child = NULL, *b_child = NULL; + struct pci_dev *acs_unsupported = NULL; + enum pci_acs_p2pdma_state state; struct pci_p2pdma *p2pdma; struct seq_buf acs_list; - int acs_cnt = 0; + int acs_redirect_cnt = 0; int dist_a = 0; int dist_b = 0; char buf[128]; @@ -781,51 +803,66 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, */ while (a) { dist_b = 0; - - if (pci_acs_p2pdma_state(a, - PCI_ACS_P2PDMA_TLP_COMPLETION) == - PCI_ACS_P2PDMA_REDIRECT) { - seq_buf_print_bus_devfn(&acs_list, a); - acs_cnt++; - } - + b_child = NULL; bb = b; while (bb) { if (a == bb) - goto check_b_path_acs; + goto check_paths_acs; + b_child = bb; bb = pci_upstream_bridge(bb); dist_b++; } + a_child = a; a = pci_upstream_bridge(a); dist_a++; } + /* + * The paths share no upstream bridge, so there is no direct path for + * ACS to gate: PCI_P2PDMA_MAP_BUS_ADDR is not reachable here and the + * request can only get to the peer through the host bridge. + */ *dist = dist_a + dist_b; goto map_through_host_bridge; -check_b_path_acs: - bb = b; - - while (bb) { - if (a == bb) - break; +check_paths_acs: + *dist = dist_a + dist_b; - if (pci_acs_p2pdma_state(bb, - PCI_ACS_P2PDMA_TLP_REQUEST) == - PCI_ACS_P2PDMA_REDIRECT) { - seq_buf_print_bus_devfn(&acs_list, bb); - acs_cnt++; + /* + * ACS P2P routing controls apply where a TLP can route toward the peer + * or upstream. Below that divergence, its only route toward the other + * branch is upstream, so redirect controls do not affect the path. + */ + if (a_child && b_child) { + state = pci_acs_p2pdma_state(a_child, NULL, + PCI_ACS_P2PDMA_TLP_COMPLETION); + if (state != PCI_ACS_P2PDMA_DIRECT) { + seq_buf_print_bus_devfn(&acs_list, a_child); + if (state == PCI_ACS_P2PDMA_REDIRECT) + acs_redirect_cnt++; + else if (!acs_unsupported) + acs_unsupported = a_child; } - bb = pci_upstream_bridge(bb); + state = pci_acs_p2pdma_state(b_child, a_child, + PCI_ACS_P2PDMA_TLP_REQUEST); + if (state != PCI_ACS_P2PDMA_DIRECT) { + seq_buf_print_bus_devfn(&acs_list, b_child); + if (state == PCI_ACS_P2PDMA_REDIRECT) + acs_redirect_cnt++; + else if (!acs_unsupported) + acs_unsupported = b_child; + } } - *dist = dist_a + dist_b; - - if (!acs_cnt) { + /* + * Below a shared upstream bridge, a path that neither divergence port + * redirects or blocks routes the request directly. + */ + if (!acs_unsupported && !acs_redirect_cnt) { map_type = PCI_P2PDMA_MAP_BUS_ADDR; goto done; } @@ -834,12 +871,22 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, /* Drop the final semicolon; the list is not empty here. */ if (!seq_buf_has_overflowed(&acs_list)) acs_list.buffer[acs_list.len - 1] = '\0'; - pci_warn(client, "ACS redirect is set between the client and provider (%s)\n", - pci_name(provider)); - pci_warn(client, "to disable ACS redirect for this path, add the kernel parameter: pci=disable_acs_redir=%s\n", + if (acs_unsupported) + pci_warn(client, "ACS leaves no usable direct P2P path to provider %s at %s\n", + pci_name(provider), pci_name(acs_unsupported)); + else + pci_warn(client, "ACS redirect is set between the client and provider (%s)\n", + pci_name(provider)); + pci_warn(client, "to disable ACS controls for this path, add the kernel parameter: pci=disable_acs_redir=%s\n", seq_buf_str(&acs_list)); } + /* A blocked or indeterminate direct path is not an upstream redirect. */ + if (acs_unsupported) { + map_type = PCI_P2PDMA_MAP_NOT_SUPPORTED; + goto done; + } + map_through_host_bridge: if (!cpu_supports_p2pdma() && !host_bridge_whitelist(provider, client, verbose)) { diff --git a/include/linux/pci-p2pdma.h b/include/linux/pci-p2pdma.h index 873de20a2247..dd17501ba1b6 100644 --- a/include/linux/pci-p2pdma.h +++ b/include/linux/pci-p2pdma.h @@ -42,10 +42,10 @@ enum pci_p2pdma_map_type { PCI_P2PDMA_MAP_NONE, /* - * PCI_P2PDMA_MAP_NOT_SUPPORTED: Indicates the transaction will - * traverse the host bridge and the host bridge is not in the - * allowlist. DMA Mapping routines should return an error when - * this is returned. + * PCI_P2PDMA_MAP_NOT_SUPPORTED: Indicates no safe mapping is available, + * for example because ACS blocks the direct path or the required host + * bridge is not in the allowlist. DMA Mapping routines should return an + * error when this is returned. */ PCI_P2PDMA_MAP_NOT_SUPPORTED, -- 2.55.0