The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH RFC 0/7] PCI/HMAT: Describe P2PDMA reachability and performance
@ 2026-08-12 19:47 Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 1/7] ACPICA: Define PCIe P2P latency and bandwidth information Leon Romanovsky
                   ` (6 more replies)
  0 siblings, 7 replies; 10+ messages in thread
From: Leon Romanovsky @ 2026-08-12 19:47 UTC (permalink / raw)
  To: Rafael J. Wysocki, Len Brown, Saket Dumbre, Bjorn Helgaas,
	Logan Gunthorpe
  Cc: linux-acpi, acpica-devel, linux-kernel, linux-pci, Lukas Wunner,
	Natu, Mahesh

Linux derives P2PDMA reachability from visible PCI topology, ACS
configuration, and a host-bridge whitelist. It cannot describe conditional
or slower cross-host-bridge paths; passthrough can hide the physical peer
relationships.

This RFC series implements the proposed ACPI HMAT Type 3 extension below.
Directional Generic Port matrices describe reachability, ordering, latency,
and bandwidth. Linux uses ordered paths to authorize P2PDMA and their
latency/bandwidth to rank providers. A VMM could translate these matrices
into guest domains; that is outside this series.

  HMAT Type 3 structure

  Field               Offset       Bytes  Meaning
  ---------------------------------------------------------------
  Type:                    0           2  value 3
  Length:                  4           4  entire structure
  Flags:                   8           1  traffic classes
  Data-type:               9           1  metric selector
  Initiators:             12           4  Generic Port count (s)
  Targets:                16           4  Generic Port count (t)
  Base-unit:              24           8  ps or MB/s; non-zero
  Initiator-PDs:          32         4*s  proximity-domain list
  Target-PDs:         32+4*s         4*t  proximity-domain list
  Matrix:         32+4*(s+t)       2*s*t  directional Entry[i][j]
  Reserved: bytes 2-3, 10-11, and 20-23

  Flags: encoding
    bit 0  non-UIO
    bit 1  UIO

  Metrics: selector
    0/1/2  access/read/write latency
    3/4/5  access/read/write bandwidth

  Direction: Initiator PD[i] -- Entry[i][j] metric --> Target PD[j]

----------------------------------------------------------------------
Based on [PATCH v3 00/17] PCI/P2PDMA: Fix ACS egress control handling
https://lore.kernel.org/all/20260811-fix-p2p-acs-v3-0-efc488ee7c03@nvidia.com/

Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
Leon Romanovsky (7):
      ACPICA: Define PCIe P2P latency and bandwidth information
      ACPI: HMAT: Make PCIe P2P paths available to consumers
      PCI/P2PDMA: Authorize HMAT-described cross-host-bridge P2P
      PCI/P2PDMA: Prefer providers with better HMAT performance
      ACPI: HMAT: Add KUnit tests for PCIe P2P matrix semantics
      PCI/P2PDMA: Add KUnit tests for HMAT policy boundaries
      PCI/P2PDMA: Add KUnit tests for HMAT provider ranking

 drivers/acpi/numa/Kconfig     |  13 ++
 drivers/acpi/numa/Makefile    |   1 +
 drivers/acpi/numa/hmat.c      | 245 +++++++++++++++++++++-
 drivers/acpi/numa/hmat_test.c | 223 ++++++++++++++++++++
 drivers/acpi/numa/hmat_test.h |  17 ++
 drivers/pci/Kconfig           |  14 ++
 drivers/pci/Makefile          |   1 +
 drivers/pci/p2pdma.c          | 300 ++++++++++++++++++++------
 drivers/pci/p2pdma_test.c     | 477 ++++++++++++++++++++++++++++++++++++++++++
 drivers/pci/pci.h             |  23 ++
 include/acpi/actbl1.h         |  27 ++-
 include/linux/acpi.h          |  26 +++
 12 files changed, 1298 insertions(+), 69 deletions(-)
---
base-commit: bc5261fca94dd94ab557cc0852ce465d9c3ab8b0
change-id: 20260715-hmat-p2p-eb4971a9d731

Best regards,
--  
Leon Romanovsky <leonro@nvidia.com>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 1/7] ACPICA: Define PCIe P2P latency and bandwidth information
  2026-08-12 19:47 [PATCH RFC 0/7] PCI/HMAT: Describe P2PDMA reachability and performance Leon Romanovsky
@ 2026-08-12 19:47 ` Leon Romanovsky
  2026-08-13 10:13   ` Rafael J. Wysocki (Intel)
  2026-08-12 19:47 ` [PATCH RFC 2/7] ACPI: HMAT: Make PCIe P2P paths available to consumers Leon Romanovsky
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 10+ messages in thread
From: Leon Romanovsky @ 2026-08-12 19:47 UTC (permalink / raw)
  To: Rafael J. Wysocki, Len Brown, Saket Dumbre, Bjorn Helgaas,
	Logan Gunthorpe
  Cc: linux-acpi, acpica-devel, linux-kernel, linux-pci, Lukas Wunner,
	Natu, Mahesh

From: Leon Romanovsky <leonro@nvidia.com>

PCIe topology alone cannot tell an operating system whether traffic may
cross host bridges, what ordering rules it needs, or how expensive the path
is. Encoding this knowledge in chipset lists duplicates platform policy and
cannot survive a virtualized topology.

Define HMAT Type 3 as a firmware contract for directional Generic Port P2P
paths, including separate ordered and Unordered I/O applicability. This
gives platform software a standard way to distinguish unsupported,
conditional, and usable paths. The definition follows a proposed ACPI ECN.

Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
 include/acpi/actbl1.h | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/include/acpi/actbl1.h b/include/acpi/actbl1.h
index d824838cb7af..0fc766a8e90f 100644
--- a/include/acpi/actbl1.h
+++ b/include/acpi/actbl1.h
@@ -2003,7 +2003,8 @@ enum acpi_hmat_type {
 	ACPI_HMAT_TYPE_PROXIMITY = 0,	/* Memory proximity domain attributes */
 	ACPI_HMAT_TYPE_LOCALITY = 1,	/* System locality latency and bandwidth information */
 	ACPI_HMAT_TYPE_CACHE = 2,	/* Memory side cache information */
-	ACPI_HMAT_TYPE_RESERVED = 3	/* 3 and greater are reserved */
+	ACPI_HMAT_TYPE_P2P_LATENCY = 3,	/* PCIe P2P latency and bandwidth information */
+	ACPI_HMAT_TYPE_RESERVED = 4	/* 4 and greater are reserved */
 };
 
 struct acpi_hmat_structure {
@@ -2108,6 +2109,30 @@ struct acpi_hmat_cache {
 #define ACPI_HMAT_CP_WB     (1)
 #define ACPI_HMAT_CP_WT     (2)
 
+/* 3: PCIe P2P Latency and Bandwidth Information */
+
+struct acpi_hmat_p2p_latency {
+	struct acpi_hmat_structure header;
+	u8 flags;
+	u8 data_type;
+	u16 reserved1;
+	u32 number_of_initiator_Pds;
+	u32 number_of_target_Pds;
+	u32 reserved2;
+	u64 entry_base_unit;
+};
+
+/* Masks for Flags field above */
+
+#define ACPI_HMAT_P2P_NON_UIO   (1)	/* Bit 0: Entries apply to non-UIO traffic */
+#define ACPI_HMAT_P2P_UIO       (1<<1)	/* Bit 1: Entries apply to UIO traffic */
+
+/*
+ * The data_type field reuses the ACPI_HMAT_ACCESS/READ/WRITE_LATENCY and
+ * ACPI_HMAT_ACCESS/READ/WRITE_BANDWIDTH values defined for the System
+ * Locality Latency and Bandwidth Information Structure above.
+ */
+
 /*******************************************************************************
  *
  * HPET - High Precision Event Timer table

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH RFC 2/7] ACPI: HMAT: Make PCIe P2P paths available to consumers
  2026-08-12 19:47 [PATCH RFC 0/7] PCI/HMAT: Describe P2PDMA reachability and performance Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 1/7] ACPICA: Define PCIe P2P latency and bandwidth information Leon Romanovsky
@ 2026-08-12 19:47 ` Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 3/7] PCI/P2PDMA: Authorize HMAT-described cross-host-bridge P2P Leon Romanovsky
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: Leon Romanovsky @ 2026-08-12 19:47 UTC (permalink / raw)
  To: Rafael J. Wysocki, Len Brown, Saket Dumbre, Bjorn Helgaas,
	Logan Gunthorpe
  Cc: linux-acpi, acpica-devel, linux-kernel, linux-pci, Lukas Wunner,
	Natu, Mahesh

From: Leon Romanovsky <leonro@nvidia.com>

P2PDMA policy must preserve the direction, ordering requirements, and
reachability described by HMAT Type 3. A path may work in only one
direction or only with Unordered I/O, and a path the platform does not
describe must never authorize ordinary DMA.

Record each Generic Port pair and traffic class independently, reporting a
pair firmware never mentions separately from a pair it describes without a
usable value. Entries that normalize to zero, whether the platform encodes
no path or publishes no number, both mean no path, so consumers fail
closed.

Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
 drivers/acpi/numa/hmat.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/acpi.h     |  26 ++++++
 2 files changed, 230 insertions(+)

diff --git a/drivers/acpi/numa/hmat.c b/drivers/acpi/numa/hmat.c
index 9792dc394756..d06a62b38fff 100644
--- a/drivers/acpi/numa/hmat.c
+++ b/drivers/acpi/numa/hmat.c
@@ -37,6 +37,7 @@ void __init disable_hmat(void)
 static LIST_HEAD(targets);
 static LIST_HEAD(initiators);
 static LIST_HEAD(localities);
+static LIST_HEAD(p2p_localities);
 
 static DEFINE_MUTEX(target_lock);
 
@@ -87,6 +88,21 @@ struct memory_locality {
 	struct acpi_hmat_locality *hmat_loc;
 };
 
+/*
+ * PCIe peer-to-peer access coordinates between two generic ports (PCIe host
+ * bridges), keyed by their initiator and target proximity domains. A separate
+ * set of coordinates is kept for ordered (non-UIO) and Unordered I/O (UIO)
+ * traffic, as described by the HMAT PCIe P2P Latency and Bandwidth Information
+ * Structure.
+ */
+struct memory_p2p_locality {
+	struct list_head node;
+	int initiator;
+	int target;
+	unsigned int valid;	/* bitmap of populated enum hmat_p2p_class */
+	struct access_coordinate coord[HMAT_P2P_MAX];
+};
+
 static struct memory_initiator *find_mem_initiator(unsigned int cpu_pxm)
 {
 	struct memory_initiator *initiator;
@@ -191,6 +207,81 @@ int acpi_get_genport_coordinates(u32 uid,
 }
 EXPORT_SYMBOL_NS_GPL(acpi_get_genport_coordinates, "CXL");
 
+/**
+ * acpi_get_genport_proximity_domain - Retrieve the proximity domain of a
+ *				       generic port
+ * @uid: ACPI unique id of the generic port (typically a PCIe host bridge)
+ *
+ * The targets list is built while parsing SRAT during boot and is never
+ * modified afterwards, and both gen_port_device_handle and memory_pxm are
+ * written once by alloc_genport_target() and alloc_target(). So this
+ * takes no target_lock, unlike acpi_get_genport_coordinates() which reads
+ * target->coord[] that hmat_calculate_adistance() updates at runtime. PCI
+ * P2PDMA calls this from the DMA mapping path, which must not sleep.
+ *
+ * Return: the proximity domain on success, negative errno on failure.
+ */
+int acpi_get_genport_proximity_domain(u32 uid)
+{
+	struct memory_target *target;
+
+	target = acpi_find_genport_target(uid);
+	if (!target)
+		return -ENOENT;
+
+	return target->memory_pxm;
+}
+EXPORT_SYMBOL_GPL(acpi_get_genport_proximity_domain);
+
+/*
+ * The p2p_localities list is fully populated while parsing the HMAT during
+ * boot and is never modified afterwards, so runtime readers below walk it
+ * without holding target_lock.
+ */
+static struct memory_p2p_locality *find_p2p_locality(int initiator, int target)
+{
+	struct memory_p2p_locality *loc;
+
+	list_for_each_entry(loc, &p2p_localities, node)
+		if (loc->initiator == initiator && loc->target == target)
+			return loc;
+	return NULL;
+}
+
+static bool p2p_coord_populated(const struct access_coordinate *coord)
+{
+	return coord->read_bandwidth || coord->write_bandwidth ||
+	       coord->read_latency || coord->write_latency;
+}
+
+/**
+ * acpi_get_p2p_coordinates - Retrieve the P2P access coordinates between two
+ *			      generic ports (PCIe host bridges)
+ * @initiator_pxm: proximity domain of the initiating generic port
+ * @target_pxm: proximity domain of the target generic port
+ * @class: traffic class (ordered/non-UIO or UIO)
+ * @coord: the access coordinates written back on success
+ *
+ * Return: 0 on success, -ENOENT if the firmware describes no entry for the
+ * pair, or -ENODATA if the described path is unreachable for @class.
+ */
+int acpi_get_p2p_coordinates(int initiator_pxm, int target_pxm,
+			     enum hmat_p2p_class class,
+			     struct access_coordinate *coord)
+{
+	struct memory_p2p_locality *loc;
+
+	loc = find_p2p_locality(initiator_pxm, target_pxm);
+	if (!loc)
+		return -ENOENT;
+	if (!(loc->valid & BIT(class)) || !p2p_coord_populated(&loc->coord[class]))
+		return -ENODATA;
+
+	*coord = loc->coord[class];
+	return 0;
+}
+EXPORT_SYMBOL_GPL(acpi_get_p2p_coordinates);
+
 static __init void alloc_memory_initiator(unsigned int cpu_pxm)
 {
 	struct memory_initiator *initiator;
@@ -479,6 +570,111 @@ static __init int hmat_parse_locality(union acpi_subtable_headers *header,
 	return 0;
 }
 
+static void hmat_update_p2p_access(struct access_coordinate *coord,
+				   u8 type, u32 value)
+{
+	switch (type) {
+	case ACPI_HMAT_ACCESS_LATENCY:
+		coord->read_latency = value;
+		coord->write_latency = value;
+		break;
+	case ACPI_HMAT_READ_LATENCY:
+		coord->read_latency = value;
+		break;
+	case ACPI_HMAT_WRITE_LATENCY:
+		coord->write_latency = value;
+		break;
+	case ACPI_HMAT_ACCESS_BANDWIDTH:
+		coord->read_bandwidth = value;
+		coord->write_bandwidth = value;
+		break;
+	case ACPI_HMAT_READ_BANDWIDTH:
+		coord->read_bandwidth = value;
+		break;
+	case ACPI_HMAT_WRITE_BANDWIDTH:
+		coord->write_bandwidth = value;
+		break;
+	default:
+		break;
+	}
+}
+
+static __init void hmat_update_p2p(int initiator, int target, u8 flags,
+				   u8 type, u32 value)
+{
+	struct memory_p2p_locality *loc;
+	enum hmat_p2p_class class;
+
+	loc = find_p2p_locality(initiator, target);
+	if (!loc) {
+		loc = kzalloc_obj(*loc);
+		if (!loc)
+			return;
+		loc->initiator = initiator;
+		loc->target = target;
+		list_add_tail(&loc->node, &p2p_localities);
+	}
+
+	for (class = 0; class < HMAT_P2P_MAX; class++) {
+		if (class == HMAT_P2P_NON_UIO && !(flags & ACPI_HMAT_P2P_NON_UIO))
+			continue;
+		if (class == HMAT_P2P_UIO && !(flags & ACPI_HMAT_P2P_UIO))
+			continue;
+		hmat_update_p2p_access(&loc->coord[class], type, value);
+		loc->valid |= BIT(class);
+	}
+}
+
+static __init int hmat_parse_p2p_latency(union acpi_subtable_headers *header,
+					 const unsigned long end)
+{
+	struct acpi_hmat_p2p_latency *p2p = (void *)header;
+	unsigned int init, targ, total_size, ipds, tpds;
+	u32 *inits, *targs, value;
+	u16 *entries;
+	u8 type, flags;
+
+	if (p2p->header.length < sizeof(*p2p)) {
+		pr_notice("Unexpected P2P header length: %u\n",
+			  p2p->header.length);
+		return -EINVAL;
+	}
+
+	type = p2p->data_type;
+	flags = p2p->flags;
+	ipds = p2p->number_of_initiator_Pds;
+	tpds = p2p->number_of_target_Pds;
+	total_size = sizeof(*p2p) + sizeof(*entries) * ipds * tpds +
+		     sizeof(*inits) * ipds + sizeof(*targs) * tpds;
+	if (p2p->header.length < total_size) {
+		pr_notice("Unexpected P2P header length:%u, minimum required:%u\n",
+			  p2p->header.length, total_size);
+		return -EINVAL;
+	}
+
+	pr_debug("P2P: Flags:%02x Type:%s Initiator Ports:%u Target Ports:%u Base:%lld\n",
+		 p2p->flags, hmat_data_type(type), ipds, tpds,
+		 p2p->entry_base_unit);
+
+	inits = (u32 *)(p2p + 1);
+	targs = inits + ipds;
+	entries = (u16 *)(targs + tpds);
+	for (init = 0; init < ipds; init++) {
+		for (targ = 0; targ < tpds; targ++) {
+			value = hmat_normalize(entries[init * tpds + targ],
+					       p2p->entry_base_unit, type);
+			pr_debug("  Initiator-Target[%u-%u]:%u%s\n",
+				 inits[init], targs[targ], value,
+				 hmat_data_type_suffix(type));
+
+			hmat_update_p2p(inits[init], targs[targ], flags,
+					type, value);
+		}
+	}
+
+	return 0;
+}
+
 static __init int hmat_parse_cache(union acpi_subtable_headers *header,
 				   const unsigned long end)
 {
@@ -603,6 +799,8 @@ static int __init hmat_parse_subtable(union acpi_subtable_headers *header,
 		return hmat_parse_locality(header, end);
 	case ACPI_HMAT_TYPE_CACHE:
 		return hmat_parse_cache(header, end);
+	case ACPI_HMAT_TYPE_P2P_LATENCY:
+		return hmat_parse_p2p_latency(header, end);
 	default:
 		return -EINVAL;
 	}
@@ -1004,6 +1202,7 @@ static __init void hmat_free_structures(void)
 {
 	struct memory_target *target, *tnext;
 	struct memory_locality *loc, *lnext;
+	struct memory_p2p_locality *ploc, *pnext;
 	struct memory_initiator *initiator, *inext;
 	struct target_cache *tcache, *cnext;
 
@@ -1035,6 +1234,11 @@ static __init void hmat_free_structures(void)
 		list_del(&loc->node);
 		kfree(loc);
 	}
+
+	list_for_each_entry_safe(ploc, pnext, &p2p_localities, node) {
+		list_del(&ploc->node);
+		kfree(ploc);
+	}
 }
 
 static __init int hmat_init(void)
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index 60ab50cb8930..3d1f564213f1 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -1152,15 +1152,41 @@ static inline int acpi_mrrm_max_mem_region(void)
 
 #endif	/* !CONFIG_ACPI */
 
+/*
+ * PCIe peer-to-peer traffic classes described by an HMAT PCIe P2P Latency
+ * and Bandwidth Information Structure (ACPI_HMAT_TYPE_P2P_LATENCY).
+ */
+enum hmat_p2p_class {
+	HMAT_P2P_NON_UIO,	/* Ordered (non-UIO) P2P traffic */
+	HMAT_P2P_UIO,		/* PCIe Unordered I/O (UIO) P2P traffic */
+	HMAT_P2P_MAX,
+};
+
 #ifdef CONFIG_ACPI_HMAT
 int hmat_get_extended_linear_cache_size(struct resource *backing_res, int nid,
 					resource_size_t *size);
+int acpi_get_genport_proximity_domain(u32 uid);
+int acpi_get_p2p_coordinates(int initiator_pxm, int target_pxm,
+			     enum hmat_p2p_class class,
+			     struct access_coordinate *coord);
 #else
 static inline int hmat_get_extended_linear_cache_size(struct resource *backing_res,
 						      int nid, resource_size_t *size)
 {
 	return -EOPNOTSUPP;
 }
+
+static inline int acpi_get_genport_proximity_domain(u32 uid)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int acpi_get_p2p_coordinates(int initiator_pxm, int target_pxm,
+					   enum hmat_p2p_class class,
+					   struct access_coordinate *coord)
+{
+	return -EOPNOTSUPP;
+}
 #endif
 
 extern void arch_post_acpi_subsys_init(void);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH RFC 3/7] PCI/P2PDMA: Authorize HMAT-described cross-host-bridge P2P
  2026-08-12 19:47 [PATCH RFC 0/7] PCI/HMAT: Describe P2PDMA reachability and performance Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 1/7] ACPICA: Define PCIe P2P latency and bandwidth information Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 2/7] ACPI: HMAT: Make PCIe P2P paths available to consumers Leon Romanovsky
@ 2026-08-12 19:47 ` Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 4/7] PCI/P2PDMA: Prefer providers with better HMAT performance Leon Romanovsky
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: Leon Romanovsky @ 2026-08-12 19:47 UTC (permalink / raw)
  To: Rafael J. Wysocki, Len Brown, Saket Dumbre, Bjorn Helgaas,
	Logan Gunthorpe
  Cc: linux-acpi, acpica-devel, linux-kernel, linux-pci, Lukas Wunner,
	Natu, Mahesh

From: Leon Romanovsky <leonro@nvidia.com>

PCI P2PDMA rejects cross-host-bridge transactions unless the platform
matches hard-coded CPU or host-bridge knowledge. That excludes capable
systems, requires recurring kernel whitelist updates, and cannot represent
firmware-qualified directional paths.

Accept a reachable ordered HMAT path from requester to completer as
platform authorization. Missing entries, unreachable entries, and UIO-only
entries do not authorize ordinary DMA. The existing safety boundary remains
in force when firmware does not describe a usable ordered path.

Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
 drivers/pci/p2pdma.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 54 insertions(+), 1 deletion(-)

diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index 34929bc6efb6..dd300d2e9887 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -9,6 +9,7 @@
  */
 
 #define pr_fmt(fmt) "pci-p2pdma: " fmt
+#include <linux/acpi.h>
 #include <linux/ctype.h>
 #include <linux/dma-map-ops.h>
 #include <linux/pci-p2pdma.h>
@@ -733,6 +734,57 @@ static bool host_bridge_whitelist(struct pci_dev *a, struct pci_dev *b,
 	return false;
 }
 
+#ifdef CONFIG_ACPI
+static 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;
+
+	adev = to_acpi_device_node(host->dev.fwnode);
+	if (!adev)
+		return -ENODEV;
+
+	uid_str = acpi_device_uid(adev);
+	if (!uid_str || kstrtou32(uid_str, 0, &uid))
+		return -ENODEV;
+
+	return acpi_get_genport_proximity_domain(uid);
+}
+#else
+static int pci_host_bridge_pxm(struct pci_dev *pdev)
+{
+	return -ENODEV;
+}
+#endif
+
+/*
+ * Check whether platform firmware describes, via an HMAT PCIe P2P Latency and
+ * Bandwidth Information Structure, a reachable ordered (non-UIO) P2P path from
+ * the client's host bridge to the provider's host bridge. When it does, the
+ * platform vouches for cross-host-bridge P2P even if the root complex is not
+ * in the static whitelist.
+ *
+ * The client is the requester of the P2P transactions and the provider is the
+ * completer, so the client's host bridge is the HMAT initiator and the
+ * provider's host bridge is the HMAT target. UIO-only paths are described by
+ * firmware but are not (yet) used to authorize ordered DMA.
+ */
+static bool host_bridge_hmat_p2p(struct pci_dev *provider, struct pci_dev *client)
+{
+	struct access_coordinate coord;
+	int init_pxm, targ_pxm;
+
+	init_pxm = pci_host_bridge_pxm(client);
+	targ_pxm = pci_host_bridge_pxm(provider);
+	if (init_pxm < 0 || targ_pxm < 0)
+		return false;
+
+	return acpi_get_p2p_coordinates(init_pxm, targ_pxm, HMAT_P2P_NON_UIO,
+					&coord) == 0;
+}
+
 static unsigned long map_types_idx(struct pci_dev *client)
 {
 	return (pci_domain_nr(client->bus) << 16) | pci_dev_id(client);
@@ -890,9 +942,10 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
 
 map_through_host_bridge:
 	if (!cpu_supports_p2pdma() &&
+	    !host_bridge_hmat_p2p(provider, client) &&
 	    !host_bridge_whitelist(provider, client, verbose)) {
 		if (verbose)
-			pci_warn(client, "cannot be used for peer-to-peer DMA as the client and provider (%s) do not share an upstream bridge or whitelisted host bridge\n",
+			pci_warn(client, "cannot be used for peer-to-peer DMA as the client and provider (%s) do not share an upstream bridge, whitelisted host bridge, or HMAT-described P2P path\n",
 				 pci_name(provider));
 		map_type = PCI_P2PDMA_MAP_NOT_SUPPORTED;
 	}

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH RFC 4/7] PCI/P2PDMA: Prefer providers with better HMAT performance
  2026-08-12 19:47 [PATCH RFC 0/7] PCI/HMAT: Describe P2PDMA reachability and performance Leon Romanovsky
                   ` (2 preceding siblings ...)
  2026-08-12 19:47 ` [PATCH RFC 3/7] PCI/P2PDMA: Authorize HMAT-described cross-host-bridge P2P Leon Romanovsky
@ 2026-08-12 19:47 ` Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 5/7] ACPI: HMAT: Add KUnit tests for PCIe P2P matrix semantics Leon Romanovsky
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: Leon Romanovsky @ 2026-08-12 19:47 UTC (permalink / raw)
  To: Rafael J. Wysocki, Len Brown, Saket Dumbre, Bjorn Helgaas,
	Logan Gunthorpe
  Cc: linux-acpi, acpica-devel, linux-kernel, linux-pci, Lukas Wunner,
	Natu, Mahesh

From: Leon Romanovsky <leonro@nvidia.com>

Provider selection uses PCI hop count as a performance proxy. That can
choose a slower cross-host-bridge path when another reachable provider is
topologically farther but offers more bandwidth or lower latency.

Rank providers reachable entirely through direct routing ahead of any
host-bridge path, then compare host-bridge paths by their worst
ordered-traffic bandwidth and latency across all clients. Hop distance
orders providers within a rank and breaks the remaining ties. Selection
therefore also changes where firmware publishes nothing, because direct
routing now outranks a shorter host-bridge path.

Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
 drivers/pci/p2pdma.c | 254 ++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 190 insertions(+), 64 deletions(-)

diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c
index dd300d2e9887..88119620890f 100644
--- a/drivers/pci/p2pdma.c
+++ b/drivers/pci/p2pdma.c
@@ -69,6 +69,22 @@ 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);
@@ -770,10 +786,14 @@ static int pci_host_bridge_pxm(struct pci_dev *pdev)
  * completer, so the client's host bridge is the HMAT initiator and the
  * provider's host bridge is the HMAT target. UIO-only paths are described by
  * firmware but are not (yet) used to authorize ordered DMA.
+ *
+ * @coord may be NULL when only the authorization answer is needed.
  */
-static bool host_bridge_hmat_p2p(struct pci_dev *provider, struct pci_dev *client)
+static bool host_bridge_hmat_p2p(struct pci_dev *provider,
+				 struct pci_dev *client,
+				 struct access_coordinate *coord)
 {
-	struct access_coordinate coord;
+	struct access_coordinate unused;
 	int init_pxm, targ_pxm;
 
 	init_pxm = pci_host_bridge_pxm(client);
@@ -782,7 +802,7 @@ static bool host_bridge_hmat_p2p(struct pci_dev *provider, struct pci_dev *clien
 		return false;
 
 	return acpi_get_p2p_coordinates(init_pxm, targ_pxm, HMAT_P2P_NON_UIO,
-					&coord) == 0;
+					coord ? coord : &unused) == 0;
 }
 
 static unsigned long map_types_idx(struct pci_dev *client)
@@ -790,6 +810,10 @@ 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.
  *
@@ -820,21 +844,29 @@ static unsigned long map_types_idx(struct pci_dev *client)
  * PCI_P2PDMA_MAP_THRU_HOST_BRIDGE. Otherwise, return
  * PCI_P2PDMA_MAP_BUS_ADDR.
  *
- * Any two devices that have a data path that goes through the host bridge
- * will consult a whitelist. If the host bridge is in the whitelist, return
- * PCI_P2PDMA_MAP_THRU_HOST_BRIDGE with the distance set to the number of
- * ports per above. If the device is not in the whitelist, return
- * PCI_P2PDMA_MAP_NOT_SUPPORTED.
+ * Any two devices that have a data path through a host bridge require
+ * platform support from the CPU, the host bridge whitelist, or a reachable
+ * 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)
 {
 	enum pci_p2pdma_map_type map_type = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE;
 	struct pci_dev *a = provider, *b = client, *bb, *target;
 	struct pci_dev *a_child = NULL, *b_child = NULL;
 	struct pci_dev *acs_unsupported = NULL;
 	enum pci_acs_p2pdma_state state;
+	bool host_bridge_allowed;
+	bool hmat_p2p = false;
 	struct pci_p2pdma *p2pdma;
 	struct seq_buf acs_list;
 	int acs_redirect_cnt = 0;
@@ -842,6 +874,9 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
 	int dist_b = 0;
 	char buf[128];
 
+	if (hmat_coord)
+		*hmat_coord = (struct access_coordinate) {};
+
 	seq_buf_init(&acs_list, buf, sizeof(buf));
 
 	/*
@@ -941,12 +976,22 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
 	}
 
 map_through_host_bridge:
-	if (!cpu_supports_p2pdma() &&
-	    !host_bridge_hmat_p2p(provider, client) &&
-	    !host_bridge_whitelist(provider, client, verbose)) {
-		if (verbose)
+	host_bridge_allowed = cpu_supports_p2pdma() ||
+			      host_bridge_whitelist(provider, client,
+						    false);
+	/*
+	 * The coordinates are only used to rank providers, which happens in
+	 * process context. Skip the firmware lookup on the mapping path once
+	 * the CPU or the whitelist has already permitted the path.
+	 */
+	if (hmat_coord || !host_bridge_allowed)
+		hmat_p2p = host_bridge_hmat_p2p(provider, client, hmat_coord);
+	if (!host_bridge_allowed && !hmat_p2p) {
+		if (verbose) {
+			host_bridge_whitelist(provider, client, true);
 			pci_warn(client, "cannot be used for peer-to-peer DMA as the client and provider (%s) do not share an upstream bridge, whitelisted host bridge, or HMAT-described P2P path\n",
 				 pci_name(provider));
+		}
 		map_type = PCI_P2PDMA_MAP_NOT_SUPPORTED;
 	}
 done:
@@ -966,32 +1011,53 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client,
 }
 EXPORT_SYMBOL_IF_KUNIT(calc_map_type_and_dist);
 
-/**
- * pci_p2pdma_distance_many - Determine the cumulative distance between
- *	a p2pdma provider and the clients in use.
- * @provider: p2pdma provider to check against the client list
- * @clients: array of devices to check (NULL-terminated)
- * @num_clients: number of clients in the array
- * @verbose: if true, print warnings for devices when we return -1
- *
- * Returns -1 if any of the clients are not compatible, otherwise returns a
- * positive number where a lower number is the preferable choice. (If there's
- * one client that's the same as the provider it will return 0, which is best
- * choice).
- *
- * "compatible" means the provider and the clients are either all behind
- * the same PCI root port or the host bridges connected to each of the devices
- * are listed in the 'pci_p2pdma_whitelist'.
+static int
+pci_p2pdma_rank_cmp(const struct pci_p2pdma_rank *a,
+		    const struct pci_p2pdma_rank *b)
+{
+	if (a->type != b->type)
+		return a->type < b->type ? -1 : 1;
+
+	if (a->type == PCI_P2PDMA_RANK_HMAT_BANDWIDTH) {
+		if (a->bandwidth != b->bandwidth)
+			return a->bandwidth > b->bandwidth ? -1 : 1;
+		if (a->latency_valid != b->latency_valid)
+			return a->latency_valid ? -1 : 1;
+	}
+
+	if ((a->type == PCI_P2PDMA_RANK_HMAT_LATENCY ||
+	     a->latency_valid) && a->latency != b->latency)
+		return a->latency < b->latency ? -1 : 1;
+
+	if (a->distance != b->distance)
+		return a->distance < b->distance ? -1 : 1;
+
+	return 0;
+}
+
+/*
+ * 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.
  */
-int pci_p2pdma_distance_many(struct pci_dev *provider, struct device **clients,
-			     int num_clients, bool verbose)
+static int
+pci_p2pdma_rank_many(struct pci_dev *provider, struct device **clients,
+		     int num_clients, bool verbose,
+		     struct pci_p2pdma_rank *rank)
 {
 	enum pci_p2pdma_map_type map;
+	struct access_coordinate coord;
+	bool bandwidth_valid = true;
+	bool latency_valid = true;
+	bool through_host_bridge = false;
 	bool not_supported = false;
 	struct pci_dev *pci_client;
-	int total_dist = 0;
 	int i, distance;
 
+	*rank = (struct pci_p2pdma_rank) {
+		.bandwidth = UINT_MAX,
+	};
+
 	if (num_clients == 0)
 		return -1;
 
@@ -1004,24 +1070,81 @@ int pci_p2pdma_distance_many(struct pci_dev *provider, struct device **clients,
 			return -1;
 		}
 
-		map = calc_map_type_and_dist(provider, pci_client, &distance,
-					     verbose);
+		map = __calc_map_type_and_dist(provider, pci_client, &distance,
+					       verbose, &coord);
 
 		pci_dev_put(pci_client);
 
 		if (map == PCI_P2PDMA_MAP_NOT_SUPPORTED)
 			not_supported = true;
+		else if (map == PCI_P2PDMA_MAP_THRU_HOST_BRIDGE) {
+			through_host_bridge = true;
+			if (!coord.read_bandwidth || !coord.write_bandwidth) {
+				bandwidth_valid = false;
+			} else {
+				rank->bandwidth = min(rank->bandwidth,
+					min(coord.read_bandwidth,
+					    coord.write_bandwidth));
+			}
+
+			if (!coord.read_latency || !coord.write_latency) {
+				latency_valid = false;
+			} else {
+				rank->latency = max(rank->latency,
+					max(coord.read_latency,
+					    coord.write_latency));
+			}
+		}
 
 		if (not_supported && !verbose)
 			break;
 
-		total_dist += distance;
+		rank->distance += distance;
 	}
 
 	if (not_supported)
 		return -1;
 
-	return total_dist;
+	if (!through_host_bridge) {
+		rank->type = PCI_P2PDMA_RANK_DIRECT;
+	} else if (bandwidth_valid) {
+		rank->type = PCI_P2PDMA_RANK_HMAT_BANDWIDTH;
+		rank->latency_valid = latency_valid;
+	} else if (latency_valid) {
+		rank->type = PCI_P2PDMA_RANK_HMAT_LATENCY;
+		rank->latency_valid = true;
+	} else {
+		rank->type = PCI_P2PDMA_RANK_DISTANCE;
+	}
+
+	return 0;
+}
+
+/**
+ * pci_p2pdma_distance_many - Determine the cumulative distance between
+ *	a p2pdma provider and the clients in use.
+ * @provider: p2pdma provider to check against the client list
+ * @clients: array of devices to check (NULL-terminated)
+ * @num_clients: number of clients in the array
+ * @verbose: if true, print warnings for devices when we return -1
+ *
+ * Returns -1 if any of the clients are not compatible, otherwise returns a
+ * positive number where a lower number is the preferable choice. (If there's
+ * one client that's the same as the provider it will return 0, which is best
+ * choice).
+ *
+ * "compatible" means the provider and the clients have a direct PCI path or
+ * the platform permits the transaction through the host bridge.
+ */
+int pci_p2pdma_distance_many(struct pci_dev *provider, struct device **clients,
+			     int num_clients, bool verbose)
+{
+	struct pci_p2pdma_rank rank;
+
+	if (pci_p2pdma_rank_many(provider, clients, num_clients, verbose, &rank))
+		return -1;
+
+	return rank.distance;
 }
 EXPORT_SYMBOL_GPL(pci_p2pdma_distance_many);
 
@@ -1049,15 +1172,15 @@ static bool pci_has_p2pmem(struct pci_dev *pdev)
 
 /**
  * pci_p2pmem_find_many - find a peer-to-peer DMA memory device compatible with
- *	the specified list of clients and shortest distance
+ *	the specified list of clients
  * @clients: array of devices to check (NULL-terminated)
  * @num_clients: number of client devices in the list
  *
- * If multiple devices are behind the same switch, the one "closest" to the
- * client devices in use will be chosen first. (So if one of the providers is
- * the same as one of the clients, that provider will be used ahead of any
- * other providers that are unrelated). If multiple providers are an equal
- * distance away, one will be chosen at random.
+ * A provider with direct paths to all clients is preferred. For paths through
+ * host bridges, complete ordered HMAT bandwidth is ranked before latency-only
+ * data, using the worst client path for each metric. Topology distance breaks
+ * performance ties and remains the fallback when HMAT data is incomplete. If
+ * multiple providers have an equal rank, one is chosen at random.
  *
  * Returns a pointer to the PCI device with a reference taken (use pci_dev_put
  * to return the reference) or NULL if no compatible device is found. The
@@ -1066,47 +1189,50 @@ static bool pci_has_p2pmem(struct pci_dev *pdev)
 struct pci_dev *pci_p2pmem_find_many(struct device **clients, int num_clients)
 {
 	struct pci_dev *pdev = NULL;
-	int distance;
-	int closest_distance = INT_MAX;
-	struct pci_dev **closest_pdevs;
+	struct pci_p2pdma_rank rank, best_rank;
+	struct pci_dev **best_pdevs;
+	bool have_best = false;
 	int dev_cnt = 0;
-	const int max_devs = PAGE_SIZE / sizeof(*closest_pdevs);
-	int i;
+	const int max_devs = PAGE_SIZE / sizeof(*best_pdevs);
+	int cmp, i;
 
-	closest_pdevs = kmalloc(PAGE_SIZE, GFP_KERNEL);
-	if (!closest_pdevs)
+	best_pdevs = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!best_pdevs)
 		return NULL;
 
 	for_each_pci_dev(pdev) {
 		if (!pci_has_p2pmem(pdev))
 			continue;
 
-		distance = pci_p2pdma_distance_many(pdev, clients,
-						    num_clients, false);
-		if (distance < 0 || distance > closest_distance)
-			continue;
-
-		if (distance == closest_distance && dev_cnt >= max_devs)
+		if (pci_p2pdma_rank_many(pdev, clients, num_clients, false,
+					  &rank))
 			continue;
 
-		if (distance < closest_distance) {
-			for (i = 0; i < dev_cnt; i++)
-				pci_dev_put(closest_pdevs[i]);
-
-			dev_cnt = 0;
-			closest_distance = distance;
+		if (have_best) {
+			cmp = pci_p2pdma_rank_cmp(&rank, &best_rank);
+			if (cmp > 0 || (!cmp && dev_cnt >= max_devs))
+				continue;
+			if (cmp < 0) {
+				for (i = 0; i < dev_cnt; i++)
+					pci_dev_put(best_pdevs[i]);
+				dev_cnt = 0;
+				best_rank = rank;
+			}
+		} else {
+			best_rank = rank;
+			have_best = true;
 		}
 
-		closest_pdevs[dev_cnt++] = pci_dev_get(pdev);
+		best_pdevs[dev_cnt++] = pci_dev_get(pdev);
 	}
 
 	if (dev_cnt)
-		pdev = pci_dev_get(closest_pdevs[get_random_u32_below(dev_cnt)]);
+		pdev = pci_dev_get(best_pdevs[get_random_u32_below(dev_cnt)]);
 
 	for (i = 0; i < dev_cnt; i++)
-		pci_dev_put(closest_pdevs[i]);
+		pci_dev_put(best_pdevs[i]);
 
-	kfree(closest_pdevs);
+	kfree(best_pdevs);
 	return pdev;
 }
 EXPORT_SYMBOL_GPL(pci_p2pmem_find_many);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH RFC 5/7] ACPI: HMAT: Add KUnit tests for PCIe P2P matrix semantics
  2026-08-12 19:47 [PATCH RFC 0/7] PCI/HMAT: Describe P2PDMA reachability and performance Leon Romanovsky
                   ` (3 preceding siblings ...)
  2026-08-12 19:47 ` [PATCH RFC 4/7] PCI/P2PDMA: Prefer providers with better HMAT performance Leon Romanovsky
@ 2026-08-12 19:47 ` Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 6/7] PCI/P2PDMA: Add KUnit tests for HMAT policy boundaries Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 7/7] PCI/P2PDMA: Add KUnit tests for HMAT provider ranking Leon Romanovsky
  6 siblings, 0 replies; 10+ messages in thread
From: Leon Romanovsky @ 2026-08-12 19:47 UTC (permalink / raw)
  To: Rafael J. Wysocki, Len Brown, Saket Dumbre, Bjorn Helgaas,
	Logan Gunthorpe
  Cc: linux-acpi, acpica-devel, linux-kernel, linux-pci, Lukas Wunner,
	Natu, Mahesh

From: Leon Romanovsky <leonro@nvidia.com>

HMAT P2P matrices are directional and merge independent records for ordered
and UIO traffic. An indexing, normalization, or merge regression could turn
an unreachable entry into another host bridge's usable path and corrupt
P2PDMA policy.

Protect this firmware trust boundary with coverage for matrix direction,
normalization, traffic-class merging, malformed tables, missing pairs, and
unreachable paths.

Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
 drivers/acpi/numa/Kconfig     |  13 +++
 drivers/acpi/numa/Makefile    |   1 +
 drivers/acpi/numa/hmat.c      |  95 ++++++++++++------
 drivers/acpi/numa/hmat_test.c | 223 ++++++++++++++++++++++++++++++++++++++++++
 drivers/acpi/numa/hmat_test.h |  17 ++++
 5 files changed, 317 insertions(+), 32 deletions(-)

diff --git a/drivers/acpi/numa/Kconfig b/drivers/acpi/numa/Kconfig
index f33194d1e43f..fed47ae67a38 100644
--- a/drivers/acpi/numa/Kconfig
+++ b/drivers/acpi/numa/Kconfig
@@ -13,3 +13,16 @@ config ACPI_HMAT
 	 register memory initiators with their targets, and export
 	 performance attributes through the node's sysfs device if
 	 provided.
+
+config ACPI_HMAT_KUNIT_TEST
+	bool "KUnit tests for ACPI HMAT P2P matrices" if !KUNIT_ALL_TESTS
+	depends on ACPI_HMAT && KUNIT=y
+	default KUNIT_ALL_TESTS
+	help
+	  Enable KUnit coverage for parsing and querying the PCIe P2P
+	  Latency and Bandwidth Information Structure, including directional
+	  matrix indexing, traffic classes, normalization, and unreachable
+	  entries.
+
+	  For more information on KUnit and unit tests in general, refer to
+	  the KUnit documentation in Documentation/dev-tools/kunit/.
diff --git a/drivers/acpi/numa/Makefile b/drivers/acpi/numa/Makefile
index 517a6c689a94..3c1a35be85bf 100644
--- a/drivers/acpi/numa/Makefile
+++ b/drivers/acpi/numa/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_ACPI_NUMA) += srat.o
 obj-$(CONFIG_ACPI_HMAT) += hmat.o
+obj-$(CONFIG_ACPI_HMAT_KUNIT_TEST) += hmat_test.o
diff --git a/drivers/acpi/numa/hmat.c b/drivers/acpi/numa/hmat.c
index d06a62b38fff..d2964548aaaf 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/visibility.h>
 #include <linux/acpi.h>
 #include <linux/bitops.h>
 #include <linux/device.h>
@@ -26,6 +27,8 @@
 #include <linux/dax.h>
 #include <linux/memory-tiers.h>
 
+#include "hmat_test.h"
+
 static u8 hmat_revision;
 static int hmat_disable __initdata;
 
@@ -238,11 +241,12 @@ EXPORT_SYMBOL_GPL(acpi_get_genport_proximity_domain);
  * boot and is never modified afterwards, so runtime readers below walk it
  * without holding target_lock.
  */
-static struct memory_p2p_locality *find_p2p_locality(int initiator, int target)
+static struct memory_p2p_locality *
+find_p2p_locality(struct list_head *localities, int initiator, int target)
 {
 	struct memory_p2p_locality *loc;
 
-	list_for_each_entry(loc, &p2p_localities, node)
+	list_for_each_entry(loc, localities, node)
 		if (loc->initiator == initiator && loc->target == target)
 			return loc;
 	return NULL;
@@ -254,6 +258,23 @@ static bool p2p_coord_populated(const struct access_coordinate *coord)
 	       coord->read_latency || coord->write_latency;
 }
 
+VISIBLE_IF_KUNIT int
+hmat_get_p2p_coordinates(struct list_head *localities, int initiator_pxm,
+			 int target_pxm, enum hmat_p2p_class class,
+			 struct access_coordinate *coord)
+{
+	struct memory_p2p_locality *loc;
+
+	loc = find_p2p_locality(localities, initiator_pxm, target_pxm);
+	if (!loc)
+		return -ENOENT;
+	if (!(loc->valid & BIT(class)) || !p2p_coord_populated(&loc->coord[class]))
+		return -ENODATA;
+
+	*coord = loc->coord[class];
+	return 0;
+}
+
 /**
  * acpi_get_p2p_coordinates - Retrieve the P2P access coordinates between two
  *			      generic ports (PCIe host bridges)
@@ -269,19 +290,10 @@ int acpi_get_p2p_coordinates(int initiator_pxm, int target_pxm,
 			     enum hmat_p2p_class class,
 			     struct access_coordinate *coord)
 {
-	struct memory_p2p_locality *loc;
-
-	loc = find_p2p_locality(initiator_pxm, target_pxm);
-	if (!loc)
-		return -ENOENT;
-	if (!(loc->valid & BIT(class)) || !p2p_coord_populated(&loc->coord[class]))
-		return -ENODATA;
-
-	*coord = loc->coord[class];
-	return 0;
+	return hmat_get_p2p_coordinates(&p2p_localities, initiator_pxm,
+					 target_pxm, class, coord);
 }
 EXPORT_SYMBOL_GPL(acpi_get_p2p_coordinates);
-
 static __init void alloc_memory_initiator(unsigned int cpu_pxm)
 {
 	struct memory_initiator *initiator;
@@ -394,7 +406,7 @@ static __init const char *hmat_data_type_suffix(u8 type)
 	}
 }
 
-static u32 hmat_normalize(u16 entry, u64 base, u8 type)
+static u32 hmat_normalize_revision(u16 entry, u64 base, u8 type, u8 revision)
 {
 	u32 value;
 
@@ -411,11 +423,11 @@ static u32 hmat_normalize(u16 entry, u64 base, u8 type)
 	 * picosenonds to nanoseconds if revision 2.
 	 */
 	value = entry * base;
-	if (hmat_revision == 1) {
+	if (revision == 1) {
 		if (value < 10)
 			return 0;
 		value = DIV_ROUND_UP(value, 10);
-	} else if (hmat_revision == 2) {
+	} else if (revision == 2) {
 		switch (type) {
 		case ACPI_HMAT_ACCESS_LATENCY:
 		case ACPI_HMAT_READ_LATENCY:
@@ -429,6 +441,11 @@ static u32 hmat_normalize(u16 entry, u64 base, u8 type)
 	return value;
 }
 
+static u32 hmat_normalize(u16 entry, u64 base, u8 type)
+{
+	return hmat_normalize_revision(entry, base, type, hmat_revision);
+}
+
 static void hmat_update_target_access(struct memory_target *target,
 				      u8 type, u32 value, int access)
 {
@@ -599,20 +616,20 @@ static void hmat_update_p2p_access(struct access_coordinate *coord,
 	}
 }
 
-static __init void hmat_update_p2p(int initiator, int target, u8 flags,
-				   u8 type, u32 value)
+static __init void hmat_update_p2p(struct list_head *localities, int initiator,
+				   int target, u8 flags, u8 type, u32 value)
 {
 	struct memory_p2p_locality *loc;
 	enum hmat_p2p_class class;
 
-	loc = find_p2p_locality(initiator, target);
+	loc = find_p2p_locality(localities, initiator, target);
 	if (!loc) {
 		loc = kzalloc_obj(*loc);
 		if (!loc)
 			return;
 		loc->initiator = initiator;
 		loc->target = target;
-		list_add_tail(&loc->node, &p2p_localities);
+		list_add_tail(&loc->node, localities);
 	}
 
 	for (class = 0; class < HMAT_P2P_MAX; class++) {
@@ -625,10 +642,10 @@ static __init void hmat_update_p2p(int initiator, int target, u8 flags,
 	}
 }
 
-static __init int hmat_parse_p2p_latency(union acpi_subtable_headers *header,
-					 const unsigned long end)
+VISIBLE_IF_KUNIT int __init
+hmat_parse_p2p(struct acpi_hmat_p2p_latency *p2p, u8 revision,
+	       struct list_head *localities)
 {
-	struct acpi_hmat_p2p_latency *p2p = (void *)header;
 	unsigned int init, targ, total_size, ipds, tpds;
 	u32 *inits, *targs, value;
 	u16 *entries;
@@ -661,20 +678,27 @@ static __init int hmat_parse_p2p_latency(union acpi_subtable_headers *header,
 	entries = (u16 *)(targs + tpds);
 	for (init = 0; init < ipds; init++) {
 		for (targ = 0; targ < tpds; targ++) {
-			value = hmat_normalize(entries[init * tpds + targ],
-					       p2p->entry_base_unit, type);
+			value = hmat_normalize_revision(entries[init * tpds + targ],
+						p2p->entry_base_unit, type,
+						revision);
 			pr_debug("  Initiator-Target[%u-%u]:%u%s\n",
 				 inits[init], targs[targ], value,
 				 hmat_data_type_suffix(type));
 
-			hmat_update_p2p(inits[init], targs[targ], flags,
-					type, value);
+			hmat_update_p2p(localities, inits[init], targs[targ],
+					flags, type, value);
 		}
 	}
 
 	return 0;
 }
 
+static __init int hmat_parse_p2p_latency(union acpi_subtable_headers *header,
+					 const unsigned long end)
+{
+	return hmat_parse_p2p((void *)header, hmat_revision, &p2p_localities);
+}
+
 static __init int hmat_parse_cache(union acpi_subtable_headers *header,
 				   const unsigned long end)
 {
@@ -1198,11 +1222,21 @@ static struct notifier_block hmat_adist_nb __meminitdata = {
 	.priority = 100,
 };
 
+VISIBLE_IF_KUNIT void __init
+hmat_free_p2p_localities(struct list_head *localities)
+{
+	struct memory_p2p_locality *loc, *next;
+
+	list_for_each_entry_safe(loc, next, localities, node) {
+		list_del(&loc->node);
+		kfree(loc);
+	}
+}
+
 static __init void hmat_free_structures(void)
 {
 	struct memory_target *target, *tnext;
 	struct memory_locality *loc, *lnext;
-	struct memory_p2p_locality *ploc, *pnext;
 	struct memory_initiator *initiator, *inext;
 	struct target_cache *tcache, *cnext;
 
@@ -1235,10 +1269,7 @@ static __init void hmat_free_structures(void)
 		kfree(loc);
 	}
 
-	list_for_each_entry_safe(ploc, pnext, &p2p_localities, node) {
-		list_del(&ploc->node);
-		kfree(ploc);
-	}
+	hmat_free_p2p_localities(&p2p_localities);
 }
 
 static __init int hmat_init(void)
diff --git a/drivers/acpi/numa/hmat_test.c b/drivers/acpi/numa/hmat_test.c
new file mode 100644
index 000000000000..3da1678feb15
--- /dev/null
+++ b/drivers/acpi/numa/hmat_test.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for ACPI HMAT PCIe peer-to-peer matrices.
+ */
+
+#include <kunit/test.h>
+
+#include <linux/acpi.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include "hmat_test.h"
+
+struct hmat_test_ctx {
+	struct list_head localities;
+};
+
+static int __init hmat_test_init(struct kunit *test)
+{
+	struct hmat_test_ctx *ctx;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&ctx->localities);
+	test->priv = ctx;
+	return 0;
+}
+
+static void __init hmat_test_exit(struct kunit *test)
+{
+	struct hmat_test_ctx *ctx = test->priv;
+
+	hmat_free_p2p_localities(&ctx->localities);
+}
+
+static struct acpi_hmat_p2p_latency * __init
+hmat_test_alloc_table(struct kunit *test, u32 initiators, u32 targets)
+{
+	struct acpi_hmat_p2p_latency *p2p;
+	size_t length;
+
+	length = sizeof(*p2p) + sizeof(u32) * (initiators + targets) +
+		 sizeof(u16) * initiators * targets;
+	p2p = kunit_kzalloc(test, length, GFP_KERNEL);
+	if (!p2p)
+		return NULL;
+
+	p2p->header.type = ACPI_HMAT_TYPE_P2P_LATENCY;
+	p2p->header.length = length;
+	p2p->number_of_initiator_Pds = initiators;
+	p2p->number_of_target_Pds = targets;
+	return p2p;
+}
+
+static u32 * __init hmat_test_initiators(struct acpi_hmat_p2p_latency *p2p)
+{
+	return (u32 *)(p2p + 1);
+}
+
+static u32 * __init hmat_test_targets(struct acpi_hmat_p2p_latency *p2p)
+{
+	return hmat_test_initiators(p2p) + p2p->number_of_initiator_Pds;
+}
+
+static u16 * __init hmat_test_entries(struct acpi_hmat_p2p_latency *p2p)
+{
+	return (u16 *)(hmat_test_targets(p2p) + p2p->number_of_target_Pds);
+}
+
+static void __init
+hmat_test_expect_coord(struct kunit *test, int initiator, int target,
+		       enum hmat_p2p_class class, u32 read_bandwidth,
+		       u32 write_bandwidth, u32 read_latency,
+		       u32 write_latency)
+{
+	struct hmat_test_ctx *ctx = test->priv;
+	struct access_coordinate coord;
+
+	KUNIT_ASSERT_EQ(test,
+			hmat_get_p2p_coordinates(&ctx->localities, initiator,
+						 target, class, &coord),
+			0);
+	KUNIT_EXPECT_EQ(test, coord.read_bandwidth, read_bandwidth);
+	KUNIT_EXPECT_EQ(test, coord.write_bandwidth, write_bandwidth);
+	KUNIT_EXPECT_EQ(test, coord.read_latency, read_latency);
+	KUNIT_EXPECT_EQ(test, coord.write_latency, write_latency);
+}
+
+static void __init hmat_p2p_matrix_test(struct kunit *test)
+{
+	struct hmat_test_ctx *ctx = test->priv;
+	struct acpi_hmat_p2p_latency *p2p;
+	struct access_coordinate coord;
+	u32 *initiators, *targets;
+	u16 *entries;
+
+	p2p = hmat_test_alloc_table(test, 2, 2);
+	KUNIT_ASSERT_NOT_NULL(test, p2p);
+	p2p->flags = ACPI_HMAT_P2P_NON_UIO | ACPI_HMAT_P2P_UIO;
+	p2p->data_type = ACPI_HMAT_ACCESS_LATENCY;
+	p2p->entry_base_unit = 1000;
+
+	initiators = hmat_test_initiators(p2p);
+	targets = hmat_test_targets(p2p);
+	entries = hmat_test_entries(p2p);
+	initiators[0] = 11;
+	initiators[1] = 22;
+	targets[0] = 33;
+	targets[1] = 44;
+	entries[0] = 5;
+	entries[1] = 10;
+	entries[2] = 0xffff;
+	entries[3] = 20;
+
+	KUNIT_ASSERT_EQ(test, hmat_parse_p2p(p2p, 2, &ctx->localities), 0);
+	hmat_test_expect_coord(test, 11, 33, HMAT_P2P_NON_UIO, 0, 0, 5, 5);
+	hmat_test_expect_coord(test, 11, 44, HMAT_P2P_UIO, 0, 0, 10, 10);
+	hmat_test_expect_coord(test, 22, 44, HMAT_P2P_NON_UIO, 0, 0, 20, 20);
+
+	KUNIT_EXPECT_EQ(test,
+			hmat_get_p2p_coordinates(&ctx->localities, 22, 33,
+						 HMAT_P2P_NON_UIO, &coord),
+			-ENODATA);
+	KUNIT_EXPECT_EQ(test,
+			hmat_get_p2p_coordinates(&ctx->localities, 33, 11,
+						 HMAT_P2P_NON_UIO, &coord),
+			-ENOENT);
+}
+
+static int __init hmat_test_add_coordinate(struct kunit *test, u8 flags,
+					   u8 type, u64 base, u16 entry)
+{
+	struct hmat_test_ctx *ctx = test->priv;
+	struct acpi_hmat_p2p_latency *p2p;
+
+	p2p = hmat_test_alloc_table(test, 1, 1);
+	if (!p2p)
+		return -ENOMEM;
+
+	p2p->flags = flags;
+	p2p->data_type = type;
+	p2p->entry_base_unit = base;
+	hmat_test_initiators(p2p)[0] = 1;
+	hmat_test_targets(p2p)[0] = 2;
+	hmat_test_entries(p2p)[0] = entry;
+	return hmat_parse_p2p(p2p, 2, &ctx->localities);
+}
+
+static void __init hmat_p2p_coordinate_merge_test(struct kunit *test)
+{
+	KUNIT_ASSERT_EQ(test,
+			hmat_test_add_coordinate(test, ACPI_HMAT_P2P_NON_UIO,
+				ACPI_HMAT_ACCESS_BANDWIDTH, 1, 400),
+			0);
+	KUNIT_ASSERT_EQ(test,
+			hmat_test_add_coordinate(test, ACPI_HMAT_P2P_NON_UIO,
+				ACPI_HMAT_READ_LATENCY, 1000, 7),
+			0);
+	KUNIT_ASSERT_EQ(test,
+			hmat_test_add_coordinate(test, ACPI_HMAT_P2P_NON_UIO,
+				ACPI_HMAT_WRITE_LATENCY, 1000, 9),
+			0);
+	KUNIT_ASSERT_EQ(test,
+			hmat_test_add_coordinate(test, ACPI_HMAT_P2P_UIO,
+				ACPI_HMAT_READ_BANDWIDTH, 1, 200),
+			0);
+	KUNIT_ASSERT_EQ(test,
+			hmat_test_add_coordinate(test, ACPI_HMAT_P2P_UIO,
+				ACPI_HMAT_WRITE_BANDWIDTH, 1, 300),
+			0);
+	KUNIT_ASSERT_EQ(test,
+			hmat_test_add_coordinate(test, ACPI_HMAT_P2P_UIO,
+				ACPI_HMAT_ACCESS_LATENCY, 1000, 11),
+			0);
+
+	hmat_test_expect_coord(test, 1, 2, HMAT_P2P_NON_UIO,
+			       400, 400, 7, 9);
+	hmat_test_expect_coord(test, 1, 2, HMAT_P2P_UIO,
+			       200, 300, 11, 11);
+}
+
+static void __init hmat_p2p_invalid_length_test(struct kunit *test)
+{
+	struct hmat_test_ctx *ctx = test->priv;
+	struct acpi_hmat_p2p_latency *p2p;
+	struct access_coordinate coord;
+
+	p2p = hmat_test_alloc_table(test, 1, 1);
+	KUNIT_ASSERT_NOT_NULL(test, p2p);
+	p2p->header.length = sizeof(*p2p) - 1;
+	KUNIT_EXPECT_EQ(test, hmat_parse_p2p(p2p, 2, &ctx->localities),
+			-EINVAL);
+
+	p2p = hmat_test_alloc_table(test, 1, 1);
+	KUNIT_ASSERT_NOT_NULL(test, p2p);
+	p2p->header.length = sizeof(*p2p);
+	KUNIT_EXPECT_EQ(test, hmat_parse_p2p(p2p, 2, &ctx->localities),
+			-EINVAL);
+	KUNIT_EXPECT_EQ(test,
+			hmat_get_p2p_coordinates(&ctx->localities, 0, 0,
+						 HMAT_P2P_NON_UIO, &coord),
+			-ENOENT);
+}
+
+static struct kunit_case __refdata hmat_p2p_test_cases[] = {
+	KUNIT_CASE(hmat_p2p_matrix_test),
+	KUNIT_CASE(hmat_p2p_coordinate_merge_test),
+	KUNIT_CASE(hmat_p2p_invalid_length_test),
+	{}
+};
+
+static struct kunit_suite __refdata hmat_p2p_test_suite = {
+	.name = "acpi_hmat_p2p",
+	.init = hmat_test_init,
+	.exit = hmat_test_exit,
+	.test_cases = hmat_p2p_test_cases,
+};
+kunit_test_init_section_suite(hmat_p2p_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KUnit tests for ACPI HMAT PCIe P2P matrices");
diff --git a/drivers/acpi/numa/hmat_test.h b/drivers/acpi/numa/hmat_test.h
new file mode 100644
index 000000000000..f53a395c6f8e
--- /dev/null
+++ b/drivers/acpi/numa/hmat_test.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ACPI_NUMA_HMAT_TEST_H
+#define __ACPI_NUMA_HMAT_TEST_H
+
+#include <linux/acpi.h>
+#include <linux/list.h>
+
+#if IS_ENABLED(CONFIG_KUNIT)
+int hmat_parse_p2p(struct acpi_hmat_p2p_latency *p2p, u8 revision,
+		   struct list_head *localities);
+int hmat_get_p2p_coordinates(struct list_head *localities, int initiator_pxm,
+			     int target_pxm, enum hmat_p2p_class class,
+			     struct access_coordinate *coord);
+void hmat_free_p2p_localities(struct list_head *localities);
+#endif
+
+#endif /* __ACPI_NUMA_HMAT_TEST_H */

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH RFC 6/7] PCI/P2PDMA: Add KUnit tests for HMAT policy boundaries
  2026-08-12 19:47 [PATCH RFC 0/7] PCI/HMAT: Describe P2PDMA reachability and performance Leon Romanovsky
                   ` (4 preceding siblings ...)
  2026-08-12 19:47 ` [PATCH RFC 5/7] ACPI: HMAT: Add KUnit tests for PCIe P2P matrix semantics Leon Romanovsky
@ 2026-08-12 19:47 ` Leon Romanovsky
  2026-08-12 19:47 ` [PATCH RFC 7/7] PCI/P2PDMA: Add KUnit tests for HMAT provider ranking Leon Romanovsky
  6 siblings, 0 replies; 10+ messages in thread
From: Leon Romanovsky @ 2026-08-12 19:47 UTC (permalink / raw)
  To: Rafael J. Wysocki, Len Brown, Saket Dumbre, Bjorn Helgaas,
	Logan Gunthorpe
  Cc: linux-acpi, acpica-devel, linux-kernel, linux-pci, Lukas Wunner,
	Natu, Mahesh

From: Leon Romanovsky <leonro@nvidia.com>

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@nvidia.com>
---
 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


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH RFC 7/7] PCI/P2PDMA: Add KUnit tests for HMAT provider ranking
  2026-08-12 19:47 [PATCH RFC 0/7] PCI/HMAT: Describe P2PDMA reachability and performance Leon Romanovsky
                   ` (5 preceding siblings ...)
  2026-08-12 19:47 ` [PATCH RFC 6/7] PCI/P2PDMA: Add KUnit tests for HMAT policy boundaries Leon Romanovsky
@ 2026-08-12 19:47 ` Leon Romanovsky
  6 siblings, 0 replies; 10+ messages in thread
From: Leon Romanovsky @ 2026-08-12 19:47 UTC (permalink / raw)
  To: Rafael J. Wysocki, Len Brown, Saket Dumbre, Bjorn Helgaas,
	Logan Gunthorpe
  Cc: linux-acpi, acpica-devel, linux-kernel, linux-pci, Lukas Wunner,
	Natu, Mahesh

From: Leon Romanovsky <leonro@nvidia.com>

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 <leonro@nvidia.com>
---
 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


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH RFC 1/7] ACPICA: Define PCIe P2P latency and bandwidth information
  2026-08-12 19:47 ` [PATCH RFC 1/7] ACPICA: Define PCIe P2P latency and bandwidth information Leon Romanovsky
@ 2026-08-13 10:13   ` Rafael J. Wysocki (Intel)
  2026-08-13 11:01     ` Leon Romanovsky
  0 siblings, 1 reply; 10+ messages in thread
From: Rafael J. Wysocki (Intel) @ 2026-08-13 10:13 UTC (permalink / raw)
  To: Leon Romanovsky
  Cc: Rafael J. Wysocki, Len Brown, Saket Dumbre, Bjorn Helgaas,
	Logan Gunthorpe, linux-acpi, acpica-devel, linux-kernel,
	linux-pci, Lukas Wunner, Natu, Mahesh

On Wed, Aug 12, 2026 at 9:48 PM Leon Romanovsky <leon@kernel.org> wrote:
>
> From: Leon Romanovsky <leonro@nvidia.com>
>
> PCIe topology alone cannot tell an operating system whether traffic may
> cross host bridges, what ordering rules it needs, or how expensive the path
> is. Encoding this knowledge in chipset lists duplicates platform policy and
> cannot survive a virtualized topology.
>
> Define HMAT Type 3 as a firmware contract for directional Generic Port P2P
> paths, including separate ordered and Unordered I/O applicability. This
> gives platform software a standard way to distinguish unsupported,
> conditional, and usable paths. The definition follows a proposed ACPI ECN.
>
> Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
> ---
>  include/acpi/actbl1.h | 27 ++++++++++++++++++++++++++-
>  1 file changed, 26 insertions(+), 1 deletion(-)

As per Documentation/driver-api/acpi/linuxized-acpica.rst, please
submit a pull request with this change to the upstream ACPICA project
on GitHub.  The Linux patch will become applicable when there is a
corresponding commit upstream.

> diff --git a/include/acpi/actbl1.h b/include/acpi/actbl1.h
> index d824838cb7af..0fc766a8e90f 100644
> --- a/include/acpi/actbl1.h
> +++ b/include/acpi/actbl1.h
> @@ -2003,7 +2003,8 @@ enum acpi_hmat_type {
>         ACPI_HMAT_TYPE_PROXIMITY = 0,   /* Memory proximity domain attributes */
>         ACPI_HMAT_TYPE_LOCALITY = 1,    /* System locality latency and bandwidth information */
>         ACPI_HMAT_TYPE_CACHE = 2,       /* Memory side cache information */
> -       ACPI_HMAT_TYPE_RESERVED = 3     /* 3 and greater are reserved */
> +       ACPI_HMAT_TYPE_P2P_LATENCY = 3, /* PCIe P2P latency and bandwidth information */
> +       ACPI_HMAT_TYPE_RESERVED = 4     /* 4 and greater are reserved */
>  };
>
>  struct acpi_hmat_structure {
> @@ -2108,6 +2109,30 @@ struct acpi_hmat_cache {
>  #define ACPI_HMAT_CP_WB     (1)
>  #define ACPI_HMAT_CP_WT     (2)
>
> +/* 3: PCIe P2P Latency and Bandwidth Information */
> +
> +struct acpi_hmat_p2p_latency {
> +       struct acpi_hmat_structure header;
> +       u8 flags;
> +       u8 data_type;
> +       u16 reserved1;
> +       u32 number_of_initiator_Pds;
> +       u32 number_of_target_Pds;
> +       u32 reserved2;
> +       u64 entry_base_unit;
> +};
> +
> +/* Masks for Flags field above */
> +
> +#define ACPI_HMAT_P2P_NON_UIO   (1)    /* Bit 0: Entries apply to non-UIO traffic */
> +#define ACPI_HMAT_P2P_UIO       (1<<1) /* Bit 1: Entries apply to UIO traffic */
> +
> +/*
> + * The data_type field reuses the ACPI_HMAT_ACCESS/READ/WRITE_LATENCY and
> + * ACPI_HMAT_ACCESS/READ/WRITE_BANDWIDTH values defined for the System
> + * Locality Latency and Bandwidth Information Structure above.
> + */
> +
>  /*******************************************************************************
>   *
>   * HPET - High Precision Event Timer table
>
> --
> 2.55.0
>

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH RFC 1/7] ACPICA: Define PCIe P2P latency and bandwidth information
  2026-08-13 10:13   ` Rafael J. Wysocki (Intel)
@ 2026-08-13 11:01     ` Leon Romanovsky
  0 siblings, 0 replies; 10+ messages in thread
From: Leon Romanovsky @ 2026-08-13 11:01 UTC (permalink / raw)
  To: Rafael J. Wysocki (Intel)
  Cc: Len Brown, Saket Dumbre, Bjorn Helgaas, Logan Gunthorpe,
	linux-acpi, acpica-devel, linux-kernel, linux-pci, Lukas Wunner,
	Natu, Mahesh

On Thu, Aug 13, 2026 at 12:13:39PM +0200, Rafael J. Wysocki (Intel) wrote:
> On Wed, Aug 12, 2026 at 9:48 PM Leon Romanovsky <leon@kernel.org> wrote:
> >
> > From: Leon Romanovsky <leonro@nvidia.com>
> >
> > PCIe topology alone cannot tell an operating system whether traffic may
> > cross host bridges, what ordering rules it needs, or how expensive the path
> > is. Encoding this knowledge in chipset lists duplicates platform policy and
> > cannot survive a virtualized topology.
> >
> > Define HMAT Type 3 as a firmware contract for directional Generic Port P2P
> > paths, including separate ordered and Unordered I/O applicability. This
> > gives platform software a standard way to distinguish unsupported,
> > conditional, and usable paths. The definition follows a proposed ACPI ECN.
> >
> > Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
> > ---
> >  include/acpi/actbl1.h | 27 ++++++++++++++++++++++++++-
> >  1 file changed, 26 insertions(+), 1 deletion(-)
> 
> As per Documentation/driver-api/acpi/linuxized-acpica.rst, please
> submit a pull request with this change to the upstream ACPICA project
> on GitHub.  The Linux patch will become applicable when there is a
> corresponding commit upstream.

Sure, we will. However, at this stage, we are looking for
feedback on the idea.

Thanks.

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-08-13 11:02 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-12 19:47 [PATCH RFC 0/7] PCI/HMAT: Describe P2PDMA reachability and performance Leon Romanovsky
2026-08-12 19:47 ` [PATCH RFC 1/7] ACPICA: Define PCIe P2P latency and bandwidth information Leon Romanovsky
2026-08-13 10:13   ` Rafael J. Wysocki (Intel)
2026-08-13 11:01     ` Leon Romanovsky
2026-08-12 19:47 ` [PATCH RFC 2/7] ACPI: HMAT: Make PCIe P2P paths available to consumers Leon Romanovsky
2026-08-12 19:47 ` [PATCH RFC 3/7] PCI/P2PDMA: Authorize HMAT-described cross-host-bridge P2P Leon Romanovsky
2026-08-12 19:47 ` [PATCH RFC 4/7] PCI/P2PDMA: Prefer providers with better HMAT performance Leon Romanovsky
2026-08-12 19:47 ` [PATCH RFC 5/7] ACPI: HMAT: Add KUnit tests for PCIe P2P matrix semantics Leon Romanovsky
2026-08-12 19:47 ` [PATCH RFC 6/7] PCI/P2PDMA: Add KUnit tests for HMAT policy boundaries Leon Romanovsky
2026-08-12 19:47 ` [PATCH RFC 7/7] PCI/P2PDMA: Add KUnit tests for HMAT provider ranking Leon Romanovsky

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox