Skip to content

Commit af74daf

Browse files
Robert Richterdavejiang
authored andcommitted
cxl: Enable AMD Zen5 address translation using ACPI PRMT
Add AMD Zen5 support for address translation. Zen5 systems may be configured to use 'Normalized addresses'. Then, host physical addresses (HPA) are different from their system physical addresses (SPA). The endpoint has its own physical address space and an incoming HPA is already converted to the device's physical address (DPA). Thus it has interleaving disabled and CXL endpoints are programmed passthrough (DPA == HPA). Host Physical Addresses (HPAs) need to be translated from the endpoint to its CXL host bridge, esp. to identify the endpoint's root decoder and region's address range. ACPI Platform Runtime Mechanism (PRM) provides a handler to translate the DPA to its SPA. This is documented in: AMD Family 1Ah Models 00h–0Fh and Models 10h–1Fh ACPI v6.5 Porting Guide, Publication # 58088 https://www.amd.com/en/search/documentation/hub.html With Normalized Addressing this PRM handler must be used to translate an HPA of an endpoint to its SPA. Do the following to implement AMD Zen5 address translation: Introduce a new file core/atl.c to handle ACPI PRM specific address translation code. Naming is loosely related to the kernel's AMD Address Translation Library (CONFIG_AMD_ATL) but implementation does not depend on it, nor it is vendor specific. Use Kbuild and Kconfig options respectively to enable the code depending on architecture and platform options. AMD Zen5 systems support the ACPI PRM CXL Address Translation firmware call (see ACPI v6.5 Porting Guide, Address Translation - CXL DPA to System Physical Address). Firmware enables the PRM handler if the platform has address translation implemented. Check firmware and kernel support of ACPI PRM using the specific GUID. On success enable address translation by setting up the earlier introduced root port callback, see function cxl_prm_setup_translation(). Setup is done in cxl_setup_prm_address_translation(), it is the only function that needs to be exported. For low level PRM firmware calls, use the ACPI framework. Identify the region's interleaving ways by inspecting the address ranges. Also determine the interleaving granularity using the address translation callback. Note that the position of the chunk from one interleaving block to the next may vary and thus cannot be considered constant. Address offsets larger than the interleaving block size cannot be used to calculate the granularity. Thus, probe the granularity using address translation for various HPAs in the same interleaving block. [ dj: Add atl.o build to cxl_test ] Reviewed-by: Dave Jiang <dave.jiang@intel.com> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Tested-by: Gregory Price <gourry@gourry.net> Signed-off-by: Robert Richter <rrichter@amd.com> Link: https://patch.msgid.link/20260114164837.1076338-11-rrichter@amd.com Signed-off-by: Dave Jiang <dave.jiang@intel.com>
1 parent 7be03ea commit af74daf

6 files changed

Lines changed: 206 additions & 0 deletions

File tree

drivers/cxl/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,9 @@ config CXL_MCE
233233
def_bool y
234234
depends on X86_MCE && MEMORY_FAILURE
235235

236+
config CXL_ATL
237+
def_bool y
238+
depends on CXL_REGION
239+
depends on ACPI_PRMT && AMD_NB
240+
236241
endif

drivers/cxl/acpi.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,8 @@ static int cxl_acpi_probe(struct platform_device *pdev)
925925
cxl_root->ops.qos_class = cxl_acpi_qos_class;
926926
root_port = &cxl_root->port;
927927

928+
cxl_setup_prm_address_translation(cxl_root);
929+
928930
rc = bus_for_each_dev(adev->dev.bus, NULL, root_port,
929931
add_host_bridge_dport);
930932
if (rc < 0)

drivers/cxl/core/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ cxl_core-$(CONFIG_CXL_REGION) += region.o
2020
cxl_core-$(CONFIG_CXL_MCE) += mce.o
2121
cxl_core-$(CONFIG_CXL_FEATURES) += features.o
2222
cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += edac.o
23+
cxl_core-$(CONFIG_CXL_ATL) += atl.o

drivers/cxl/core/atl.c

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Copyright (C) 2025 Advanced Micro Devices, Inc.
4+
*/
5+
6+
#include <linux/prmt.h>
7+
#include <linux/pci.h>
8+
#include <linux/acpi.h>
9+
10+
#include <cxlmem.h>
11+
#include "core.h"
12+
13+
/*
14+
* PRM Address Translation - CXL DPA to System Physical Address
15+
*
16+
* Reference:
17+
*
18+
* AMD Family 1Ah Models 00h–0Fh and Models 10h–1Fh
19+
* ACPI v6.5 Porting Guide, Publication # 58088
20+
*/
21+
22+
static const guid_t prm_cxl_dpa_spa_guid =
23+
GUID_INIT(0xee41b397, 0x25d4, 0x452c, 0xad, 0x54, 0x48, 0xc6, 0xe3,
24+
0x48, 0x0b, 0x94);
25+
26+
struct prm_cxl_dpa_spa_data {
27+
u64 dpa;
28+
u8 reserved;
29+
u8 devfn;
30+
u8 bus;
31+
u8 segment;
32+
u64 *spa;
33+
} __packed;
34+
35+
static u64 prm_cxl_dpa_spa(struct pci_dev *pci_dev, u64 dpa)
36+
{
37+
struct prm_cxl_dpa_spa_data data;
38+
u64 spa;
39+
int rc;
40+
41+
data = (struct prm_cxl_dpa_spa_data) {
42+
.dpa = dpa,
43+
.devfn = pci_dev->devfn,
44+
.bus = pci_dev->bus->number,
45+
.segment = pci_domain_nr(pci_dev->bus),
46+
.spa = &spa,
47+
};
48+
49+
rc = acpi_call_prm_handler(prm_cxl_dpa_spa_guid, &data);
50+
if (rc) {
51+
pci_dbg(pci_dev, "failed to get SPA for %#llx: %d\n", dpa, rc);
52+
return ULLONG_MAX;
53+
}
54+
55+
pci_dbg(pci_dev, "PRM address translation: DPA -> SPA: %#llx -> %#llx\n", dpa, spa);
56+
57+
return spa;
58+
}
59+
60+
static int cxl_prm_setup_root(struct cxl_root *cxl_root, void *data)
61+
{
62+
struct cxl_region_context *ctx = data;
63+
struct cxl_endpoint_decoder *cxled = ctx->cxled;
64+
struct cxl_decoder *cxld = &cxled->cxld;
65+
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
66+
struct range hpa_range = ctx->hpa_range;
67+
struct pci_dev *pci_dev;
68+
u64 spa_len, len;
69+
u64 addr, base_spa, base;
70+
int ways, gran;
71+
72+
/*
73+
* When Normalized Addressing is enabled, the endpoint maintains a 1:1
74+
* mapping between HPA and DPA. If disabled, skip address translation
75+
* and perform only a range check.
76+
*/
77+
if (hpa_range.start != cxled->dpa_res->start)
78+
return 0;
79+
80+
/*
81+
* Endpoints are programmed passthrough in Normalized Addressing mode.
82+
*/
83+
if (ctx->interleave_ways != 1) {
84+
dev_dbg(&cxld->dev, "unexpected interleaving config: ways: %d granularity: %d\n",
85+
ctx->interleave_ways, ctx->interleave_granularity);
86+
return -ENXIO;
87+
}
88+
89+
if (!cxlmd || !dev_is_pci(cxlmd->dev.parent)) {
90+
dev_dbg(&cxld->dev, "No endpoint found: %s, range %#llx-%#llx\n",
91+
dev_name(cxld->dev.parent), hpa_range.start,
92+
hpa_range.end);
93+
return -ENXIO;
94+
}
95+
96+
pci_dev = to_pci_dev(cxlmd->dev.parent);
97+
98+
/* Translate HPA range to SPA. */
99+
base = hpa_range.start;
100+
hpa_range.start = prm_cxl_dpa_spa(pci_dev, hpa_range.start);
101+
hpa_range.end = prm_cxl_dpa_spa(pci_dev, hpa_range.end);
102+
base_spa = hpa_range.start;
103+
104+
if (hpa_range.start == ULLONG_MAX || hpa_range.end == ULLONG_MAX) {
105+
dev_dbg(cxld->dev.parent,
106+
"CXL address translation: Failed to translate HPA range: %#llx-%#llx:%#llx-%#llx(%s)\n",
107+
hpa_range.start, hpa_range.end, ctx->hpa_range.start,
108+
ctx->hpa_range.end, dev_name(&cxld->dev));
109+
return -ENXIO;
110+
}
111+
112+
/*
113+
* Since translated addresses include the interleaving offsets, align
114+
* the range to 256 MB.
115+
*/
116+
hpa_range.start = ALIGN_DOWN(hpa_range.start, SZ_256M);
117+
hpa_range.end = ALIGN(hpa_range.end, SZ_256M) - 1;
118+
119+
len = range_len(&ctx->hpa_range);
120+
spa_len = range_len(&hpa_range);
121+
if (!len || !spa_len || spa_len % len) {
122+
dev_dbg(cxld->dev.parent,
123+
"CXL address translation: HPA range not contiguous: %#llx-%#llx:%#llx-%#llx(%s)\n",
124+
hpa_range.start, hpa_range.end, ctx->hpa_range.start,
125+
ctx->hpa_range.end, dev_name(&cxld->dev));
126+
return -ENXIO;
127+
}
128+
129+
ways = spa_len / len;
130+
gran = SZ_256;
131+
132+
/*
133+
* Determine interleave granularity
134+
*
135+
* Note: The position of the chunk from one interleaving block to the
136+
* next may vary and thus cannot be considered constant. Address offsets
137+
* larger than the interleaving block size cannot be used to calculate
138+
* the granularity.
139+
*/
140+
if (ways > 1) {
141+
while (gran <= SZ_16M) {
142+
addr = prm_cxl_dpa_spa(pci_dev, base + gran);
143+
if (addr != base_spa + gran)
144+
break;
145+
gran <<= 1;
146+
}
147+
}
148+
149+
if (gran > SZ_16M) {
150+
dev_dbg(cxld->dev.parent,
151+
"CXL address translation: Cannot determine granularity: %#llx-%#llx:%#llx-%#llx(%s)\n",
152+
hpa_range.start, hpa_range.end, ctx->hpa_range.start,
153+
ctx->hpa_range.end, dev_name(&cxld->dev));
154+
return -ENXIO;
155+
}
156+
157+
ctx->hpa_range = hpa_range;
158+
ctx->interleave_ways = ways;
159+
ctx->interleave_granularity = gran;
160+
161+
dev_dbg(&cxld->dev,
162+
"address mapping found for %s (hpa -> spa): %#llx+%#llx -> %#llx+%#llx ways:%d granularity:%d\n",
163+
dev_name(cxlmd->dev.parent), base, len, hpa_range.start,
164+
spa_len, ways, gran);
165+
166+
return 0;
167+
}
168+
169+
void cxl_setup_prm_address_translation(struct cxl_root *cxl_root)
170+
{
171+
struct device *host = cxl_root->port.uport_dev;
172+
u64 spa;
173+
struct prm_cxl_dpa_spa_data data = { .spa = &spa };
174+
int rc;
175+
176+
/*
177+
* Applies only to PCIe Host Bridges which are children of the CXL Root
178+
* Device (HID=“ACPI0017”). Check this and drop cxl_test instances.
179+
*/
180+
if (!acpi_match_device(host->driver->acpi_match_table, host))
181+
return;
182+
183+
/* Check kernel (-EOPNOTSUPP) and firmware support (-ENODEV) */
184+
rc = acpi_call_prm_handler(prm_cxl_dpa_spa_guid, &data);
185+
if (rc == -EOPNOTSUPP || rc == -ENODEV)
186+
return;
187+
188+
cxl_root->ops.translation_setup_root = cxl_prm_setup_root;
189+
}
190+
EXPORT_SYMBOL_NS_GPL(cxl_setup_prm_address_translation, "CXL");

drivers/cxl/cxl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,13 @@ static inline void cxl_dport_init_ras_reporting(struct cxl_dport *dport,
817817
struct device *host) { }
818818
#endif
819819

820+
#ifdef CONFIG_CXL_ATL
821+
void cxl_setup_prm_address_translation(struct cxl_root *cxl_root);
822+
#else
823+
static inline
824+
void cxl_setup_prm_address_translation(struct cxl_root *cxl_root) {}
825+
#endif
826+
820827
struct cxl_decoder *to_cxl_decoder(struct device *dev);
821828
struct cxl_root_decoder *to_cxl_root_decoder(struct device *dev);
822829
struct cxl_switch_decoder *to_cxl_switch_decoder(struct device *dev);

tools/testing/cxl/Kbuild

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ cxl_core-$(CONFIG_CXL_REGION) += $(CXL_CORE_SRC)/region.o
6363
cxl_core-$(CONFIG_CXL_MCE) += $(CXL_CORE_SRC)/mce.o
6464
cxl_core-$(CONFIG_CXL_FEATURES) += $(CXL_CORE_SRC)/features.o
6565
cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += $(CXL_CORE_SRC)/edac.o
66+
cxl_core-$(CONFIG_CXL_ATL) += $(CXL_CORE_SRC)/atl.o
6667
cxl_core-y += config_check.o
6768
cxl_core-y += cxl_core_test.o
6869
cxl_core-y += cxl_core_exports.o

0 commit comments

Comments
 (0)