[PATCH v4 14/18] PCI/P2PDMA: Extract pure ACS routing decision helpers
Leon Romanovsky <[email protected]>
| Newsgroups | org.kernel.vger.linux-pci,dev.linux.lists.iommu,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Leon Romanovsky <[email protected]> The ACS routing decision and Egress Control Vector size calculation are mixed with config-space I/O, which makes their branch-heavy behavior difficult to exercise independently. Extract pci_acs_p2pdma_decision() with explicit Request or Completion type, target availability, and vector state inputs. Extract pci_acs_egress_port_valid() for the encoded vector-size boundary. Keep config access in their callers and expose only the pure helpers under CONFIG_KUNIT. Tested-by: Tushar Dave <[email protected]> Signed-off-by: Leon Romanovsky <[email protected]> --- drivers/pci/p2pdma.c | 70 ++++++++++++++++++++++++++++------------------------ drivers/pci/pci.c | 26 ++++++++++++++++--- drivers/pci/pci.h | 23 +++++++++++++++++ 3 files changed, 83 insertions(+), 36 deletions(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index bcbbdbd8ee47..632f2d8c51e0 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -538,22 +538,45 @@ 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 TLP at an + * ACS-capable ingress port routes, given its ACS Control register @ctrl and + * whether it is a Request or Completion (@tlp). + * + * Egress Control applies only to Requests 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 the Request to route directly. @has_target and @egress + * describe that target and its Egress Control Vector bit. Completion routing + * depends only on Completion Redirect. + */ +VISIBLE_IF_KUNIT enum pci_acs_p2pdma_state +pci_acs_p2pdma_decision(u16 ctrl, enum pci_acs_p2pdma_tlp tlp, + bool has_target, int egress) +{ + if (tlp == PCI_ACS_P2PDMA_TLP_COMPLETION) + return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT : + PCI_ACS_P2PDMA_DIRECT; -enum pci_acs_p2pdma_tlp { - PCI_ACS_P2PDMA_TLP_REQUEST, - PCI_ACS_P2PDMA_TLP_COMPLETION, -}; + if (!has_target || !(ctrl & PCI_ACS_EC)) + return ctrl & PCI_ACS_RR ? + 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 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, enum pci_acs_p2pdma_tlp tlp) { - int pos, ret; + int pos, egress = 0; u16 ctrl; pos = pdev->acs_cap; @@ -563,29 +586,12 @@ pci_acs_p2pdma_state(struct pci_dev *pdev, struct pci_dev *target, if (pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl)) return PCI_ACS_P2PDMA_NOT_SUPPORTED; - if (tlp == PCI_ACS_P2PDMA_TLP_COMPLETION) - return ctrl & PCI_ACS_CR ? PCI_ACS_P2PDMA_REDIRECT : - PCI_ACS_P2PDMA_DIRECT; - - /* EC applies only to Requests where the target is known. */ - if (!target || !(ctrl & PCI_ACS_EC)) - return ctrl & PCI_ACS_RR ? - PCI_ACS_P2PDMA_REDIRECT : PCI_ACS_P2PDMA_DIRECT; + /* Egress Control applies only to Requests with a known target. */ + if (tlp == PCI_ACS_P2PDMA_TLP_REQUEST && target && + (ctrl & PCI_ACS_EC)) + egress = pci_acs_egress_ctrl_is_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 the Request to route directly. - */ - ret = pci_acs_egress_ctrl_is_set(pdev, target); - if (ret < 0) - return PCI_ACS_P2PDMA_NOT_SUPPORTED; - if (ret) - return ctrl & PCI_ACS_RR ? PCI_ACS_P2PDMA_REDIRECT : - PCI_ACS_P2PDMA_NOT_SUPPORTED; - - return PCI_ACS_P2PDMA_DIRECT; + return pci_acs_p2pdma_decision(ctrl, tlp, !!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 a633f473590f..d900fdb6f37d 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_is_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_is_set(struct pci_dev *pdev, struct pci_dev *target) { - unsigned int vector_size; u32 lnkcap, vector; u8 target_port; int ret; @@ -3597,10 +3616,8 @@ int pci_acs_egress_ctrl_is_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, @@ -3612,6 +3629,7 @@ int pci_acs_egress_ctrl_is_set(struct pci_dev *pdev, struct pci_dev *target) return !!(vector & BIT(target_port % 32)); } +EXPORT_SYMBOL_IF_KUNIT(pci_acs_egress_ctrl_is_set); static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags, enum pci_acs_scope scope) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 32394e349766..4593fb43f96c 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> @@ -1079,6 +1080,28 @@ static inline bool pci_acs_rr_ineffective(u32 ctrl, u16 acs_flags, } int pci_acs_egress_ctrl_is_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 */ +}; + +enum pci_acs_p2pdma_tlp { + PCI_ACS_P2PDMA_TLP_REQUEST, + PCI_ACS_P2PDMA_TLP_COMPLETION, +}; + +#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, enum pci_acs_p2pdma_tlp tlp, + bool has_target, int egress); +#endif #ifdef CONFIG_PCI_QUIRKS int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags, enum pci_acs_scope scope); -- 2.55.0