[PATCH RFC 6/7] PCI/P2PDMA: Add KUnit tests for HMAT policy boundaries

From: Leon Romanovsky

Date: Wed Aug 12 2026 - 15:48:59 EST


From: Leon Romanovsky <leonro@xxxxxxxxxx>

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 <leonro@xxxxxxxxxx>
---
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