[PATCH RFC 2/7] ACPI: HMAT: Make PCIe P2P paths available to consumers

From: Leon Romanovsky

Date: Wed Aug 12 2026 - 15:49:46 EST


From: Leon Romanovsky <leonro@xxxxxxxxxx>

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