[PATCH RFC 2/7] ACPI: HMAT: Make PCIe P2P paths available to consumers
Leon Romanovsky <[email protected]> Wed, 12 Aug 2026 22:47:39 +0300
| Newsgroups | gmane.linux.kernel.pci,gmane.linux.acpi.devel,gmane.linux.kernel |
|---|---|
| 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