[PATCH 05/13] PCI: Add ACS egress control vector accessor
Leon Romanovsky <[email protected]> Sun, 2 Aug 2026 18:09:43 +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]> Whether ACS P2P Egress Control routes, redirects, or blocks a peer-to-peer request depends on the Egress Control Vector bit for the target port, not on the enable bit alone (PCIe r7.0, sec 6.12.3). Provide a helper to read that bit for a peer Root or Switch Downstream Port. Report an unreadable or uncovered vector as an error rather than as a clear bit, so callers do not mistake it for permission to route directly. Signed-off-by: Leon Romanovsky <[email protected]> --- drivers/pci/pci.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci.h | 1 + 2 files changed, 59 insertions(+) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index c586481949dc..154eb08036ad 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3545,6 +3545,64 @@ void pci_configure_ari(struct pci_dev *dev) } } +/* + * PCIe r7.0, sec 7.7.12: only for Root Ports and Switch Downstream Ports does + * each Egress Control Vector bit correspond to a Port Number. Elsewhere the + * vector is indexed by Function or Function Group Number, so a Link + * Capabilities Port Number must not be used to select a bit. + * + * pcie_downstream_port() is too permissive here because it also accepts a + * PCI/PCI-X to PCIe Bridge. + */ +static bool pci_acs_egress_vector_port(const struct pci_dev *dev) +{ + int type = pci_pcie_type(dev); + + return type == PCI_EXP_TYPE_ROOT_PORT || + type == PCI_EXP_TYPE_DOWNSTREAM; +} + +/** + * pci_acs_egress_ctrl_set - Read an ACS Egress Control Vector bit + * @pdev: ingress Root or Switch Downstream Port + * @target: target Root or Switch Downstream Port + * + * Return: 1 if @pdev's Egress Control Vector bit for @target is set, 0 if + * it is clear, or a negative errno if the bit cannot be determined. + */ +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; + + if (!(pdev->acs_capabilities & PCI_ACS_EC) || + !pci_acs_egress_vector_port(pdev) || + !pci_acs_egress_vector_port(target)) + return -EOPNOTSUPP; + + ret = pcie_capability_read_dword(target, PCI_EXP_LNKCAP, &lnkcap); + if (ret) + 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) + return -ERANGE; + + ret = pci_read_config_dword(pdev, + pdev->acs_cap + PCI_ACS_EGRESS_CTL_V + + (target_port / 32) * sizeof(vector), + &vector); + if (ret) + return pcibios_err_to_errno(ret); + + return !!(vector & BIT(target_port % 32)); +} + static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) { bool request_redirect = acs_flags & PCI_ACS_RR; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 4469e1a77f3c..5da067f8abc3 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -1045,6 +1045,7 @@ 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); #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