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

Leon Romanovsky <[email protected]>
Newsgroups org.kernel.vger.linux-acpi,dev.linux.lists.acpica-devel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.