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 into pci.h.
Signed-off-by: Leon Romanovsky leonro@nvidia.com --- drivers/pci/Kconfig | 15 ++++++ drivers/pci/Makefile | 1 + drivers/pci/p2pdma.c | 13 ++--- drivers/pci/pci.h | 16 ++++++ drivers/pci/pci_acs_test.c | 121 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 157 insertions(+), 9 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 60bd9ec5f993..955e3b41ec1f 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -515,13 +515,6 @@ static struct pci_dev *find_parent_pci_dev(struct device *dev) return NULL; }
-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. @@ -531,7 +524,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) { @@ -558,6 +551,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. @@ -565,7 +559,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) @@ -574,6 +568,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..c0d2711d73f9 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,21 @@ 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_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..ce6b9375da36 --- /dev/null +++ b/drivers/pci/pci_acs_test.c @@ -0,0 +1,121 @@ +// 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_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");