[PATCH RFC 7/7] PCI/P2PDMA: Add KUnit tests for HMAT provider ranking

Leon Romanovsky <[email protected]> Wed, 12 Aug 2026 22:47:44 +0300
Newsgroups gmane.linux.acpi.devel,gmane.linux.kernel,gmane.linux.kernel.pci
Message-ID <[email protected]>
From: Leon Romanovsky <[email protected]>

HMAT provider selection combines directional coordinates across every
client. A regression in metric completeness, worst-path aggregation, or
precedence could select a slower provider or change the distance fallback.

Protect the ranking contract across complete, partial, direct, and
platform-authorized paths, including its ordering and distance tie-break.

Signed-off-by: Leon Romanovsky <[email protected]>
---
 drivers/pci/Kconfig       |   3 +-
 drivers/pci/p2pdma.c      |  22 +---
 drivers/pci/p2pdma_test.c | 267 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/pci/pci.h         |  21 ++++
 4 files changed, 294 insertions(+), 19 deletions(-)

diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 47b92b289faf..b46833ff10e1 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -247,7 +247,8 @@ config PCI_P2PDMA_HMAT_KUNIT_TEST
 	default KUNIT_ALL_TESTS
 	help
 	  Enable KUnit coverage for authorizing cross-host-bridge P2P DMA
-	  through ordered HMAT paths.
+	  through ordered HMAT paths and ranking providers by their
+	  ordered-path performance.
 
 	  For more information on KUnit and unit tests in general, refer to
 	  the KUnit documentation in Documentation/dev-tools/kunit/.
diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index fe3e7ea0776c..ca822f60e9d3 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -70,22 +70,6 @@ struct pci_p2pdma_pagemap {
 	struct p2pdma_provider *mem;
 };
 
-/* Provider rank classes, ordered from most to least preferable. */
-enum pci_p2pdma_rank_type {
-	PCI_P2PDMA_RANK_DIRECT,
-	PCI_P2PDMA_RANK_HMAT_BANDWIDTH,
-	PCI_P2PDMA_RANK_HMAT_LATENCY,
-	PCI_P2PDMA_RANK_DISTANCE,
-};
-
-struct pci_p2pdma_rank {
-	enum pci_p2pdma_rank_type type;
-	u32 bandwidth;
-	u32 latency;
-	int distance;
-	bool latency_valid;
-};
-
 static struct pci_p2pdma_pagemap *to_p2p_pgmap(struct dev_pagemap *pgmap)
 {
 	return container_of(pgmap, struct pci_p2pdma_pagemap, pgmap);
@@ -1016,7 +1000,7 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
 }
 EXPORT_SYMBOL_IF_KUNIT(calc_map_type_and_dist);
 
-static int
+VISIBLE_IF_KUNIT int
 pci_p2pdma_rank_cmp(const struct pci_p2pdma_rank *a,
 		    const struct pci_p2pdma_rank *b)
 {
@@ -1039,13 +1023,14 @@ pci_p2pdma_rank_cmp(const struct pci_p2pdma_rank *a,
 
 	return 0;
 }
+EXPORT_SYMBOL_IF_KUNIT(pci_p2pdma_rank_cmp);
 
 /*
  * P2P bandwidth is limited by the slowest direction and client path, while
  * the largest latency bounds the worst path. Only compare a metric when every
  * host-bridge path supplies both its read and write values.
  */
-static int
+VISIBLE_IF_KUNIT int
 pci_p2pdma_rank_many(struct pci_dev *provider, struct device **clients,
 		     int num_clients, bool verbose,
 		     struct pci_p2pdma_rank *rank)
@@ -1124,6 +1109,7 @@ pci_p2pdma_rank_many(struct pci_dev *provider, struct device **clients,
 
 	return 0;
 }
+EXPORT_SYMBOL_IF_KUNIT(pci_p2pdma_rank_many);
 
 /**
  * pci_p2pdma_distance_many - Determine the cumulative distance between
diff --git a/drivers/pci/p2pdma_test.c b/drivers/pci/p2pdma_test.c
index 1430185f69b7..8475c40c0eaf 100644
--- a/drivers/pci/p2pdma_test.c
+++ b/drivers/pci/p2pdma_test.c
@@ -92,6 +92,15 @@ struct hmat_route_ctx {
 	bool unexpected_device;
 };
 
+static void hmat_test_device_release(struct device *dev)
+{
+}
+
+static void hmat_test_put_device(void *data)
+{
+	put_device(data);
+}
+
 static struct pci_dev *hmat_add_root_device(struct kunit *test, u8 busnr)
 {
 	struct pci_host_bridge *host;
@@ -104,6 +113,13 @@ static struct pci_dev *hmat_add_root_device(struct kunit *test, u8 busnr)
 	KUNIT_ASSERT_NOT_NULL(test, bus);
 	pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
 	KUNIT_ASSERT_NOT_NULL(test, pdev);
+	device_initialize(&pdev->dev);
+	pdev->dev.bus = &pci_bus_type;
+	pdev->dev.release = hmat_test_device_release;
+	KUNIT_ASSERT_EQ(test,
+			kunit_add_action_or_reset(test, hmat_test_put_device,
+						  &pdev->dev),
+			0);
 
 	bus->number = busnr;
 	bus->bridge = &host->dev;
@@ -194,8 +210,259 @@ static void pci_p2pdma_hmat_route_test(struct kunit *test)
 	}
 }
 
+struct hmat_rank_case {
+	const char *desc;
+	struct access_coordinate coord[2];
+	enum pci_p2pdma_rank_type expected_type;
+	u32 expected_bandwidth;
+	u32 expected_latency;
+	bool expected_latency_valid;
+};
+
+static const struct hmat_rank_case hmat_rank_cases[] = {
+	{
+		.desc = "bandwidth_and_latency",
+		.coord = {
+			{
+				.read_bandwidth = 100,
+				.write_bandwidth = 90,
+				.read_latency = 7,
+				.write_latency = 8,
+			},
+			{
+				.read_bandwidth = 75,
+				.write_bandwidth = 80,
+				.read_latency = 10,
+				.write_latency = 9,
+			},
+		},
+		.expected_type = PCI_P2PDMA_RANK_HMAT_BANDWIDTH,
+		.expected_bandwidth = 75,
+		.expected_latency = 10,
+		.expected_latency_valid = true,
+	},
+	{
+		.desc = "bandwidth_only",
+		.coord = {
+			{
+				.read_bandwidth = 100,
+				.write_bandwidth = 90,
+			},
+			{
+				.read_bandwidth = 75,
+				.write_bandwidth = 80,
+			},
+		},
+		.expected_type = PCI_P2PDMA_RANK_HMAT_BANDWIDTH,
+		.expected_bandwidth = 75,
+	},
+	{
+		.desc = "latency_only",
+		.coord = {
+			{
+				.read_latency = 7,
+				.write_latency = 8,
+			},
+			{
+				.read_latency = 10,
+				.write_latency = 9,
+			},
+		},
+		.expected_type = PCI_P2PDMA_RANK_HMAT_LATENCY,
+		.expected_latency = 10,
+		.expected_latency_valid = true,
+	},
+	{
+		.desc = "incomplete_coordinates",
+		.coord = {
+			{
+				.read_bandwidth = 100,
+				.write_bandwidth = 90,
+				.read_latency = 7,
+				.write_latency = 8,
+			},
+			{
+				.read_bandwidth = 75,
+				.read_latency = 10,
+			},
+		},
+		.expected_type = PCI_P2PDMA_RANK_DISTANCE,
+	},
+};
+
+static void hmat_rank_case_desc(const struct hmat_rank_case *c, char *desc)
+{
+	strscpy(desc, c->desc, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(hmat_rank, hmat_rank_cases, hmat_rank_case_desc);
+
+struct hmat_rank_ctx {
+	const struct hmat_rank_case *test_case;
+	struct pci_dev *provider;
+	struct pci_dev *client[2];
+	int lookup_calls;
+	bool unexpected_lookup;
+};
+
+static int pci_host_bridge_rank_pxm_stub(struct pci_dev *pdev)
+{
+	struct kunit *test = kunit_get_current_test();
+	struct hmat_rank_ctx *ctx = test->priv;
+	unsigned int i;
+
+	if (pdev == ctx->provider)
+		return 22;
+	for (i = 0; i < ARRAY_SIZE(ctx->client); i++)
+		if (pdev == ctx->client[i])
+			return 11 + i;
+
+	ctx->unexpected_lookup = true;
+	return -ENODEV;
+}
+
+static int acpi_get_p2p_rank_coordinates_stub(int initiator, int target,
+					      enum hmat_p2p_class class,
+					      struct access_coordinate *coord)
+{
+	struct kunit *test = kunit_get_current_test();
+	struct hmat_rank_ctx *ctx = test->priv;
+	int index = initiator - 11;
+
+	ctx->lookup_calls++;
+	if (index < 0 || index >= (int)ARRAY_SIZE(ctx->client) || target != 22 ||
+	    class != HMAT_P2P_NON_UIO) {
+		ctx->unexpected_lookup = true;
+		return -ENOENT;
+	}
+
+	*coord = ctx->test_case->coord[index];
+	return 0;
+}
+
+static void pci_p2pdma_hmat_rank_many_test(struct kunit *test)
+{
+	const struct hmat_rank_case *test_case = test->param_value;
+	struct hmat_rank_ctx ctx = { .test_case = test_case };
+	struct device *clients[ARRAY_SIZE(ctx.client)];
+	struct pci_p2pdma_rank rank;
+	unsigned int i;
+
+	ctx.provider = hmat_add_root_device(test, 0);
+	for (i = 0; i < ARRAY_SIZE(ctx.client); i++) {
+		ctx.client[i] = hmat_add_root_device(test, i + 1);
+		clients[i] = &ctx.client[i]->dev;
+	}
+	test->priv = &ctx;
+
+	kunit_activate_static_stub(test, cpu_supports_p2pdma,
+				   cpu_supports_p2pdma_stub);
+	kunit_activate_static_stub(test, pci_host_bridge_pxm,
+				   pci_host_bridge_rank_pxm_stub);
+	kunit_activate_static_stub(test, acpi_get_p2p_coordinates,
+				   acpi_get_p2p_rank_coordinates_stub);
+
+	KUNIT_ASSERT_EQ(test,
+			pci_p2pdma_rank_many(ctx.provider, clients,
+					       ARRAY_SIZE(clients), false, &rank),
+			0);
+	KUNIT_EXPECT_EQ(test, rank.type, test_case->expected_type);
+	KUNIT_EXPECT_EQ(test, rank.distance, 4);
+	KUNIT_EXPECT_EQ(test, ctx.lookup_calls, 2);
+	KUNIT_EXPECT_FALSE(test, ctx.unexpected_lookup);
+	if (rank.type == PCI_P2PDMA_RANK_HMAT_BANDWIDTH)
+		KUNIT_EXPECT_EQ(test, rank.bandwidth,
+				test_case->expected_bandwidth);
+	if (rank.latency_valid)
+		KUNIT_EXPECT_EQ(test, rank.latency,
+				test_case->expected_latency);
+	KUNIT_EXPECT_EQ(test, rank.latency_valid,
+			test_case->expected_latency_valid);
+}
+
+static void pci_p2pdma_hmat_rank_compare_test(struct kunit *test)
+{
+	struct pci_p2pdma_rank direct = {
+		.type = PCI_P2PDMA_RANK_DIRECT,
+		.distance = 8,
+	};
+	struct pci_p2pdma_rank bandwidth = {
+		.type = PCI_P2PDMA_RANK_HMAT_BANDWIDTH,
+		.bandwidth = 100,
+		.latency = 20,
+		.distance = 4,
+		.latency_valid = true,
+	};
+	struct pci_p2pdma_rank other = bandwidth;
+
+	KUNIT_EXPECT_LT(test, pci_p2pdma_rank_cmp(&direct, &bandwidth), 0);
+
+	other.bandwidth = 90;
+	KUNIT_EXPECT_LT(test, pci_p2pdma_rank_cmp(&bandwidth, &other), 0);
+	other = bandwidth;
+	other.latency = 30;
+	KUNIT_EXPECT_LT(test, pci_p2pdma_rank_cmp(&bandwidth, &other), 0);
+	other = bandwidth;
+	other.latency_valid = false;
+	KUNIT_EXPECT_LT(test, pci_p2pdma_rank_cmp(&bandwidth, &other), 0);
+	other = bandwidth;
+	other.distance = 5;
+	KUNIT_EXPECT_LT(test, pci_p2pdma_rank_cmp(&bandwidth, &other), 0);
+	KUNIT_EXPECT_EQ(test, pci_p2pdma_rank_cmp(&bandwidth, &bandwidth), 0);
+
+	bandwidth.type = PCI_P2PDMA_RANK_HMAT_LATENCY;
+	other = bandwidth;
+	other.type = PCI_P2PDMA_RANK_DISTANCE;
+	KUNIT_EXPECT_LT(test, pci_p2pdma_rank_cmp(&bandwidth, &other), 0);
+	other = bandwidth;
+	other.latency = 30;
+	KUNIT_EXPECT_LT(test, pci_p2pdma_rank_cmp(&bandwidth, &other), 0);
+
+	bandwidth.type = PCI_P2PDMA_RANK_DISTANCE;
+	bandwidth.distance = 4;
+	other = bandwidth;
+	other.distance = 5;
+	KUNIT_EXPECT_LT(test, pci_p2pdma_rank_cmp(&bandwidth, &other), 0);
+}
+
+static void pci_p2pdma_direct_rank_test(struct kunit *test)
+{
+	struct pci_dev *provider = hmat_add_root_device(test, 0);
+	struct device *clients[] = { &provider->dev };
+	struct pci_p2pdma_rank rank;
+
+	KUNIT_ASSERT_EQ(test,
+			pci_p2pdma_rank_many(provider, clients,
+					       ARRAY_SIZE(clients), false, &rank),
+			0);
+	KUNIT_EXPECT_EQ(test, rank.type, PCI_P2PDMA_RANK_DIRECT);
+	KUNIT_EXPECT_EQ(test, rank.distance, 0);
+}
+
+static void pci_p2pdma_distance_rank_fallback_test(struct kunit *test)
+{
+	struct pci_dev *provider = hmat_add_root_device(test, 0);
+	struct pci_dev *client = hmat_add_root_device(test, 1);
+	struct device *clients[] = { &client->dev };
+	struct pci_p2pdma_rank rank;
+
+	kunit_activate_static_stub(test, cpu_supports_p2pdma,
+				   cpu_supports_p2pdma_true_stub);
+
+	KUNIT_ASSERT_EQ(test,
+			pci_p2pdma_rank_many(provider, clients,
+					       ARRAY_SIZE(clients), false, &rank),
+			0);
+	KUNIT_EXPECT_EQ(test, rank.type, PCI_P2PDMA_RANK_DISTANCE);
+	KUNIT_EXPECT_EQ(test, rank.distance, 2);
+}
+
 static struct kunit_case pci_p2pdma_hmat_test_cases[] = {
 	KUNIT_CASE_PARAM(pci_p2pdma_hmat_route_test, hmat_route_gen_params),
+	KUNIT_CASE_PARAM(pci_p2pdma_hmat_rank_many_test, hmat_rank_gen_params),
+	KUNIT_CASE(pci_p2pdma_hmat_rank_compare_test),
+	KUNIT_CASE(pci_p2pdma_direct_rank_test),
+	KUNIT_CASE(pci_p2pdma_distance_rank_fallback_test),
 	{}
 };
 
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 7a44158fdbd0..9872e081ec07 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1092,6 +1092,22 @@ enum pci_acs_p2pdma_state {
 	PCI_ACS_P2PDMA_NOT_SUPPORTED,	/* no usable peer-to-peer route */
 };
 
+/* Provider rank classes, ordered from most to least preferable. */
+enum pci_p2pdma_rank_type {
+	PCI_P2PDMA_RANK_DIRECT,
+	PCI_P2PDMA_RANK_HMAT_BANDWIDTH,
+	PCI_P2PDMA_RANK_HMAT_LATENCY,
+	PCI_P2PDMA_RANK_DISTANCE,
+};
+
+struct pci_p2pdma_rank {
+	enum pci_p2pdma_rank_type type;
+	u32 bandwidth;
+	u32 latency;
+	int distance;
+	bool latency_valid;
+};
+
 #if IS_ENABLED(CONFIG_KUNIT)
 bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags,
 			   enum pci_acs_scope scope);
@@ -1103,6 +1119,11 @@ enum pci_p2pdma_map_type calc_map_type_and_dist(struct pci_dev *provider,
 						int *dist, bool verbose);
 int pci_host_bridge_pxm(struct pci_dev *pdev);
 bool cpu_supports_p2pdma(void);
+int pci_p2pdma_rank_many(struct pci_dev *provider, struct device **clients,
+			 int num_clients, bool verbose,
+			 struct pci_p2pdma_rank *rank);
+int pci_p2pdma_rank_cmp(const struct pci_p2pdma_rank *a,
+			const struct pci_p2pdma_rank *b);
 #endif
 #ifdef CONFIG_PCI_QUIRKS
 int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags,

-- 
2.55.0