[PATCH RFC 6/7] PCI/P2PDMA: Add KUnit tests for HMAT policy boundaries
Leon Romanovsky <[email protected]> Wed, 12 Aug 2026 22:47:43 +0300
| Newsgroups | gmane.linux.acpi.devel,gmane.linux.kernel,gmane.linux.kernel.pci |
|---|---|
| Message-ID | <[email protected]> |
From: Leon Romanovsky <[email protected]> HMAT becomes an authorization input for cross-host-bridge DMA. Reversing the requester and completer or accepting UIO-only data for ordered traffic could permit unsafe DMA. Protect the directional lookup and the ordered-traffic requirement. A missing proximity domain or a missing, unreachable, or UIO-only path must produce PCI_P2PDMA_MAP_NOT_SUPPORTED, and a path the CPU already permits must not consult firmware at all. Signed-off-by: Leon Romanovsky <[email protected]> --- drivers/acpi/numa/hmat.c | 4 + drivers/pci/Kconfig | 13 +++ drivers/pci/Makefile | 1 + drivers/pci/p2pdma.c | 33 ++++---- drivers/pci/p2pdma_test.c | 210 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci.h | 2 + 6 files changed, 249 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/numa/hmat.c b/drivers/acpi/numa/hmat.c index d2964548aaaf..4593cccf9b9e 100644 --- a/drivers/acpi/numa/hmat.c +++ b/drivers/acpi/numa/hmat.c @@ -10,6 +10,7 @@ #define pr_fmt(fmt) "acpi/hmat: " fmt +#include <kunit/static_stub.h> #include <kunit/visibility.h> #include <linux/acpi.h> #include <linux/bitops.h> @@ -290,6 +291,9 @@ int acpi_get_p2p_coordinates(int initiator_pxm, int target_pxm, enum hmat_p2p_class class, struct access_coordinate *coord) { + KUNIT_STATIC_STUB_REDIRECT(acpi_get_p2p_coordinates, initiator_pxm, + target_pxm, class, coord); + return hmat_get_p2p_coordinates(&p2p_localities, initiator_pxm, target_pxm, class, coord); } diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 30ad7f407c6f..47b92b289faf 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -241,6 +241,19 @@ config PCI_ACS_KUNIT_TEST If unsure, say N. +config PCI_P2PDMA_HMAT_KUNIT_TEST + tristate "KUnit tests for PCI P2PDMA HMAT routing" if !KUNIT_ALL_TESTS + depends on PCI_P2PDMA && ACPI_HMAT && KUNIT + default KUNIT_ALL_TESTS + help + Enable KUnit coverage for authorizing cross-host-bridge P2P DMA + through ordered HMAT paths. + + 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 6305d128d3df..b0c392d5c8a3 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -32,6 +32,7 @@ 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_PCI_P2PDMA_HMAT_KUNIT_TEST) += p2pdma_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 88119620890f..fe3e7ea0776c 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -9,6 +9,7 @@ */ #define pr_fmt(fmt) "pci-p2pdma: " fmt +#include <kunit/static_stub.h> #include <linux/acpi.h> #include <linux/ctype.h> #include <linux/dma-map-ops.h> @@ -613,8 +614,10 @@ static void seq_buf_print_bus_devfn(struct seq_buf *buf, struct pci_dev *pdev) seq_buf_printf(buf, "%s;", pci_name(pdev)); } -static bool cpu_supports_p2pdma(void) +VISIBLE_IF_KUNIT bool cpu_supports_p2pdma(void) { + KUNIT_STATIC_STUB_REDIRECT(cpu_supports_p2pdma); + #ifdef CONFIG_X86 struct cpuinfo_x86 *c = &cpu_data(0); @@ -625,6 +628,7 @@ static bool cpu_supports_p2pdma(void) return false; } +EXPORT_SYMBOL_IF_KUNIT(cpu_supports_p2pdma); static const struct pci_p2pdma_whitelist_entry { unsigned short vendor; @@ -751,13 +755,15 @@ static bool host_bridge_whitelist(struct pci_dev *a, struct pci_dev *b, } #ifdef CONFIG_ACPI -static int pci_host_bridge_pxm(struct pci_dev *pdev) +VISIBLE_IF_KUNIT int pci_host_bridge_pxm(struct pci_dev *pdev) { struct pci_host_bridge *host = pci_find_host_bridge(pdev->bus); struct acpi_device *adev; const char *uid_str; u32 uid; + KUNIT_STATIC_STUB_REDIRECT(pci_host_bridge_pxm, pdev); + adev = to_acpi_device_node(host->dev.fwnode); if (!adev) return -ENODEV; @@ -769,11 +775,14 @@ static int pci_host_bridge_pxm(struct pci_dev *pdev) return acpi_get_genport_proximity_domain(uid); } #else -static int pci_host_bridge_pxm(struct pci_dev *pdev) +VISIBLE_IF_KUNIT int pci_host_bridge_pxm(struct pci_dev *pdev) { + KUNIT_STATIC_STUB_REDIRECT(pci_host_bridge_pxm, pdev); + return -ENODEV; } #endif +EXPORT_SYMBOL_IF_KUNIT(pci_host_bridge_pxm); /* * Check whether platform firmware describes, via an HMAT PCIe P2P Latency and @@ -810,10 +819,6 @@ static unsigned long map_types_idx(struct pci_dev *client) return (pci_domain_nr(client->bus) << 16) | pci_dev_id(client); } -static enum pci_p2pdma_map_type -__calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, - int *dist, bool verbose, struct access_coordinate *hmat_coord); - /* * Calculate the P2PDMA mapping type and distance between two PCI devices. * @@ -849,13 +854,6 @@ __calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, * ordered HMAT path. Return PCI_P2PDMA_MAP_NOT_SUPPORTED when none of those * sources permits the path. */ -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) -{ - return __calc_map_type_and_dist(provider, client, dist, verbose, NULL); -} - static enum pci_p2pdma_map_type __calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, int *dist, bool verbose, struct access_coordinate *hmat_coord) @@ -1009,6 +1007,13 @@ __calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, rcu_read_unlock(); return 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) +{ + return __calc_map_type_and_dist(provider, client, dist, verbose, NULL); +} EXPORT_SYMBOL_IF_KUNIT(calc_map_type_and_dist); static int diff --git a/drivers/pci/p2pdma_test.c b/drivers/pci/p2pdma_test.c new file mode 100644 index 000000000000..1430185f69b7 --- /dev/null +++ b/drivers/pci/p2pdma_test.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for HMAT-described PCI peer-to-peer routing. + */ + +#include <kunit/static_stub.h> +#include <kunit/test.h> + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci-p2pdma.h> + +#include "pci.h" + +struct hmat_route_case { + const char *desc; + bool cpu_p2pdma; + int client_pxm; + int provider_pxm; + int lookup_ret; + int lookup_calls; + enum pci_p2pdma_map_type expected; +}; + +static const struct hmat_route_case hmat_route_cases[] = { + { + .desc = "ordered_path", + .client_pxm = 11, + .provider_pxm = 22, + .lookup_ret = 0, + .lookup_calls = 1, + .expected = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE, + }, + { + .desc = "missing_path", + .client_pxm = 11, + .provider_pxm = 22, + .lookup_ret = -ENOENT, + .lookup_calls = 1, + .expected = PCI_P2PDMA_MAP_NOT_SUPPORTED, + }, + { + .desc = "uio_only_path", + .client_pxm = 11, + .provider_pxm = 22, + .lookup_ret = -ENODATA, + .lookup_calls = 1, + .expected = PCI_P2PDMA_MAP_NOT_SUPPORTED, + }, + { + .desc = "missing_client_pxm", + .client_pxm = -ENODEV, + .provider_pxm = 22, + .lookup_ret = 0, + .lookup_calls = 0, + .expected = PCI_P2PDMA_MAP_NOT_SUPPORTED, + }, + { + .desc = "missing_provider_pxm", + .client_pxm = 11, + .provider_pxm = -ENODEV, + .lookup_ret = 0, + .lookup_calls = 0, + .expected = PCI_P2PDMA_MAP_NOT_SUPPORTED, + }, + { + .desc = "platform_authorized", + .cpu_p2pdma = true, + .client_pxm = 11, + .provider_pxm = 22, + .lookup_calls = 0, + .expected = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE, + }, +}; + +static void hmat_route_case_desc(const struct hmat_route_case *c, char *desc) +{ + strscpy(desc, c->desc, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(hmat_route, hmat_route_cases, hmat_route_case_desc); + +struct hmat_route_ctx { + const struct hmat_route_case *test_case; + struct pci_dev *provider; + struct pci_dev *client; + int lookup_calls; + int lookup_initiator; + int lookup_target; + enum hmat_p2p_class lookup_class; + bool unexpected_device; +}; + +static struct pci_dev *hmat_add_root_device(struct kunit *test, u8 busnr) +{ + struct pci_host_bridge *host; + struct pci_bus *bus; + struct pci_dev *pdev; + + host = kunit_kzalloc(test, sizeof(*host), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, host); + bus = kunit_kzalloc(test, sizeof(*bus), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bus); + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + bus->number = busnr; + bus->bridge = &host->dev; + INIT_LIST_HEAD(&bus->devices); + host->bus = bus; + + pdev->bus = bus; + pdev->devfn = PCI_DEVFN(0, 0); + pdev->vendor = 0xffff; + pdev->device = 0xffff; + list_add_tail(&pdev->bus_list, &bus->devices); + return pdev; +} + +static bool cpu_supports_p2pdma_stub(void) +{ + return false; +} + +static bool cpu_supports_p2pdma_true_stub(void) +{ + return true; +} + +static int pci_host_bridge_pxm_stub(struct pci_dev *pdev) +{ + struct kunit *test = kunit_get_current_test(); + struct hmat_route_ctx *ctx = test->priv; + + if (pdev == ctx->client) + return ctx->test_case->client_pxm; + if (pdev == ctx->provider) + return ctx->test_case->provider_pxm; + + ctx->unexpected_device = true; + return -ENODEV; +} + +static int acpi_get_p2p_coordinates_stub(int initiator, int target, + enum hmat_p2p_class class, + struct access_coordinate *coord) +{ + struct kunit *test = kunit_get_current_test(); + struct hmat_route_ctx *ctx = test->priv; + + ctx->lookup_calls++; + ctx->lookup_initiator = initiator; + ctx->lookup_target = target; + ctx->lookup_class = class; + if (!ctx->test_case->lookup_ret) + coord->read_bandwidth = 1; + return ctx->test_case->lookup_ret; +} + +static void pci_p2pdma_hmat_route_test(struct kunit *test) +{ + const struct hmat_route_case *test_case = test->param_value; + struct hmat_route_ctx ctx = { .test_case = test_case }; + enum pci_p2pdma_map_type map; + int distance; + + ctx.provider = hmat_add_root_device(test, 0); + ctx.client = hmat_add_root_device(test, 1); + test->priv = &ctx; + + if (test_case->cpu_p2pdma) + kunit_activate_static_stub(test, cpu_supports_p2pdma, + cpu_supports_p2pdma_true_stub); + else + kunit_activate_static_stub(test, cpu_supports_p2pdma, + cpu_supports_p2pdma_stub); + kunit_activate_static_stub(test, pci_host_bridge_pxm, + pci_host_bridge_pxm_stub); + kunit_activate_static_stub(test, acpi_get_p2p_coordinates, + acpi_get_p2p_coordinates_stub); + + map = calc_map_type_and_dist(ctx.provider, ctx.client, &distance, false); + KUNIT_EXPECT_EQ(test, map, test_case->expected); + KUNIT_EXPECT_EQ(test, distance, 2); + KUNIT_EXPECT_EQ(test, ctx.lookup_calls, test_case->lookup_calls); + KUNIT_EXPECT_FALSE(test, ctx.unexpected_device); + if (ctx.lookup_calls) { + KUNIT_EXPECT_EQ(test, ctx.lookup_initiator, + test_case->client_pxm); + KUNIT_EXPECT_EQ(test, ctx.lookup_target, + test_case->provider_pxm); + KUNIT_EXPECT_EQ(test, ctx.lookup_class, HMAT_P2P_NON_UIO); + } +} + +static struct kunit_case pci_p2pdma_hmat_test_cases[] = { + KUNIT_CASE_PARAM(pci_p2pdma_hmat_route_test, hmat_route_gen_params), + {} +}; + +static struct kunit_suite pci_p2pdma_hmat_test_suite = { + .name = "pci_p2pdma_hmat", + .test_cases = pci_p2pdma_hmat_test_cases, +}; +kunit_test_suite(pci_p2pdma_hmat_test_suite); + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("KUnit tests for HMAT-described PCI P2P routing"); diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index f8f9a15e411a..7a44158fdbd0 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -1101,6 +1101,8 @@ enum pci_acs_p2pdma_state pci_acs_p2pdma_decision(u16 ctrl, bool has_target, enum pci_p2pdma_map_type calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, int *dist, bool verbose); +int pci_host_bridge_pxm(struct pci_dev *pdev); +bool cpu_supports_p2pdma(void); #endif #ifdef CONFIG_PCI_QUIRKS int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags, -- 2.55.0