* [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class
@ 2026-10-13 14:25 Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 01/19] PCI/P2PDMA: Honor device-specific ACS redirection Leon Romanovsky
` (18 more replies)
0 siblings, 19 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
PCI P2PDMA applies Request and Completion Redirect throughout both paths.
This misclassifies asymmetric and nested switches, and reports one answer
for every kind of TLP.
Three ACS controls act on TLP attributes the Requester chooses rather
than on the topology: Translation Blocking and Direct Translated P2P act on
a Request's Address Type, and Completion Redirect skips Completions carrying
Relaxed Ordering.
Evaluate each direction at the path divergence, decide every class from the
one walk, and treat a client with ATS enabled as translating unless its
driver declares per-mapping ATS.
This completes the P2PDMA side; dma-buf and mlx5 follow separately.
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
Changes in v11:
- Applied the client ATS policy in pci_p2pdma_distance_many() too, as
local Sashiko pointed out.
- Read the ATS Enable bit once per query, so the class and the ATS policy
always agree.
- Documented that pcim_p2pdma_set_ats_per_mapping() needs a successful
pcim_p2pdma_init().
- The path diagnostics no longer read ACS Control or the bridge bus range,
and they report a failed cache store.
- Added a KUnit case for Direct Translated P2P without a redirect.
- Clarified the Relaxed Ordering and diagnostics commit messages.
- Honored device-specific ACS redirection, such as Zhaoxin Downstream
Ports, in a new first patch with a KUnit case, as local Sashiko
pointed out. Relaxed Ordering Completions are redirected at such a
port too.
- Clarified in the documentation that a quirk also redirects Relaxed
Ordering Completions and that Translation Blocking applies only to
upstream Requests, and labelled the ACS routing debug line with its
class, as local Sashiko pointed out.
- Treated ACS registers that read as all ones as unreadable, and named
ports that a quirk redirects instead of suggesting
pci=disable_acs_redir= for them, as local Sashiko pointed out.
- Ignored ACS quirks of endpoints, which only describe traffic between
their own functions.
- Link to v10: https://patch.msgid.link/20261012-fix-p2p-acs-v4-0-v10-0-6ffe7b7a4517@nvidia.com
Changes in v10:
- Fixed the Relaxed Ordering description: a Completion carries the
attribute of its Request (PCIe r7.0 sec 2.2.9.1).
- Used the PCIe terms Requester and Completer for TLPs, ACS and ports;
client and provider remain for P2PDMA objects.
- Rebased on pci/p2pdma, which includes the prerequisite P2PDMA fixes.
- Added a topology example to the path divergence patch.
- Removed a repeated sentence from the disable_acs_redir documentation.
- Explained the path divergence in the documentation.
- Link to v9: https://patch.msgid.link/20261001-fix-p2p-acs-v4-0-v9-0-1a8e0f50ddd9@nvidia.com
Changes in v9:
- Removed dmabuf patches for now.
- Removed mlx5 per-mapping ATS declaration for now; it comes back with
the dmabuf patches.
- Link to v8: https://patch.msgid.link/20260928-fix-p2p-acs-v4-0-v8-0-404453b9c435@nvidia.com
Changes in v8:
- Added extra Reviewed-by from Logan.
- Added patches to take care of ATS per-device vs. per-mapping option
- Link to v7: https://patch.msgid.link/20260920-fix-p2p-acs-v4-0-v7-0-ca0828ab697c@nvidia.com
Changes in v7:
- Removed "Document pdev->p2pdma lifetime rules" patch, it gives nothing
after p2pmem fix.
- Split dmabuf patch.
- Tushar retested the series, so added his Tested-by.
- Added Logan's ROB tags and fixed minor documentation issues pointed
by him.
- Link to v6: https://lore.kernel.org/all/20260914-fix-p2p-acs-v4-0-v6-0-5ef07ec9ef06@nvidia.com/
Changes in v6:
- Changed DMABUF to use callbacks and not directly stored pointer.
- Link to v5: https://patch.msgid.link/20260910-fix-p2p-acs-v4-0-v5-0-856087f63c0d@nvidia.com
Changes in v5:
- Rebase on the posted fixes.
- Dropped tags from changed patches.
- Remove Egress Control Vector interpretation and coverage.
- Keep enabled Egress Control conservative as a Request redirect.
- Use pci_dbg()/dev_dbg() for diagnostics and drop the "debug" prefix.
- Removed code comments from "Document the pdev->p2pdma lifetime and RCU
rules" patch and reduced description to actual lifetime explanation.
- Added note that Linux assumes that TLPs are in strict-ordering and
untranslated.
- Added code to calculate p2p paths per-TLP type.
- Converted mlx5 to use that new proposed API.
- Link to https://patch.msgid.link/20260821-fix-p2p-acs-v4-0-v4-0-94426b96de73@nvidia.com
Changes in v4:
- Reject ACS Violations and unreadable routing state instead of treating
them as host-bridge redirects
- Added Tested-by tags from Tushar Dave
- Added support to asymmetric ACS routing
- Limited redirect checks to the two ports at the path divergence
- Added standalone ACS routing diagnostics for hardware retesting
- Dropped " PCI: Account for Direct Translated P2P in ACS isolation checks" patch
- Link to v3: https://patch.msgid.link/20260811-fix-p2p-acs-v3-0-efc488ee7c03@nvidia.com
Changes in v3:
- Fixed pci_p2pdma_add_resource() error unwinding
- Made pdev->p2pdma teardown wait unconditionally for RCU readers
- Restricted pci_p2pmem_find_many() to pool-backed providers
- Documented the pdev->p2pdma lifetime and RCU rules
- Fixed calc_map_type_and_dist() handling of the verbose argument
- Required the ACS port and target to share a bus before indexing the
Egress Control Vector
- Gave pci_acs_enabled() and pci_acs_path_enabled() a scope, so the ACS
Direct Translated P2P rule no longer stops pci_enable_pasid() from
enabling PASID
- Dropped "Report ACS ports when the paths share no upstream bridge":
the mapping type cannot change without a shared upstream bridge, so
the pci=disable_acs_redir= hint was not actionable there and the ACS
walk only cost config space reads
- Folded the Request Redirect rule into pci_acs_rr_ineffective(), so
pci_acs_flags_enabled() and the Intel SPT PCH quirk share one copy
- Renamed pci_acs_egress_ctrl_set() to pci_acs_egress_ctrl_is_set(), it
reads the bit rather than setting it
- Reworded the blocked-path warning: ACS may also leave the direct route
indeterminate rather than blocked
- Added KUnit coverage for the shared-bus guard, a device with no ACS
capability and an unreadable ACS Control register
- Added the missing Fixes: tags, a second one on the
pci_p2pdma_add_resource() unwinding fix (the dangling devres action
dates to f58ef9d1d135) and one on the Egress Control isolation change
- Link to v2: https://patch.msgid.link/20260806-fix-p2p-acs-v2-0-0cec14812965@nvidia.com
Changes in v2:
- Added Logan's ROB tags
- Added commas in Documentation patch
- Link to v1: https://patch.msgid.link/20260802-fix-p2p-acs-v1-0-a7c5eb64fff6@nvidia.com
---
Leon Romanovsky (19):
PCI/P2PDMA: Honor device-specific ACS redirection
PCI/P2PDMA: Document the TLP attribute assumptions
PCI/P2PDMA: Derive routing from directional ACS controls
PCI: Reject unreadable ACS controls in isolation checks
PCI/P2PDMA: Evaluate ACS controls at the path divergence
PCI/P2PDMA: Document directional ACS routing
PCI/P2PDMA: Collect the path's ACS controls before deciding
PCI/P2PDMA: Answer routing per TLP class
PCI/P2PDMA: Route Relaxed Ordering Completions directly
PCI/P2PDMA: Reject Translated Requests blocked by Translation Blocking
PCI/P2PDMA: Route Translated Requests under Direct Translated P2P
PCI/P2PDMA: Log detailed ACS routing diagnostics
PCI/P2PDMA: Add KUnit tests for the ACS routing decisions
PCI/P2PDMA: Test the ACS P2P routing walk
PCI: Add KUnit coverage for ACS isolation checks
PCI/P2PDMA: Document TLP-class routing
PCI/P2PDMA: Let a client declare that it selects ATS per mapping
PCI/P2PDMA: Evaluate the ATS path for clients with ATS enabled
PCI/P2PDMA: Test the routing of clients with ATS enabled
Documentation/admin-guide/kernel-parameters.txt | 11 +-
Documentation/driver-api/pci/p2pdma.rst | 105 ++
drivers/pci/Kconfig | 15 +
drivers/pci/Makefile | 1 +
drivers/pci/p2pdma.c | 942 +++++++++++++++--
drivers/pci/pci.c | 8 +-
drivers/pci/pci.h | 57 ++
drivers/pci/pci_acs_test.c | 1225 +++++++++++++++++++++++
drivers/pci/quirks.c | 8 +-
include/linux/pci-p2pdma.h | 12 +-
10 files changed, 2292 insertions(+), 92 deletions(-)
---
base-commit: 36038f0e8377ef316b723d0d85b1510bc64069ab
change-id: 20260821-fix-p2p-acs-v4-0-e72455e3a261
Best regards,
--
Leon Romanovsky <leonro@nvidia.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v11 01/19] PCI/P2PDMA: Honor device-specific ACS redirection
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:44 ` sashiko-bot
2026-10-13 14:25 ` [PATCH v11 02/19] PCI/P2PDMA: Document the TLP attribute assumptions Leon Romanovsky
` (17 subsequent siblings)
18 siblings, 1 reply; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
Some ports, such as Zhaoxin Switch Downstream Ports, redirect peer-to-peer
traffic without an ACS capability, as their device-specific ACS quirks
report. P2PDMA reads only ACS Control, so below such a port it hands out
bus addresses for traffic that goes to the host bridge.
Take Request and Completion Redirect from a bridge's quirk too. Ignore
endpoint quirks, which only describe traffic between an endpoint's own
functions. Skip bridges on a root bus: they never sit between two peers,
and the AMD southbridge quirk may sleep there during DMA mapping.
pci=disable_acs_redir= cannot override a quirk, so name such ports
separately.
Fixes: 52916982af48 ("PCI/P2PDMA: Support peer-to-peer memory")
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 114 +++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 96 insertions(+), 18 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index 45260423dfb0..ba94b0f33928 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -21,6 +21,8 @@
#include <linux/seq_buf.h>
#include <linux/xarray.h>
+#include "pci.h"
+
struct pci_p2pdma {
struct gen_pool *pool;
bool p2pmem_published;
@@ -490,6 +492,44 @@ static struct pci_dev *find_parent_pci_dev(struct device *dev)
return NULL;
}
+/*
+ * Some ports send peer-to-peer traffic upstream without a standard ACS
+ * capability, such as Zhaoxin Root and Switch Downstream Ports. Their
+ * device-specific ACS quirks say so. Report whether @pdev's quirk says that
+ * @flag is in effect.
+ *
+ * Only ask bridges. The quirks for multi-function endpoints only say that
+ * their functions do not talk to each other directly, which says nothing
+ * about their traffic to other devices. Skip bridges on a root bus too. They
+ * never decide whether two peers connect directly, and for them the AMD
+ * southbridge quirk calls acpi_get_table(), which may sleep. DMA mapping may
+ * call this code where sleeping is not allowed.
+ */
+static bool pci_acs_p2pdma_quirk(struct pci_dev *pdev, u16 flag)
+{
+ if (!pci_is_bridge(pdev) || pci_is_root_bus(pdev->bus))
+ return false;
+
+ return pci_dev_specific_acs_enabled(pdev, flag) > 0;
+}
+
+/*
+ * Add the Request and Completion Redirect that @pdev's quirk reports to its
+ * ACS Control value. Only ever add them: a quirk that covers every device of
+ * a vendor says "no" for the ones it does not know, and that must not hide
+ * their own ACS controls. What a quirk says about other controls only means
+ * that the control does not apply, so ignore it.
+ */
+static u16 pci_acs_p2pdma_quirk_redirect(struct pci_dev *pdev, u16 ctrl)
+{
+ if (pci_acs_p2pdma_quirk(pdev, PCI_ACS_RR))
+ ctrl |= PCI_ACS_RR;
+ if (pci_acs_p2pdma_quirk(pdev, PCI_ACS_CR))
+ ctrl |= PCI_ACS_CR;
+
+ return ctrl;
+}
+
/*
* Check if a PCI bridge has its ACS redirection bits set to redirect P2P
* TLPs upstream via ACS. Returns 1 if the packets will be redirected
@@ -498,14 +538,13 @@ static struct pci_dev *find_parent_pci_dev(struct device *dev)
static int pci_bridge_has_acs_redir(struct pci_dev *pdev)
{
int pos;
- u16 ctrl;
+ u16 ctrl = 0;
pos = pdev->acs_cap;
- if (!pos)
- return 0;
-
- pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl);
+ if (pos)
+ pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl);
+ ctrl = pci_acs_p2pdma_quirk_redirect(pdev, ctrl);
if (ctrl & (PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_EC))
return 1;
@@ -520,6 +559,51 @@ static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev)
seq_buf_printf(buf, "%s;", pci_name(pdev));
}
+/*
+ * Sort a port that redirects into @acs_list, the ports to suggest for
+ * pci=disable_acs_redir=, or @quirk_list. That parameter only clears the ACS
+ * Control register, so it cannot undo a redirect that a device quirk reports.
+ */
+static void pci_p2pdma_list_redir(struct seq_buf *acs_list,
+ struct seq_buf *quirk_list,
+ struct pci_dev *pdev)
+{
+ if (pci_acs_p2pdma_quirk(pdev, PCI_ACS_RR) ||
+ pci_acs_p2pdma_quirk(pdev, PCI_ACS_CR))
+ seq_buf_print_bus_devfn(quirk_list, pdev);
+ else
+ seq_buf_print_bus_devfn(acs_list, pdev);
+}
+
+/* Drop the final semicolon. Returns false if no port was listed. */
+static bool pci_p2pdma_end_list(struct seq_buf *list)
+{
+ if (!seq_buf_used(list))
+ return false;
+
+ if (!seq_buf_has_overflowed(list))
+ list->buffer[list->len - 1] = '\0';
+ return true;
+}
+
+static void pci_p2pdma_warn_redir(struct pci_dev *client,
+ struct pci_dev *provider,
+ struct seq_buf *acs_list,
+ struct seq_buf *quirk_list)
+{
+ pci_warn(client,
+ "ACS redirect is set between the client and provider (%s)\n",
+ pci_name(provider));
+ if (pci_p2pdma_end_list(quirk_list))
+ pci_warn(client,
+ "hardware redirects peer-to-peer traffic at %s, which no kernel parameter changes\n",
+ seq_buf_str(quirk_list));
+ if (pci_p2pdma_end_list(acs_list))
+ pci_warn(client,
+ "to disable ACS redirect for this path, add the kernel parameter: pci=disable_acs_redir=%s\n",
+ seq_buf_str(acs_list));
+}
+
static bool cpu_supports_p2pdma(void)
{
#ifdef CONFIG_X86
@@ -722,13 +806,14 @@ 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_p2pdma *p2pdma;
- struct seq_buf acs_list;
+ struct seq_buf acs_list, quirk_list;
int acs_cnt = 0;
int dist_a = 0;
int dist_b = 0;
- char buf[128];
+ char buf[128], quirk_buf[64];
seq_buf_init(&acs_list, buf, sizeof(buf));
+ seq_buf_init(&quirk_list, quirk_buf, sizeof(quirk_buf));
/*
* Note, we don't need to take references to devices returned by
@@ -739,7 +824,7 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
dist_b = 0;
if (pci_bridge_has_acs_redir(a)) {
- seq_buf_print_bus_devfn(&acs_list, a);
+ pci_p2pdma_list_redir(&acs_list, &quirk_list, a);
acs_cnt++;
}
@@ -768,7 +853,7 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
break;
if (pci_bridge_has_acs_redir(bb)) {
- seq_buf_print_bus_devfn(&acs_list, bb);
+ pci_p2pdma_list_redir(&acs_list, &quirk_list, bb);
acs_cnt++;
}
@@ -782,15 +867,8 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
goto done;
}
- if (verbose) {
- /* 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",
- seq_buf_str(&acs_list));
- }
+ if (verbose)
+ pci_p2pdma_warn_redir(client, provider, &acs_list, &quirk_list);
map_through_host_bridge:
if (!cpu_supports_p2pdma() &&
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 02/19] PCI/P2PDMA: Document the TLP attribute assumptions
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 01/19] PCI/P2PDMA: Honor device-specific ACS redirection Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 03/19] PCI/P2PDMA: Derive routing from directional ACS controls Leon Romanovsky
` (16 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
P2PDMA selects a mapping without receiving the Request's ordering or
Address Type attributes. Its ACS handles only strictly ordered Requests
carrying an Untranslated address.
Document that the result is not defined for Relaxed Ordering or
ATS-translated Requests because those TLP attributes can select different
routes through the fabric.
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
Tested-by: Tushar Dave <tdave@nvidia.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
Documentation/driver-api/pci/p2pdma.rst | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Documentation/driver-api/pci/p2pdma.rst b/Documentation/driver-api/pci/p2pdma.rst
index 63cff9e4d2c9..80f8fec9b0e9 100644
--- a/Documentation/driver-api/pci/p2pdma.rst
+++ b/Documentation/driver-api/pci/p2pdma.rst
@@ -15,6 +15,13 @@ then based on the ACS settings the transaction can route entirely within
the PCIe hierarchy and never reach the root port. The kernel will evaluate
the PCIe topology and always permit P2P in these well-defined cases.
+This evaluation assumes clients issue strictly ordered Requests carrying an
+Untranslated address. Its result is not defined when clients use Relaxed
+Ordering or issue ATS-translated Requests because those TLP attributes can
+select different routes through the fabric. Unless ACS Translation Blocking
+is enabled, a Port with ACS Direct Translated P2P enabled routes a
+Translated Request directly to the peer regardless of the redirect controls.
+
However, if the P2P transaction reaches the host bridge then it might have to
hairpin back out the same root port, be routed inside the CPU SOC to another
PCIe root port, or routed internally to the SOC.
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 03/19] PCI/P2PDMA: Derive routing from directional ACS controls
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 01/19] PCI/P2PDMA: Honor device-specific ACS redirection Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 02/19] PCI/P2PDMA: Document the TLP attribute assumptions Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 04/19] PCI: Reject unreadable ACS controls in isolation checks Leon Romanovsky
` (15 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
pci_bridge_has_acs_redir() treats Request and Completion Redirect as
interchangeable. On asymmetric fabrics, a control for only the reverse TLP
direction can unnecessarily force P2PDMA through the host bridge.
Evaluate Request Redirect for the Requester's Requests and Completion
Redirect for the Completer's read Completions. Continue treating enabled
Egress Control conservatively as a Request redirect.
Fixes: 52916982af48 ("PCI/P2PDMA: Support peer-to-peer memory")
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
Tested-by: Tushar Dave <tdave@nvidia.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 77 ++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 62 insertions(+), 15 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index ba94b0f33928..dbab955dcbdb 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -530,25 +530,63 @@ static u16 pci_acs_p2pdma_quirk_redirect(struct pci_dev *pdev, u16 ctrl)
return ctrl;
}
+enum pci_acs_p2pdma_state {
+ PCI_ACS_P2PDMA_DIRECT,
+ PCI_ACS_P2PDMA_REDIRECT,
+};
+
+/*
+ * Decide how a peer-to-peer Request at an ACS-capable ingress port routes,
+ * from that port's ACS Control register.
+ *
+ * Linux does not read the Egress Control Vector, so Egress Control is treated
+ * conservatively as a redirect. Per PCIe r7.0 Table 6-11 the outcomes it
+ * selects are a direct route and an ACS Violation, and neither one lets peer
+ * bus addressing be assumed.
+ */
+static enum pci_acs_p2pdma_state
+pci_acs_p2pdma_request(u16 ctrl)
+{
+ return ctrl & (PCI_ACS_RR | PCI_ACS_EC) ?
+ PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT;
+}
+
/*
- * Check if a PCI bridge has its ACS redirection bits set to redirect P2P
- * TLPs upstream via ACS. Returns 1 if the packets will be redirected
- * upstream, 0 otherwise.
+ * Decide how a peer-to-peer Completion at an ACS-capable ingress port routes.
+ * PCIe r7.0 sec 6.12.1.1: no ACS control other than P2P Completion Redirect
+ * affects a Completion.
*/
-static int pci_bridge_has_acs_redir(struct pci_dev *pdev)
+static enum pci_acs_p2pdma_state
+pci_acs_p2pdma_completion(u16 ctrl)
+{
+ return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT :
+ PCI_ACS_P2PDMA_DIRECT;
+}
+
+/*
+ * Read @pdev's ACS Control register, with Request and Completion Redirect
+ * taken from its ACS quirk if it has one. A device with neither has no
+ * peer-to-peer controls at all, which routes the same as having them all
+ * clear. Returns false when the register is present but cannot be read, or
+ * reads as all ones, which is what a device that does not answer returns;
+ * @ctrl is then meaningless.
+ */
+static bool pci_acs_p2pdma_ctrl(struct pci_dev *pdev, u16 *ctrl)
{
int pos;
- u16 ctrl = 0;
pos = pdev->acs_cap;
- if (pos)
- pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl);
+ if (!pos) {
+ *ctrl = pci_acs_p2pdma_quirk_redirect(pdev, 0);
+ return true;
+ }
- ctrl = pci_acs_p2pdma_quirk_redirect(pdev, ctrl);
- if (ctrl & (PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_EC))
- return 1;
+ if (pci_read_config_word(pdev, pos + PCI_ACS_CTRL, ctrl) ||
+ PCI_POSSIBLE_ERROR(*ctrl))
+ return false;
- return 0;
+ *ctrl = pci_acs_p2pdma_quirk_redirect(pdev, *ctrl);
+ return true;
}
static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev)
@@ -788,6 +826,10 @@ 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 is the Requester and the provider the Completer. Check Request
+ * Redirect on the Requester's path and Completion Redirect for read
+ * Completions on the Completer's path.
+ *
* 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
@@ -811,6 +853,7 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
int dist_a = 0;
int dist_b = 0;
char buf[128], quirk_buf[64];
+ u16 ctrl;
seq_buf_init(&acs_list, buf, sizeof(buf));
seq_buf_init(&quirk_list, quirk_buf, sizeof(quirk_buf));
@@ -823,7 +866,9 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
while (a) {
dist_b = 0;
- if (pci_bridge_has_acs_redir(a)) {
+ if (!pci_acs_p2pdma_ctrl(a, &ctrl) ||
+ pci_acs_p2pdma_completion(ctrl) ==
+ PCI_ACS_P2PDMA_REDIRECT) {
pci_p2pdma_list_redir(&acs_list, &quirk_list, a);
acs_cnt++;
}
@@ -852,7 +897,9 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
if (a == bb)
break;
- if (pci_bridge_has_acs_redir(bb)) {
+ if (!pci_acs_p2pdma_ctrl(bb, &ctrl) ||
+ pci_acs_p2pdma_request(ctrl) ==
+ PCI_ACS_P2PDMA_REDIRECT) {
pci_p2pdma_list_redir(&acs_list, &quirk_list, bb);
acs_cnt++;
}
@@ -1194,10 +1241,10 @@ EXPORT_SYMBOL_GPL(pci_p2pmem_publish);
/**
* pci_p2pdma_map_type - Determine the mapping type for P2PDMA transfers
* @provider: P2PDMA provider structure
- * @dev: Target device for the transfer
+ * @dev: Client device that initiates the transfer
*
* Determines how peer-to-peer DMA transfers should be mapped between
- * the provider and the target device. The mapping type indicates whether
+ * the provider and the client device. The mapping type indicates whether
* the transfer can be done directly through PCI switches or must go
* through the host bridge.
*/
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 04/19] PCI: Reject unreadable ACS controls in isolation checks
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (2 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 03/19] PCI/P2PDMA: Derive routing from directional ACS controls Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 05/19] PCI/P2PDMA: Evaluate ACS controls at the path divergence Leon Romanovsky
` (14 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
pci_acs_flags_enabled() and the Intel SPT PCH quirk use ACS registers
without checking config-space read errors. A failed read may leave control
state indeterminate yet allow the device to satisfy requested isolation
controls. So can a read that returns all ones with a successful status,
which is what a device that does not answer usually produces.
Return false when either ACS capability or control state cannot be read
or reads as all ones, which neither register can legitimately hold. An
unknown state cannot prove isolation.
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
Tested-by: Tushar Dave <tdave@nvidia.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/pci.c | 4 +++-
drivers/pci/quirks.c | 8 ++++++--
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index b2879a6be5f8..5f6db2416cee 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -3594,7 +3594,9 @@ static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags)
*/
acs_flags &= (pdev->acs_capabilities | PCI_ACS_EC);
- pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl);
+ if (pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl) ||
+ PCI_POSSIBLE_ERROR(ctrl))
+ return false;
return (ctrl & acs_flags) == acs_flags;
}
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index de9bbccda21f..849619e270fd 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -4992,10 +4992,14 @@ static int pci_quirk_intel_spt_pch_acs(struct pci_dev *dev, u16 acs_flags)
return -ENOTTY;
/* see pci_acs_flags_enabled() */
- pci_read_config_dword(dev, pos + PCI_ACS_CAP, &cap);
+ if (pci_read_config_dword(dev, pos + PCI_ACS_CAP, &cap) ||
+ PCI_POSSIBLE_ERROR(cap))
+ return 0;
acs_flags &= (cap | PCI_ACS_EC);
- pci_read_config_dword(dev, pos + INTEL_SPT_ACS_CTRL, &ctrl);
+ if (pci_read_config_dword(dev, pos + INTEL_SPT_ACS_CTRL, &ctrl) ||
+ PCI_POSSIBLE_ERROR(ctrl))
+ return 0;
return pci_acs_ctrl_enabled(acs_flags, ctrl);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 05/19] PCI/P2PDMA: Evaluate ACS controls at the path divergence
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (3 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 04/19] PCI: Reject unreadable ACS controls in isolation checks Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 06/19] PCI/P2PDMA: Document directional ACS routing Leon Romanovsky
` (13 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
ACS redirect controls below the path divergence reject valid nested
topologies, where traffic already has only an upstream route. Sibling
functions can lack an internal peer route even with redirect controls
clear, so ignoring their shared ports can incorrectly permit bus
addressing.
Evaluate directional controls at the divergence and account separately for
unsupported internal routes between functions sharing a PCIe link. Require
usable Upstream Forwarding through their shared ports before considering
the host-bridge route. Reject unreadable ACS controls.
Fixes: 52916982af48 ("PCI/P2PDMA: Support peer-to-peer memory")
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 194 ++++++++++++++++++++++++++++++++++++---------
include/linux/pci-p2pdma.h | 8 +-
2 files changed, 159 insertions(+), 43 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index dbab955dcbdb..8ca0c9ba87c2 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -531,6 +531,7 @@ static u16 pci_acs_p2pdma_quirk_redirect(struct pci_dev *pdev, u16 ctrl)
}
enum pci_acs_p2pdma_state {
+ PCI_ACS_P2PDMA_NOT_SUPPORTED,
PCI_ACS_P2PDMA_DIRECT,
PCI_ACS_P2PDMA_REDIRECT,
};
@@ -566,8 +567,8 @@ pci_acs_p2pdma_completion(u16 ctrl)
/*
* Read @pdev's ACS Control register, with Request and Completion Redirect
* taken from its ACS quirk if it has one. A device with neither has no
- * peer-to-peer controls at all, which routes the same as having them all
- * clear. Returns false when the register is present but cannot be read, or
+ * peer-to-peer controls at all. Whether functions can route internally is
+ * checked separately. Returns false when the register cannot be read, or
* reads as all ones, which is what a device that does not answer returns;
* @ctrl is then meaningless.
*/
@@ -589,6 +590,65 @@ static bool pci_acs_p2pdma_ctrl(struct pci_dev *pdev, u16 *ctrl)
return true;
}
+static bool pci_acs_p2pdma_downstream_port(struct pci_dev *pdev)
+{
+ return pci_is_pcie(pdev) &&
+ (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT ||
+ pci_pcie_type(pdev) == PCI_EXP_TYPE_DOWNSTREAM);
+}
+
+/*
+ * Only use this for functions sharing a PCIe link. An absent RR or CR
+ * capability means that the corresponding internal peer route does not
+ * exist, rather than that a programmable redirect is disabled. Likewise,
+ * SR-IOV functions that support internal peer traffic must implement ACS.
+ * Use each VF's own capability, not its PF's.
+ */
+static bool pci_acs_p2pdma_no_internal_route(struct pci_dev *pdev, u16 flag)
+{
+ if (pci_dev_specific_acs_enabled(pdev, flag) > 0)
+ return true;
+
+ if (pdev->acs_cap)
+ return !(pdev->acs_capabilities & flag);
+
+ return pdev->is_physfn || pdev->is_virtfn;
+}
+
+/*
+ * Traffic between functions on one link has the same ingress and egress
+ * at every shared Downstream Port. ACS Upstream Forwarding must carry it
+ * toward the host instead of back down that link. Missing UF support does
+ * not establish a forwarding route, even though it satisfies an isolation
+ * query. Return the first port that cannot establish this route.
+ */
+static struct pci_dev *
+pci_acs_p2pdma_no_upstream_forwarding(struct pci_dev *common)
+{
+ struct pci_dev *pdev;
+ u16 ctrl;
+
+ for (pdev = common; pdev; pdev = pci_upstream_bridge(pdev)) {
+ if (!pci_acs_p2pdma_downstream_port(pdev))
+ continue;
+
+ if (!pci_acs_p2pdma_ctrl(pdev, &ctrl))
+ return pdev;
+
+ /*
+ * Include Root Ports here. The PCIe port type check excludes
+ * the legacy AMD southbridge quirk, which may sleep.
+ */
+ if (pci_dev_specific_acs_enabled(pdev, PCI_ACS_UF) > 0)
+ continue;
+
+ if (!(pdev->acs_cap && pdev->acs_capabilities & ctrl & PCI_ACS_UF))
+ return pdev;
+ }
+
+ return NULL;
+}
+
static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev)
{
if (!buf)
@@ -638,7 +698,7 @@ static void pci_p2pdma_warn_redir(struct pci_dev *client,
seq_buf_str(quirk_list));
if (pci_p2pdma_end_list(acs_list))
pci_warn(client,
- "to disable ACS redirect for this path, add the kernel parameter: pci=disable_acs_redir=%s\n",
+ "to disable ACS controls for this path, add the kernel parameter: pci=disable_acs_redir=%s\n",
seq_buf_str(acs_list));
}
@@ -807,9 +867,10 @@ static unsigned long map_types_idx(struct pci_dev *client)
* If the two devices are the same PCI function, return
* PCI_P2PDMA_MAP_BUS_ADDR and a distance of 0.
*
- * If they are two functions of the same device, return
- * PCI_P2PDMA_MAP_BUS_ADDR and a distance of 2 (one hop up to the bridge,
- * then one hop back down to another function of the same device).
+ * If they are two functions of the same device, return a distance of 2.
+ * A bus address requires an internal route between the functions. Otherwise,
+ * Upstream Forwarding must carry the traffic through their shared ports
+ * toward a supported host bridge.
*
* In the case where two devices are connected to the same PCIe switch,
* return a distance of 4. This corresponds to the following PCI tree:
@@ -826,13 +887,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 is the Requester and the provider the Completer. Check Request
- * Redirect on the Requester's path and Completion Redirect for read
- * Completions on the Completer's path.
+ * The client is the Requester and the provider the Completer. At the path
+ * divergence, check Request Redirect and Egress Control on the Requester-side
+ * port, and Completion Redirect for read Completions on the Completer-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
+ * If ACS redirects traffic at either divergence port, return
+ * PCI_P2PDMA_MAP_THRU_HOST_BRIDGE. If the ACS Control register cannot be
+ * read, 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
@@ -846,10 +908,15 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
int *dist, bool verbose)
{
enum pci_p2pdma_map_type map_type = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE;
+ enum pci_acs_p2pdma_state state = PCI_ACS_P2PDMA_NOT_SUPPORTED;
struct pci_dev *a = provider, *b = client, *bb;
+ struct pci_dev *a_child = NULL, *b_child = NULL;
+ struct pci_dev *acs_unreadable = NULL;
+ struct pci_dev *uf_blocked = NULL;
struct pci_p2pdma *p2pdma;
struct seq_buf acs_list, quirk_list;
- int acs_cnt = 0;
+ bool req_upstream = false, cpl_upstream = false;
+ int acs_redirect_cnt = 0;
int dist_a = 0;
int dist_b = 0;
char buf[128], quirk_buf[64];
@@ -865,57 +932,106 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
*/
while (a) {
dist_b = 0;
-
- if (!pci_acs_p2pdma_ctrl(a, &ctrl) ||
- pci_acs_p2pdma_completion(ctrl) ==
- PCI_ACS_P2PDMA_REDIRECT) {
- pci_p2pdma_list_redir(&acs_list, &quirk_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_ctrl(bb, &ctrl) ||
- pci_acs_p2pdma_request(ctrl) ==
- PCI_ACS_P2PDMA_REDIRECT) {
- pci_p2pdma_list_redir(&acs_list, &quirk_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) {
+ if (pci_acs_p2pdma_ctrl(a_child, &ctrl))
+ state = pci_acs_p2pdma_completion(ctrl);
+ if (state != PCI_ACS_P2PDMA_DIRECT) {
+ pci_p2pdma_list_redir(&acs_list, &quirk_list, a_child);
+ if (state == PCI_ACS_P2PDMA_REDIRECT)
+ acs_redirect_cnt++;
+ else if (!acs_unreadable)
+ acs_unreadable = a_child;
}
- bb = pci_upstream_bridge(bb);
+ state = PCI_ACS_P2PDMA_NOT_SUPPORTED;
+ if (pci_acs_p2pdma_ctrl(b_child, &ctrl))
+ state = pci_acs_p2pdma_request(ctrl);
+ if (state != PCI_ACS_P2PDMA_DIRECT) {
+ pci_p2pdma_list_redir(&acs_list, &quirk_list, b_child);
+ if (state == PCI_ACS_P2PDMA_REDIRECT)
+ acs_redirect_cnt++;
+ else if (!acs_unreadable)
+ acs_unreadable = b_child;
+ }
+ /*
+ * Two children of one Downstream or Root Port share a link.
+ * Use topology rather than slot numbers or the multifunction
+ * bit, since ARI functions and VFs need not share those.
+ */
+ if (pci_acs_p2pdma_downstream_port(a)) {
+ req_upstream =
+ pci_acs_p2pdma_no_internal_route(b_child, PCI_ACS_RR);
+ cpl_upstream =
+ pci_acs_p2pdma_no_internal_route(a_child, PCI_ACS_CR);
+ uf_blocked = pci_acs_p2pdma_no_upstream_forwarding(a);
+ }
}
- *dist = dist_a + dist_b;
-
- if (!acs_cnt) {
+ /*
+ * Below a shared upstream bridge, a path whose divergence ports do not
+ * redirect routes the request directly.
+ */
+ if (!acs_unreadable && !acs_redirect_cnt &&
+ !req_upstream && !cpl_upstream) {
map_type = PCI_P2PDMA_MAP_BUS_ADDR;
goto done;
}
- if (verbose)
- pci_p2pdma_warn_redir(client, provider, &acs_list, &quirk_list);
+ if (verbose) {
+ if (acs_unreadable)
+ pci_warn(client, "ACS Control is unreadable for provider %s at %s\n",
+ pci_name(provider), pci_name(acs_unreadable));
+ else if (uf_blocked)
+ pci_warn(client,
+ "cannot forward peer-to-peer DMA for provider %s upstream at %s\n",
+ pci_name(provider), pci_name(uf_blocked));
+ else if (req_upstream || cpl_upstream)
+ pci_warn(client,
+ "peer-to-peer DMA with provider %s requires the host bridge; no internal route between functions\n",
+ pci_name(provider));
+ else
+ pci_p2pdma_warn_redir(client, provider, &acs_list,
+ &quirk_list);
+ }
+
+ /* An unreadable control or unusable forwarding leaves no route. */
+ if (acs_unreadable || uf_blocked) {
+ map_type = PCI_P2PDMA_MAP_NOT_SUPPORTED;
+ goto done;
+ }
map_through_host_bridge:
if (!cpu_supports_p2pdma() &&
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
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 06/19] PCI/P2PDMA: Document directional ACS routing
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (4 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 05/19] PCI/P2PDMA: Evaluate ACS controls at the path divergence Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 07/19] PCI/P2PDMA: Collect the path's ACS controls before deciding Leon Romanovsky
` (12 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
P2PDMA documentation describes ACS controls as path-wide, although Request
and Completion controls apply to different transaction directions at the
path divergence. Functions sharing one PCIe link may instead have no
internal peer route even with redirect controls clear.
Document the Requester and Completer roles, the directional controls and
the separate requirements for sibling functions. Explain that a missing
internal route requires upstream forwarding and host-bridge support, and
cannot be created by clearing redirect controls.
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
Documentation/admin-guide/kernel-parameters.txt | 11 ++++----
Documentation/driver-api/pci/p2pdma.rst | 34 +++++++++++++++++++++++++
2 files changed, 40 insertions(+), 5 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 68647ff4bdd2..8507ac79a919 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -5292,11 +5292,12 @@ Kernel parameters
Specify one or more PCI devices (in the format
specified above) separated by semicolons.
Each device specified will have the PCI ACS
- redirect capabilities forced off which will
- allow P2P traffic between devices through
- bridges without forcing it upstream. Note:
- this removes isolation between devices and
- may put more devices in an IOMMU group.
+ P2P Request Redirect, Completion Redirect, and
+ Egress Control features forced off. This may
+ allow P2P traffic through bridges that would
+ otherwise be redirected upstream. Note: this
+ removes isolation between devices and may
+ cause affected devices to share an IOMMU group.
config_acs=
Format:
<ACS flags>@<pci_dev>[; ...]
diff --git a/Documentation/driver-api/pci/p2pdma.rst b/Documentation/driver-api/pci/p2pdma.rst
index 80f8fec9b0e9..7b11d7d1fdc6 100644
--- a/Documentation/driver-api/pci/p2pdma.rst
+++ b/Documentation/driver-api/pci/p2pdma.rst
@@ -15,6 +15,40 @@ then based on the ACS settings the transaction can route entirely within
the PCIe hierarchy and never reach the root port. The kernel will evaluate
the PCIe topology and always permit P2P in these well-defined cases.
+The client is the PCIe Requester and the provider is the Completer, whether
+the client reads or writes provider memory. For devices on separate links
+below the same Root Port, their paths first meet in a Switch, where they
+diverge.
+A Request enters that Switch through the Downstream Port above the
+Requester, the Requester-side port. If ACS P2P Request Redirect is enabled
+there, the Request is routed upstream; otherwise it is routed back
+downstream through the Downstream Port leading to the Completer, the
+Completer-side port. The path of Completions from a read likewise depends on
+ACS P2P Completion Redirect in the Completer-side port. An enabled Egress
+Control is conservatively treated as a Request redirect.
+
+Below the divergence, the route toward the other branch is already upstream,
+so those P2P redirect controls do not affect it. Redirect controls for the
+reverse transaction directions do not affect the mapping. P2P DMA is routed
+through the host bridge when either applicable port redirects. If an ACS
+Control register cannot be read, P2P DMA is rejected because the kernel cannot
+establish a usable route.
+
+Functions sharing one PCIe link can instead route internally within their
+device. An ACS capability without Request or Completion Redirect support
+means that the corresponding internal route does not exist. Device-specific
+ACS quirks can report the same restriction for functions without ACS, and
+SR-IOV functions without ACS cannot support internal peer traffic. These
+restrictions apply only between functions sharing the link, including ARI
+functions and VFs; they do not redirect traffic to a separate device.
+
+When traffic between sibling functions goes upstream, ACS Upstream Forwarding
+must be enabled, or provided by a device-specific quirk, at every shared
+Downstream and Root Port on the way to the host bridge. The topology walk
+rejects a host route without this forwarding support. The host bridge must
+also support P2P. Clearing redirect controls cannot create an internal route
+that the device does not support.
+
This evaluation assumes clients issue strictly ordered Requests carrying an
Untranslated address. Its result is not defined when clients use Relaxed
Ordering or issue ATS-translated Requests because those TLP attributes can
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 07/19] PCI/P2PDMA: Collect the path's ACS controls before deciding
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (5 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 06/19] PCI/P2PDMA: Document directional ACS routing Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 08/19] PCI/P2PDMA: Answer routing per TLP class Leon Romanovsky
` (11 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
calc_map_type_and_dist() combines ACS reads, routing decisions and
diagnostics. Adding decisions for different TLP attributes would duplicate
the walk or thread each attribute through its counters.
Collect the divergence controls, internal-route restrictions and upstream
forwarding state in struct pci_p2pdma_acs_path, then decide each direction
from it. Keep the existing rule that both directions must route directly
for bus addressing, and reject unusable upstream forwarding before
considering the host bridge.
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 194 +++++++++++++++++++++++++++++++++++----------------
1 file changed, 133 insertions(+), 61 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index 8ca0c9ba87c2..c643f275890c 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -702,6 +702,113 @@ static void pci_p2pdma_warn_redir(struct pci_dev *client,
seq_buf_str(acs_list));
}
+/*
+ * What the topology walk found out about one provider/client path. Producing
+ * this costs a walk and config reads, none of which depends on the TLP being
+ * routed.
+ *
+ * @req_ctrl: ACS Control of the Requester-side divergence port. That is the
+ * first port at which a Request can route toward the peer rather
+ * than upstream, so it is where the Request controls apply.
+ * @cpl_ctrl: ACS Control of the Completer-side divergence port, likewise for
+ * the Completions travelling back.
+ * @req_upstream: The Requester has no internal route to its sibling.
+ * @cpl_upstream: The Completer has no internal route to its sibling.
+ * @unreadable: First port whose ACS Control could not be read, if any.
+ * @uf_blocked: First shared port without usable Upstream Forwarding, if any.
+ */
+struct pci_p2pdma_acs_path {
+ u16 req_ctrl;
+ u16 cpl_ctrl;
+ bool req_upstream;
+ bool cpl_upstream;
+ struct pci_dev *unreadable;
+ struct pci_dev *uf_blocked;
+};
+
+static enum pci_acs_p2pdma_state
+pci_p2pdma_request_state(const struct pci_p2pdma_acs_path *path)
+{
+ if (path->req_upstream)
+ return PCI_ACS_P2PDMA_REDIRECT;
+
+ return pci_acs_p2pdma_request(path->req_ctrl);
+}
+
+static enum pci_acs_p2pdma_state
+pci_p2pdma_completion_state(const struct pci_p2pdma_acs_path *path)
+{
+ if (path->cpl_upstream)
+ return PCI_ACS_P2PDMA_REDIRECT;
+
+ return pci_acs_p2pdma_completion(path->cpl_ctrl);
+}
+
+/*
+ * Combine both directions into a mapping type. Only a path that routes the
+ * Request and the Completions it generates directly can be programmed with
+ * the peer's bus addresses.
+ */
+static enum pci_p2pdma_map_type
+pci_p2pdma_route(const struct pci_p2pdma_acs_path *path)
+{
+ if (path->unreadable)
+ return PCI_P2PDMA_MAP_NOT_SUPPORTED;
+
+ if (pci_p2pdma_request_state(path) == PCI_ACS_P2PDMA_DIRECT &&
+ pci_p2pdma_completion_state(path) == PCI_ACS_P2PDMA_DIRECT)
+ return PCI_P2PDMA_MAP_BUS_ADDR;
+
+ if (path->uf_blocked)
+ return PCI_P2PDMA_MAP_NOT_SUPPORTED;
+
+ return PCI_P2PDMA_MAP_THRU_HOST_BRIDGE;
+}
+
+/*
+ * Name the ports that keep this path off a direct route, so that the admin
+ * can hand them to pci=disable_acs_redir=.
+ */
+static void pci_p2pdma_warn_path(struct pci_dev *client,
+ struct pci_dev *provider,
+ const struct pci_p2pdma_acs_path *path,
+ struct pci_dev *a_child,
+ struct pci_dev *b_child)
+{
+ struct seq_buf acs_list, quirk_list;
+ char buf[128], quirk_buf[64];
+
+ if (path->unreadable) {
+ pci_warn(client,
+ "ACS Control is unreadable for provider %s at %s\n",
+ pci_name(provider), pci_name(path->unreadable));
+ return;
+ }
+
+ if (path->uf_blocked) {
+ pci_warn(client,
+ "cannot forward peer-to-peer DMA for provider %s upstream at %s\n",
+ pci_name(provider), pci_name(path->uf_blocked));
+ return;
+ }
+
+ if (path->req_upstream || path->cpl_upstream) {
+ pci_warn(client,
+ "peer-to-peer DMA with provider %s requires the host bridge; no internal route between functions\n",
+ pci_name(provider));
+ return;
+ }
+
+ seq_buf_init(&acs_list, buf, sizeof(buf));
+ seq_buf_init(&quirk_list, quirk_buf, sizeof(quirk_buf));
+ if (pci_acs_p2pdma_completion(path->cpl_ctrl) != PCI_ACS_P2PDMA_DIRECT)
+ pci_p2pdma_list_redir(&acs_list, &quirk_list, a_child);
+ if (pci_acs_p2pdma_request(path->req_ctrl) != PCI_ACS_P2PDMA_DIRECT)
+ pci_p2pdma_list_redir(&acs_list, &quirk_list, b_child);
+
+ pci_p2pdma_warn_redir(client, provider, &acs_list, &quirk_list);
+}
+
static bool cpu_supports_p2pdma(void)
{
#ifdef CONFIG_X86
@@ -908,22 +1015,13 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
int *dist, bool verbose)
{
enum pci_p2pdma_map_type map_type = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE;
- enum pci_acs_p2pdma_state state = PCI_ACS_P2PDMA_NOT_SUPPORTED;
struct pci_dev *a = provider, *b = client, *bb;
struct pci_dev *a_child = NULL, *b_child = NULL;
- struct pci_dev *acs_unreadable = NULL;
- struct pci_dev *uf_blocked = NULL;
+ struct pci_p2pdma_acs_path path = {};
struct pci_p2pdma *p2pdma;
- struct seq_buf acs_list, quirk_list;
- bool req_upstream = false, cpl_upstream = false;
- int acs_redirect_cnt = 0;
+ bool cpu_p2pdma, host_whitelisted = false;
int dist_a = 0;
int dist_b = 0;
- char buf[128], quirk_buf[64];
- u16 ctrl;
-
- seq_buf_init(&acs_list, buf, sizeof(buf));
- seq_buf_init(&quirk_list, quirk_buf, sizeof(quirk_buf));
/*
* Note, we don't need to take references to devices returned by
@@ -966,37 +1064,23 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
* branch is upstream, so redirect controls do not affect the path.
*/
if (a_child && b_child) {
- if (pci_acs_p2pdma_ctrl(a_child, &ctrl))
- state = pci_acs_p2pdma_completion(ctrl);
- if (state != PCI_ACS_P2PDMA_DIRECT) {
- pci_p2pdma_list_redir(&acs_list, &quirk_list, a_child);
- if (state == PCI_ACS_P2PDMA_REDIRECT)
- acs_redirect_cnt++;
- else if (!acs_unreadable)
- acs_unreadable = a_child;
- }
-
- state = PCI_ACS_P2PDMA_NOT_SUPPORTED;
- if (pci_acs_p2pdma_ctrl(b_child, &ctrl))
- state = pci_acs_p2pdma_request(ctrl);
- if (state != PCI_ACS_P2PDMA_DIRECT) {
- pci_p2pdma_list_redir(&acs_list, &quirk_list, b_child);
- if (state == PCI_ACS_P2PDMA_REDIRECT)
- acs_redirect_cnt++;
- else if (!acs_unreadable)
- acs_unreadable = b_child;
- }
+ if (!pci_acs_p2pdma_ctrl(a_child, &path.cpl_ctrl))
+ path.unreadable = a_child;
+ if (!pci_acs_p2pdma_ctrl(b_child, &path.req_ctrl) &&
+ !path.unreadable)
+ path.unreadable = b_child;
/*
* Two children of one Downstream or Root Port share a link.
* Use topology rather than slot numbers or the multifunction
* bit, since ARI functions and VFs need not share those.
*/
if (pci_acs_p2pdma_downstream_port(a)) {
- req_upstream =
+ path.req_upstream =
pci_acs_p2pdma_no_internal_route(b_child, PCI_ACS_RR);
- cpl_upstream =
+ path.cpl_upstream =
pci_acs_p2pdma_no_internal_route(a_child, PCI_ACS_CR);
- uf_blocked = pci_acs_p2pdma_no_upstream_forwarding(a);
+ path.uf_blocked =
+ pci_acs_p2pdma_no_upstream_forwarding(a);
}
}
@@ -1004,38 +1088,24 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
* Below a shared upstream bridge, a path whose divergence ports do not
* redirect routes the request directly.
*/
- if (!acs_unreadable && !acs_redirect_cnt &&
- !req_upstream && !cpl_upstream) {
- map_type = PCI_P2PDMA_MAP_BUS_ADDR;
+ map_type = pci_p2pdma_route(&path);
+ if (map_type == PCI_P2PDMA_MAP_BUS_ADDR)
goto done;
- }
- if (verbose) {
- if (acs_unreadable)
- pci_warn(client, "ACS Control is unreadable for provider %s at %s\n",
- pci_name(provider), pci_name(acs_unreadable));
- else if (uf_blocked)
- pci_warn(client,
- "cannot forward peer-to-peer DMA for provider %s upstream at %s\n",
- pci_name(provider), pci_name(uf_blocked));
- else if (req_upstream || cpl_upstream)
- pci_warn(client,
- "peer-to-peer DMA with provider %s requires the host bridge; no internal route between functions\n",
- pci_name(provider));
- else
- pci_p2pdma_warn_redir(client, provider, &acs_list,
- &quirk_list);
- }
+ if (verbose)
+ pci_p2pdma_warn_path(client, provider, &path, a_child, b_child);
/* An unreadable control or unusable forwarding leaves no route. */
- if (acs_unreadable || uf_blocked) {
- map_type = PCI_P2PDMA_MAP_NOT_SUPPORTED;
+ if (map_type == PCI_P2PDMA_MAP_NOT_SUPPORTED)
goto done;
- }
map_through_host_bridge:
- if (!cpu_supports_p2pdma() &&
- !host_bridge_whitelist(provider, client, verbose)) {
+ cpu_p2pdma = cpu_supports_p2pdma();
+ if (!cpu_p2pdma)
+ host_whitelisted = host_bridge_whitelist(provider, client,
+ verbose);
+
+ if (!cpu_p2pdma && !host_whitelisted) {
if (verbose)
pci_warn(client, "cannot be used for peer-to-peer DMA as the client and provider (%s) do not share an upstream bridge or whitelisted host bridge\n",
pci_name(provider));
@@ -1369,8 +1439,9 @@ enum pci_p2pdma_map_type pci_p2pdma_map_type(struct p2pdma_provider *provider,
{
enum pci_p2pdma_map_type type = PCI_P2PDMA_MAP_NOT_SUPPORTED;
struct pci_dev *pdev = to_pci_dev(provider->owner);
- struct pci_dev *client;
struct pci_p2pdma *p2pdma;
+ unsigned long cache_index;
+ struct pci_dev *client;
int dist;
if (!pdev->p2pdma)
@@ -1380,13 +1451,14 @@ enum pci_p2pdma_map_type pci_p2pdma_map_type(struct p2pdma_provider *provider,
return PCI_P2PDMA_MAP_NOT_SUPPORTED;
client = to_pci_dev(dev);
+ cache_index = map_types_idx(client);
rcu_read_lock();
p2pdma = rcu_dereference(pdev->p2pdma);
if (p2pdma)
type = xa_to_value(xa_load(&p2pdma->map_types,
- map_types_idx(client)));
+ cache_index));
rcu_read_unlock();
if (type == PCI_P2PDMA_MAP_UNKNOWN)
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 08/19] PCI/P2PDMA: Answer routing per TLP class
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (6 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 07/19] PCI/P2PDMA: Collect the path's ACS controls before deciding Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 09/19] PCI/P2PDMA: Route Relaxed Ordering Completions directly Leon Romanovsky
` (10 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
calc_map_type_and_dist() returns one mapping type per provider and client,
valid only for strictly ordered Requests carrying an Untranslated address.
Clients that use Relaxed Ordering or ATS cannot ask what the fabric would
do with their traffic.
Add enum pci_p2pdma_tlp_flags to name a class and pci_p2pdma_map_type_tlp()
to ask about one. The topology walk and the ACS Control reads do not depend
on the class, so decide all of them from the one walk and cache them
together, four bits each. Every class still answers alike; the controls
that tell them apart come next.
Tested-by: Tushar Dave <tdave@nvidia.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 167 ++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 132 insertions(+), 35 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index c643f275890c..9d416329aa4b 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -530,6 +530,31 @@ static u16 pci_acs_p2pdma_quirk_redirect(struct pci_dev *pdev, u16 ctrl)
return ctrl;
}
+/**
+ * enum pci_p2pdma_tlp_flags - Properties of the TLPs a client will issue
+ *
+ * These describe the traffic rather than the topology, and select which ACS
+ * controls apply along the peer-to-peer path. A value of 0 means strictly
+ * ordered Requests carrying an Untranslated address.
+ *
+ * @PCI_P2PDMA_TLP_TRANSLATED: Requests carry an ATS Translated address. PCIe
+ * r7.0 sec 6.12.3 routes those to the peer regardless of ACS P2P Request
+ * Redirect and ACS P2P Egress Control wherever ACS Direct Translated P2P
+ * is enabled.
+ * @PCI_P2PDMA_TLP_RELAXED_CPL: Non-Posted Requests carry the Relaxed
+ * Ordering attribute, and PCIe r7.0 sec 2.2.9.1 requires the Completer to
+ * return it in their Completions. Sec 6.12.1.1 never redirects those, so
+ * ACS P2P Completion Redirect does not gate the path.
+ */
+enum pci_p2pdma_tlp_flags {
+ PCI_P2PDMA_TLP_TRANSLATED = 1 << 0,
+ PCI_P2PDMA_TLP_RELAXED_CPL = 1 << 1,
+};
+
+/* Every combination of the flags above selects one routing class. */
+#define PCI_P2PDMA_TLP_CLASSES \
+ ((PCI_P2PDMA_TLP_TRANSLATED | PCI_P2PDMA_TLP_RELAXED_CPL) + 1)
+
enum pci_acs_p2pdma_state {
PCI_ACS_P2PDMA_NOT_SUPPORTED,
PCI_ACS_P2PDMA_DIRECT,
@@ -546,7 +571,7 @@ enum pci_acs_p2pdma_state {
* bus addressing be assumed.
*/
static enum pci_acs_p2pdma_state
-pci_acs_p2pdma_request(u16 ctrl)
+pci_acs_p2pdma_request(u16 ctrl, unsigned int tlp_flags)
{
return ctrl & (PCI_ACS_RR | PCI_ACS_EC) ?
PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT;
@@ -558,7 +583,7 @@ pci_acs_p2pdma_request(u16 ctrl)
* affects a Completion.
*/
static enum pci_acs_p2pdma_state
-pci_acs_p2pdma_completion(u16 ctrl)
+pci_acs_p2pdma_completion(u16 ctrl, unsigned int tlp_flags)
{
return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT :
PCI_ACS_P2PDMA_DIRECT;
@@ -727,21 +752,23 @@ struct pci_p2pdma_acs_path {
};
static enum pci_acs_p2pdma_state
-pci_p2pdma_request_state(const struct pci_p2pdma_acs_path *path)
+pci_p2pdma_request_state(const struct pci_p2pdma_acs_path *path,
+ unsigned int tlp_flags)
{
if (path->req_upstream)
return PCI_ACS_P2PDMA_REDIRECT;
- return pci_acs_p2pdma_request(path->req_ctrl);
+ return pci_acs_p2pdma_request(path->req_ctrl, tlp_flags);
}
static enum pci_acs_p2pdma_state
-pci_p2pdma_completion_state(const struct pci_p2pdma_acs_path *path)
+pci_p2pdma_completion_state(const struct pci_p2pdma_acs_path *path,
+ unsigned int tlp_flags)
{
if (path->cpl_upstream)
return PCI_ACS_P2PDMA_REDIRECT;
- return pci_acs_p2pdma_completion(path->cpl_ctrl);
+ return pci_acs_p2pdma_completion(path->cpl_ctrl, tlp_flags);
}
/*
@@ -750,13 +777,16 @@ pci_p2pdma_completion_state(const struct pci_p2pdma_acs_path *path)
* the peer's bus addresses.
*/
static enum pci_p2pdma_map_type
-pci_p2pdma_route(const struct pci_p2pdma_acs_path *path)
+pci_p2pdma_route(const struct pci_p2pdma_acs_path *path,
+ unsigned int tlp_flags)
{
if (path->unreadable)
return PCI_P2PDMA_MAP_NOT_SUPPORTED;
- if (pci_p2pdma_request_state(path) == PCI_ACS_P2PDMA_DIRECT &&
- pci_p2pdma_completion_state(path) == PCI_ACS_P2PDMA_DIRECT)
+ if (pci_p2pdma_request_state(path, tlp_flags) ==
+ PCI_ACS_P2PDMA_DIRECT &&
+ pci_p2pdma_completion_state(path, tlp_flags) ==
+ PCI_ACS_P2PDMA_DIRECT)
return PCI_P2PDMA_MAP_BUS_ADDR;
if (path->uf_blocked)
@@ -767,7 +797,9 @@ pci_p2pdma_route(const struct pci_p2pdma_acs_path *path)
/*
* Name the ports that keep this path off a direct route, so that the admin
- * can hand them to pci=disable_acs_redir=.
+ * can hand them to pci=disable_acs_redir=. That parameter clears the
+ * redirect controls, which decide the default class, so that is the class
+ * this reports on.
*/
static void pci_p2pdma_warn_path(struct pci_dev *client,
struct pci_dev *provider,
@@ -801,9 +833,10 @@ static void pci_p2pdma_warn_path(struct pci_dev *client,
seq_buf_init(&acs_list, buf, sizeof(buf));
seq_buf_init(&quirk_list, quirk_buf, sizeof(quirk_buf));
- if (pci_acs_p2pdma_completion(path->cpl_ctrl) != PCI_ACS_P2PDMA_DIRECT)
+ if (pci_acs_p2pdma_completion(path->cpl_ctrl, 0) !=
+ PCI_ACS_P2PDMA_DIRECT)
pci_p2pdma_list_redir(&acs_list, &quirk_list, a_child);
- if (pci_acs_p2pdma_request(path->req_ctrl) != PCI_ACS_P2PDMA_DIRECT)
+ if (pci_acs_p2pdma_request(path->req_ctrl, 0) != PCI_ACS_P2PDMA_DIRECT)
pci_p2pdma_list_redir(&acs_list, &quirk_list, b_child);
pci_p2pdma_warn_redir(client, provider, &acs_list, &quirk_list);
@@ -968,6 +1001,31 @@ static unsigned long map_types_idx(struct pci_dev *client)
return (pci_domain_nr(client->bus) << 16) | pci_dev_id(client);
}
+/*
+ * One cache entry holds the routing of every TLP class, four bits each,
+ * indexed by the &enum pci_p2pdma_tlp_flags combination that selects it. An
+ * absent entry reads back as PCI_P2PDMA_MAP_UNKNOWN in every class.
+ */
+static_assert(PCI_P2PDMA_MAP_THRU_HOST_BRIDGE < 16);
+
+static unsigned long
+pci_p2pdma_map_types_pack(const enum pci_p2pdma_map_type *type)
+{
+ unsigned long val = 0;
+ unsigned int flags;
+
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
+ val |= (unsigned long)type[flags] << (flags * 4);
+
+ return val;
+}
+
+static enum pci_p2pdma_map_type
+pci_p2pdma_map_types_unpack(unsigned long val, unsigned int tlp_flags)
+{
+ return (val >> (tlp_flags * 4)) & 0xf;
+}
+
/*
* Calculate the P2PDMA mapping type and distance between two PCI devices.
*
@@ -999,6 +1057,10 @@ static unsigned long map_types_idx(struct pci_dev *client)
* port, and Completion Redirect for read Completions on the Completer-side
* port.
*
+ * Those controls apply to different TLPs, so every class named by &enum
+ * pci_p2pdma_tlp_flags is decided from the one walk and cached together;
+ * @tlp_flags selects which one is returned.
+ *
* If ACS redirects traffic at either divergence port, return
* PCI_P2PDMA_MAP_THRU_HOST_BRIDGE. If the ACS Control register cannot be
* read, return PCI_P2PDMA_MAP_NOT_SUPPORTED. Otherwise, return
@@ -1012,14 +1074,16 @@ static unsigned long map_types_idx(struct pci_dev *client)
*/
static enum pci_p2pdma_map_type
calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
- int *dist, bool verbose)
+ int *dist, unsigned int tlp_flags, bool verbose)
{
- enum pci_p2pdma_map_type map_type = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE;
+ enum pci_p2pdma_map_type map_type[PCI_P2PDMA_TLP_CLASSES];
struct pci_dev *a = provider, *b = client, *bb;
struct pci_dev *a_child = NULL, *b_child = NULL;
struct pci_p2pdma_acs_path path = {};
struct pci_p2pdma *p2pdma;
bool cpu_p2pdma, host_whitelisted = false;
+ bool host_fallback = false;
+ unsigned int flags;
int dist_a = 0;
int dist_b = 0;
@@ -1053,6 +1117,8 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
* request can only get to the peer through the host bridge.
*/
*dist = dist_a + dist_b;
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
+ map_type[flags] = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE;
goto map_through_host_bridge;
check_paths_acs:
@@ -1085,18 +1151,24 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
}
/*
- * Below a shared upstream bridge, a path whose divergence ports do not
- * redirect routes the request directly.
+ * The walk and the config reads above serve every class; only the
+ * decision below depends on the kind of TLP being routed.
*/
- map_type = pci_p2pdma_route(&path);
- if (map_type == PCI_P2PDMA_MAP_BUS_ADDR)
- goto done;
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++) {
+ map_type[flags] = pci_p2pdma_route(&path, flags);
+ if (map_type[flags] == PCI_P2PDMA_MAP_THRU_HOST_BRIDGE)
+ host_fallback = true;
+ }
- if (verbose)
- pci_p2pdma_warn_path(client, provider, &path, a_child, b_child);
+ if (verbose && map_type[0] != PCI_P2PDMA_MAP_BUS_ADDR)
+ pci_p2pdma_warn_path(client, provider, &path, a_child,
+ b_child);
- /* An unreadable control or unusable forwarding leaves no route. */
- if (map_type == PCI_P2PDMA_MAP_NOT_SUPPORTED)
+ /*
+ * Nothing needs the host bridge: the classes that did not get a direct
+ * route have no fallback that would use it.
+ */
+ if (!host_fallback)
goto done;
map_through_host_bridge:
@@ -1109,16 +1181,19 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
if (verbose)
pci_warn(client, "cannot be used for peer-to-peer DMA as the client and provider (%s) do not share an upstream bridge or whitelisted host bridge\n",
pci_name(provider));
- map_type = PCI_P2PDMA_MAP_NOT_SUPPORTED;
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
+ if (map_type[flags] == PCI_P2PDMA_MAP_THRU_HOST_BRIDGE)
+ map_type[flags] = PCI_P2PDMA_MAP_NOT_SUPPORTED;
}
done:
rcu_read_lock();
p2pdma = rcu_dereference(provider->p2pdma);
if (p2pdma)
xa_store(&p2pdma->map_types, map_types_idx(client),
- xa_mk_value(map_type), GFP_ATOMIC);
+ xa_mk_value(pci_p2pdma_map_types_pack(map_type)),
+ GFP_ATOMIC);
rcu_read_unlock();
- return map_type;
+ return map_type[tlp_flags];
}
/**
@@ -1159,7 +1234,7 @@ int pci_p2pdma_distance_many(struct pci_dev *provider, struct device **clients,
return -1;
}
- map = calc_map_type_and_dist(provider, pci_client, &distance,
+ map = calc_map_type_and_dist(provider, pci_client, &distance, 0,
verbose);
pci_dev_put(pci_client);
@@ -1425,22 +1500,28 @@ void pci_p2pmem_publish(struct pci_dev *pdev, bool publish)
EXPORT_SYMBOL_GPL(pci_p2pmem_publish);
/**
- * pci_p2pdma_map_type - Determine the mapping type for P2PDMA transfers
+ * pci_p2pdma_map_type_tlp - Determine the mapping type for P2PDMA transfers
* @provider: P2PDMA provider structure
* @dev: Client device that initiates the transfer
+ * @tlp_flags: &enum pci_p2pdma_tlp_flags describing the TLPs @dev will issue
*
* Determines how peer-to-peer DMA transfers should be mapped between
* the provider and the client device. The mapping type indicates whether
* the transfer can be done directly through PCI switches or must go
* through the host bridge.
+ *
+ * ACS routes a peer-to-peer transaction by the attributes its TLPs carry, so
+ * the answer depends on @tlp_flags. A caller that passes flags its traffic
+ * does not match gets a mapping the fabric will not deliver.
*/
-enum pci_p2pdma_map_type pci_p2pdma_map_type(struct p2pdma_provider *provider,
- struct device *dev)
+static enum pci_p2pdma_map_type
+pci_p2pdma_map_type_tlp(struct p2pdma_provider *provider, struct device *dev,
+ unsigned int tlp_flags)
{
- enum pci_p2pdma_map_type type = PCI_P2PDMA_MAP_NOT_SUPPORTED;
struct pci_dev *pdev = to_pci_dev(provider->owner);
+ unsigned long cache_index, cached = 0;
+ enum pci_p2pdma_map_type type;
struct pci_p2pdma *p2pdma;
- unsigned long cache_index;
struct pci_dev *client;
int dist;
@@ -1457,16 +1538,32 @@ enum pci_p2pdma_map_type pci_p2pdma_map_type(struct p2pdma_provider *provider,
p2pdma = rcu_dereference(pdev->p2pdma);
if (p2pdma)
- type = xa_to_value(xa_load(&p2pdma->map_types,
- cache_index));
+ cached = xa_to_value(xa_load(&p2pdma->map_types,
+ cache_index));
rcu_read_unlock();
+ type = pci_p2pdma_map_types_unpack(cached, tlp_flags);
if (type == PCI_P2PDMA_MAP_UNKNOWN)
- return calc_map_type_and_dist(pdev, client, &dist, true);
+ return calc_map_type_and_dist(pdev, client, &dist, tlp_flags,
+ true);
return type;
}
+/**
+ * pci_p2pdma_map_type - Determine the mapping type for P2PDMA transfers
+ * @provider: P2PDMA provider structure
+ * @dev: Client device that initiates the transfer
+ *
+ * Same as pci_p2pdma_map_type_tlp() for a client issuing strictly ordered
+ * Requests that carry an Untranslated address.
+ */
+enum pci_p2pdma_map_type pci_p2pdma_map_type(struct p2pdma_provider *provider,
+ struct device *dev)
+{
+ return pci_p2pdma_map_type_tlp(provider, dev, 0);
+}
+
void __pci_p2pdma_update_state(struct pci_p2pdma_map_state *state,
struct device *dev, struct page *page)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 09/19] PCI/P2PDMA: Route Relaxed Ordering Completions directly
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (7 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 08/19] PCI/P2PDMA: Answer routing per TLP class Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:37 ` sashiko-bot
2026-10-13 14:25 ` [PATCH v11 10/19] PCI/P2PDMA: Reject Translated Requests blocked by Translation Blocking Leon Romanovsky
` (9 subsequent siblings)
18 siblings, 1 reply; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
ACS P2P Completion Redirect leaves Completions carrying Relaxed Ordering
alone, per PCIe r7.0 sec 6.12.1.1. A Completion copies that attribute from
its Request, so a client setting it on Non-Posted Requests need not use the
host bridge solely because Completion Redirect is enabled.
Route Completions directly for PCI_P2PDMA_TLP_RELAXED_CPL unless a device
quirk redirects them or the Completer lacks an internal route to its
sibling. Neither restriction promises a Relaxed Ordering exception. No
caller selects these classes yet.
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index 9d416329aa4b..785e4668a2af 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -580,11 +580,15 @@ pci_acs_p2pdma_request(u16 ctrl, unsigned int tlp_flags)
/*
* Decide how a peer-to-peer Completion at an ACS-capable ingress port routes.
* PCIe r7.0 sec 6.12.1.1: no ACS control other than P2P Completion Redirect
- * affects a Completion.
+ * affects a Completion, and that one leaves Completions carrying the Relaxed
+ * Ordering attribute alone.
*/
static enum pci_acs_p2pdma_state
pci_acs_p2pdma_completion(u16 ctrl, unsigned int tlp_flags)
{
+ if (tlp_flags & PCI_P2PDMA_TLP_RELAXED_CPL)
+ return PCI_ACS_P2PDMA_DIRECT;
+
return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT :
PCI_ACS_P2PDMA_DIRECT;
}
@@ -737,6 +741,7 @@ static void pci_p2pdma_warn_redir(struct pci_dev *client,
* than upstream, so it is where the Request controls apply.
* @cpl_ctrl: ACS Control of the Completer-side divergence port, likewise for
* the Completions travelling back.
+ * @cpl_quirk: A device quirk reports Completion Redirect at that port.
* @req_upstream: The Requester has no internal route to its sibling.
* @cpl_upstream: The Completer has no internal route to its sibling.
* @unreadable: First port whose ACS Control could not be read, if any.
@@ -745,6 +750,7 @@ static void pci_p2pdma_warn_redir(struct pci_dev *client,
struct pci_p2pdma_acs_path {
u16 req_ctrl;
u16 cpl_ctrl;
+ bool cpl_quirk;
bool req_upstream;
bool cpl_upstream;
struct pci_dev *unreadable;
@@ -761,11 +767,16 @@ pci_p2pdma_request_state(const struct pci_p2pdma_acs_path *path,
return pci_acs_p2pdma_request(path->req_ctrl, tlp_flags);
}
+/*
+ * ACS P2P Completion Redirect leaves Relaxed Ordering Completions alone. A
+ * device quirk that reports Completion Redirect does not promise that, so
+ * treat every Completion through that port as redirected.
+ */
static enum pci_acs_p2pdma_state
pci_p2pdma_completion_state(const struct pci_p2pdma_acs_path *path,
unsigned int tlp_flags)
{
- if (path->cpl_upstream)
+ if (path->cpl_quirk || path->cpl_upstream)
return PCI_ACS_P2PDMA_REDIRECT;
return pci_acs_p2pdma_completion(path->cpl_ctrl, tlp_flags);
@@ -1132,6 +1143,7 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
if (a_child && b_child) {
if (!pci_acs_p2pdma_ctrl(a_child, &path.cpl_ctrl))
path.unreadable = a_child;
+ path.cpl_quirk = pci_acs_p2pdma_quirk(a_child, PCI_ACS_CR);
if (!pci_acs_p2pdma_ctrl(b_child, &path.req_ctrl) &&
!path.unreadable)
path.unreadable = b_child;
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 10/19] PCI/P2PDMA: Reject Translated Requests blocked by Translation Blocking
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (8 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 09/19] PCI/P2PDMA: Route Relaxed Ordering Completions directly Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:49 ` sashiko-bot
2026-10-13 14:25 ` [PATCH v11 11/19] PCI/P2PDMA: Route Translated Requests under Direct Translated P2P Leon Romanovsky
` (8 subsequent siblings)
18 siblings, 1 reply; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
ACS Translation Blocking rejects upstream Memory Requests with a Translated
address before any peer-routing controls apply, per PCIe r7.0 sec 6.12.1.1.
P2PDMA ignores that bit and can report a route whose ports reject the
Requester's address type.
Add PCI_ACS_P2PDMA_BLOCKED and scan the ports traversed by each route. A
direct route passes only ports below the divergence; a host-bridge route
also passes the common port and its ancestors. Reject the affected class
without falling back to another route, and preserve blocking precedence
over internal-route restrictions.
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 102 insertions(+), 7 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index 785e4668a2af..fc4dea28c860 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -559,11 +559,12 @@ enum pci_acs_p2pdma_state {
PCI_ACS_P2PDMA_NOT_SUPPORTED,
PCI_ACS_P2PDMA_DIRECT,
PCI_ACS_P2PDMA_REDIRECT,
+ PCI_ACS_P2PDMA_BLOCKED,
};
/*
* Decide how a peer-to-peer Request at an ACS-capable ingress port routes,
- * from that port's ACS Control register.
+ * from that port's ACS Control register and the Request's Address Type.
*
* Linux does not read the Egress Control Vector, so Egress Control is treated
* conservatively as a redirect. Per PCIe r7.0 Table 6-11 the outcomes it
@@ -573,6 +574,18 @@ enum pci_acs_p2pdma_state {
static enum pci_acs_p2pdma_state
pci_acs_p2pdma_request(u16 ctrl, unsigned int tlp_flags)
{
+ if (tlp_flags & PCI_P2PDMA_TLP_TRANSLATED) {
+ /*
+ * PCIe r7.0 sec 6.12.1.1: Translation Blocking makes every
+ * Upstream Memory Request whose Address Type is not
+ * Untranslated an ACS Violation, taking precedence over the
+ * P2P controls. Sec 7.7.12.5: Direct Translated P2P "is
+ * ignored if ACS Translation Blocking Enable is 1b".
+ */
+ if (ctrl & PCI_ACS_TB)
+ return PCI_ACS_P2PDMA_BLOCKED;
+ }
+
return ctrl & (PCI_ACS_RR | PCI_ACS_EC) ?
PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT;
}
@@ -678,6 +691,39 @@ pci_acs_p2pdma_no_upstream_forwarding(struct pci_dev *common)
return NULL;
}
+/*
+ * Report whether any port between @client and @common rejects Translated
+ * addresses. If @common is NULL, every port up to the host bridge is walked.
+ * A port whose ACS Control cannot be read counts as blocking, which withdraws
+ * only the Translated classes because an Untranslated Request is routed by the
+ * redirect controls instead.
+ */
+static bool pci_p2pdma_path_blocks_translation(struct pci_dev *client,
+ struct pci_dev *common)
+{
+ struct pci_dev *pdev;
+ u16 ctrl;
+
+ /*
+ * @common is @client itself when the provider is the client or sits
+ * below it. The Request never travels upstream then, so no port sees
+ * it and none can reject its Address Type.
+ */
+ if (client == common)
+ return false;
+
+ for (pdev = pci_upstream_bridge(client); pdev && pdev != common;
+ pdev = pci_upstream_bridge(pdev)) {
+ if (!pci_acs_p2pdma_ctrl(pdev, &ctrl))
+ return true;
+
+ if (ctrl & PCI_ACS_TB)
+ return true;
+ }
+
+ return false;
+}
+
static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev)
{
if (!buf)
@@ -744,6 +790,13 @@ static void pci_p2pdma_warn_redir(struct pci_dev *client,
* @cpl_quirk: A device quirk reports Completion Redirect at that port.
* @req_upstream: The Requester has no internal route to its sibling.
* @cpl_upstream: The Completer has no internal route to its sibling.
+ * @tb_on_path: A port below the divergence blocks Translated addresses.
+ * Every route out of @client passes those, so none carries them.
+ * @tb_above_divergence: A port at or above the divergence blocks Translated
+ * addresses. Only a Request continuing to the host bridge passes
+ * those, so a direct route is still open to them.
+ * @no_common_bridge: The two paths share no upstream bridge, so no direct
+ * route exists for ACS to gate.
* @unreadable: First port whose ACS Control could not be read, if any.
* @uf_blocked: First shared port without usable Upstream Forwarding, if any.
*/
@@ -753,14 +806,28 @@ struct pci_p2pdma_acs_path {
bool cpl_quirk;
bool req_upstream;
bool cpl_upstream;
+ bool tb_on_path;
+ bool tb_above_divergence;
+ bool no_common_bridge;
struct pci_dev *unreadable;
struct pci_dev *uf_blocked;
};
+/*
+ * ACS Translation Blocking is not a routing control, so unlike the redirect
+ * controls it is not decided at the divergence alone. PCIe r7.0 sec 6.12.1.1
+ * has every Downstream Port check the Address Type of each Upstream Memory
+ * Request it receives, ahead of "any applicable ACS P2P control mechanisms".
+ * A port below the divergence cannot redirect the Request anywhere it was not
+ * already going, but it can still reject a Translated address.
+ */
static enum pci_acs_p2pdma_state
pci_p2pdma_request_state(const struct pci_p2pdma_acs_path *path,
unsigned int tlp_flags)
{
+ if (tlp_flags & PCI_P2PDMA_TLP_TRANSLATED && path->tb_on_path)
+ return PCI_ACS_P2PDMA_BLOCKED;
+
if (path->req_upstream)
return PCI_ACS_P2PDMA_REDIRECT;
@@ -791,11 +858,23 @@ static enum pci_p2pdma_map_type
pci_p2pdma_route(const struct pci_p2pdma_acs_path *path,
unsigned int tlp_flags)
{
+ enum pci_acs_p2pdma_state req;
+
if (path->unreadable)
return PCI_P2PDMA_MAP_NOT_SUPPORTED;
- if (pci_p2pdma_request_state(path, tlp_flags) ==
- PCI_ACS_P2PDMA_DIRECT &&
+ req = pci_p2pdma_request_state(path, tlp_flags);
+
+ /*
+ * Translation Blocking rejects the Address Type rather than the
+ * target, so a blocked Request stays blocked however it is addressed.
+ * No host bridge fallback keeps a Translated address working; the
+ * caller has to issue a different kind of Request instead.
+ */
+ if (req == PCI_ACS_P2PDMA_BLOCKED)
+ return PCI_P2PDMA_MAP_NOT_SUPPORTED;
+
+ if (!path->no_common_bridge && req == PCI_ACS_P2PDMA_DIRECT &&
pci_p2pdma_completion_state(path, tlp_flags) ==
PCI_ACS_P2PDMA_DIRECT)
return PCI_P2PDMA_MAP_BUS_ADDR;
@@ -803,6 +882,14 @@ pci_p2pdma_route(const struct pci_p2pdma_acs_path *path,
if (path->uf_blocked)
return PCI_P2PDMA_MAP_NOT_SUPPORTED;
+ /*
+ * A Request that turns around at the divergence never reaches the
+ * ports above it, but one that keeps climbing to the host bridge
+ * does, so that route has to clear their Translation Blocking too.
+ */
+ if (tlp_flags & PCI_P2PDMA_TLP_TRANSLATED && path->tb_above_divergence)
+ return PCI_P2PDMA_MAP_NOT_SUPPORTED;
+
return PCI_P2PDMA_MAP_THRU_HOST_BRIDGE;
}
@@ -1066,7 +1153,8 @@ pci_p2pdma_map_types_unpack(unsigned long val, unsigned int tlp_flags)
* The client is the Requester and the provider the Completer. At the path
* divergence, check Request Redirect and Egress Control on the Requester-side
* port, and Completion Redirect for read Completions on the Completer-side
- * port.
+ * port. Translation Blocking is checked on every port the Request passes
+ * instead, because it rejects a Request rather than routing it.
*
* Those controls apply to different TLPs, so every class named by &enum
* pci_p2pdma_tlp_flags is decided from the one walk and cached together;
@@ -1074,8 +1162,8 @@ pci_p2pdma_map_types_unpack(unsigned long val, unsigned int tlp_flags)
*
* If ACS redirects traffic at either divergence port, return
* PCI_P2PDMA_MAP_THRU_HOST_BRIDGE. If the ACS Control register cannot be
- * read, return PCI_P2PDMA_MAP_NOT_SUPPORTED. Otherwise, return
- * PCI_P2PDMA_MAP_BUS_ADDR.
+ * read, or Translation Blocking rejects the class being asked about, 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
@@ -1128,8 +1216,10 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
* request can only get to the peer through the host bridge.
*/
*dist = dist_a + dist_b;
+ path.no_common_bridge = true;
+ path.tb_on_path = pci_p2pdma_path_blocks_translation(client, NULL);
for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
- map_type[flags] = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE;
+ map_type[flags] = pci_p2pdma_route(&path, flags);
goto map_through_host_bridge;
check_paths_acs:
@@ -1162,6 +1252,11 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
}
}
+ path.tb_on_path = pci_p2pdma_path_blocks_translation(client, a);
+ if (b_child)
+ path.tb_above_divergence =
+ pci_p2pdma_path_blocks_translation(b_child, NULL);
+
/*
* The walk and the config reads above serve every class; only the
* decision below depends on the kind of TLP being routed.
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 11/19] PCI/P2PDMA: Route Translated Requests under Direct Translated P2P
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (9 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 10/19] PCI/P2PDMA: Reject Translated Requests blocked by Translation Blocking Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 12/19] PCI/P2PDMA: Log detailed ACS routing diagnostics Leon Romanovsky
` (7 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
A Downstream Port with ACS Direct Translated P2P enabled routes a Request
whose Address Type is Translated "to the peer Egress Port without
redirection, regardless of ACS P2P Request Redirect and ACS P2P Egress
Control", per PCIe r7.0 sec 6.12.3. P2PDMA assumes every Request carries an
Untranslated address, so it sends an ATS client through the host bridge
even where the fabric would route it straight to the peer.
Consult Direct Translated P2P for the PCI_P2PDMA_TLP_TRANSLATED classes.
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
Tested-by: Tushar Dave <tdave@nvidia.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index fc4dea28c860..600c3ef7a36a 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -584,6 +584,15 @@ pci_acs_p2pdma_request(u16 ctrl, unsigned int tlp_flags)
*/
if (ctrl & PCI_ACS_TB)
return PCI_ACS_P2PDMA_BLOCKED;
+
+ /*
+ * PCIe r7.0 sec 6.12.3: ACS Direct Translated P2P routes a
+ * Request carrying a Translated address to the peer "without
+ * redirection, regardless of ACS P2P Request Redirect and ACS
+ * P2P Egress Control settings".
+ */
+ if (ctrl & PCI_ACS_DT)
+ return PCI_ACS_P2PDMA_DIRECT;
}
return ctrl & (PCI_ACS_RR | PCI_ACS_EC) ?
@@ -1151,10 +1160,11 @@ pci_p2pdma_map_types_unpack(unsigned long val, unsigned int tlp_flags)
* redirection setting of the ports along the path.
*
* The client is the Requester and the provider the Completer. At the path
- * divergence, check Request Redirect and Egress Control on the Requester-side
- * port, and Completion Redirect for read Completions on the Completer-side
- * port. Translation Blocking is checked on every port the Request passes
- * instead, because it rejects a Request rather than routing it.
+ * divergence, check Request Redirect, Egress Control and Direct Translated
+ * P2P on the Requester-side port, and Completion Redirect for read
+ * Completions on the Completer-side port. Translation Blocking is checked on
+ * every port the Request passes instead, because it rejects a Request rather
+ * than routing it.
*
* Those controls apply to different TLPs, so every class named by &enum
* pci_p2pdma_tlp_flags is decided from the one walk and cached together;
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 12/19] PCI/P2PDMA: Log detailed ACS routing diagnostics
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (10 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 11/19] PCI/P2PDMA: Route Translated Requests under Direct Translated P2P Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 13/19] PCI/P2PDMA: Add KUnit tests for the ACS routing decisions Leon Romanovsky
` (6 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
When P2PDMA rejects a mapping, existing warnings identify only the final
ACS or host-bridge result. They omit topology, live controls, divergence
ports, cache state, and intermediate routing decisions.
Emit debug-level messages for verbose calculations and cache lookups.
Report both paths, decoded ACS controls, directional decisions, host
fallback, and the final mapping. The messages stay silent until enabled,
and the existing warnings are unchanged.
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
Tested-by: Tushar Dave <tdave@nvidia.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 218 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 196 insertions(+), 22 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index 600c3ef7a36a..d9f6b05cb3c0 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -615,6 +615,40 @@ pci_acs_p2pdma_completion(u16 ctrl, unsigned int tlp_flags)
PCI_ACS_P2PDMA_DIRECT;
}
+static const char *pci_acs_p2pdma_state_name(enum pci_acs_p2pdma_state state)
+{
+ switch (state) {
+ case PCI_ACS_P2PDMA_DIRECT:
+ return "direct";
+ case PCI_ACS_P2PDMA_REDIRECT:
+ return "redirect";
+ case PCI_ACS_P2PDMA_BLOCKED:
+ return "blocked";
+ case PCI_ACS_P2PDMA_NOT_SUPPORTED:
+ return "not-supported";
+ }
+
+ return "invalid";
+}
+
+static const char *pci_p2pdma_map_type_name(enum pci_p2pdma_map_type type)
+{
+ switch (type) {
+ case PCI_P2PDMA_MAP_UNKNOWN:
+ return "unknown";
+ case PCI_P2PDMA_MAP_NONE:
+ return "none";
+ case PCI_P2PDMA_MAP_NOT_SUPPORTED:
+ return "not-supported";
+ case PCI_P2PDMA_MAP_BUS_ADDR:
+ return "bus-address";
+ case PCI_P2PDMA_MAP_THRU_HOST_BRIDGE:
+ return "through-host-bridge";
+ }
+
+ return "invalid";
+}
+
/*
* Read @pdev's ACS Control register, with Request and Completion Redirect
* taken from its ACS quirk if it has one. A device with neither has no
@@ -623,21 +657,42 @@ pci_acs_p2pdma_completion(u16 ctrl, unsigned int tlp_flags)
* reads as all ones, which is what a device that does not answer returns;
* @ctrl is then meaningless.
*/
-static bool pci_acs_p2pdma_ctrl(struct pci_dev *pdev, u16 *ctrl)
+static bool pci_acs_p2pdma_ctrl(struct pci_dev *pdev, const char *what,
+ u16 *ctrl, bool verbose)
{
- int pos;
+ int pos, ret;
pos = pdev->acs_cap;
if (!pos) {
+ if (verbose)
+ pci_dbg(pdev,
+ "P2PDMA ACS: %s has no ACS capability\n", what);
*ctrl = pci_acs_p2pdma_quirk_redirect(pdev, 0);
return true;
}
- if (pci_read_config_word(pdev, pos + PCI_ACS_CTRL, ctrl) ||
- PCI_POSSIBLE_ERROR(*ctrl))
+ ret = pci_read_config_word(pdev, pos + PCI_ACS_CTRL, ctrl);
+ if (ret || PCI_POSSIBLE_ERROR(*ctrl)) {
+ if (verbose)
+ pci_dbg(pdev,
+ "P2PDMA ACS: %s ACS Control read failed at %#x: %#x value=%#06x\n",
+ what, pos + PCI_ACS_CTRL, ret, *ctrl);
return false;
+ }
*ctrl = pci_acs_p2pdma_quirk_redirect(pdev, *ctrl);
+ if (verbose) {
+ pci_dbg(pdev,
+ "P2PDMA ACS: %s cap=%#x caps=%#06x ctrl=%#06x\n",
+ what, pos, pdev->acs_capabilities, *ctrl);
+ pci_dbg(pdev,
+ "P2PDMA ACS: control bits SV=%u TB=%u RR=%u CR=%u UF=%u EC=%u DT=%u\n",
+ !!(*ctrl & PCI_ACS_SV), !!(*ctrl & PCI_ACS_TB),
+ !!(*ctrl & PCI_ACS_RR), !!(*ctrl & PCI_ACS_CR),
+ !!(*ctrl & PCI_ACS_UF), !!(*ctrl & PCI_ACS_EC),
+ !!(*ctrl & PCI_ACS_DT));
+ }
+
return true;
}
@@ -674,7 +729,7 @@ static bool pci_acs_p2pdma_no_internal_route(struct pci_dev *pdev, u16 flag)
* query. Return the first port that cannot establish this route.
*/
static struct pci_dev *
-pci_acs_p2pdma_no_upstream_forwarding(struct pci_dev *common)
+pci_acs_p2pdma_no_upstream_forwarding(struct pci_dev *common, bool verbose)
{
struct pci_dev *pdev;
u16 ctrl;
@@ -683,7 +738,8 @@ pci_acs_p2pdma_no_upstream_forwarding(struct pci_dev *common)
if (!pci_acs_p2pdma_downstream_port(pdev))
continue;
- if (!pci_acs_p2pdma_ctrl(pdev, &ctrl))
+ if (!pci_acs_p2pdma_ctrl(pdev, "upstream forwarding", &ctrl,
+ verbose))
return pdev;
/*
@@ -700,6 +756,30 @@ pci_acs_p2pdma_no_upstream_forwarding(struct pci_dev *common)
return NULL;
}
+static void pci_p2pdma_log_path(const char *name, struct pci_dev *start,
+ struct pci_dev *common)
+{
+ struct pci_dev *pdev, *upstream;
+ int hop = 0, type;
+
+ for (pdev = start; pdev; pdev = upstream, hop++) {
+ upstream = pci_upstream_bridge(pdev);
+ type = pci_is_pcie(pdev) ? pci_pcie_type(pdev) : -1;
+ pci_dbg(pdev,
+ "P2PDMA ACS: %s path hop=%d common=%u pcie=%u type=%d class=%#08x vendor=%04x device=%04x upstream=%s\n",
+ name, hop, pdev == common, pci_is_pcie(pdev), type,
+ pdev->class, pdev->vendor, pdev->device,
+ upstream ? pci_name(upstream) : "<none>");
+
+ /* Cached state only: the routing logs the ACS Control it reads. */
+ if (pdev->acs_cap)
+ pci_dbg(pdev, "P2PDMA ACS: ACS cap=%#x caps=%#06x\n",
+ pdev->acs_cap, pdev->acs_capabilities);
+ else
+ pci_dbg(pdev, "P2PDMA ACS: ACS capability absent\n");
+ }
+}
+
/*
* Report whether any port between @client and @common rejects Translated
* addresses. If @common is NULL, every port up to the host bridge is walked.
@@ -708,7 +788,8 @@ pci_acs_p2pdma_no_upstream_forwarding(struct pci_dev *common)
* redirect controls instead.
*/
static bool pci_p2pdma_path_blocks_translation(struct pci_dev *client,
- struct pci_dev *common)
+ struct pci_dev *common,
+ bool verbose)
{
struct pci_dev *pdev;
u16 ctrl;
@@ -723,11 +804,15 @@ static bool pci_p2pdma_path_blocks_translation(struct pci_dev *client,
for (pdev = pci_upstream_bridge(client); pdev && pdev != common;
pdev = pci_upstream_bridge(pdev)) {
- if (!pci_acs_p2pdma_ctrl(pdev, &ctrl))
+ if (!pci_acs_p2pdma_ctrl(pdev, "path hop", &ctrl, verbose))
return true;
- if (ctrl & PCI_ACS_TB)
+ if (ctrl & PCI_ACS_TB) {
+ if (verbose)
+ pci_dbg(pdev,
+ "P2PDMA ACS: Translation Blocking rejects Translated Requests on this path\n");
return true;
+ }
}
return false;
@@ -1188,13 +1273,22 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
enum pci_p2pdma_map_type map_type[PCI_P2PDMA_TLP_CLASSES];
struct pci_dev *a = provider, *b = client, *bb;
struct pci_dev *a_child = NULL, *b_child = NULL;
+ struct pci_host_bridge *provider_host, *client_host;
struct pci_p2pdma_acs_path path = {};
struct pci_p2pdma *p2pdma;
bool cpu_p2pdma, host_whitelisted = false;
+ bool cache_store = false;
bool host_fallback = false;
unsigned int flags;
int dist_a = 0;
int dist_b = 0;
+ void *old;
+
+ if (verbose)
+ pci_dbg(client,
+ "P2PDMA ACS: begin provider=%s client=%s cache-index=%#lx\n",
+ pci_name(provider), pci_name(client),
+ map_types_idx(client));
/*
* Note, we don't need to take references to devices returned by
@@ -1226,14 +1320,32 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
* request can only get to the peer through the host bridge.
*/
*dist = dist_a + dist_b;
+ if (verbose) {
+ pci_dbg(client,
+ "P2PDMA ACS: no common upstream bridge provider-distance=%d client-distance=%d total=%d\n",
+ dist_a, dist_b, *dist);
+ pci_p2pdma_log_path("provider", provider, NULL);
+ pci_p2pdma_log_path("client", client, NULL);
+ }
path.no_common_bridge = true;
- path.tb_on_path = pci_p2pdma_path_blocks_translation(client, NULL);
+ path.tb_on_path = pci_p2pdma_path_blocks_translation(client, NULL,
+ verbose);
for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
map_type[flags] = pci_p2pdma_route(&path, flags);
goto map_through_host_bridge;
check_paths_acs:
*dist = dist_a + dist_b;
+ if (verbose) {
+ pci_dbg(client,
+ "P2PDMA ACS: common=%s provider-divergence=%s client-divergence=%s provider-distance=%d client-distance=%d total=%d\n",
+ pci_name(a),
+ a_child ? pci_name(a_child) : "<none>",
+ b_child ? pci_name(b_child) : "<none>",
+ dist_a, dist_b, *dist);
+ pci_p2pdma_log_path("provider", provider, a);
+ pci_p2pdma_log_path("client", client, a);
+ }
/*
* ACS P2P routing controls apply where a TLP can route toward the peer
@@ -1241,11 +1353,12 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
* branch is upstream, so redirect controls do not affect the path.
*/
if (a_child && b_child) {
- if (!pci_acs_p2pdma_ctrl(a_child, &path.cpl_ctrl))
+ if (!pci_acs_p2pdma_ctrl(a_child, "completion", &path.cpl_ctrl,
+ verbose))
path.unreadable = a_child;
path.cpl_quirk = pci_acs_p2pdma_quirk(a_child, PCI_ACS_CR);
- if (!pci_acs_p2pdma_ctrl(b_child, &path.req_ctrl) &&
- !path.unreadable)
+ if (!pci_acs_p2pdma_ctrl(b_child, "request", &path.req_ctrl,
+ verbose) && !path.unreadable)
path.unreadable = b_child;
/*
* Two children of one Downstream or Root Port share a link.
@@ -1258,14 +1371,28 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
path.cpl_upstream =
pci_acs_p2pdma_no_internal_route(a_child, PCI_ACS_CR);
path.uf_blocked =
- pci_acs_p2pdma_no_upstream_forwarding(a);
+ pci_acs_p2pdma_no_upstream_forwarding(a, verbose);
}
+ if (verbose && !path.unreadable)
+ pci_dbg(client,
+ "P2PDMA ACS: class=strict request=%s at %s completion=%s at %s\n",
+ pci_acs_p2pdma_state_name(
+ pci_p2pdma_request_state(&path, 0)),
+ pci_name(b_child),
+ pci_acs_p2pdma_state_name(
+ pci_p2pdma_completion_state(&path, 0)),
+ pci_name(a_child));
+ } else if (verbose) {
+ pci_dbg(client,
+ "P2PDMA ACS: peer divergence is incomplete; no ACS peer-routing controls evaluated\n");
}
- path.tb_on_path = pci_p2pdma_path_blocks_translation(client, a);
+ path.tb_on_path = pci_p2pdma_path_blocks_translation(client, a,
+ verbose);
if (b_child)
path.tb_above_divergence =
- pci_p2pdma_path_blocks_translation(b_child, NULL);
+ pci_p2pdma_path_blocks_translation(b_child, NULL,
+ verbose);
/*
* The walk and the config reads above serve every class; only the
@@ -1294,6 +1421,19 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
host_whitelisted = host_bridge_whitelist(provider, client,
verbose);
+ if (verbose) {
+ provider_host = pci_find_host_bridge(provider->bus);
+ client_host = pci_find_host_bridge(client->bus);
+ pci_dbg(client,
+ "P2PDMA ACS: host fallback cpu-support=%u whitelist=%s provider-host=%s client-host=%s same-host=%u\n",
+ cpu_p2pdma,
+ cpu_p2pdma ? "not-consulted" :
+ (host_whitelisted ? "yes" : "no"),
+ provider_host ? dev_name(&provider_host->dev) : "<none>",
+ client_host ? dev_name(&client_host->dev) : "<none>",
+ provider_host && provider_host == client_host);
+ }
+
if (!cpu_p2pdma && !host_whitelisted) {
if (verbose)
pci_warn(client, "cannot be used for peer-to-peer DMA as the client and provider (%s) do not share an upstream bridge or whitelisted host bridge\n",
@@ -1305,11 +1445,32 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
done:
rcu_read_lock();
p2pdma = rcu_dereference(provider->p2pdma);
- if (p2pdma)
- xa_store(&p2pdma->map_types, map_types_idx(client),
- xa_mk_value(pci_p2pdma_map_types_pack(map_type)),
- GFP_ATOMIC);
+ if (p2pdma) {
+ old = xa_store(&p2pdma->map_types, map_types_idx(client),
+ xa_mk_value(pci_p2pdma_map_types_pack(map_type)),
+ GFP_ATOMIC);
+ cache_store = !xa_is_err(old);
+ }
rcu_read_unlock();
+ if (verbose) {
+ pci_dbg(client,
+ "P2PDMA ACS: final provider=%s result=%s(%d) tlp-flags=%#x distance=%d unreadable=%s cache-store=%u index=%#lx\n",
+ pci_name(provider),
+ pci_p2pdma_map_type_name(map_type[tlp_flags]),
+ map_type[tlp_flags], tlp_flags, *dist,
+ path.unreadable ? pci_name(path.unreadable) : "<none>",
+ cache_store, map_types_idx(client));
+ pci_dbg(client,
+ "P2PDMA ACS: classes strict=%s relaxed=%s translated=%s translated+relaxed=%s\n",
+ pci_p2pdma_map_type_name(map_type[0]),
+ pci_p2pdma_map_type_name(
+ map_type[PCI_P2PDMA_TLP_RELAXED_CPL]),
+ pci_p2pdma_map_type_name(
+ map_type[PCI_P2PDMA_TLP_TRANSLATED]),
+ pci_p2pdma_map_type_name(
+ map_type[PCI_P2PDMA_TLP_TRANSLATED |
+ PCI_P2PDMA_TLP_RELAXED_CPL]));
+ }
return map_type[tlp_flags];
}
@@ -1640,13 +1801,21 @@ pci_p2pdma_map_type_tlp(struct p2pdma_provider *provider, struct device *dev,
enum pci_p2pdma_map_type type;
struct pci_p2pdma *p2pdma;
struct pci_dev *client;
+ bool provider_state;
int dist;
- if (!pdev->p2pdma)
+ if (!pdev->p2pdma) {
+ pci_dbg(pdev,
+ "P2PDMA ACS: map lookup rejected; provider state is absent\n");
return PCI_P2PDMA_MAP_NOT_SUPPORTED;
+ }
- if (!dev_is_pci(dev))
+ if (!dev_is_pci(dev)) {
+ dev_dbg(dev,
+ "P2PDMA ACS: provider=%s map lookup rejected; client is not PCI\n",
+ pci_name(pdev));
return PCI_P2PDMA_MAP_NOT_SUPPORTED;
+ }
client = to_pci_dev(dev);
cache_index = map_types_idx(client);
@@ -1657,8 +1826,13 @@ pci_p2pdma_map_type_tlp(struct p2pdma_provider *provider, struct device *dev,
if (p2pdma)
cached = xa_to_value(xa_load(&p2pdma->map_types,
cache_index));
+ provider_state = !!p2pdma;
rcu_read_unlock();
type = pci_p2pdma_map_types_unpack(cached, tlp_flags);
+ pci_dbg(client,
+ "P2PDMA ACS: map lookup provider=%s index=%#lx tlp-flags=%#x cached=%s(%d) provider-state=%u\n",
+ pci_name(pdev), cache_index, tlp_flags,
+ pci_p2pdma_map_type_name(type), type, provider_state);
if (type == PCI_P2PDMA_MAP_UNKNOWN)
return calc_map_type_and_dist(pdev, client, &dist, tlp_flags,
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 13/19] PCI/P2PDMA: Add KUnit tests for the ACS routing decisions
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (11 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 12/19] PCI/P2PDMA: Log detailed ACS routing diagnostics Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 14/19] PCI/P2PDMA: Test the ACS P2P routing walk Leon Romanovsky
` (5 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
pci_acs_p2pdma_request() and pci_acs_p2pdma_completion() turn an ACS
Control register and a TLP class into a routing decision. Which bits apply
to which direction and which class is easy to get wrong, and hardware that
exposes a given combination may not be at hand.
Drive both from a table of register values and classes, covering the
redirect controls per direction and Translation Blocking, Direct Translated
P2P and Relaxed Ordering. Direct Translated P2P gets a case with and
without a redirect to override, since it changes nothing without one.
Exposing the two helpers moves their state enum and the TLP flags into
pci.h.
Tested-by: Tushar Dave <tdave@nvidia.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/Kconfig | 15 ++++++
drivers/pci/Makefile | 1 +
drivers/pci/p2pdma.c | 38 ++------------
drivers/pci/pci.h | 41 +++++++++++++++
drivers/pci/pci_acs_test.c | 122 +++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 183 insertions(+), 34 deletions(-)
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 0c7408509ba2..7a3eb5beb328 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -226,6 +226,21 @@ config PCI_P2PDMA
If unsure, say N.
+config PCI_ACS_KUNIT_TEST
+ tristate "KUnit tests for PCI ACS P2P routing" if !KUNIT_ALL_TESTS
+ depends on PCI_P2PDMA && KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit tests for the PCI ACS peer-to-peer routing decision
+ logic, including direction-specific Request and Completion
+ controls that cannot all be exercised on typical peer-to-peer
+ hardware.
+
+ For more information on KUnit and unit tests in general, refer to
+ the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
+
config PCI_LABEL
def_bool y if (DMI || ACPI)
select NLS
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..6305d128d3df 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_PCI_STUB) += pci-stub.o
obj-$(CONFIG_PCI_PF_STUB) += pci-pf-stub.o
obj-$(CONFIG_PCI_ECAM) += ecam.o
obj-$(CONFIG_PCI_P2PDMA) += p2pdma.o
+obj-$(CONFIG_PCI_ACS_KUNIT_TEST) += pci_acs_test.o
obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
obj-$(CONFIG_VGA_ARB) += vgaarb.o
obj-$(CONFIG_PCI_DOE) += doe.o
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index d9f6b05cb3c0..340f149a4d88 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -530,38 +530,6 @@ static u16 pci_acs_p2pdma_quirk_redirect(struct pci_dev *pdev, u16 ctrl)
return ctrl;
}
-/**
- * enum pci_p2pdma_tlp_flags - Properties of the TLPs a client will issue
- *
- * These describe the traffic rather than the topology, and select which ACS
- * controls apply along the peer-to-peer path. A value of 0 means strictly
- * ordered Requests carrying an Untranslated address.
- *
- * @PCI_P2PDMA_TLP_TRANSLATED: Requests carry an ATS Translated address. PCIe
- * r7.0 sec 6.12.3 routes those to the peer regardless of ACS P2P Request
- * Redirect and ACS P2P Egress Control wherever ACS Direct Translated P2P
- * is enabled.
- * @PCI_P2PDMA_TLP_RELAXED_CPL: Non-Posted Requests carry the Relaxed
- * Ordering attribute, and PCIe r7.0 sec 2.2.9.1 requires the Completer to
- * return it in their Completions. Sec 6.12.1.1 never redirects those, so
- * ACS P2P Completion Redirect does not gate the path.
- */
-enum pci_p2pdma_tlp_flags {
- PCI_P2PDMA_TLP_TRANSLATED = 1 << 0,
- PCI_P2PDMA_TLP_RELAXED_CPL = 1 << 1,
-};
-
-/* Every combination of the flags above selects one routing class. */
-#define PCI_P2PDMA_TLP_CLASSES \
- ((PCI_P2PDMA_TLP_TRANSLATED | PCI_P2PDMA_TLP_RELAXED_CPL) + 1)
-
-enum pci_acs_p2pdma_state {
- PCI_ACS_P2PDMA_NOT_SUPPORTED,
- PCI_ACS_P2PDMA_DIRECT,
- PCI_ACS_P2PDMA_REDIRECT,
- PCI_ACS_P2PDMA_BLOCKED,
-};
-
/*
* Decide how a peer-to-peer Request at an ACS-capable ingress port routes,
* from that port's ACS Control register and the Request's Address Type.
@@ -571,7 +539,7 @@ enum pci_acs_p2pdma_state {
* selects are a direct route and an ACS Violation, and neither one lets peer
* bus addressing be assumed.
*/
-static enum pci_acs_p2pdma_state
+VISIBLE_IF_KUNIT enum pci_acs_p2pdma_state
pci_acs_p2pdma_request(u16 ctrl, unsigned int tlp_flags)
{
if (tlp_flags & PCI_P2PDMA_TLP_TRANSLATED) {
@@ -598,6 +566,7 @@ pci_acs_p2pdma_request(u16 ctrl, unsigned int tlp_flags)
return ctrl & (PCI_ACS_RR | PCI_ACS_EC) ?
PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT;
}
+EXPORT_SYMBOL_IF_KUNIT(pci_acs_p2pdma_request);
/*
* Decide how a peer-to-peer Completion at an ACS-capable ingress port routes.
@@ -605,7 +574,7 @@ pci_acs_p2pdma_request(u16 ctrl, unsigned int tlp_flags)
* affects a Completion, and that one leaves Completions carrying the Relaxed
* Ordering attribute alone.
*/
-static enum pci_acs_p2pdma_state
+VISIBLE_IF_KUNIT enum pci_acs_p2pdma_state
pci_acs_p2pdma_completion(u16 ctrl, unsigned int tlp_flags)
{
if (tlp_flags & PCI_P2PDMA_TLP_RELAXED_CPL)
@@ -614,6 +583,7 @@ pci_acs_p2pdma_completion(u16 ctrl, unsigned int tlp_flags)
return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT :
PCI_ACS_P2PDMA_DIRECT;
}
+EXPORT_SYMBOL_IF_KUNIT(pci_acs_p2pdma_completion);
static const char *pci_acs_p2pdma_state_name(enum pci_acs_p2pdma_state state)
{
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index ba3c3fddddc2..302b79d3977f 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -2,6 +2,7 @@
#ifndef DRIVERS_PCI_H
#define DRIVERS_PCI_H
+#include <kunit/visibility.h>
#include <linux/bug.h>
#include <linux/align.h>
#include <linux/bitfield.h>
@@ -1093,6 +1094,46 @@ resource_size_t pci_min_window_alignment(struct pci_bus *bus,
void pci_acs_init(struct pci_dev *dev);
void pci_enable_acs(struct pci_dev *dev);
+
+/**
+ * enum pci_p2pdma_tlp_flags - Properties of the TLPs a client will issue
+ *
+ * These describe the traffic rather than the topology, and select which ACS
+ * controls apply along the peer-to-peer path. A value of 0 means strictly
+ * ordered Requests carrying an Untranslated address.
+ *
+ * @PCI_P2PDMA_TLP_TRANSLATED: Requests carry an ATS Translated address. PCIe
+ * r7.0 sec 6.12.3 routes those to the peer regardless of ACS P2P Request
+ * Redirect and ACS P2P Egress Control wherever ACS Direct Translated P2P
+ * is enabled.
+ * @PCI_P2PDMA_TLP_RELAXED_CPL: Non-Posted Requests carry the Relaxed
+ * Ordering attribute, and PCIe r7.0 sec 2.2.9.1 requires the Completer to
+ * return it in their Completions. Sec 6.12.1.1 never redirects those, so
+ * ACS P2P Completion Redirect does not gate the path.
+ */
+enum pci_p2pdma_tlp_flags {
+ PCI_P2PDMA_TLP_TRANSLATED = 1 << 0,
+ PCI_P2PDMA_TLP_RELAXED_CPL = 1 << 1,
+};
+
+/* Every combination of the flags above selects one routing class. */
+#define PCI_P2PDMA_TLP_CLASSES \
+ ((PCI_P2PDMA_TLP_TRANSLATED | PCI_P2PDMA_TLP_RELAXED_CPL) + 1)
+
+enum pci_acs_p2pdma_state {
+ PCI_ACS_P2PDMA_NOT_SUPPORTED,
+ PCI_ACS_P2PDMA_DIRECT,
+ PCI_ACS_P2PDMA_REDIRECT,
+ PCI_ACS_P2PDMA_BLOCKED,
+};
+
+#if IS_ENABLED(CONFIG_KUNIT)
+enum pci_acs_p2pdma_state pci_acs_p2pdma_request(u16 ctrl,
+ unsigned int tlp_flags);
+enum pci_acs_p2pdma_state pci_acs_p2pdma_completion(u16 ctrl,
+ unsigned int tlp_flags);
+#endif
+
#ifdef CONFIG_PCI_QUIRKS
int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags);
int pci_dev_specific_enable_acs(struct pci_dev *dev);
diff --git a/drivers/pci/pci_acs_test.c b/drivers/pci/pci_acs_test.c
new file mode 100644
index 000000000000..18bcc4b8e758
--- /dev/null
+++ b/drivers/pci/pci_acs_test.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for PCI ACS peer-to-peer routing decisions.
+ *
+ * These exercise Request and Completion routing independently of the ACS
+ * settings exposed by available PCIe hardware.
+ */
+#include <kunit/test.h>
+
+#include <linux/pci.h>
+#include <linux/pci-p2pdma.h>
+#include <linux/pci_regs.h>
+
+#include "pci.h"
+
+struct acs_decision_case {
+ const char *desc;
+ u16 ctrl;
+ unsigned int tlp_flags;
+ enum pci_acs_p2pdma_state expect;
+};
+
+/* Shorthands to keep the tables below readable. */
+#define ACS_DIRECT PCI_ACS_P2PDMA_DIRECT
+#define ACS_REDIR PCI_ACS_P2PDMA_REDIRECT
+#define ACS_RO PCI_P2PDMA_TLP_RELAXED_CPL
+#define ACS_AT PCI_P2PDMA_TLP_TRANSLATED
+#define ACS_BLOCK PCI_ACS_P2PDMA_BLOCKED
+
+/* Request routing ignores Completion Redirect. */
+static const struct acs_decision_case acs_request_cases[] = {
+ { "req/none", 0, 0, ACS_DIRECT },
+ { "req/rr", PCI_ACS_RR, 0, ACS_REDIR },
+ { "req/cr", PCI_ACS_CR, 0, ACS_DIRECT },
+ { "req/rr_cr", PCI_ACS_RR | PCI_ACS_CR, 0, ACS_REDIR },
+ { "req/ec", PCI_ACS_EC, 0, ACS_REDIR },
+ { "req/ec_cr", PCI_ACS_EC | PCI_ACS_CR, 0, ACS_REDIR },
+
+ /*
+ * Direct Translated P2P overrides the redirect controls, but only for
+ * a Request that actually carries a Translated address.
+ */
+ { "req/dt", PCI_ACS_DT, 0, ACS_DIRECT },
+ { "req/dt_rr", PCI_ACS_DT | PCI_ACS_RR, 0, ACS_REDIR },
+ { "req/at", 0, ACS_AT, ACS_DIRECT },
+ { "req/at_rr", PCI_ACS_RR, ACS_AT, ACS_REDIR },
+ { "req/at_dt", PCI_ACS_DT, ACS_AT, ACS_DIRECT },
+ { "req/at_dt_rr", PCI_ACS_DT | PCI_ACS_RR, ACS_AT, ACS_DIRECT },
+ { "req/at_dt_ec", PCI_ACS_DT | PCI_ACS_EC, ACS_AT, ACS_DIRECT },
+
+ /*
+ * Translation Blocking rejects a Translated address outright, and
+ * makes the port ignore Direct Translated P2P.
+ */
+ { "req/tb", PCI_ACS_TB, 0, ACS_DIRECT },
+ { "req/tb_rr", PCI_ACS_TB | PCI_ACS_RR, 0, ACS_REDIR },
+ { "req/at_tb", PCI_ACS_TB, ACS_AT, ACS_BLOCK },
+ { "req/at_tb_dt", PCI_ACS_TB | PCI_ACS_DT, ACS_AT, ACS_BLOCK },
+};
+
+/* Completion routing depends only on Completion Redirect. */
+static const struct acs_decision_case acs_completion_cases[] = {
+ { "cpl/none", 0, 0, ACS_DIRECT },
+ { "cpl/rr", PCI_ACS_RR, 0, ACS_DIRECT },
+ { "cpl/cr", PCI_ACS_CR, 0, ACS_REDIR },
+ { "cpl/rr_cr", PCI_ACS_RR | PCI_ACS_CR, 0, ACS_REDIR },
+ { "cpl/ec", PCI_ACS_EC, 0, ACS_DIRECT },
+ { "cpl/ec_cr", PCI_ACS_EC | PCI_ACS_CR, 0, ACS_REDIR },
+
+ /* Relaxed Ordering Completions are never redirected. */
+ { "cpl/ro", 0, ACS_RO, ACS_DIRECT },
+ { "cpl/ro_cr", PCI_ACS_CR, ACS_RO, ACS_DIRECT },
+ { "cpl/ro_rr_cr", PCI_ACS_RR | PCI_ACS_CR, ACS_RO, ACS_DIRECT },
+};
+
+#undef ACS_DIRECT
+#undef ACS_REDIR
+#undef ACS_RO
+#undef ACS_AT
+#undef ACS_BLOCK
+
+static void acs_decision_desc(const struct acs_decision_case *c, char *desc)
+{
+ strscpy(desc, c->desc, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(acs_request, acs_request_cases, acs_decision_desc);
+KUNIT_ARRAY_PARAM(acs_completion, acs_completion_cases, acs_decision_desc);
+
+static void pci_acs_p2pdma_request_test(struct kunit *test)
+{
+ const struct acs_decision_case *c = test->param_value;
+
+ KUNIT_EXPECT_EQ(test, pci_acs_p2pdma_request(c->ctrl, c->tlp_flags),
+ c->expect);
+}
+
+static void pci_acs_p2pdma_completion_test(struct kunit *test)
+{
+ const struct acs_decision_case *c = test->param_value;
+
+ KUNIT_EXPECT_EQ(test, pci_acs_p2pdma_completion(c->ctrl, c->tlp_flags),
+ c->expect);
+}
+
+static struct kunit_case pci_acs_test_cases[] = {
+ KUNIT_CASE_PARAM(pci_acs_p2pdma_request_test,
+ acs_request_gen_params),
+ KUNIT_CASE_PARAM(pci_acs_p2pdma_completion_test,
+ acs_completion_gen_params),
+ {}
+};
+
+static struct kunit_suite pci_acs_test_suite = {
+ .name = "pci_acs",
+ .test_cases = pci_acs_test_cases,
+};
+kunit_test_suite(pci_acs_test_suite);
+
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KUnit tests for PCI ACS peer-to-peer routing decisions");
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 14/19] PCI/P2PDMA: Test the ACS P2P routing walk
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (12 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 13/19] PCI/P2PDMA: Add KUnit tests for the ACS routing decisions Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 15/19] PCI: Add KUnit coverage for ACS isolation checks Leon Romanovsky
` (4 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
Routing depends on which ports share each path, not just individual ACS
registers. Sibling functions without internal peer routes must use the host
bridge even when redirect controls read as zero.
Drive the walk over fabricated switch and multifunction topologies with
fake configuration space. Cover directional controls, nested ports, TLP
attributes, hardwired isolation, ARI, PF/VF paths, forwarding failures,
translation blocking, host allowlisting and packed cache values. Keep
external peers and self-DMA independent of sibling routing restrictions.
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 9 +-
drivers/pci/pci.h | 9 +
drivers/pci/pci_acs_test.c | 841 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 856 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index 340f149a4d88..6a05a5019daf 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -1170,7 +1170,7 @@ static unsigned long map_types_idx(struct pci_dev *client)
*/
static_assert(PCI_P2PDMA_MAP_THRU_HOST_BRIDGE < 16);
-static unsigned long
+VISIBLE_IF_KUNIT unsigned long
pci_p2pdma_map_types_pack(const enum pci_p2pdma_map_type *type)
{
unsigned long val = 0;
@@ -1181,12 +1181,14 @@ pci_p2pdma_map_types_pack(const enum pci_p2pdma_map_type *type)
return val;
}
+EXPORT_SYMBOL_IF_KUNIT(pci_p2pdma_map_types_pack);
-static enum pci_p2pdma_map_type
+VISIBLE_IF_KUNIT enum pci_p2pdma_map_type
pci_p2pdma_map_types_unpack(unsigned long val, unsigned int tlp_flags)
{
return (val >> (tlp_flags * 4)) & 0xf;
}
+EXPORT_SYMBOL_IF_KUNIT(pci_p2pdma_map_types_unpack);
/*
* Calculate the P2PDMA mapping type and distance between two PCI devices.
@@ -1236,7 +1238,7 @@ pci_p2pdma_map_types_unpack(unsigned long val, unsigned int tlp_flags)
* ports per above. If the device is not in the whitelist, return
* PCI_P2PDMA_MAP_NOT_SUPPORTED.
*/
-static enum pci_p2pdma_map_type
+VISIBLE_IF_KUNIT enum pci_p2pdma_map_type
calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
int *dist, unsigned int tlp_flags, bool verbose)
{
@@ -1443,6 +1445,7 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
}
return map_type[tlp_flags];
}
+EXPORT_SYMBOL_IF_KUNIT(calc_map_type_and_dist);
/**
* pci_p2pdma_distance_many - Determine the cumulative distance between
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 302b79d3977f..b9b983ad6c34 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -7,6 +7,7 @@
#include <linux/align.h>
#include <linux/bitfield.h>
#include <linux/pci.h>
+#include <linux/pci-p2pdma.h>
#include <trace/events/pci.h>
struct pcie_tlp_log;
@@ -1132,6 +1133,14 @@ enum pci_acs_p2pdma_state pci_acs_p2pdma_request(u16 ctrl,
unsigned int tlp_flags);
enum pci_acs_p2pdma_state pci_acs_p2pdma_completion(u16 ctrl,
unsigned int tlp_flags);
+unsigned long pci_p2pdma_map_types_pack(const enum pci_p2pdma_map_type *type);
+enum pci_p2pdma_map_type pci_p2pdma_map_types_unpack(unsigned long val,
+ unsigned int tlp_flags);
+enum pci_p2pdma_map_type calc_map_type_and_dist(struct pci_dev *provider,
+ struct pci_dev *client,
+ int *dist,
+ unsigned int tlp_flags,
+ bool verbose);
#endif
#ifdef CONFIG_PCI_QUIRKS
diff --git a/drivers/pci/pci_acs_test.c b/drivers/pci/pci_acs_test.c
index 18bcc4b8e758..18c697226762 100644
--- a/drivers/pci/pci_acs_test.c
+++ b/drivers/pci/pci_acs_test.c
@@ -103,11 +103,852 @@ static void pci_acs_p2pdma_completion_test(struct kunit *test)
c->expect);
}
+#define ACS_ALL_CAPS (PCI_ACS_SV | PCI_ACS_TB | PCI_ACS_RR | PCI_ACS_CR | \
+ PCI_ACS_UF | PCI_ACS_DT)
+#define ACS_TEST_CAP 0x100
+
+/*
+ * Drive calc_map_type_and_dist() over a fabricated PCIe fabric matching the
+ * canonical topology of two devices below one switch:
+ *
+ * host bridge / root bus
+ * Root Port
+ * Switch Upstream Port
+ * Switch Downstream Port 0
+ * Nested Switch -- provider
+ * Switch Downstream Port 1
+ * Nested Switch -- client
+ *
+ * Fake config-space operations supply the ACS Control registers. This lets
+ * the cases vary both divergence ports and controls below the divergence
+ * without depending on real hardware.
+ */
+struct acs_port_cfg {
+ u16 ctrl;
+ bool fail_read;
+};
+
+struct acs_fabric {
+ struct pci_dev *rootport;
+ struct pci_dev *provider;
+ struct pci_dev *client;
+ struct pci_dev *dn0; /* Downstream Port 0 (provider side) */
+ struct pci_dev *dn1; /* Downstream Port 1 (client side) */
+ struct pci_dev *provider_leaf;
+ struct pci_dev *client_leaf;
+ struct acs_port_cfg dn0_cfg;
+ struct acs_port_cfg dn1_cfg;
+ struct acs_port_cfg provider_leaf_cfg;
+ struct acs_port_cfg client_leaf_cfg;
+ struct acs_port_cfg rootport_cfg;
+ struct acs_port_cfg provider_cfg;
+ struct acs_port_cfg client_cfg;
+};
+
+static int acs_port_read(struct pci_dev *port, struct acs_port_cfg *cfg,
+ int where, int size, u32 *val)
+{
+ if (port->acs_cap && size == 2 &&
+ where == port->acs_cap + PCI_ACS_CTRL) {
+ if (cfg->fail_read)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ *val = cfg->ctrl;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int acs_fabric_read(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 *val)
+{
+ struct acs_fabric *f = bus->sysdata;
+
+ *val = 0;
+ if (bus == f->provider->bus && devfn == f->provider->devfn)
+ return acs_port_read(f->provider, &f->provider_cfg,
+ where, size, val);
+ if (bus == f->client->bus && devfn == f->client->devfn)
+ return acs_port_read(f->client, &f->client_cfg,
+ where, size, val);
+ if (bus == f->rootport->bus && devfn == f->rootport->devfn)
+ return acs_port_read(f->rootport, &f->rootport_cfg,
+ where, size, val);
+ if (bus == f->dn0->bus && devfn == f->dn0->devfn)
+ return acs_port_read(f->dn0, &f->dn0_cfg, where, size, val);
+ if (bus == f->dn1->bus && devfn == f->dn1->devfn)
+ return acs_port_read(f->dn1, &f->dn1_cfg, where, size, val);
+ if (bus == f->provider_leaf->bus &&
+ devfn == f->provider_leaf->devfn)
+ return acs_port_read(f->provider_leaf, &f->provider_leaf_cfg,
+ where, size, val);
+ if (bus == f->client_leaf->bus && devfn == f->client_leaf->devfn)
+ return acs_port_read(f->client_leaf, &f->client_leaf_cfg,
+ where, size, val);
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int acs_fabric_write(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 val)
+{
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops acs_fabric_ops = {
+ .read = acs_fabric_read,
+ .write = acs_fabric_write,
+};
+
+static struct pci_bus *acs_add_bus(struct kunit *test, struct pci_bus *parent,
+ struct pci_dev *self, u8 nr, void *sysdata)
+{
+ struct pci_bus *bus = kunit_kzalloc(test, sizeof(*bus), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, bus);
+ bus->parent = parent;
+ bus->self = self;
+ bus->number = nr;
+ bus->ops = &acs_fabric_ops;
+ bus->sysdata = sysdata;
+ INIT_LIST_HEAD(&bus->devices);
+ return bus;
+}
+
+static struct pci_dev *acs_add_dev(struct kunit *test, struct pci_bus *bus,
+ unsigned int devfn, int pcie_type)
+{
+ struct pci_dev *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, dev);
+ dev->bus = bus;
+ dev->devfn = devfn;
+ dev->pcie_cap = 0x40;
+ dev->pcie_flags_reg = (pcie_type << 4) | 0x2;
+ if (pcie_type != PCI_EXP_TYPE_ENDPOINT)
+ dev->hdr_type = PCI_HEADER_TYPE_BRIDGE;
+ list_add_tail(&dev->bus_list, &bus->devices);
+ return dev;
+}
+
+static void acs_build_fabric(struct kunit *test, struct acs_fabric *f)
+{
+ struct pci_bus *bus0, *bus1, *bus2, *bus3, *bus4, *bus5, *bus6;
+ struct pci_bus *bus7, *bus8;
+ struct pci_dev *swup, *provider_swup, *client_swup;
+ struct pci_dev *rootport;
+ struct pci_host_bridge *host;
+
+ host = kunit_kzalloc(test, sizeof(*host), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, host);
+
+ bus0 = acs_add_bus(test, NULL, NULL, 0, f);
+ /* The Root Port doubles as the whitelisted host-bridge device. */
+ rootport = acs_add_dev(test, bus0, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_ROOT_PORT);
+ f->rootport = rootport;
+ rootport->vendor = PCI_VENDOR_ID_GOOGLE;
+ rootport->device = 0x1234;
+ host->bus = bus0;
+ bus0->bridge = &host->dev;
+
+ bus1 = acs_add_bus(test, bus0, rootport, 1, f);
+ swup = acs_add_dev(test, bus1, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_UPSTREAM);
+
+ bus2 = acs_add_bus(test, bus1, swup, 2, f);
+ f->dn0 = acs_add_dev(test, bus2, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_DOWNSTREAM);
+ f->dn1 = acs_add_dev(test, bus2, PCI_DEVFN(1, 0),
+ PCI_EXP_TYPE_DOWNSTREAM);
+
+ bus3 = acs_add_bus(test, bus2, f->dn0, 3, f);
+ provider_swup = acs_add_dev(test, bus3, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_UPSTREAM);
+ bus5 = acs_add_bus(test, bus3, provider_swup, 5, f);
+ f->provider_leaf = acs_add_dev(test, bus5, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_DOWNSTREAM);
+ bus7 = acs_add_bus(test, bus5, f->provider_leaf, 7, f);
+ f->provider = acs_add_dev(test, bus7, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_ENDPOINT);
+
+ bus4 = acs_add_bus(test, bus2, f->dn1, 4, f);
+ client_swup = acs_add_dev(test, bus4, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_UPSTREAM);
+ bus6 = acs_add_bus(test, bus4, client_swup, 6, f);
+ f->client_leaf = acs_add_dev(test, bus6, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_DOWNSTREAM);
+ bus8 = acs_add_bus(test, bus6, f->client_leaf, 8, f);
+ f->client = acs_add_dev(test, bus8, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_ENDPOINT);
+}
+
+static enum pci_p2pdma_map_type acs_walk_map(struct acs_fabric *f,
+ unsigned int tlp_flags)
+{
+ int dist;
+
+ return calc_map_type_and_dist(f->provider, f->client, &dist, tlp_flags,
+ false);
+}
+
+static void acs_walk_bus_addr_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0), PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+static void acs_walk_request_redirect_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.dn1->acs_cap = 0x100;
+ f.dn1->acs_capabilities = PCI_ACS_RR;
+ f.dn1_cfg.ctrl = PCI_ACS_RR;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+}
+
+static void acs_walk_completion_redirect_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.dn0->acs_cap = 0x100;
+ f.dn0->acs_capabilities = PCI_ACS_CR;
+ f.dn0_cfg.ctrl = PCI_ACS_CR;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+}
+
+static void acs_walk_egress_control_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.dn1->acs_cap = 0x100;
+ f.dn1->acs_capabilities = PCI_ACS_EC;
+ f.dn1_cfg.ctrl = PCI_ACS_EC;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+}
+
+/*
+ * Zhaoxin Downstream Ports that lack an ACS capability redirect through a
+ * quirk, which covers every class, Relaxed Ordering Completions included.
+ * A Zhaoxin port the quirk does not know keeps its own ACS controls.
+ */
+static void acs_walk_quirk_redirect_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ if (!IS_ENABLED(CONFIG_PCI_QUIRKS))
+ kunit_skip(test, "needs CONFIG_PCI_QUIRKS");
+
+ acs_build_fabric(test, &f);
+ f.dn0->vendor = PCI_VENDOR_ID_ZHAOXIN;
+ f.dn0->device = 0x0710;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, PCI_P2PDMA_TLP_RELAXED_CPL),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+
+ f.dn0->device = 0x0700;
+ f.dn1->vendor = PCI_VENDOR_ID_ZHAOXIN;
+ f.dn1->device = 0x0700;
+ f.dn1->acs_cap = 0x100;
+ f.dn1->acs_capabilities = PCI_ACS_RR;
+ f.dn1_cfg.ctrl = PCI_ACS_RR;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+}
+
+static void acs_walk_asymmetric_direct_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ /* These controls affect only the reverse transaction directions. */
+ f.dn0->acs_cap = 0x100;
+ f.dn0->acs_capabilities = PCI_ACS_RR | PCI_ACS_EC;
+ f.dn0_cfg.ctrl = PCI_ACS_RR | PCI_ACS_EC;
+ f.dn1->acs_cap = 0x100;
+ f.dn1->acs_capabilities = PCI_ACS_CR;
+ f.dn1_cfg.ctrl = PCI_ACS_CR;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0), PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+static void acs_walk_nested_completion_redirect_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.provider_leaf->acs_cap = 0x100;
+ f.provider_leaf->acs_capabilities = PCI_ACS_CR;
+ f.provider_leaf_cfg.ctrl = PCI_ACS_CR;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0), PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+static void acs_walk_nested_request_redirect_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.client_leaf->acs_cap = 0x100;
+ f.client_leaf->acs_capabilities = PCI_ACS_RR;
+ f.client_leaf_cfg.ctrl = PCI_ACS_RR;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0), PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+static void acs_walk_translation_blocking_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.client_leaf->acs_cap = 0x100;
+ f.client_leaf->acs_capabilities = PCI_ACS_TB;
+ f.client_leaf_cfg.ctrl = PCI_ACS_TB;
+
+ /* Untranslated Requests are unaffected by Translation Blocking. */
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0), PCI_P2PDMA_MAP_BUS_ADDR);
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, PCI_P2PDMA_TLP_TRANSLATED),
+ PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
+static void acs_walk_relaxed_completion_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.dn0->acs_cap = 0x100;
+ f.dn0->acs_capabilities = PCI_ACS_CR;
+ f.dn0_cfg.ctrl = PCI_ACS_CR;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, PCI_P2PDMA_TLP_RELAXED_CPL),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+static void acs_walk_direct_translated_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.dn1->acs_cap = 0x100;
+ f.dn1->acs_capabilities = PCI_ACS_RR | PCI_ACS_DT;
+ f.dn1_cfg.ctrl = PCI_ACS_RR | PCI_ACS_DT;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, PCI_P2PDMA_TLP_TRANSLATED),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+static void acs_enable_forwarding(struct pci_dev *port,
+ struct acs_port_cfg *cfg)
+{
+ port->acs_cap = ACS_TEST_CAP;
+ port->acs_capabilities = PCI_ACS_UF;
+ cfg->ctrl = PCI_ACS_UF;
+}
+
+/*
+ * Put both functions on one downstream link. Like the Intel E810, each
+ * advertises ACS with neither Request nor Completion Redirect support,
+ * indicating that there is no internal peer path. The shared Downstream
+ * Port and its ancestors forward traffic upstream towards the host.
+ */
+static void acs_build_mf_fabric(struct kunit *test, struct acs_fabric *f)
+{
+ acs_build_fabric(test, f);
+ f->client->bus = f->provider->bus;
+ f->client->devfn = PCI_DEVFN(0, 1);
+ list_move_tail(&f->client->bus_list, &f->provider->bus->devices);
+ f->provider->multifunction = 1;
+ f->client->multifunction = 1;
+ f->provider->acs_cap = ACS_TEST_CAP;
+ f->client->acs_cap = ACS_TEST_CAP;
+ acs_enable_forwarding(f->provider_leaf, &f->provider_leaf_cfg);
+ acs_enable_forwarding(f->dn0, &f->dn0_cfg);
+ acs_enable_forwarding(f->rootport, &f->rootport_cfg);
+}
+
+static void acs_expect_classes(struct kunit *test, struct acs_fabric *f,
+ enum pci_p2pdma_map_type expect)
+{
+ unsigned int flags;
+
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
+ KUNIT_EXPECT_EQ_MSG(test, acs_walk_map(f, flags), expect,
+ "TLP flags %#x", flags);
+}
+
+static void acs_walk_mf_isolation_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_mf_fabric(test, &f);
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+}
+
+static void acs_walk_mf_quirk_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ if (!IS_ENABLED(CONFIG_PCI_QUIRKS))
+ kunit_skip(test, "needs CONFIG_PCI_QUIRKS");
+
+ acs_build_mf_fabric(test, &f);
+ f.provider->acs_cap = 0;
+ f.client->acs_cap = 0;
+ f.provider->vendor = PCI_VENDOR_ID_WANGXUN;
+ f.client->vendor = PCI_VENDOR_ID_WANGXUN;
+ f.provider->device = 0x0100;
+ f.client->device = 0x0100;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+
+ /* A port quirk can also establish hardwired Upstream Forwarding. */
+ f.provider_leaf->acs_cap = 0;
+ f.provider_leaf->vendor = PCI_VENDOR_ID_ZHAOXIN;
+ f.provider_leaf->device = 0x0710;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+
+ /* An unknown device must keep its own programmable ACS controls. */
+ f.provider->device = 0xffff;
+ f.client->device = 0xffff;
+ f.provider->acs_cap = ACS_TEST_CAP;
+ f.client->acs_cap = ACS_TEST_CAP;
+ f.provider->acs_capabilities = ACS_ALL_CAPS;
+ f.client->acs_capabilities = ACS_ALL_CAPS;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+/* DT and Relaxed Ordering cannot create a missing internal peer path. */
+static void acs_walk_mf_direction_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_mf_fabric(test, &f);
+ f.provider->acs_capabilities = ACS_ALL_CAPS;
+ f.client->acs_capabilities = ACS_ALL_CAPS & ~PCI_ACS_RR;
+ f.client_cfg.ctrl = PCI_ACS_DT;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+
+ f.provider->acs_capabilities = ACS_ALL_CAPS & ~PCI_ACS_CR;
+ f.client->acs_capabilities = ACS_ALL_CAPS;
+ f.client_cfg.ctrl = 0;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+}
+
+static void acs_walk_mf_programmable_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_mf_fabric(test, &f);
+ f.provider->acs_capabilities = ACS_ALL_CAPS;
+ f.client->acs_capabilities = ACS_ALL_CAPS;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_BUS_ADDR);
+
+ f.provider_cfg.ctrl = PCI_ACS_CR;
+ f.client_cfg.ctrl = PCI_ACS_RR | PCI_ACS_DT;
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, PCI_P2PDMA_TLP_TRANSLATED),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, PCI_P2PDMA_TLP_RELAXED_CPL),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+ KUNIT_EXPECT_EQ(test,
+ acs_walk_map(&f, PCI_P2PDMA_TLP_TRANSLATED |
+ PCI_P2PDMA_TLP_RELAXED_CPL),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+
+ /* A route that stays within the device does not need forwarding. */
+ f.provider_leaf_cfg.ctrl = 0;
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_NOT_SUPPORTED);
+ KUNIT_EXPECT_EQ(test,
+ acs_walk_map(&f, PCI_P2PDMA_TLP_TRANSLATED |
+ PCI_P2PDMA_TLP_RELAXED_CPL),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+struct acs_forwarding_case {
+ const char *desc;
+ u16 cap;
+ u16 capabilities;
+ u16 ctrl;
+ bool fail_read;
+};
+
+static const struct acs_forwarding_case acs_forwarding_cases[] = {
+ { "disabled", ACS_TEST_CAP, PCI_ACS_UF, 0, false },
+ { "unsupported", ACS_TEST_CAP, 0, 0, false },
+ { "absent_acs", 0, 0, 0, false },
+ { "read_error", ACS_TEST_CAP, PCI_ACS_UF, PCI_ACS_UF, true },
+ { "all_ones", ACS_TEST_CAP, PCI_ACS_UF, 0xffff, false },
+};
+
+static void acs_forwarding_desc(const struct acs_forwarding_case *c,
+ char *desc)
+{
+ strscpy(desc, c->desc, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(acs_forwarding, acs_forwarding_cases, acs_forwarding_desc);
+
+static void acs_walk_mf_forwarding_test(struct kunit *test)
+{
+ const struct acs_forwarding_case *c = test->param_value;
+ struct acs_fabric f = {};
+
+ acs_build_mf_fabric(test, &f);
+ f.provider_leaf->acs_cap = c->cap;
+ f.provider_leaf->acs_capabilities = c->capabilities;
+ f.provider_leaf_cfg.ctrl = c->ctrl;
+ f.provider_leaf_cfg.fail_read = c->fail_read;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
+static void acs_walk_mf_upstream_forwarding_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_mf_fabric(test, &f);
+ f.dn0_cfg.ctrl = 0;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_NOT_SUPPORTED);
+
+ f.dn0_cfg.ctrl = PCI_ACS_UF;
+ f.rootport_cfg.ctrl = 0;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
+static void acs_walk_mf_translation_blocking_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+ unsigned int flags;
+
+ acs_build_mf_fabric(test, &f);
+ f.provider_leaf->acs_capabilities |= PCI_ACS_TB;
+ f.provider_leaf_cfg.ctrl |= PCI_ACS_TB;
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
+ KUNIT_EXPECT_EQ_MSG(test, acs_walk_map(&f, flags),
+ flags & PCI_P2PDMA_TLP_TRANSLATED ?
+ PCI_P2PDMA_MAP_NOT_SUPPORTED :
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE,
+ "TLP flags %#x", flags);
+}
+
+static void acs_walk_mf_unlisted_host_test(struct kunit *test)
+{
+ struct acs_fabric f = {}, g = {};
+ int dist;
+
+ acs_build_mf_fabric(test, &f);
+ acs_build_fabric(test, &g);
+ f.rootport->vendor = 0;
+ g.rootport->vendor = 0;
+ /* Some CPUs permit host routing regardless of the host allowlist. */
+ if (calc_map_type_and_dist(f.provider, g.client, &dist, 0, false) ==
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE)
+ kunit_skip(test, "CPU supports P2PDMA on unlisted hosts");
+
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
+static void acs_walk_mf_ari_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_mf_fabric(test, &f);
+ f.provider->bus->self->ari_enabled = 1;
+ f.client->devfn = 8;
+ f.client->multifunction = 0;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+
+ /* The same shared-link topology can be directly below a Root Port. */
+ f.provider->bus->parent = f.rootport->bus;
+ f.provider->bus->self = f.rootport;
+ f.rootport->ari_enabled = 1;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+}
+
+static void acs_walk_mf_vf_test(struct kunit *test)
+{
+#ifdef CONFIG_PCI_IOV
+ struct acs_fabric f = {};
+ struct pci_dev *physfn;
+
+ acs_build_mf_fabric(test, &f);
+ physfn = acs_add_dev(test, f.provider->bus, PCI_DEVFN(0, 0),
+ PCI_EXP_TYPE_ENDPOINT);
+ physfn->is_physfn = 1;
+ physfn->acs_cap = ACS_TEST_CAP;
+ physfn->acs_capabilities = ACS_ALL_CAPS;
+ f.provider->devfn = 1;
+ f.client->devfn = 8;
+ f.provider->bus->self->ari_enabled = 1;
+ f.provider->multifunction = 0;
+ f.client->multifunction = 0;
+ f.provider->is_virtfn = 1;
+ f.client->is_virtfn = 1;
+ f.provider->physfn = physfn;
+ f.client->physfn = physfn;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+
+ /* SR-IOV functions without ACS cannot route internally either. */
+ f.provider->acs_cap = 0;
+ f.client->acs_cap = 0;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+
+ /* Check the physical function as the peer of one of its VFs. */
+ f.provider->is_virtfn = 0;
+ f.provider->physfn = NULL;
+ f.provider->is_physfn = 1;
+ f.client->physfn = f.provider;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+#else
+ kunit_skip(test, "needs CONFIG_PCI_IOV");
+#endif
+}
+
+static void acs_walk_mf_external_peer_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.provider->multifunction = 1;
+ f.client->multifunction = 1;
+ f.provider->acs_cap = ACS_TEST_CAP;
+ f.client->acs_cap = ACS_TEST_CAP;
+ acs_expect_classes(test, &f, PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+static void acs_walk_mf_self_dma_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+ unsigned int flags;
+ int dist;
+
+ acs_build_mf_fabric(test, &f);
+ f.provider_leaf->acs_capabilities = PCI_ACS_TB;
+ f.provider_leaf_cfg.ctrl = PCI_ACS_TB;
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++) {
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(f.client, f.client, &dist,
+ flags, false),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+ KUNIT_EXPECT_EQ(test, dist, 0);
+ }
+}
+
+/*
+ * The cache stores one packed value per client, so every class has to come
+ * back out under the flags that selected it.
+ */
+static void acs_map_types_pack_test(struct kunit *test)
+{
+ static const enum pci_p2pdma_map_type type[PCI_P2PDMA_TLP_CLASSES] = {
+ [0] = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE,
+ [PCI_P2PDMA_TLP_TRANSLATED] = PCI_P2PDMA_MAP_NOT_SUPPORTED,
+ [PCI_P2PDMA_TLP_RELAXED_CPL] = PCI_P2PDMA_MAP_BUS_ADDR,
+ [PCI_P2PDMA_TLP_TRANSLATED | PCI_P2PDMA_TLP_RELAXED_CPL] =
+ PCI_P2PDMA_MAP_UNKNOWN,
+ };
+ unsigned long packed = pci_p2pdma_map_types_pack(type);
+ unsigned int flags;
+
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
+ KUNIT_EXPECT_EQ(test,
+ pci_p2pdma_map_types_unpack(packed, flags),
+ type[flags]);
+
+ /* An absent cache entry reads back as unknown in every class. */
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
+ KUNIT_EXPECT_EQ(test, pci_p2pdma_map_types_unpack(0, flags),
+ PCI_P2PDMA_MAP_UNKNOWN);
+}
+
+/*
+ * The provider can be an ancestor of the client, which leaves no divergence
+ * to evaluate. Translation Blocking still applies to every port the Request
+ * passes on its way up.
+ */
+static void acs_walk_ancestor_provider_tb_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+ int dist;
+
+ acs_build_fabric(test, &f);
+ f.client_leaf->acs_cap = 0x100;
+ f.client_leaf->acs_capabilities = PCI_ACS_TB;
+ f.client_leaf_cfg.ctrl = PCI_ACS_TB;
+
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(f.dn1, f.client, &dist, 0,
+ false),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(f.dn1, f.client, &dist,
+ PCI_P2PDMA_TLP_TRANSLATED,
+ false),
+ PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
+/*
+ * Without a common upstream bridge the Request still climbs towards the host
+ * bridge, so Translation Blocking on the way withdraws the Translated classes
+ * there too.
+ */
+static void acs_walk_no_common_bridge_tb_test(struct kunit *test)
+{
+ struct acs_fabric f = {}, g = {};
+ int dist;
+
+ acs_build_fabric(test, &f);
+ acs_build_fabric(test, &g);
+ f.client_leaf->acs_cap = 0x100;
+ f.client_leaf->acs_capabilities = PCI_ACS_TB;
+ f.client_leaf_cfg.ctrl = PCI_ACS_TB;
+
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(g.provider, f.client, &dist, 0,
+ false),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(g.provider, f.client, &dist,
+ PCI_P2PDMA_TLP_TRANSLATED,
+ false),
+ PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
+/*
+ * A Request between the client and itself never leaves the device, so no port
+ * is in a position to inspect its Address Type. Translation Blocking directly
+ * above the client must not withdraw the Translated classes.
+ */
+static void acs_walk_self_dma_tb_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+ int dist;
+
+ acs_build_fabric(test, &f);
+ f.client_leaf->acs_cap = 0x100;
+ f.client_leaf->acs_capabilities = PCI_ACS_TB;
+ f.client_leaf_cfg.ctrl = PCI_ACS_TB;
+
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(f.client, f.client, &dist, 0,
+ false),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(f.client, f.client, &dist,
+ PCI_P2PDMA_TLP_TRANSLATED,
+ false),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+}
+
+/*
+ * A direct route turns around at the divergence, so Translation Blocking above
+ * it does not touch one. The host bridge route keeps climbing past that port,
+ * and the Translated classes have to go without it.
+ */
+static void acs_walk_tb_above_divergence_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+ int dist;
+
+ acs_build_fabric(test, &f);
+ f.rootport->acs_cap = 0x100;
+ f.rootport->acs_capabilities = PCI_ACS_TB;
+ f.rootport_cfg.ctrl = PCI_ACS_TB;
+
+ /* Nothing redirects yet, so the Request never reaches the Root Port. */
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(f.provider, f.client, &dist,
+ PCI_P2PDMA_TLP_TRANSLATED,
+ false),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+
+ /* Request Redirect sends it up past the Root Port instead. */
+ f.dn1->acs_cap = 0x100;
+ f.dn1->acs_capabilities = PCI_ACS_RR;
+ f.dn1_cfg.ctrl = PCI_ACS_RR;
+
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(f.provider, f.client, &dist, 0,
+ false),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+ KUNIT_EXPECT_EQ(test,
+ calc_map_type_and_dist(f.provider, f.client, &dist,
+ PCI_P2PDMA_TLP_TRANSLATED,
+ false),
+ PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
+static void acs_walk_unreadable_control_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.dn1->acs_cap = 0x100;
+ f.dn1_cfg.fail_read = true;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_NOT_SUPPORTED);
+
+ /* A port that does not answer reads as all ones, without an error */
+ f.dn1_cfg.fail_read = false;
+ f.dn1_cfg.ctrl = 0xffff;
+
+ KUNIT_EXPECT_EQ(test, acs_walk_map(&f, 0),
+ PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
static struct kunit_case pci_acs_test_cases[] = {
KUNIT_CASE_PARAM(pci_acs_p2pdma_request_test,
acs_request_gen_params),
KUNIT_CASE_PARAM(pci_acs_p2pdma_completion_test,
acs_completion_gen_params),
+ KUNIT_CASE(acs_walk_bus_addr_test),
+ KUNIT_CASE(acs_walk_request_redirect_test),
+ KUNIT_CASE(acs_walk_completion_redirect_test),
+ KUNIT_CASE(acs_walk_egress_control_test),
+ KUNIT_CASE(acs_walk_quirk_redirect_test),
+ KUNIT_CASE(acs_walk_asymmetric_direct_test),
+ KUNIT_CASE(acs_walk_nested_completion_redirect_test),
+ KUNIT_CASE(acs_walk_nested_request_redirect_test),
+ KUNIT_CASE(acs_walk_translation_blocking_test),
+ KUNIT_CASE(acs_walk_ancestor_provider_tb_test),
+ KUNIT_CASE(acs_walk_no_common_bridge_tb_test),
+ KUNIT_CASE(acs_walk_self_dma_tb_test),
+ KUNIT_CASE(acs_walk_relaxed_completion_test),
+ KUNIT_CASE(acs_walk_direct_translated_test),
+ KUNIT_CASE(acs_walk_mf_isolation_test),
+ KUNIT_CASE(acs_walk_mf_quirk_test),
+ KUNIT_CASE(acs_walk_mf_direction_test),
+ KUNIT_CASE(acs_walk_mf_programmable_test),
+ KUNIT_CASE_PARAM(acs_walk_mf_forwarding_test, acs_forwarding_gen_params),
+ KUNIT_CASE(acs_walk_mf_upstream_forwarding_test),
+ KUNIT_CASE(acs_walk_mf_translation_blocking_test),
+ KUNIT_CASE(acs_walk_mf_unlisted_host_test),
+ KUNIT_CASE(acs_walk_mf_ari_test),
+ KUNIT_CASE(acs_walk_mf_vf_test),
+ KUNIT_CASE(acs_walk_mf_external_peer_test),
+ KUNIT_CASE(acs_walk_mf_self_dma_test),
+ KUNIT_CASE(acs_walk_tb_above_divergence_test),
+ KUNIT_CASE(acs_walk_unreadable_control_test),
+ KUNIT_CASE(acs_map_types_pack_test),
{}
};
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 15/19] PCI: Add KUnit coverage for ACS isolation checks
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (13 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 14/19] PCI/P2PDMA: Test the ACS P2P routing walk Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 16/19] PCI/P2PDMA: Document TLP-class routing Leon Romanovsky
` (3 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
Direct Translated P2P does not weaken IOMMU isolation because a Translated
Request carries an address supplied by the IOMMU. Config-space reads that
fail or return all ones, however, leave ACS state unknown and must not
report isolation.
Exercise both cases with fake config-space operations. Also cover missing
and unrequested controls and a missing ACS capability.
Tested-by: Tushar Dave <tdave@nvidia.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/pci.c | 4 +-
drivers/pci/pci.h | 1 +
drivers/pci/pci_acs_test.c | 139 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 143 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 5f6db2416cee..1c3dee706670 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -3578,7 +3578,8 @@ void pci_configure_ari(struct pci_dev *dev)
}
}
-static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags)
+VISIBLE_IF_KUNIT
+bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags)
{
int pos;
u16 ctrl;
@@ -3599,6 +3600,7 @@ static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags)
return false;
return (ctrl & acs_flags) == acs_flags;
}
+EXPORT_SYMBOL_IF_KUNIT(pci_acs_flags_enabled);
/**
* pci_acs_enabled - test ACS against required flags for a given device
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index b9b983ad6c34..99852cf99732 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1129,6 +1129,7 @@ enum pci_acs_p2pdma_state {
};
#if IS_ENABLED(CONFIG_KUNIT)
+bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags);
enum pci_acs_p2pdma_state pci_acs_p2pdma_request(u16 ctrl,
unsigned int tlp_flags);
enum pci_acs_p2pdma_state pci_acs_p2pdma_completion(u16 ctrl,
diff --git a/drivers/pci/pci_acs_test.c b/drivers/pci/pci_acs_test.c
index 18c697226762..369444d2a04a 100644
--- a/drivers/pci/pci_acs_test.c
+++ b/drivers/pci/pci_acs_test.c
@@ -103,10 +103,145 @@ static void pci_acs_p2pdma_completion_test(struct kunit *test)
c->expect);
}
+/* Flags an IOMMU asks for; see REQ_ACS_FLAGS in drivers/iommu/iommu.c. */
+#define ACS_REQ_FLAGS (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF)
#define ACS_ALL_CAPS (PCI_ACS_SV | PCI_ACS_TB | PCI_ACS_RR | PCI_ACS_CR | \
PCI_ACS_UF | PCI_ACS_DT)
#define ACS_TEST_CAP 0x100
+struct acs_ctrl_cfg {
+ unsigned int devfn;
+ u16 cap; /* Offset where the ACS capability responds */
+ u16 ctrl;
+ bool fail_read;
+};
+
+static int acs_ctrl_read(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 *val)
+{
+ struct acs_ctrl_cfg *cfg = bus->sysdata;
+
+ *val = 0;
+ if (cfg->fail_read)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ if (devfn == cfg->devfn && size == 2 &&
+ where == cfg->cap + PCI_ACS_CTRL)
+ *val = cfg->ctrl;
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int acs_ctrl_write(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 val)
+{
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops acs_ctrl_ops = {
+ .read = acs_ctrl_read,
+ .write = acs_ctrl_write,
+};
+
+struct acs_isolation_case {
+ const char *desc;
+ u16 ctrl;
+ u16 req;
+ bool expect;
+};
+
+static const struct acs_isolation_case acs_isolation_cases[] = {
+ { "all_enabled", ACS_REQ_FLAGS, ACS_REQ_FLAGS, true },
+ /* Translated Requests remain isolated by their IOMMU translation. */
+ { "dt", ACS_REQ_FLAGS | PCI_ACS_DT, ACS_REQ_FLAGS, true },
+ { "rr_not_enabled", PCI_ACS_SV | PCI_ACS_CR | PCI_ACS_UF,
+ ACS_REQ_FLAGS, false },
+ { "rr_not_required", PCI_ACS_SV | PCI_ACS_CR | PCI_ACS_UF,
+ PCI_ACS_SV | PCI_ACS_CR | PCI_ACS_UF, true },
+};
+
+static void acs_isolation_desc(const struct acs_isolation_case *c, char *desc)
+{
+ strscpy(desc, c->desc, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(acs_isolation, acs_isolation_cases, acs_isolation_desc);
+
+static void pci_acs_flags_enabled_test(struct kunit *test)
+{
+ const struct acs_isolation_case *c = test->param_value;
+ struct acs_ctrl_cfg cfg = {
+ .devfn = PCI_DEVFN(0, 0),
+ .cap = ACS_TEST_CAP,
+ .ctrl = c->ctrl,
+ };
+ struct pci_bus *bus = kunit_kzalloc(test, sizeof(*bus), GFP_KERNEL);
+ struct pci_dev *pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, bus);
+ KUNIT_ASSERT_NOT_NULL(test, pdev);
+
+ bus->ops = &acs_ctrl_ops;
+ bus->sysdata = &cfg;
+
+ pdev->bus = bus;
+ pdev->devfn = cfg.devfn;
+ pdev->acs_cap = ACS_TEST_CAP;
+ pdev->acs_capabilities = ACS_ALL_CAPS;
+
+ KUNIT_EXPECT_EQ(test, pci_acs_flags_enabled(pdev, c->req), c->expect);
+}
+
+static bool acs_isolated(struct kunit *test, struct acs_ctrl_cfg *cfg,
+ u16 acs_cap, u16 acs_flags)
+{
+ struct pci_bus *bus = kunit_kzalloc(test, sizeof(*bus), GFP_KERNEL);
+ struct pci_dev *pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, bus);
+ KUNIT_ASSERT_NOT_NULL(test, pdev);
+
+ bus->ops = &acs_ctrl_ops;
+ bus->sysdata = cfg;
+
+ pdev->bus = bus;
+ pdev->devfn = cfg->devfn;
+ pdev->acs_cap = acs_cap;
+ pdev->acs_capabilities = ACS_ALL_CAPS;
+
+ return pci_acs_flags_enabled(pdev, acs_flags);
+}
+
+static void pci_acs_flags_no_cap_test(struct kunit *test)
+{
+ struct acs_ctrl_cfg cfg = {
+ .devfn = PCI_DEVFN(0, 0),
+ .cap = 0,
+ .ctrl = ACS_REQ_FLAGS,
+ };
+
+ KUNIT_EXPECT_FALSE(test, acs_isolated(test, &cfg, 0, ACS_REQ_FLAGS));
+}
+
+static void pci_acs_flags_read_fails_test(struct kunit *test)
+{
+ u16 no_rr = ACS_REQ_FLAGS & ~PCI_ACS_RR;
+ struct acs_ctrl_cfg cfg = {
+ .devfn = PCI_DEVFN(0, 0),
+ .cap = ACS_TEST_CAP,
+ .ctrl = ACS_REQ_FLAGS,
+ };
+
+ KUNIT_EXPECT_TRUE(test, acs_isolated(test, &cfg, ACS_TEST_CAP, no_rr));
+
+ cfg.fail_read = true;
+ KUNIT_EXPECT_FALSE(test, acs_isolated(test, &cfg, ACS_TEST_CAP, no_rr));
+
+ /* A device that does not answer reads as all ones, without an error */
+ cfg.fail_read = false;
+ cfg.ctrl = 0xffff;
+ KUNIT_EXPECT_FALSE(test, acs_isolated(test, &cfg, ACS_TEST_CAP, no_rr));
+}
+
/*
* Drive calc_map_type_and_dist() over a fabricated PCIe fabric matching the
* canonical topology of two devices below one switch:
@@ -920,6 +1055,10 @@ static struct kunit_case pci_acs_test_cases[] = {
acs_request_gen_params),
KUNIT_CASE_PARAM(pci_acs_p2pdma_completion_test,
acs_completion_gen_params),
+ KUNIT_CASE_PARAM(pci_acs_flags_enabled_test,
+ acs_isolation_gen_params),
+ KUNIT_CASE(pci_acs_flags_no_cap_test),
+ KUNIT_CASE(pci_acs_flags_read_fails_test),
KUNIT_CASE(acs_walk_bus_addr_test),
KUNIT_CASE(acs_walk_request_redirect_test),
KUNIT_CASE(acs_walk_completion_redirect_test),
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 16/19] PCI/P2PDMA: Document TLP-class routing
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (14 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 15/19] PCI: Add KUnit coverage for ACS isolation checks Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 17/19] PCI/P2PDMA: Let a client declare that it selects ATS per mapping Leon Romanovsky
` (2 subsequent siblings)
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
The P2PDMA documentation leaves Relaxed Ordering and ATS-translated
Requests undefined, although routing now depends on their attributes.
Describe the three TLP-sensitive ACS controls and the outcomes for each
class. Clarify that Direct Translated P2P and Relaxed Ordering cannot
create an internal route between sibling functions when hardware lacks that
route.
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
Documentation/driver-api/pci/p2pdma.rst | 68 ++++++++++++++++++++++++++++++---
1 file changed, 62 insertions(+), 6 deletions(-)
diff --git a/Documentation/driver-api/pci/p2pdma.rst b/Documentation/driver-api/pci/p2pdma.rst
index 7b11d7d1fdc6..05f896a0814b 100644
--- a/Documentation/driver-api/pci/p2pdma.rst
+++ b/Documentation/driver-api/pci/p2pdma.rst
@@ -49,12 +49,68 @@ rejects a host route without this forwarding support. The host bridge must
also support P2P. Clearing redirect controls cannot create an internal route
that the device does not support.
-This evaluation assumes clients issue strictly ordered Requests carrying an
-Untranslated address. Its result is not defined when clients use Relaxed
-Ordering or issue ATS-translated Requests because those TLP attributes can
-select different routes through the fabric. Unless ACS Translation Blocking
-is enabled, a Port with ACS Direct Translated P2P enabled routes a
-Translated Request directly to the peer regardless of the redirect controls.
+Three of those controls act on TLP attributes that the Requester chooses rather
+than on the topology, so the same path routes differently for different
+traffic. ACS Translation Blocking rejects any upstream Request whose Address
+Type is not Untranslated, and takes precedence over every other P2P control.
+ACS Direct Translated P2P routes a Translated Request to the peer regardless of
+Request Redirect and Egress Control. ACS Completion Redirect leaves alone
+Completions that carry the Relaxed Ordering attribute, which a Completion
+copies from the Request it answers.
+
+P2PDMA therefore decides each class of traffic separately, and
+``pci_p2pdma_map_type()`` answers for the one a DMA mapping is built for:
+strictly ordered Requests carrying an Untranslated address.
+
+The two directions are decided independently. Translation Blocking (TB),
+Direct Translated P2P (DT), Request Redirect (RR) and Egress Control (EC) on
+the Requester-side port decide the Request:
+
+===== ===== ======= ============ ==========
+TB DT RR/EC TLP class Request
+===== ===== ======= ============ ==========
+set x x translated blocked
+clear set x translated direct
+clear clear clear translated direct
+clear clear set translated redirected
+x x clear untranslated direct
+x x set untranslated redirected
+===== ===== ======= ============ ==========
+
+Completion Redirect (CR) on the Completer-side port decides the Completions:
+
+===== ========= ==========
+CR TLP class Completion
+===== ========= ==========
+x relaxed direct
+clear strict direct
+set strict redirected
+===== ========= ==========
+
+Combining the two tables gives the mapping type. A class is bus addressable
+when its Request and its Completions both route directly, and anything else
+goes through the host bridge. A blocked Request has neither route, because
+Translation Blocking rejects the Address Type itself rather than the
+destination. With no ACS control set anywhere, every class routes directly
+provided that any required internal route between sibling functions exists.
+Neither Direct Translated P2P nor Relaxed Ordering can create such a route.
+
+Some ports without an ACS capability, such as certain Zhaoxin Switch
+Downstream Ports, redirect peer-to-peer traffic anyway, and a device-specific
+ACS quirk tells the kernel so. P2PDMA treats such a port as having Request and
+Completion Redirect set, but with no exception for Relaxed Ordering, so it
+redirects every Completion.
+
+Note that DT only matters where RR or EC would otherwise redirect: it
+overrides them for a Translated address rather than granting a direct route
+that was not already there.
+
+Translation Blocking is not a routing control, so it is evaluated on every
+port the Request passes on its way upstream rather than at the divergence
+alone. A direct route turns around at the divergence and only passes the ports
+below it, while the host bridge route keeps climbing and passes that port and
+everything above it as well. Neither route falls back to the other, because
+the Address Type is rejected wherever the Request is addressed.
However, if the P2P transaction reaches the host bridge then it might have to
hairpin back out the same root port, be routed inside the CPU SOC to another
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 17/19] PCI/P2PDMA: Let a client declare that it selects ATS per mapping
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (15 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 16/19] PCI/P2PDMA: Document TLP-class routing Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 18/19] PCI/P2PDMA: Evaluate the ATS path for clients with ATS enabled Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 19/19] PCI/P2PDMA: Test the routing of " Leon Romanovsky
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
The PCIe ATS Enable bit covers the whole device, so P2PDMA cannot tell
from it whether a client will translate a given address. Most devices
translate any address once ATS is enabled, but some choose ATS per DMA
mapping and can still use bus addresses for the rest.
Add pcim_p2pdma_set_ats_per_mapping() so the driver of such a device can
declare that before P2PDMA starts treating clients with ATS enabled as
translating everything. Keep it in the device's P2PDMA state, which
already ends with the driver binding.
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 30 ++++++++++++++++++++++++++++++
include/linux/pci-p2pdma.h | 4 ++++
2 files changed, 34 insertions(+)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index 6a05a5019daf..144314ab5fae 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -26,6 +26,7 @@
struct pci_p2pdma {
struct gen_pool *pool;
bool p2pmem_published;
+ bool ats_per_mapping;
struct xarray map_types;
struct p2pdma_provider mem[PCI_STD_NUM_BARS];
};
@@ -1750,6 +1751,35 @@ void pci_p2pmem_publish(struct pci_dev *pdev, bool publish)
}
EXPORT_SYMBOL_GPL(pci_p2pmem_publish);
+/**
+ * pcim_p2pdma_set_ats_per_mapping - Declare per-mapping ATS for a client
+ * @pdev: PCI device that initiates peer-to-peer DMA
+ *
+ * Declare that @pdev issues Translated Requests only for the DMA mappings its
+ * driver sets up to use ATS, rather than for any address once ATS is enabled.
+ * P2PDMA then routes this client's Requests with the Address Type each query
+ * asks about, rather than the one its ATS Enable bit implies.
+ *
+ * Call it once pcim_p2pdma_init() has succeeded. Without P2PDMA state the
+ * declaration is dropped and the ATS Enable bit keeps deciding.
+ */
+void pcim_p2pdma_set_ats_per_mapping(struct pci_dev *pdev)
+{
+ struct pci_p2pdma *p2p;
+
+ p2p = rcu_dereference_protected(pdev->p2pdma, 1);
+ if (!p2p)
+ /*
+ * ats_per_mapping is a performance optimization,
+ * if pcim_p2pdma_init() didn't set pdev->p2pdma pointer
+ * for some reason, let's simply use ATS global settings.
+ */
+ return;
+
+ p2p->ats_per_mapping = true;
+}
+EXPORT_SYMBOL_GPL(pcim_p2pdma_set_ats_per_mapping);
+
/**
* pci_p2pdma_map_type_tlp - Determine the mapping type for P2PDMA transfers
* @provider: P2PDMA provider structure
diff --git a/include/linux/pci-p2pdma.h b/include/linux/pci-p2pdma.h
index dd17501ba1b6..ce389e18d023 100644
--- a/include/linux/pci-p2pdma.h
+++ b/include/linux/pci-p2pdma.h
@@ -82,6 +82,7 @@ struct scatterlist *pci_p2pmem_alloc_sgl(struct pci_dev *pdev,
unsigned int *nents, u32 length);
void pci_p2pmem_free_sgl(struct pci_dev *pdev, struct scatterlist *sgl);
void pci_p2pmem_publish(struct pci_dev *pdev, bool publish);
+void pcim_p2pdma_set_ats_per_mapping(struct pci_dev *pdev);
int pci_p2pdma_enable_store(const char *page, struct pci_dev **p2p_dev,
bool *use_p2pdma);
ssize_t pci_p2pdma_enable_show(char *page, struct pci_dev *p2p_dev,
@@ -138,6 +139,9 @@ static inline void pci_p2pmem_free_sgl(struct pci_dev *pdev,
static inline void pci_p2pmem_publish(struct pci_dev *pdev, bool publish)
{
}
+static inline void pcim_p2pdma_set_ats_per_mapping(struct pci_dev *pdev)
+{
+}
static inline int pci_p2pdma_enable_store(const char *page,
struct pci_dev **p2p_dev, bool *use_p2pdma)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 18/19] PCI/P2PDMA: Evaluate the ATS path for clients with ATS enabled
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (16 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 17/19] PCI/P2PDMA: Let a client declare that it selects ATS per mapping Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 19/19] PCI/P2PDMA: Test the routing of " Leon Romanovsky
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
P2PDMA assumes every client issues Untranslated Requests. A device with
ATS enabled may translate any address it is handed, including a bus
address, and Translation Blocking can reject its Translated Requests on
a route the Untranslated answer called usable.
Unless the client declared per-mapping ATS, take the Address Type from
its ATS Enable bit. Never hand such a client bus addresses: only an IOVA
survives translation, and whatever it issues untranslated goes through
the host bridge, whose route must therefore be usable. Apply this after
the topology-only cache, and in pci_p2pdma_distance_many() too, so that
provider selection agrees with the mapping.
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
Documentation/driver-api/pci/p2pdma.rst | 8 +++
drivers/pci/p2pdma.c | 92 ++++++++++++++++++++++++++++++---
2 files changed, 93 insertions(+), 7 deletions(-)
diff --git a/Documentation/driver-api/pci/p2pdma.rst b/Documentation/driver-api/pci/p2pdma.rst
index 05f896a0814b..c004b6dc028f 100644
--- a/Documentation/driver-api/pci/p2pdma.rst
+++ b/Documentation/driver-api/pci/p2pdma.rst
@@ -62,6 +62,14 @@ P2PDMA therefore decides each class of traffic separately, and
``pci_p2pdma_map_type()`` answers for the one a DMA mapping is built for:
strictly ordered Requests carrying an Untranslated address.
+The PCIe ATS Enable bit covers the whole device, and most devices translate
+any address they are handed once it is set. Unless a driver has declared with
+``pcim_p2pdma_set_ats_per_mapping()`` that its device chooses ATS per mapping,
+that bit decides which Address Type P2PDMA assumes, and a client with ATS
+enabled is never handed a bus address, which it would translate as though it
+were an IOVA. Its Translated Requests may still route directly, but anything
+it issues untranslated reaches the host bridge, so that route has to work too.
+
The two directions are decided independently. Translation Blocking (TB),
Direct Translated P2P (DT), Request Redirect (RR) and Egress Control (EC) on
the Requester-side port decide the Request:
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index 144314ab5fae..a81cd37a1834 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -1448,6 +1448,59 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
}
EXPORT_SYMBOL_IF_KUNIT(calc_map_type_and_dist);
+static unsigned int pci_p2pdma_client_tlp_flags(struct pci_dev *client,
+ unsigned int tlp_flags)
+{
+ if (client->ats_enabled)
+ return tlp_flags | PCI_P2PDMA_TLP_TRANSLATED;
+
+ return tlp_flags & ~PCI_P2PDMA_TLP_TRANSLATED;
+}
+
+/*
+ * A client that translates every address it is handed cannot be handed a bus
+ * address, which it would translate as though it were an IOVA. Its Translated
+ * Requests may still route directly, but a Request it issues without a
+ * translation carries the IOVA to the host bridge, so that route has to work
+ * as well.
+ *
+ * Take the Address Type from @tlp_flags rather than reading the ATS Enable
+ * bit again, so the policy and the class it applies to always agree.
+ */
+static enum pci_p2pdma_map_type
+pci_p2pdma_client_map_type(struct pci_dev *provider, struct pci_dev *client,
+ bool per_mapping, unsigned int tlp_flags,
+ enum pci_p2pdma_map_type type)
+{
+ if (per_mapping || !(tlp_flags & PCI_P2PDMA_TLP_TRANSLATED) ||
+ type != PCI_P2PDMA_MAP_BUS_ADDR)
+ return type;
+
+ pci_dbg(client,
+ "P2PDMA ACS: provider=%s bus address withheld; ATS is enabled for the whole client\n",
+ pci_name(provider));
+
+ if (cpu_supports_p2pdma() ||
+ host_bridge_whitelist(provider, client, false))
+ return PCI_P2PDMA_MAP_THRU_HOST_BRIDGE;
+
+ return PCI_P2PDMA_MAP_NOT_SUPPORTED;
+}
+
+/* Whether the driver of @client called pcim_p2pdma_set_ats_per_mapping(). */
+static bool pci_p2pdma_client_per_mapping(struct pci_dev *client)
+{
+ struct pci_p2pdma *p2pdma;
+ bool per_mapping;
+
+ rcu_read_lock();
+ p2pdma = rcu_dereference(client->p2pdma);
+ per_mapping = p2pdma && p2pdma->ats_per_mapping;
+ rcu_read_unlock();
+
+ return per_mapping;
+}
+
/**
* pci_p2pdma_distance_many - Determine the cumulative distance between
* a p2pdma provider and the clients in use.
@@ -1463,15 +1516,19 @@ EXPORT_SYMBOL_IF_KUNIT(calc_map_type_and_dist);
*
* "compatible" means the provider and the clients are either all behind
* the same PCI root port or the host bridges connected to each of the devices
- * are listed in the 'pci_p2pdma_whitelist'.
+ * are listed in the 'pci_p2pdma_whitelist'. Each client is judged the way
+ * pci_p2pdma_map_type() maps it, so one with ATS enabled for the whole device
+ * also needs a host bridge that supports peer-to-peer DMA.
*/
int pci_p2pdma_distance_many(struct pci_dev *provider, struct device **clients,
int num_clients, bool verbose)
{
- enum pci_p2pdma_map_type map;
+ enum pci_p2pdma_map_type map, type;
bool not_supported = false;
struct pci_dev *pci_client;
+ unsigned int tlp_flags;
int total_dist = 0;
+ bool per_mapping;
int i, distance;
if (num_clients == 0)
@@ -1486,8 +1543,18 @@ int pci_p2pdma_distance_many(struct pci_dev *provider, struct device **clients,
return -1;
}
- map = calc_map_type_and_dist(provider, pci_client, &distance, 0,
- verbose);
+ per_mapping = pci_p2pdma_client_per_mapping(pci_client);
+ tlp_flags = per_mapping ? 0 :
+ pci_p2pdma_client_tlp_flags(pci_client, 0);
+ type = calc_map_type_and_dist(provider, pci_client, &distance,
+ tlp_flags, verbose);
+ map = pci_p2pdma_client_map_type(provider, pci_client,
+ per_mapping, tlp_flags, type);
+ if (verbose && map != type &&
+ map == PCI_P2PDMA_MAP_NOT_SUPPORTED)
+ pci_warn(pci_client,
+ "cannot be used for peer-to-peer DMA with %s: ATS is enabled for the whole device and the host bridge is not whitelisted\n",
+ pci_name(provider));
pci_dev_put(pci_client);
@@ -1794,6 +1861,11 @@ EXPORT_SYMBOL_GPL(pcim_p2pdma_set_ats_per_mapping);
* ACS routes a peer-to-peer transaction by the attributes its TLPs carry, so
* the answer depends on @tlp_flags. A caller that passes flags its traffic
* does not match gets a mapping the fabric will not deliver.
+ *
+ * Only a client whose driver called pcim_p2pdma_set_ats_per_mapping() takes
+ * the Address Type from @tlp_flags. For any other client its ATS Enable bit
+ * decides, and a client that translates every address it is handed never gets
+ * %PCI_P2PDMA_MAP_BUS_ADDR.
*/
static enum pci_p2pdma_map_type
pci_p2pdma_map_type_tlp(struct p2pdma_provider *provider, struct device *dev,
@@ -1805,6 +1877,7 @@ pci_p2pdma_map_type_tlp(struct p2pdma_provider *provider, struct device *dev,
struct pci_p2pdma *p2pdma;
struct pci_dev *client;
bool provider_state;
+ bool per_mapping;
int dist;
if (!pdev->p2pdma) {
@@ -1822,6 +1895,9 @@ pci_p2pdma_map_type_tlp(struct p2pdma_provider *provider, struct device *dev,
client = to_pci_dev(dev);
cache_index = map_types_idx(client);
+ per_mapping = pci_p2pdma_client_per_mapping(client);
+ if (!per_mapping)
+ tlp_flags = pci_p2pdma_client_tlp_flags(client, tlp_flags);
rcu_read_lock();
p2pdma = rcu_dereference(pdev->p2pdma);
@@ -1838,10 +1914,11 @@ pci_p2pdma_map_type_tlp(struct p2pdma_provider *provider, struct device *dev,
pci_p2pdma_map_type_name(type), type, provider_state);
if (type == PCI_P2PDMA_MAP_UNKNOWN)
- return calc_map_type_and_dist(pdev, client, &dist, tlp_flags,
+ type = calc_map_type_and_dist(pdev, client, &dist, tlp_flags,
true);
- return type;
+ return pci_p2pdma_client_map_type(pdev, client, per_mapping, tlp_flags,
+ type);
}
/**
@@ -1850,7 +1927,8 @@ pci_p2pdma_map_type_tlp(struct p2pdma_provider *provider, struct device *dev,
* @dev: Client device that initiates the transfer
*
* Same as pci_p2pdma_map_type_tlp() for a client issuing strictly ordered
- * Requests that carry an Untranslated address.
+ * Requests. Their Address Type is Untranslated unless the client enables ATS
+ * for the whole device.
*/
enum pci_p2pdma_map_type pci_p2pdma_map_type(struct p2pdma_provider *provider,
struct device *dev)
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11 19/19] PCI/P2PDMA: Test the routing of clients with ATS enabled
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
` (17 preceding siblings ...)
2026-10-13 14:25 ` [PATCH v11 18/19] PCI/P2PDMA: Evaluate the ATS path for clients with ATS enabled Leon Romanovsky
@ 2026-10-13 14:25 ` Leon Romanovsky
18 siblings, 0 replies; 23+ messages in thread
From: Leon Romanovsky @ 2026-10-13 14:25 UTC (permalink / raw)
To: Bjorn Helgaas, Logan Gunthorpe, Leon Romanovsky, Jason Gunthorpe,
Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Christian König, Thomas Hellström
Cc: linux-pci, linux-kernel, linux-doc, iommu, Tushar Dave,
linux-media, dri-devel, linaro-mm-sig, linux-rdma, kvm,
Chaitanya Kulkarni, Greg Kroah-Hartman, Jens Axboe,
Alex Williamson, Ankit Agrawal, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Sumit Semwal
From: Leon Romanovsky <leonro@nvidia.com>
A client that enables ATS for the whole device must never be handed a
bus address, and Translation Blocking must reject its peer-to-peer
traffic even when the caller asks for the default class. That has to
hold for answers taken from the cache as well.
Cover those on the fabricated fabric, together with a client that
declared per-mapping ATS keeping the per-class answers and a client
without ATS answering for the Untranslated Requests it will issue.
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
drivers/pci/p2pdma.c | 8 +--
drivers/pci/pci.h | 6 +++
drivers/pci/pci_acs_test.c | 123 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 134 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index a81cd37a1834..df664d43c502 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -1448,14 +1448,15 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
}
EXPORT_SYMBOL_IF_KUNIT(calc_map_type_and_dist);
-static unsigned int pci_p2pdma_client_tlp_flags(struct pci_dev *client,
- unsigned int tlp_flags)
+VISIBLE_IF_KUNIT unsigned int
+pci_p2pdma_client_tlp_flags(struct pci_dev *client, unsigned int tlp_flags)
{
if (client->ats_enabled)
return tlp_flags | PCI_P2PDMA_TLP_TRANSLATED;
return tlp_flags & ~PCI_P2PDMA_TLP_TRANSLATED;
}
+EXPORT_SYMBOL_IF_KUNIT(pci_p2pdma_client_tlp_flags);
/*
* A client that translates every address it is handed cannot be handed a bus
@@ -1467,7 +1468,7 @@ static unsigned int pci_p2pdma_client_tlp_flags(struct pci_dev *client,
* Take the Address Type from @tlp_flags rather than reading the ATS Enable
* bit again, so the policy and the class it applies to always agree.
*/
-static enum pci_p2pdma_map_type
+VISIBLE_IF_KUNIT enum pci_p2pdma_map_type
pci_p2pdma_client_map_type(struct pci_dev *provider, struct pci_dev *client,
bool per_mapping, unsigned int tlp_flags,
enum pci_p2pdma_map_type type)
@@ -1486,6 +1487,7 @@ pci_p2pdma_client_map_type(struct pci_dev *provider, struct pci_dev *client,
return PCI_P2PDMA_MAP_NOT_SUPPORTED;
}
+EXPORT_SYMBOL_IF_KUNIT(pci_p2pdma_client_map_type);
/* Whether the driver of @client called pcim_p2pdma_set_ats_per_mapping(). */
static bool pci_p2pdma_client_per_mapping(struct pci_dev *client)
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 99852cf99732..882a1d8e9316 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1142,6 +1142,12 @@ enum pci_p2pdma_map_type calc_map_type_and_dist(struct pci_dev *provider,
int *dist,
unsigned int tlp_flags,
bool verbose);
+unsigned int pci_p2pdma_client_tlp_flags(struct pci_dev *client,
+ unsigned int tlp_flags);
+enum pci_p2pdma_map_type
+pci_p2pdma_client_map_type(struct pci_dev *provider, struct pci_dev *client,
+ bool per_mapping, unsigned int tlp_flags,
+ enum pci_p2pdma_map_type type);
#endif
#ifdef CONFIG_PCI_QUIRKS
diff --git a/drivers/pci/pci_acs_test.c b/drivers/pci/pci_acs_test.c
index 369444d2a04a..11c86949c3d1 100644
--- a/drivers/pci/pci_acs_test.c
+++ b/drivers/pci/pci_acs_test.c
@@ -1050,6 +1050,125 @@ static void acs_walk_unreadable_control_test(struct kunit *test)
PCI_P2PDMA_MAP_NOT_SUPPORTED);
}
+/*
+ * Route the way pci_p2pdma_map_type_tlp() does once it has a client: the
+ * client decides which flags apply, and the answer is then checked against
+ * what the client can be handed.
+ */
+static enum pci_p2pdma_map_type acs_client_map(struct acs_fabric *f,
+ bool per_mapping,
+ unsigned int tlp_flags)
+{
+ enum pci_p2pdma_map_type type;
+ int dist;
+
+ if (!per_mapping)
+ tlp_flags = pci_p2pdma_client_tlp_flags(f->client, tlp_flags);
+ type = calc_map_type_and_dist(f->provider, f->client, &dist, tlp_flags,
+ false);
+ return pci_p2pdma_client_map_type(f->provider, f->client, per_mapping,
+ tlp_flags, type);
+}
+
+/*
+ * A client with ATS enabled for the whole device translates whatever it is
+ * handed, so it never gets bus addresses even on a direct route, and
+ * Translation Blocking rejects it even when its caller asks for the default
+ * class.
+ */
+static void acs_client_ats_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.client->ats_enabled = 1;
+
+ KUNIT_EXPECT_EQ(test, acs_client_map(&f, false, 0),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+
+ f.client_leaf->acs_cap = 0x100;
+ f.client_leaf->acs_capabilities = PCI_ACS_TB;
+ f.client_leaf_cfg.ctrl = PCI_ACS_TB;
+
+ KUNIT_EXPECT_EQ(test, acs_client_map(&f, false, 0),
+ PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
+/*
+ * The cache holds what the topology allows, filled when the client first
+ * asked. A client whose ATS was enabled after that must still be kept off the
+ * bus addresses the cache recorded.
+ */
+static void acs_client_ats_cached_test(struct kunit *test)
+{
+ enum pci_p2pdma_map_type type[PCI_P2PDMA_TLP_CLASSES], hit;
+ struct acs_fabric f = {};
+ unsigned long cached;
+ unsigned int flags;
+ int dist;
+
+ acs_build_fabric(test, &f);
+ for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++)
+ type[flags] = calc_map_type_and_dist(f.provider, f.client,
+ &dist, flags, false);
+ cached = pci_p2pdma_map_types_pack(type);
+
+ f.client->ats_enabled = 1;
+ flags = pci_p2pdma_client_tlp_flags(f.client, 0);
+ hit = pci_p2pdma_map_types_unpack(cached, flags);
+
+ KUNIT_EXPECT_EQ(test,
+ pci_p2pdma_client_map_type(f.provider, f.client, false,
+ flags, hit),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+}
+
+/*
+ * A client that declared per-mapping ATS picks the Address Type per mapping,
+ * so it keeps the per-class answers even with ATS enabled.
+ */
+static void acs_client_ats_per_mapping_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.client->ats_enabled = 1;
+
+ KUNIT_EXPECT_EQ(test, acs_client_map(&f, true, 0),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+ KUNIT_EXPECT_EQ(test,
+ acs_client_map(&f, true, PCI_P2PDMA_TLP_TRANSLATED),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+
+ f.client_leaf->acs_cap = 0x100;
+ f.client_leaf->acs_capabilities = PCI_ACS_TB;
+ f.client_leaf_cfg.ctrl = PCI_ACS_TB;
+
+ KUNIT_EXPECT_EQ(test, acs_client_map(&f, true, 0),
+ PCI_P2PDMA_MAP_BUS_ADDR);
+ KUNIT_EXPECT_EQ(test,
+ acs_client_map(&f, true, PCI_P2PDMA_TLP_TRANSLATED),
+ PCI_P2PDMA_MAP_NOT_SUPPORTED);
+}
+
+/*
+ * A client with ATS disabled cannot issue Translated Requests, so a caller
+ * asking about them gets the answer for the Untranslated ones it will issue.
+ */
+static void acs_client_no_ats_test(struct kunit *test)
+{
+ struct acs_fabric f = {};
+
+ acs_build_fabric(test, &f);
+ f.dn1->acs_cap = 0x100;
+ f.dn1->acs_capabilities = PCI_ACS_RR | PCI_ACS_DT;
+ f.dn1_cfg.ctrl = PCI_ACS_RR | PCI_ACS_DT;
+
+ KUNIT_EXPECT_EQ(test,
+ acs_client_map(&f, false, PCI_P2PDMA_TLP_TRANSLATED),
+ PCI_P2PDMA_MAP_THRU_HOST_BRIDGE);
+}
+
static struct kunit_case pci_acs_test_cases[] = {
KUNIT_CASE_PARAM(pci_acs_p2pdma_request_test,
acs_request_gen_params),
@@ -1088,6 +1207,10 @@ static struct kunit_case pci_acs_test_cases[] = {
KUNIT_CASE(acs_walk_tb_above_divergence_test),
KUNIT_CASE(acs_walk_unreadable_control_test),
KUNIT_CASE(acs_map_types_pack_test),
+ KUNIT_CASE(acs_client_ats_test),
+ KUNIT_CASE(acs_client_ats_cached_test),
+ KUNIT_CASE(acs_client_ats_per_mapping_test),
+ KUNIT_CASE(acs_client_no_ats_test),
{}
};
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v11 09/19] PCI/P2PDMA: Route Relaxed Ordering Completions directly
2026-10-13 14:25 ` [PATCH v11 09/19] PCI/P2PDMA: Route Relaxed Ordering Completions directly Leon Romanovsky
@ 2026-10-13 14:37 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-10-13 14:37 UTC (permalink / raw)
To: Leon Romanovsky; +Cc: linux-pci, dri-devel, linux-rdma, kvm, media-ci
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] pci/p2pdma: false positive warning if Relaxed Ordering route succeeds
--
commit e6e15dbbdb2b5d6dee258b1197f71025a221015f
Author: Leon Romanovsky <leon@kernel.org>
PCI/P2PDMA: Route Relaxed Ordering Completions directly
ACS P2P Completion Redirect leaves Completions carrying Relaxed Ordering
alone, per PCIe r7.0 sec 6.12.1.1. A Completion copies that attribute from
its Request, so a client setting it on Non-Posted Requests need not use the
host bridge solely because Completion Redirect is enabled.
Route Completions directly for PCI_P2PDMA_TLP_RELAXED_CPL unless a device
quirk redirects them or the Completer lacks an internal route to its
sibling. Neither restriction promises a Relaxed Ordering exception. No
caller selects these classes yet.
> diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
> index 9d416329aa4b8..785e4668a2af5 100644
> --- a/drivers/pci/p2pdma.c
> +++ b/drivers/pci/p2pdma.c
> @@ -580,11 +580,15 @@ pci_acs_p2pdma_request(u16 ctrl, unsigned int tlp_flags)
> /*
> * Decide how a peer-to-peer Completion at an ACS-capable ingress port routes.
> * PCIe r7.0 sec 6.12.1.1: no ACS control other than P2P Completion Redirect
> - * affects a Completion.
> + * affects a Completion, and that one leaves Completions carrying the Relaxed
> + * Ordering attribute alone.
> */
> static enum pci_acs_p2pdma_state
> pci_acs_p2pdma_completion(u16 ctrl, unsigned int tlp_flags)
> {
> + if (tlp_flags & PCI_P2PDMA_TLP_RELAXED_CPL)
> + return PCI_ACS_P2PDMA_DIRECT;
> +
[Severity: Low]
Now that Relaxed Ordering completions can route directly while strict ordering
might still redirect, does this cause a false positive warning in
calc_map_type_and_dist() in drivers/pci/p2pdma.c?
calc_map_type_and_dist() iterates over all PCI_P2PDMA_TLP_CLASSES and sets
host_fallback = true if any class requires the host bridge:
for (flags = 0; flags < PCI_P2PDMA_TLP_CLASSES; flags++) {
map_type[flags] = pci_p2pdma_route(&path, flags);
if (map_type[flags] == PCI_P2PDMA_MAP_THRU_HOST_BRIDGE)
host_fallback = true;
}
Later, if the host bridge does not support P2PDMA, it emits an unconditional
warning for the device:
if (!cpu_p2pdma && !host_whitelisted) {
if (verbose)
pci_warn(client, "cannot be used for peer-to-peer DMA...");
}
If a caller successfully requests a Relaxed Ordering route that maps directly,
but the strict ordering route required the host bridge, will this
unconditional warning mislead users into thinking P2P DMA is completely
broken for their setup?
> return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT :
> PCI_ACS_P2PDMA_DIRECT;
> }
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261013-fix-p2p-acs-v4-0-v11-0-72cf8c96e617@nvidia.com?part=9
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v11 01/19] PCI/P2PDMA: Honor device-specific ACS redirection
2026-10-13 14:25 ` [PATCH v11 01/19] PCI/P2PDMA: Honor device-specific ACS redirection Leon Romanovsky
@ 2026-10-13 14:44 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-10-13 14:44 UTC (permalink / raw)
To: Leon Romanovsky; +Cc: linux-pci, dri-devel, linux-rdma, kvm, media-ci
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] pci/p2pdma: deadlock in calc_map_type_and_dist() calling xa_store() from softirq
--
commit 6120f97b22446453f3e4fa09135e1a44135ecfe8
Author: Leon Romanovsky <leon@kernel.org>
PCI/P2PDMA: Honor device-specific ACS redirection
This commit updates PCI P2PDMA to respect device-specific ACS redirection
quirks for switches and ports that redirect peer-to-peer traffic without a
standard ACS capability. It queries quirks to set Request and Completion
Redirect flags and modifies how affected ports are warned about.
> diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
> index 45260423dfb09..ba94b0f339285 100644
> --- a/drivers/pci/p2pdma.c
> +++ b/drivers/pci/p2pdma.c
[ ... ]
> @@ -782,15 +867,8 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
> goto done;
> }
>
> - if (verbose) {
> - /* 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",
> - seq_buf_str(&acs_list));
> - }
> + if (verbose)
> + pci_p2pdma_warn_redir(client, provider, &acs_list, &quirk_list);
>
> map_through_host_bridge:
> if (!cpu_supports_p2pdma() &&
[Severity: High]
Does calling xa_store() further down in this function risk a deadlock?
The map_types xarray is initialized with a plain xa_init() (without
XA_FLAGS_LOCK_IRQ) in pcim_p2pdma_init(). This means its internal lock
operates as a simple process-context spin_lock.
Since calc_map_type_and_dist() can be called from softirq/hardirq context
during P2P DMA mapping (e.g., via dma_map_sg()), and xa_store() takes the
xarray lock without disabling interrupts, a process-context thread interrupted
by a DMA mapping on the same CPU could cause a deadlock if they both try to
access this xarray:
drivers/pci/p2pdma.c:calc_map_type_and_dist() {
...
done:
rcu_read_lock();
p2pdma = rcu_dereference(provider->p2pdma);
if (p2pdma)
xa_store(&p2pdma->map_types, map_types_idx(client),
xa_mk_value(map_type), GFP_ATOMIC);
rcu_read_unlock();
return map_type;
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261013-fix-p2p-acs-v4-0-v11-0-72cf8c96e617@nvidia.com?part=1
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v11 10/19] PCI/P2PDMA: Reject Translated Requests blocked by Translation Blocking
2026-10-13 14:25 ` [PATCH v11 10/19] PCI/P2PDMA: Reject Translated Requests blocked by Translation Blocking Leon Romanovsky
@ 2026-10-13 14:49 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-10-13 14:49 UTC (permalink / raw)
To: Leon Romanovsky; +Cc: linux-pci, dri-devel, linux-rdma, kvm, media-ci
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] pci/p2pdma: deadlock in calc_map_type_and_dist() due to non-IRQ-safe xa_store()
--
commit f019da119f00a99a75cbfe2deec0db131a89232d
Author: Leon Romanovsky <leon@kernel.org>
PCI/P2PDMA: Reject Translated Requests blocked by Translation Blocking
ACS Translation Blocking rejects upstream Memory Requests with a Translated
address before any peer-routing controls apply, per PCIe r7.0 sec 6.12.1.1.
P2PDMA ignores that bit and can report a route whose ports reject the
Requester's address type.
Add PCI_ACS_P2PDMA_BLOCKED and scan the ports traversed by each route. A
direct route passes only ports below the divergence; a host-bridge route
also passes the common port and its ancestors. Reject the affected class
without falling back to another route, and preserve blocking precedence
over internal-route restrictions.
> diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
> index 785e4668a2af5..fc4dea28c8606 100644
> --- a/drivers/pci/p2pdma.c
> +++ b/drivers/pci/p2pdma.c
[ ... ]
> @@ -1162,6 +1252,11 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
> }
> }
>
> + path.tb_on_path = pci_p2pdma_path_blocks_translation(client, a);
> + if (b_child)
> + path.tb_above_divergence =
> + pci_p2pdma_path_blocks_translation(b_child, NULL);
> +
> /*
> * The walk and the config reads above serve every class; only the
> * decision below depends on the kind of TLP being routed.
[Severity: High]
Does xa_store() use an IRQ-safe lock here?
The calc_map_type_and_dist() function caches routing results by calling
xa_store() later in this function:
xa_store(&p2pdma->map_types, map_types_idx(client),
xa_mk_value(pci_p2pdma_map_types_pack(map_type)),
GFP_ATOMIC);
xa_store() implicitly acquires xa->xa_lock using a plain spin_lock(), which
does not disable hardware or software interrupts.
Since calc_map_type_and_dist() is reachable from process context
(e.g., during target configuration via pci_p2pmem_find_many()), what happens
if a P2PDMA IO mapping request is processed in softirq or hardirq
(e.g., dma_map_sg -> pci_p2pdma_map_type_tlp during an NVMe-oF RDMA
completion) on the same CPU and experiences a cache miss?
It seems it will interrupt the process context, re-enter
calc_map_type_and_dist(), and attempt to acquire the same xa->xa_lock via
xa_store().
Could this result in a hard deadlock since the lock is already held by the
interrupted thread on the same CPU?
Additionally, the XArray appears to be initialized without XA_FLAGS_LOCK_IRQ
in pcim_p2pdma_init():
xa_init(&p2p->map_types);
To safely update the XArray from contexts that include interrupts, should the
driver acquire the lock with IRQs disabled (e.g., using
xa_lock_irqsave(&p2pdma->map_types, flags); __xa_store(...);
xa_unlock_irqrestore(...))?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261013-fix-p2p-acs-v4-0-v11-0-72cf8c96e617@nvidia.com?part=10
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-10-13 14:49 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-13 14:25 [PATCH v11 00/19] PCI/P2PDMA: Route peer-to-peer DMA by TLP class Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 01/19] PCI/P2PDMA: Honor device-specific ACS redirection Leon Romanovsky
2026-10-13 14:44 ` sashiko-bot
2026-10-13 14:25 ` [PATCH v11 02/19] PCI/P2PDMA: Document the TLP attribute assumptions Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 03/19] PCI/P2PDMA: Derive routing from directional ACS controls Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 04/19] PCI: Reject unreadable ACS controls in isolation checks Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 05/19] PCI/P2PDMA: Evaluate ACS controls at the path divergence Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 06/19] PCI/P2PDMA: Document directional ACS routing Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 07/19] PCI/P2PDMA: Collect the path's ACS controls before deciding Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 08/19] PCI/P2PDMA: Answer routing per TLP class Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 09/19] PCI/P2PDMA: Route Relaxed Ordering Completions directly Leon Romanovsky
2026-10-13 14:37 ` sashiko-bot
2026-10-13 14:25 ` [PATCH v11 10/19] PCI/P2PDMA: Reject Translated Requests blocked by Translation Blocking Leon Romanovsky
2026-10-13 14:49 ` sashiko-bot
2026-10-13 14:25 ` [PATCH v11 11/19] PCI/P2PDMA: Route Translated Requests under Direct Translated P2P Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 12/19] PCI/P2PDMA: Log detailed ACS routing diagnostics Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 13/19] PCI/P2PDMA: Add KUnit tests for the ACS routing decisions Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 14/19] PCI/P2PDMA: Test the ACS P2P routing walk Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 15/19] PCI: Add KUnit coverage for ACS isolation checks Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 16/19] PCI/P2PDMA: Document TLP-class routing Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 17/19] PCI/P2PDMA: Let a client declare that it selects ATS per mapping Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 18/19] PCI/P2PDMA: Evaluate the ATS path for clients with ATS enabled Leon Romanovsky
2026-10-13 14:25 ` [PATCH v11 19/19] PCI/P2PDMA: Test the routing of " Leon Romanovsky
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox