[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