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

Leon Romanovsky <[email protected]>
Newsgroups org.kernel.vger.linux-acpi,dev.linux.lists.acpica-devel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci
Message-ID <[email protected]>
From: Leon Romanovsky <[email protected]>

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