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

Leon Romanovsky <[email protected]>
Newsgroups dev.linux.lists.acpica-devel,org.kernel.vger.linux-acpi,org.kernel.vger.linux-kernel,org.kernel.vger.linux-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
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.