[PATCH v4 16/18] PCI/P2PDMA: Add KUnit coverage for the ACS P2P routing walk
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]> calc_map_type_and_dist() must apply Request controls to the client-side divergence port and Completion controls to the provider-side port. Pure helper tests cannot verify that topology assignment. Build a nested fake PCIe switch fabric and exercise the full walk. Cover direct routing, Request and Completion redirects, Egress Control outcomes, and the asymmetric provider-RR/client-CR configuration. Also verify that redirect bits below the divergence do not affect the direct path and an ACS Violation without a redirect is rejected. Tested-by: Tushar Dave <[email protected]> Signed-off-by: Leon Romanovsky <[email protected]> --- drivers/pci/p2pdma.c | 3 +- drivers/pci/pci.h | 4 + drivers/pci/pci_acs_test.c | 295 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 301 insertions(+), 1 deletion(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 632f2d8c51e0..e54901d21d8a 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -784,7 +784,7 @@ static unsigned long map_types_idx(struct pci_dev *client) * 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, bool verbose) { @@ -916,6 +916,7 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, rcu_read_unlock(); return map_type; } +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 4593fb43f96c..4d0f4478158f 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -6,6 +6,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; @@ -1101,6 +1102,9 @@ 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); +enum pci_p2pdma_map_type calc_map_type_and_dist(struct pci_dev *provider, + struct pci_dev *client, + int *dist, bool verbose); #endif #ifdef CONFIG_PCI_QUIRKS int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags, diff --git a/drivers/pci/pci_acs_test.c b/drivers/pci/pci_acs_test.c index aec7d7636f70..dd1054bb4f09 100644 --- a/drivers/pci/pci_acs_test.c +++ b/drivers/pci/pci_acs_test.c @@ -10,6 +10,7 @@ #include <kunit/test.h> #include <linux/pci.h> +#include <linux/pci-p2pdma.h> #include <linux/pci_regs.h> #include "pci.h" @@ -416,6 +417,292 @@ static void acs_egress_root_port_test(struct kunit *test) 1); } +/* + * calc_map_type_and_dist(): drive the full provider->client hierarchy walk + * over a fabricated PCIe fabric matching the canonical "two devices behind one + * switch" tree: + * + * host bridge / root bus + * Root Port + * Switch Upstream Port + * Switch Downstream Port 0 + * Nested Switch -- provider + * Switch Downstream Port 1 + * Nested Switch -- client + * + * A fake pci_ops answers the ACS Control, Egress Control Vector and LNKCAP + * reads for the downstream ports, so the ACS Egress Control evaluated at the + * path divergence (Downstream Port 1 targeting Downstream Port 0) decides the + * mapping without any real hardware. + */ + +struct acs_dn_cfg { + u16 acs_ctrl; /* ACS Control register value */ + u8 port; /* this port's LNKCAP Port Number */ + u32 egress[8]; /* Egress Control Vector (256 bits) */ +}; + +struct acs_fabric { + 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_dn_cfg dn0_cfg; + struct acs_dn_cfg dn1_cfg; + struct acs_dn_cfg provider_leaf_cfg; + struct acs_dn_cfg client_leaf_cfg; +}; + +static void acs_dn_read(struct pci_dev *dn, struct acs_dn_cfg *c, + int where, int size, u32 *val) +{ + int vec = dn->acs_cap + PCI_ACS_EGRESS_CTL_V; + + if (size == 4 && where == dn->pcie_cap + PCI_EXP_LNKCAP) + *val = FIELD_PREP(PCI_EXP_LNKCAP_PN, c->port); + else if (dn->acs_cap && size == 2 && where == dn->acs_cap + PCI_ACS_CTRL) + *val = c->acs_ctrl; + else if (dn->acs_cap && size == 4 && + where >= vec && where < vec + (int)sizeof(c->egress)) + *val = c->egress[(where - vec) / 4]; +} + +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->dn0->bus && devfn == f->dn0->devfn) + acs_dn_read(f->dn0, &f->dn0_cfg, where, size, val); + else if (bus == f->dn1->bus && devfn == f->dn1->devfn) + acs_dn_read(f->dn1, &f->dn1_cfg, where, size, val); + else if (bus == f->provider_leaf->bus && + devfn == f->provider_leaf->devfn) + acs_dn_read(f->provider_leaf, &f->provider_leaf_cfg, where, + size, val); + else if (bus == f->client_leaf->bus && + devfn == f->client_leaf->devfn) + acs_dn_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 = ACS_TEST_PCIE_FLAGS(pcie_type); + 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 *rootport, *swup, *provider_swup, *client_swup; + 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); /* root bus */ + /* 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); + 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) +{ + int dist; + + return calc_map_type_and_dist(f->provider, f->client, &dist, false); +} + +static void acs_walk_bus_addr_test(struct kunit *test) +{ + struct acs_fabric f = {}; + + acs_build_fabric(test, &f); + /* No ACS on the path: peer-to-peer is allowed directly. */ + KUNIT_EXPECT_EQ(test, acs_walk_map(&f), PCI_P2PDMA_MAP_BUS_ADDR); +} + +static void acs_walk_ec_violation_test(struct kunit *test) +{ + struct acs_fabric f = {}; + + acs_build_fabric(test, &f); + /* + * Client Downstream Port 1 has Egress Control enabled with the vector bit + * for provider Downstream Port 0 set and Request Redirect clear: an ACS + * Violation. The direct path is unusable, and no redirect establishes an + * upstream route. + */ + f.dn1->acs_cap = 0x100; + f.dn1->acs_capabilities = PCI_ACS_EC | (64 << 8); + f.dn1_cfg.acs_ctrl = PCI_ACS_EC; + f.dn0_cfg.port = 5; + f.dn1_cfg.egress[0] = BIT(5); + + KUNIT_EXPECT_EQ(test, acs_walk_map(&f), + PCI_P2PDMA_MAP_NOT_SUPPORTED); +} + +static void acs_walk_ec_vector_clear_test(struct kunit *test) +{ + struct acs_fabric f = {}; + + acs_build_fabric(test, &f); + /* Egress Control enabled but the provider vector bit is clear. */ + f.dn1->acs_cap = 0x100; + f.dn1->acs_capabilities = PCI_ACS_EC | (64 << 8); + f.dn1_cfg.acs_ctrl = PCI_ACS_EC; + f.dn0_cfg.port = 5; /* egress vector left all-zero */ + + KUNIT_EXPECT_EQ(test, acs_walk_map(&f), PCI_P2PDMA_MAP_BUS_ADDR); +} + +static void acs_walk_request_redirect_test(struct kunit *test) +{ + struct acs_fabric f = {}; + + acs_build_fabric(test, &f); + /* Client Request Redirect sends traffic through the host bridge. */ + f.dn1->acs_cap = 0x100; + f.dn1->acs_capabilities = PCI_ACS_RR; + f.dn1_cfg.acs_ctrl = PCI_ACS_RR; + + /* The Google root port is whitelisted, so the host-bridge path is OK. */ + KUNIT_EXPECT_EQ(test, acs_walk_map(&f), + 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); + /* Provider Completion Redirect sends traffic through the host bridge. */ + f.dn0->acs_cap = 0x100; + f.dn0->acs_capabilities = PCI_ACS_CR; + f.dn0_cfg.acs_ctrl = PCI_ACS_CR; + + /* The Google root port is whitelisted, so the host-bridge path is OK. */ + KUNIT_EXPECT_EQ(test, acs_walk_map(&f), + 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); + /* Provider RR/EC and client CR act on the reverse transaction paths. */ + f.dn0->acs_cap = 0x100; + f.dn0->acs_capabilities = PCI_ACS_RR | PCI_ACS_EC | (64 << 8); + f.dn0_cfg.acs_ctrl = PCI_ACS_RR | PCI_ACS_EC; + f.dn1_cfg.port = 6; + f.dn0_cfg.egress[0] = BIT(6); + f.dn1->acs_cap = 0x100; + f.dn1->acs_capabilities = PCI_ACS_CR | PCI_ACS_EC | (64 << 8); + f.dn1_cfg.acs_ctrl = PCI_ACS_CR | PCI_ACS_EC; + f.dn0_cfg.port = 5; /* client vector left all-zero */ + + KUNIT_EXPECT_EQ(test, acs_walk_map(&f), 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); + /* The completion already routes upstream at this nested port. */ + f.provider_leaf->acs_cap = 0x100; + f.provider_leaf->acs_capabilities = PCI_ACS_CR; + f.provider_leaf_cfg.acs_ctrl = PCI_ACS_CR; + + KUNIT_EXPECT_EQ(test, acs_walk_map(&f), 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); + /* The request already routes upstream at this nested port. */ + f.client_leaf->acs_cap = 0x100; + f.client_leaf->acs_capabilities = PCI_ACS_RR; + f.client_leaf_cfg.acs_ctrl = PCI_ACS_RR; + + KUNIT_EXPECT_EQ(test, acs_walk_map(&f), PCI_P2PDMA_MAP_BUS_ADDR); +} + static struct kunit_case pci_acs_test_cases[] = { KUNIT_CASE_PARAM(pci_acs_p2pdma_decision_test, acs_decision_gen_params), KUNIT_CASE_PARAM(pci_acs_egress_port_valid_test, egress_valid_gen_params), @@ -430,6 +717,14 @@ static struct kunit_case pci_acs_test_cases[] = { KUNIT_CASE(acs_egress_target_pcie_bridge_test), KUNIT_CASE(acs_egress_target_other_bus_test), KUNIT_CASE(acs_egress_root_port_test), + KUNIT_CASE(acs_walk_bus_addr_test), + KUNIT_CASE(acs_walk_ec_violation_test), + KUNIT_CASE(acs_walk_ec_vector_clear_test), + KUNIT_CASE(acs_walk_request_redirect_test), + KUNIT_CASE(acs_walk_completion_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), {} }; -- 2.55.0