[PATCH 10/13] PCI/P2PDMA: Extract pure ACS routing decision helpers
Leon Romanovsky <[email protected]> Sun, 2 Aug 2026 18:09:48 +0300
| Newsgroups | org.kernel.vger.linux-pci,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Leon Romanovsky <[email protected]> The ACS Egress Control routing decision (PCIe r7.0, sec 6.12.3, table 6-11) and the Egress Control Vector Size rule were embedded in functions that also perform config-space I/O and walk the PCIe hierarchy. That made the branch-heavy logic -- in particular the paths that require an Egress Control Vector, which are unreachable on most hardware -- difficult to exercise in isolation. Factor the logic into two pure helpers: - pci_acs_p2pdma_decision() maps the ACS control word, whether the target port is known, and the target's Egress Control Vector bit to a routing state. - pci_acs_egress_port_valid() applies the "a vector size of 0 encodes 256 bits" rule to decide whether a target port is within the vector. pci_acs_p2pdma_state() and pci_acs_egress_ctrl_set() now call these. No functional change intended: pci_acs_egress_ctrl_set() still checks the port range before reading the vector DWORD. The helpers are exposed under CONFIG_KUNIT via VISIBLE_IF_KUNIT so the following patch can unit-test them. Signed-off-by: Leon Romanovsky <[email protected]> Assisted-by: Claude Opus 4.8 <[email protected]> --- drivers/pci/p2pdma.c | 65 +++++++++++++++++++++++++++++----------------------- drivers/pci/pci.c | 26 +++++++++++++++++---- drivers/pci/pci.h | 17 ++++++++++++++ 3 files changed, 75 insertions(+), 33 deletions(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index dbccc1d0c4e3..62e715a4ac84 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -491,46 +491,53 @@ static struct pci_dev *find_parent_pci_dev(struct device *dev) return NULL; } -enum pci_acs_p2pdma_state { - PCI_ACS_P2PDMA_DIRECT, - PCI_ACS_P2PDMA_REDIRECT, - PCI_ACS_P2PDMA_NOT_SUPPORTED, -}; +/* + * PCIe r7.0, sec 6.12.3, table 6-11: decide how a peer-to-peer request at an + * ACS-capable ingress port routes, given its Egress Control register @ctrl, + * whether the target port is known (@has_target), and that target's Egress + * Control Vector bit (@egress: 1 set, 0 clear, negative if it could not be + * read). + * + * Egress Control applies only where the target is known (the path divergence). + * There, a set vector bit redirects the request only when Request Redirect is + * set; with Request Redirect clear it is an ACS Violation. A clear vector bit + * permits direct routing, subject to Completion Redirect. + */ +VISIBLE_IF_KUNIT enum pci_acs_p2pdma_state +pci_acs_p2pdma_decision(u16 ctrl, bool has_target, int egress) +{ + if (!has_target || !(ctrl & PCI_ACS_EC)) + return ctrl & (PCI_ACS_RR | PCI_ACS_CR) ? + PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT; + + if (egress < 0) + return PCI_ACS_P2PDMA_NOT_SUPPORTED; + if (egress) + return ctrl & PCI_ACS_RR ? PCI_ACS_P2PDMA_REDIRECT : + PCI_ACS_P2PDMA_NOT_SUPPORTED; + + return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT : + PCI_ACS_P2PDMA_DIRECT; +} +EXPORT_SYMBOL_IF_KUNIT(pci_acs_p2pdma_decision); static enum pci_acs_p2pdma_state pci_acs_p2pdma_state(struct pci_dev *pdev, struct pci_dev *target) { - int pos, ret; + int egress = 0; u16 ctrl; - pos = pdev->acs_cap; - if (!pos) + if (!pdev->acs_cap) return PCI_ACS_P2PDMA_DIRECT; - if (pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl)) + if (pci_read_config_word(pdev, pdev->acs_cap + PCI_ACS_CTRL, &ctrl)) return PCI_ACS_P2PDMA_NOT_SUPPORTED; - /* EC applies only at the path divergence where the target is known. */ - if (!target || !(ctrl & PCI_ACS_EC)) - return ctrl & (PCI_ACS_RR | PCI_ACS_CR) ? - PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT; + /* Egress Control is evaluated only where the target is known. */ + if (target && (ctrl & PCI_ACS_EC)) + egress = pci_acs_egress_ctrl_set(pdev, target); - /* - * PCIe r7.0, sec 6.12.3, table 6-11: a set Egress Control Vector - * bit redirects the request only when Request Redirect is set. With - * Request Redirect clear, the request is handled as an ACS Violation. - * A clear vector bit permits direct routing, subject to Completion - * Redirect. - */ - ret = pci_acs_egress_ctrl_set(pdev, target); - if (ret < 0) - return PCI_ACS_P2PDMA_NOT_SUPPORTED; - if (ret) - return ctrl & PCI_ACS_RR ? PCI_ACS_P2PDMA_REDIRECT : - PCI_ACS_P2PDMA_NOT_SUPPORTED; - - return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT : - PCI_ACS_P2PDMA_DIRECT; + return pci_acs_p2pdma_decision(ctrl, target, egress); } static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index bc1c3b68c131..c9e64003f8d2 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3562,6 +3562,26 @@ static bool pci_acs_egress_vector_port(const struct pci_dev *dev) type == PCI_EXP_TYPE_DOWNSTREAM; } +/** + * pci_acs_egress_port_valid - Is a target port within the Egress Control Vector + * @acs_caps: the ingress port's ACS Capability register + * @target_port: the target Downstream Port number + * + * The Egress Control Vector Size occupies bits 15:8 of the ACS Capability + * register (PCIe r7.0, sec 7.7.12). A size of 0 encodes 256 bits, so + * every port number is addressable. + * + * Return: %true if @target_port has a bit in the Egress Control Vector. + */ +VISIBLE_IF_KUNIT +bool pci_acs_egress_port_valid(u16 acs_caps, u8 target_port) +{ + unsigned int vector_size = acs_caps >> 8; + + return !vector_size || target_port < vector_size; +} +EXPORT_SYMBOL_IF_KUNIT(pci_acs_egress_port_valid); + /** * pci_acs_egress_ctrl_set - Read an ACS Egress Control Vector bit * @pdev: ingress Root or Switch Downstream Port @@ -3572,7 +3592,6 @@ static bool pci_acs_egress_vector_port(const struct pci_dev *dev) */ int pci_acs_egress_ctrl_set(struct pci_dev *pdev, struct pci_dev *target) { - unsigned int vector_size; u32 lnkcap, vector; u8 target_port; int ret; @@ -3587,10 +3606,8 @@ int pci_acs_egress_ctrl_set(struct pci_dev *pdev, struct pci_dev *target) return pcibios_err_to_errno(ret); target_port = FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap); - vector_size = pdev->acs_capabilities >> 8; - /* An Egress Control Vector Size of 0 encodes 256 bits. */ - if (vector_size && target_port >= vector_size) + if (!pci_acs_egress_port_valid(pdev->acs_capabilities, target_port)) return -ERANGE; ret = pci_read_config_dword(pdev, @@ -3602,6 +3619,7 @@ int pci_acs_egress_ctrl_set(struct pci_dev *pdev, struct pci_dev *target) return !!(vector & BIT(target_port % 32)); } +EXPORT_SYMBOL_IF_KUNIT(pci_acs_egress_ctrl_set); static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) { diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 5da067f8abc3..6f40b43d3c3f 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/align.h> #include <linux/bitfield.h> #include <linux/pci.h> @@ -1046,6 +1047,22 @@ 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); int pci_acs_egress_ctrl_set(struct pci_dev *pdev, struct pci_dev *target); + +/* + * Peer-to-peer routing decision for an ACS-capable ingress port, per + * PCIe r7.0, sec 6.12.3, table 6-11. + */ +enum pci_acs_p2pdma_state { + PCI_ACS_P2PDMA_DIRECT, /* peer-to-peer permitted directly */ + PCI_ACS_P2PDMA_REDIRECT, /* redirected upstream to host bridge */ + PCI_ACS_P2PDMA_NOT_SUPPORTED, /* no usable peer-to-peer route */ +}; + +#if IS_ENABLED(CONFIG_KUNIT) +bool pci_acs_egress_port_valid(u16 acs_caps, u8 target_port); +enum pci_acs_p2pdma_state pci_acs_p2pdma_decision(u16 ctrl, bool has_target, + int egress); +#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); -- 2.55.0