summaryrefslogtreecommitdiff
path: root/drivers/cxl
diff options
context:
space:
mode:
authorDave Jiang <dave.jiang@intel.com>2026-02-04 10:53:33 -0700
committerDave Jiang <dave.jiang@intel.com>2026-02-04 10:53:33 -0700
commit63fbf275fa9f18f7020fb8acf54fa107e51d0f23 (patch)
tree4e553e1e9e69a72131019667a8682efce3ce210d /drivers/cxl
parent0da3050bdded5f121aaca6b5247ea50681d7129e (diff)
parent208f432406b7ed446c061d68cc73efd85b575d3f (diff)
Merge branch 'for-7.0/cxl-prm-translation' into cxl-for-next
Add support for normalized CXL address translation through ACPI PRM method to support AMD Zen5 platforms. Including a conventions doc that explains how the translation is implemented and for future implementations that need such setup to comply with the current implementation method. cxl: Disable HPA/SPA translation handlers for Normalized Addressing cxl/region: Factor out code into cxl_region_setup_poison() cxl/atl: Lock decoders that need address translation cxl: Enable AMD Zen5 address translation using ACPI PRMT cxl/acpi: Prepare use of EFI runtime services cxl: Introduce callback for HPA address ranges translation cxl/region: Use region data to get the root decoder cxl/region: Add @hpa_range argument to function cxl_calc_interleave_pos() cxl/region: Separate region parameter setup and region construction cxl: Simplify cxl_root_ops allocation and handling cxl/region: Store HPA range in struct cxl_region cxl/region: Store root decoder in struct cxl_region cxl/region: Rename misleading variable name @hpa to @hpa_range Documentation/driver-api/cxl: ACPI PRM Address Translation Support and AMD Zen5 enablement cxl, doc: Moving conventions in separate files cxl, doc: Remove isonum.txt inclusion
Diffstat (limited to 'drivers/cxl')
-rw-r--r--drivers/cxl/Kconfig5
-rw-r--r--drivers/cxl/acpi.c17
-rw-r--r--drivers/cxl/core/Makefile1
-rw-r--r--drivers/cxl/core/atl.c211
-rw-r--r--drivers/cxl/core/cdat.c8
-rw-r--r--drivers/cxl/core/core.h8
-rw-r--r--drivers/cxl/core/port.c8
-rw-r--r--drivers/cxl/core/region.c247
-rw-r--r--drivers/cxl/cxl.h40
9 files changed, 426 insertions, 119 deletions
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index 5b5aa941ad2f..4589bf11d3fe 100644
--- a/drivers/cxl/Kconfig
+++ b/drivers/cxl/Kconfig
@@ -237,4 +237,9 @@ config CXL_RAS
def_bool y
depends on ACPI_APEI_GHES && PCIEAER && CXL_BUS
+config CXL_ATL
+ def_bool y
+ depends on CXL_REGION
+ depends on ACPI_PRMT && AMD_NB
+
endif
diff --git a/drivers/cxl/acpi.c b/drivers/cxl/acpi.c
index e65dfae42bde..c959fca69b3f 100644
--- a/drivers/cxl/acpi.c
+++ b/drivers/cxl/acpi.c
@@ -318,10 +318,6 @@ static int cxl_acpi_qos_class(struct cxl_root *cxl_root,
return cxl_acpi_evaluate_qtg_dsm(handle, coord, entries, qos_class);
}
-static const struct cxl_root_ops acpi_root_ops = {
- .qos_class = cxl_acpi_qos_class,
-};
-
static void del_cxl_resource(struct resource *res)
{
if (!res)
@@ -904,11 +900,14 @@ static int cxl_acpi_probe(struct platform_device *pdev)
cxl_res->end = -1;
cxl_res->flags = IORESOURCE_MEM;
- cxl_root = devm_cxl_add_root(host, &acpi_root_ops);
+ cxl_root = devm_cxl_add_root(host);
if (IS_ERR(cxl_root))
return PTR_ERR(cxl_root);
+ cxl_root->ops.qos_class = cxl_acpi_qos_class;
root_port = &cxl_root->port;
+ cxl_setup_prm_address_translation(cxl_root);
+
rc = bus_for_each_dev(adev->dev.bus, NULL, root_port,
add_host_bridge_dport);
if (rc < 0)
@@ -989,8 +988,12 @@ static void __exit cxl_acpi_exit(void)
cxl_bus_drain();
}
-/* load before dax_hmem sees 'Soft Reserved' CXL ranges */
-subsys_initcall(cxl_acpi_init);
+/*
+ * Load before dax_hmem sees 'Soft Reserved' CXL ranges. Use
+ * subsys_initcall_sync() since there is an order dependency with
+ * subsys_initcall(efisubsys_init), which must run first.
+ */
+subsys_initcall_sync(cxl_acpi_init);
/*
* Arrange for host-bridge ports to be active synchronous with
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index b37f38d502d8..a639a9499972 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -21,3 +21,4 @@ cxl_core-$(CONFIG_CXL_FEATURES) += features.o
cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += edac.o
cxl_core-$(CONFIG_CXL_RAS) += ras.o
cxl_core-$(CONFIG_CXL_RAS) += ras_rch.o
+cxl_core-$(CONFIG_CXL_ATL) += atl.o
diff --git a/drivers/cxl/core/atl.c b/drivers/cxl/core/atl.c
new file mode 100644
index 000000000000..310668786189
--- /dev/null
+++ b/drivers/cxl/core/atl.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/prmt.h>
+#include <linux/pci.h>
+#include <linux/acpi.h>
+
+#include <cxlmem.h>
+#include "core.h"
+
+/*
+ * PRM Address Translation - CXL DPA to System Physical Address
+ *
+ * Reference:
+ *
+ * AMD Family 1Ah Models 00h–0Fh and Models 10h–1Fh
+ * ACPI v6.5 Porting Guide, Publication # 58088
+ */
+
+static const guid_t prm_cxl_dpa_spa_guid =
+ GUID_INIT(0xee41b397, 0x25d4, 0x452c, 0xad, 0x54, 0x48, 0xc6, 0xe3,
+ 0x48, 0x0b, 0x94);
+
+struct prm_cxl_dpa_spa_data {
+ u64 dpa;
+ u8 reserved;
+ u8 devfn;
+ u8 bus;
+ u8 segment;
+ u64 *spa;
+} __packed;
+
+static u64 prm_cxl_dpa_spa(struct pci_dev *pci_dev, u64 dpa)
+{
+ struct prm_cxl_dpa_spa_data data;
+ u64 spa;
+ int rc;
+
+ data = (struct prm_cxl_dpa_spa_data) {
+ .dpa = dpa,
+ .devfn = pci_dev->devfn,
+ .bus = pci_dev->bus->number,
+ .segment = pci_domain_nr(pci_dev->bus),
+ .spa = &spa,
+ };
+
+ rc = acpi_call_prm_handler(prm_cxl_dpa_spa_guid, &data);
+ if (rc) {
+ pci_dbg(pci_dev, "failed to get SPA for %#llx: %d\n", dpa, rc);
+ return ULLONG_MAX;
+ }
+
+ pci_dbg(pci_dev, "PRM address translation: DPA -> SPA: %#llx -> %#llx\n", dpa, spa);
+
+ return spa;
+}
+
+static int cxl_prm_setup_root(struct cxl_root *cxl_root, void *data)
+{
+ struct cxl_region_context *ctx = data;
+ struct cxl_endpoint_decoder *cxled = ctx->cxled;
+ struct cxl_decoder *cxld = &cxled->cxld;
+ struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
+ struct range hpa_range = ctx->hpa_range;
+ struct pci_dev *pci_dev;
+ u64 spa_len, len;
+ u64 addr, base_spa, base;
+ int ways, gran;
+
+ /*
+ * When Normalized Addressing is enabled, the endpoint maintains a 1:1
+ * mapping between HPA and DPA. If disabled, skip address translation
+ * and perform only a range check.
+ */
+ if (hpa_range.start != cxled->dpa_res->start)
+ return 0;
+
+ /*
+ * Endpoints are programmed passthrough in Normalized Addressing mode.
+ */
+ if (ctx->interleave_ways != 1) {
+ dev_dbg(&cxld->dev, "unexpected interleaving config: ways: %d granularity: %d\n",
+ ctx->interleave_ways, ctx->interleave_granularity);
+ return -ENXIO;
+ }
+
+ if (!cxlmd || !dev_is_pci(cxlmd->dev.parent)) {
+ dev_dbg(&cxld->dev, "No endpoint found: %s, range %#llx-%#llx\n",
+ dev_name(cxld->dev.parent), hpa_range.start,
+ hpa_range.end);
+ return -ENXIO;
+ }
+
+ pci_dev = to_pci_dev(cxlmd->dev.parent);
+
+ /* Translate HPA range to SPA. */
+ base = hpa_range.start;
+ hpa_range.start = prm_cxl_dpa_spa(pci_dev, hpa_range.start);
+ hpa_range.end = prm_cxl_dpa_spa(pci_dev, hpa_range.end);
+ base_spa = hpa_range.start;
+
+ if (hpa_range.start == ULLONG_MAX || hpa_range.end == ULLONG_MAX) {
+ dev_dbg(cxld->dev.parent,
+ "CXL address translation: Failed to translate HPA range: %#llx-%#llx:%#llx-%#llx(%s)\n",
+ hpa_range.start, hpa_range.end, ctx->hpa_range.start,
+ ctx->hpa_range.end, dev_name(&cxld->dev));
+ return -ENXIO;
+ }
+
+ /*
+ * Since translated addresses include the interleaving offsets, align
+ * the range to 256 MB.
+ */
+ hpa_range.start = ALIGN_DOWN(hpa_range.start, SZ_256M);
+ hpa_range.end = ALIGN(hpa_range.end, SZ_256M) - 1;
+
+ len = range_len(&ctx->hpa_range);
+ spa_len = range_len(&hpa_range);
+ if (!len || !spa_len || spa_len % len) {
+ dev_dbg(cxld->dev.parent,
+ "CXL address translation: HPA range not contiguous: %#llx-%#llx:%#llx-%#llx(%s)\n",
+ hpa_range.start, hpa_range.end, ctx->hpa_range.start,
+ ctx->hpa_range.end, dev_name(&cxld->dev));
+ return -ENXIO;
+ }
+
+ ways = spa_len / len;
+ gran = SZ_256;
+
+ /*
+ * Determine interleave granularity
+ *
+ * Note: 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.
+ */
+ if (ways > 1) {
+ while (gran <= SZ_16M) {
+ addr = prm_cxl_dpa_spa(pci_dev, base + gran);
+ if (addr != base_spa + gran)
+ break;
+ gran <<= 1;
+ }
+ }
+
+ if (gran > SZ_16M) {
+ dev_dbg(cxld->dev.parent,
+ "CXL address translation: Cannot determine granularity: %#llx-%#llx:%#llx-%#llx(%s)\n",
+ hpa_range.start, hpa_range.end, ctx->hpa_range.start,
+ ctx->hpa_range.end, dev_name(&cxld->dev));
+ return -ENXIO;
+ }
+
+ /*
+ * The current kernel implementation does not support endpoint
+ * setup with Normalized Addressing. It only translates an
+ * endpoint's DPA to the SPA range of the host bridge.
+ * Therefore, the endpoint address range cannot be determined,
+ * making a non-auto setup impossible. If a decoder requires
+ * address translation, reprogramming should be disabled and
+ * the decoder locked.
+ *
+ * The BIOS, however, provides all the necessary address
+ * translation data, which the kernel can use to reconfigure
+ * endpoint decoders with normalized addresses. Locking the
+ * decoders in the BIOS would prevent a capable kernel (or
+ * other operating systems) from shutting down auto-generated
+ * regions and managing resources dynamically.
+ *
+ * Indicate that Normalized Addressing is enabled.
+ */
+ cxld->flags |= CXL_DECODER_F_LOCK;
+ cxld->flags |= CXL_DECODER_F_NORMALIZED_ADDRESSING;
+
+ ctx->hpa_range = hpa_range;
+ ctx->interleave_ways = ways;
+ ctx->interleave_granularity = gran;
+
+ dev_dbg(&cxld->dev,
+ "address mapping found for %s (hpa -> spa): %#llx+%#llx -> %#llx+%#llx ways:%d granularity:%d\n",
+ dev_name(cxlmd->dev.parent), base, len, hpa_range.start,
+ spa_len, ways, gran);
+
+ return 0;
+}
+
+void cxl_setup_prm_address_translation(struct cxl_root *cxl_root)
+{
+ struct device *host = cxl_root->port.uport_dev;
+ u64 spa;
+ struct prm_cxl_dpa_spa_data data = { .spa = &spa };
+ int rc;
+
+ /*
+ * Applies only to PCIe Host Bridges which are children of the CXL Root
+ * Device (HID=“ACPI0017”). Check this and drop cxl_test instances.
+ */
+ if (!acpi_match_device(host->driver->acpi_match_table, host))
+ return;
+
+ /* Check kernel (-EOPNOTSUPP) and firmware support (-ENODEV) */
+ rc = acpi_call_prm_handler(prm_cxl_dpa_spa_guid, &data);
+ if (rc == -EOPNOTSUPP || rc == -ENODEV)
+ return;
+
+ cxl_root->ops.translation_setup_root = cxl_prm_setup_root;
+}
+EXPORT_SYMBOL_NS_GPL(cxl_setup_prm_address_translation, "CXL");
diff --git a/drivers/cxl/core/cdat.c b/drivers/cxl/core/cdat.c
index 7120b5f2e31f..18f0f2a25113 100644
--- a/drivers/cxl/core/cdat.c
+++ b/drivers/cxl/core/cdat.c
@@ -213,7 +213,7 @@ static int cxl_port_perf_data_calculate(struct cxl_port *port,
if (!cxl_root)
return -ENODEV;
- if (!cxl_root->ops || !cxl_root->ops->qos_class)
+ if (!cxl_root->ops.qos_class)
return -EOPNOTSUPP;
xa_for_each(dsmas_xa, index, dent) {
@@ -221,9 +221,9 @@ static int cxl_port_perf_data_calculate(struct cxl_port *port,
cxl_coordinates_combine(dent->coord, dent->cdat_coord, ep_c);
dent->entries = 1;
- rc = cxl_root->ops->qos_class(cxl_root,
- &dent->coord[ACCESS_COORDINATE_CPU],
- 1, &qos_class);
+ rc = cxl_root->ops.qos_class(cxl_root,
+ &dent->coord[ACCESS_COORDINATE_CPU],
+ 1, &qos_class);
if (rc != 1)
continue;
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index be3c7b137115..007b8aff0238 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -19,6 +19,14 @@ enum cxl_detach_mode {
};
#ifdef CONFIG_CXL_REGION
+
+struct cxl_region_context {
+ struct cxl_endpoint_decoder *cxled;
+ struct range hpa_range;
+ int interleave_ways;
+ int interleave_granularity;
+};
+
extern struct device_attribute dev_attr_create_pmem_region;
extern struct device_attribute dev_attr_create_ram_region;
extern struct device_attribute dev_attr_delete_region;
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 27ad30fe4fd2..5c82e6f32572 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -957,19 +957,15 @@ struct cxl_port *devm_cxl_add_port(struct device *host,
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_add_port, "CXL");
-struct cxl_root *devm_cxl_add_root(struct device *host,
- const struct cxl_root_ops *ops)
+struct cxl_root *devm_cxl_add_root(struct device *host)
{
- struct cxl_root *cxl_root;
struct cxl_port *port;
port = devm_cxl_add_port(host, host, CXL_RESOURCE_NONE, NULL);
if (IS_ERR(port))
return ERR_CAST(port);
- cxl_root = to_cxl_root(port);
- cxl_root->ops = ops;
- return cxl_root;
+ return to_cxl_root(port);
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_add_root, "CXL");
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 96888d87a8df..bd4c4a4a27da 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -489,9 +489,9 @@ static ssize_t interleave_ways_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
- struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev->parent);
- struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region *cxlr = to_cxl_region(dev);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
+ struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region_params *p = &cxlr->params;
unsigned int val, save;
int rc;
@@ -552,9 +552,9 @@ static ssize_t interleave_granularity_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
- struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev->parent);
- struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region *cxlr = to_cxl_region(dev);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
+ struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region_params *p = &cxlr->params;
int rc, val;
u16 ig;
@@ -628,7 +628,7 @@ static DEVICE_ATTR_RO(mode);
static int alloc_hpa(struct cxl_region *cxlr, resource_size_t size)
{
- struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
struct cxl_region_params *p = &cxlr->params;
struct resource *res;
u64 remainder = 0;
@@ -664,6 +664,8 @@ static int alloc_hpa(struct cxl_region *cxlr, resource_size_t size)
return PTR_ERR(res);
}
+ cxlr->hpa_range = DEFINE_RANGE(res->start, res->end);
+
p->res = res;
p->state = CXL_CONFIG_INTERLEAVE_ACTIVE;
@@ -700,6 +702,8 @@ static int free_hpa(struct cxl_region *cxlr)
if (p->state >= CXL_CONFIG_ACTIVE)
return -EBUSY;
+ cxlr->hpa_range = DEFINE_RANGE(0, -1);
+
cxl_region_iomem_release(cxlr);
p->state = CXL_CONFIG_IDLE;
return 0;
@@ -1093,14 +1097,16 @@ static int cxl_rr_assign_decoder(struct cxl_port *port, struct cxl_region *cxlr,
return 0;
}
-static void cxl_region_set_lock(struct cxl_region *cxlr,
- struct cxl_decoder *cxld)
+static void cxl_region_setup_flags(struct cxl_region *cxlr,
+ struct cxl_decoder *cxld)
{
- if (!test_bit(CXL_DECODER_F_LOCK, &cxld->flags))
- return;
+ if (test_bit(CXL_DECODER_F_LOCK, &cxld->flags)) {
+ set_bit(CXL_REGION_F_LOCK, &cxlr->flags);
+ clear_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
+ }
- set_bit(CXL_REGION_F_LOCK, &cxlr->flags);
- clear_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
+ if (test_bit(CXL_DECODER_F_NORMALIZED_ADDRESSING, &cxld->flags))
+ set_bit(CXL_REGION_F_NORMALIZED_ADDRESSING, &cxlr->flags);
}
/**
@@ -1214,7 +1220,7 @@ static int cxl_port_attach_region(struct cxl_port *port,
}
}
- cxl_region_set_lock(cxlr, cxld);
+ cxl_region_setup_flags(cxlr, cxld);
rc = cxl_rr_ep_add(cxl_rr, cxled);
if (rc) {
@@ -1373,7 +1379,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled)
{
- struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
int parent_iw, parent_ig, ig, iw, rc, pos = cxled->pos;
struct cxl_port *parent_port = to_cxl_port(port->dev.parent);
struct cxl_region_ref *cxl_rr = cxl_rr_load(port, cxlr);
@@ -1731,10 +1737,10 @@ static int cxl_region_validate_position(struct cxl_region *cxlr,
}
static int cxl_region_attach_position(struct cxl_region *cxlr,
- struct cxl_root_decoder *cxlrd,
struct cxl_endpoint_decoder *cxled,
const struct cxl_dport *dport, int pos)
{
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_switch_decoder *cxlsd = &cxlrd->cxlsd;
struct cxl_decoder *cxld = &cxlsd->cxld;
@@ -1874,6 +1880,7 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
/**
* cxl_calc_interleave_pos() - calculate an endpoint position in a region
* @cxled: endpoint decoder member of given region
+ * @hpa_range: translated HPA range of the endpoint
*
* The endpoint position is calculated by traversing the topology from
* the endpoint to the root decoder and iteratively applying this
@@ -1886,11 +1893,11 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
* Return: position >= 0 on success
* -ENXIO on failure
*/
-static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled)
+static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled,
+ struct range *hpa_range)
{
struct cxl_port *iter, *port = cxled_to_port(cxled);
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
- struct range *range = &cxled->cxld.hpa_range;
int parent_ways = 0, parent_pos = 0, pos = 0;
int rc;
@@ -1928,7 +1935,8 @@ static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled)
if (is_cxl_root(iter))
break;
- rc = find_pos_and_ways(iter, range, &parent_pos, &parent_ways);
+ rc = find_pos_and_ways(iter, hpa_range, &parent_pos,
+ &parent_ways);
if (rc)
return rc;
@@ -1938,7 +1946,7 @@ static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled)
dev_dbg(&cxlmd->dev,
"decoder:%s parent:%s port:%s range:%#llx-%#llx pos:%d\n",
dev_name(&cxled->cxld.dev), dev_name(cxlmd->dev.parent),
- dev_name(&port->dev), range->start, range->end, pos);
+ dev_name(&port->dev), hpa_range->start, hpa_range->end, pos);
return pos;
}
@@ -1951,7 +1959,7 @@ static int cxl_region_sort_targets(struct cxl_region *cxlr)
for (i = 0; i < p->nr_targets; i++) {
struct cxl_endpoint_decoder *cxled = p->targets[i];
- cxled->pos = cxl_calc_interleave_pos(cxled);
+ cxled->pos = cxl_calc_interleave_pos(cxled, &cxlr->hpa_range);
/*
* Record that sorting failed, but still continue to calc
* cxled->pos so that follow-on code paths can reliably
@@ -1971,7 +1979,7 @@ static int cxl_region_sort_targets(struct cxl_region *cxlr)
static int cxl_region_attach(struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled, int pos)
{
- struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_dev_state *cxlds = cxlmd->cxlds;
struct cxl_region_params *p = &cxlr->params;
@@ -2076,8 +2084,7 @@ static int cxl_region_attach(struct cxl_region *cxlr,
ep_port = cxled_to_port(cxled);
dport = cxl_find_dport_by_dev(root_port,
ep_port->host_bridge);
- rc = cxl_region_attach_position(cxlr, cxlrd, cxled,
- dport, i);
+ rc = cxl_region_attach_position(cxlr, cxled, dport, i);
if (rc)
return rc;
}
@@ -2100,7 +2107,7 @@ static int cxl_region_attach(struct cxl_region *cxlr,
if (rc)
return rc;
- rc = cxl_region_attach_position(cxlr, cxlrd, cxled, dport, pos);
+ rc = cxl_region_attach_position(cxlr, cxled, dport, pos);
if (rc)
return rc;
@@ -2136,7 +2143,7 @@ static int cxl_region_attach(struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled = p->targets[i];
int test_pos;
- test_pos = cxl_calc_interleave_pos(cxled);
+ test_pos = cxl_calc_interleave_pos(cxled, &cxlr->hpa_range);
dev_dbg(&cxled->cxld.dev,
"Test cxl_calc_interleave_pos(): %s test_pos:%d cxled->pos:%d\n",
(test_pos == cxled->pos) ? "success" : "fail",
@@ -2396,8 +2403,8 @@ static const struct attribute_group *region_groups[] = {
static void cxl_region_release(struct device *dev)
{
- struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev->parent);
struct cxl_region *cxlr = to_cxl_region(dev);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
int id = atomic_read(&cxlrd->region_id);
/*
@@ -2454,6 +2461,8 @@ static void unregister_region(void *_cxlr)
for (i = 0; i < p->interleave_ways; i++)
detach_target(cxlr, i);
+ cxlr->hpa_range = DEFINE_RANGE(0, -1);
+
cxl_region_iomem_release(cxlr);
put_device(&cxlr->dev);
}
@@ -2480,11 +2489,13 @@ static struct cxl_region *cxl_region_alloc(struct cxl_root_decoder *cxlrd, int i
* region id allocations
*/
get_device(dev->parent);
+ cxlr->cxlrd = cxlrd;
+ cxlr->id = id;
+
device_set_pm_not_required(dev);
dev->bus = &cxl_bus_type;
dev->type = &cxl_region_type;
- cxlr->id = id;
- cxl_region_set_lock(cxlr, &cxlrd->cxlsd.cxld);
+ cxl_region_setup_flags(cxlr, &cxlrd->cxlsd.cxld);
return cxlr;
}
@@ -3246,7 +3257,7 @@ static bool region_is_unaligned_mod3(struct cxl_region *cxlr)
u64 cxl_dpa_to_hpa(struct cxl_region *cxlr, const struct cxl_memdev *cxlmd,
u64 dpa)
{
- struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region_params *p = &cxlr->params;
struct cxl_endpoint_decoder *cxled = NULL;
@@ -3256,6 +3267,13 @@ u64 cxl_dpa_to_hpa(struct cxl_region *cxlr, const struct cxl_memdev *cxlmd,
u8 eiw = 0;
int pos;
+ /*
+ * Conversion between SPA and DPA is not supported in
+ * Normalized Address mode.
+ */
+ if (test_bit(CXL_REGION_F_NORMALIZED_ADDRESSING, &cxlr->flags))
+ return ULLONG_MAX;
+
for (int i = 0; i < p->nr_targets; i++) {
if (cxlmd == cxled_to_memdev(p->targets[i])) {
cxled = p->targets[i];
@@ -3361,7 +3379,7 @@ static int region_offset_to_dpa_result(struct cxl_region *cxlr, u64 offset,
struct dpa_result *result)
{
struct cxl_region_params *p = &cxlr->params;
- struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
struct cxl_endpoint_decoder *cxled;
u64 hpa, hpa_offset, dpa_offset;
u16 eig = 0;
@@ -3657,47 +3675,68 @@ err:
return rc;
}
-static int match_decoder_by_range(struct device *dev, const void *data)
+static int match_root_decoder(struct device *dev, const void *data)
{
const struct range *r1, *r2 = data;
- struct cxl_decoder *cxld;
+ struct cxl_root_decoder *cxlrd;
- if (!is_switch_decoder(dev))
+ if (!is_root_decoder(dev))
return 0;
- cxld = to_cxl_decoder(dev);
- r1 = &cxld->hpa_range;
+ cxlrd = to_cxl_root_decoder(dev);
+ r1 = &cxlrd->cxlsd.cxld.hpa_range;
+
return range_contains(r1, r2);
}
-static struct cxl_decoder *
-cxl_port_find_switch_decoder(struct cxl_port *port, struct range *hpa)
+static int cxl_root_setup_translation(struct cxl_root *cxl_root,
+ struct cxl_region_context *ctx)
{
- struct device *cxld_dev = device_find_child(&port->dev, hpa,
- match_decoder_by_range);
+ if (!cxl_root->ops.translation_setup_root)
+ return 0;
- return cxld_dev ? to_cxl_decoder(cxld_dev) : NULL;
+ return cxl_root->ops.translation_setup_root(cxl_root, ctx);
}
+/*
+ * Note, when finished with the device, drop the reference with
+ * put_device() or use the put_cxl_root_decoder helper.
+ */
static struct cxl_root_decoder *
-cxl_find_root_decoder(struct cxl_endpoint_decoder *cxled)
+get_cxl_root_decoder(struct cxl_endpoint_decoder *cxled,
+ struct cxl_region_context *ctx)
{
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_port *port = cxled_to_port(cxled);
struct cxl_root *cxl_root __free(put_cxl_root) = find_cxl_root(port);
- struct cxl_decoder *root, *cxld = &cxled->cxld;
- struct range *hpa = &cxld->hpa_range;
+ struct device *cxlrd_dev;
+ int rc;
- root = cxl_port_find_switch_decoder(&cxl_root->port, hpa);
- if (!root) {
+ /*
+ * Adjust the endpoint's HPA range and interleaving
+ * configuration to the root decoder’s memory space before
+ * setting up the root decoder.
+ */
+ rc = cxl_root_setup_translation(cxl_root, ctx);
+ if (rc) {
+ dev_err(cxlmd->dev.parent,
+ "%s:%s Failed to setup translation for address range %#llx:%#llx\n",
+ dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
+ ctx->hpa_range.start, ctx->hpa_range.end);
+ return ERR_PTR(rc);
+ }
+
+ cxlrd_dev = device_find_child(&cxl_root->port.dev, &ctx->hpa_range,
+ match_root_decoder);
+ if (!cxlrd_dev) {
dev_err(cxlmd->dev.parent,
"%s:%s no CXL window for range %#llx:%#llx\n",
- dev_name(&cxlmd->dev), dev_name(&cxld->dev),
- cxld->hpa_range.start, cxld->hpa_range.end);
- return NULL;
+ dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
+ ctx->hpa_range.start, ctx->hpa_range.end);
+ return ERR_PTR(-ENXIO);
}
- return to_cxl_root_decoder(&root->dev);
+ return to_cxl_root_decoder(cxlrd_dev);
}
static int match_region_by_range(struct device *dev, const void *data)
@@ -3719,7 +3758,7 @@ static int match_region_by_range(struct device *dev, const void *data)
static int cxl_extended_linear_cache_resize(struct cxl_region *cxlr,
struct resource *res)
{
- struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
struct cxl_region_params *p = &cxlr->params;
resource_size_t size = resource_size(res);
resource_size_t cache_size, start;
@@ -3755,11 +3794,12 @@ static int cxl_extended_linear_cache_resize(struct cxl_region *cxlr,
}
static int __construct_region(struct cxl_region *cxlr,
- struct cxl_root_decoder *cxlrd,
- struct cxl_endpoint_decoder *cxled)
+ struct cxl_region_context *ctx)
{
+ struct cxl_endpoint_decoder *cxled = ctx->cxled;
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
- struct range *hpa = &cxled->cxld.hpa_range;
+ struct range *hpa_range = &ctx->hpa_range;
struct cxl_region_params *p;
struct resource *res;
int rc;
@@ -3775,12 +3815,13 @@ static int __construct_region(struct cxl_region *cxlr,
}
set_bit(CXL_REGION_F_AUTO, &cxlr->flags);
+ cxlr->hpa_range = *hpa_range;
res = kmalloc(sizeof(*res), GFP_KERNEL);
if (!res)
return -ENOMEM;
- *res = DEFINE_RES_MEM_NAMED(hpa->start, range_len(hpa),
+ *res = DEFINE_RES_MEM_NAMED(hpa_range->start, range_len(hpa_range),
dev_name(&cxlr->dev));
rc = cxl_extended_linear_cache_resize(cxlr, res);
@@ -3811,8 +3852,8 @@ static int __construct_region(struct cxl_region *cxlr,
}
p->res = res;
- p->interleave_ways = cxled->cxld.interleave_ways;
- p->interleave_granularity = cxled->cxld.interleave_granularity;
+ p->interleave_ways = ctx->interleave_ways;
+ p->interleave_granularity = ctx->interleave_granularity;
p->state = CXL_CONFIG_INTERLEAVE_ACTIVE;
rc = sysfs_update_group(&cxlr->dev.kobj, get_cxl_region_target_group());
@@ -3832,8 +3873,9 @@ static int __construct_region(struct cxl_region *cxlr,
/* Establish an empty region covering the given HPA range */
static struct cxl_region *construct_region(struct cxl_root_decoder *cxlrd,
- struct cxl_endpoint_decoder *cxled)
+ struct cxl_region_context *ctx)
{
+ struct cxl_endpoint_decoder *cxled = ctx->cxled;
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_port *port = cxlrd_to_port(cxlrd);
struct cxl_dev_state *cxlds = cxlmd->cxlds;
@@ -3853,7 +3895,7 @@ static struct cxl_region *construct_region(struct cxl_root_decoder *cxlrd,
return cxlr;
}
- rc = __construct_region(cxlr, cxlrd, cxled);
+ rc = __construct_region(cxlr, ctx);
if (rc) {
devm_release_action(port->uport_dev, unregister_region, cxlr);
return ERR_PTR(rc);
@@ -3863,11 +3905,12 @@ static struct cxl_region *construct_region(struct cxl_root_decoder *cxlrd,
}
static struct cxl_region *
-cxl_find_region_by_range(struct cxl_root_decoder *cxlrd, struct range *hpa)
+cxl_find_region_by_range(struct cxl_root_decoder *cxlrd,
+ struct range *hpa_range)
{
struct device *region_dev;
- region_dev = device_find_child(&cxlrd->cxlsd.cxld.dev, hpa,
+ region_dev = device_find_child(&cxlrd->cxlsd.cxld.dev, hpa_range,
match_region_by_range);
if (!region_dev)
return NULL;
@@ -3877,25 +3920,34 @@ cxl_find_region_by_range(struct cxl_root_decoder *cxlrd, struct range *hpa)
int cxl_add_to_region(struct cxl_endpoint_decoder *cxled)
{
- struct range *hpa = &cxled->cxld.hpa_range;
+ struct cxl_region_context ctx;
struct cxl_region_params *p;
bool attach = false;
int rc;
+ ctx = (struct cxl_region_context) {
+ .cxled = cxled,
+ .hpa_range = cxled->cxld.hpa_range,
+ .interleave_ways = cxled->cxld.interleave_ways,
+ .interleave_granularity = cxled->cxld.interleave_granularity,
+ };
+
struct cxl_root_decoder *cxlrd __free(put_cxl_root_decoder) =
- cxl_find_root_decoder(cxled);
- if (!cxlrd)
- return -ENXIO;
+ get_cxl_root_decoder(cxled, &ctx);
+
+ if (IS_ERR(cxlrd))
+ return PTR_ERR(cxlrd);
/*
- * Ensure that if multiple threads race to construct_region() for @hpa
- * one does the construction and the others add to that.
+ * Ensure that, if multiple threads race to construct_region()
+ * for the HPA range, one does the construction and the others
+ * add to that.
*/
mutex_lock(&cxlrd->range_lock);
struct cxl_region *cxlr __free(put_cxl_region) =
- cxl_find_region_by_range(cxlrd, hpa);
+ cxl_find_region_by_range(cxlrd, &ctx.hpa_range);
if (!cxlr)
- cxlr = construct_region(cxlrd, cxled);
+ cxlr = construct_region(cxlrd, &ctx);
mutex_unlock(&cxlrd->range_lock);
rc = PTR_ERR_OR_ZERO(cxlr);
@@ -4070,6 +4122,39 @@ static int cxl_region_debugfs_poison_clear(void *data, u64 offset)
DEFINE_DEBUGFS_ATTRIBUTE(cxl_poison_clear_fops, NULL,
cxl_region_debugfs_poison_clear, "%llx\n");
+static int cxl_region_setup_poison(struct cxl_region *cxlr)
+{
+ struct device *dev = &cxlr->dev;
+ struct cxl_region_params *p = &cxlr->params;
+ struct dentry *dentry;
+
+ /*
+ * Do not enable poison injection in Normalized Address mode.
+ * Conversion between SPA and DPA is required for this, but it is
+ * not supported in this mode.
+ */
+ if (test_bit(CXL_REGION_F_NORMALIZED_ADDRESSING, &cxlr->flags))
+ return 0;
+
+ /* Create poison attributes if all memdevs support the capabilities */
+ for (int i = 0; i < p->nr_targets; i++) {
+ struct cxl_endpoint_decoder *cxled = p->targets[i];
+ struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
+
+ if (!cxl_memdev_has_poison_cmd(cxlmd, CXL_POISON_ENABLED_INJECT) ||
+ !cxl_memdev_has_poison_cmd(cxlmd, CXL_POISON_ENABLED_CLEAR))
+ return 0;
+ }
+
+ dentry = cxl_debugfs_create_dir(dev_name(dev));
+ debugfs_create_file("inject_poison", 0200, dentry, cxlr,
+ &cxl_poison_inject_fops);
+ debugfs_create_file("clear_poison", 0200, dentry, cxlr,
+ &cxl_poison_clear_fops);
+
+ return devm_add_action_or_reset(dev, remove_debugfs, dentry);
+}
+
static int cxl_region_can_probe(struct cxl_region *cxlr)
{
struct cxl_region_params *p = &cxlr->params;
@@ -4099,7 +4184,6 @@ static int cxl_region_probe(struct device *dev)
{
struct cxl_region *cxlr = to_cxl_region(dev);
struct cxl_region_params *p = &cxlr->params;
- bool poison_supported = true;
int rc;
rc = cxl_region_can_probe(cxlr);
@@ -4123,30 +4207,9 @@ static int cxl_region_probe(struct device *dev)
if (rc)
return rc;
- /* Create poison attributes if all memdevs support the capabilities */
- for (int i = 0; i < p->nr_targets; i++) {
- struct cxl_endpoint_decoder *cxled = p->targets[i];
- struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
-
- if (!cxl_memdev_has_poison_cmd(cxlmd, CXL_POISON_ENABLED_INJECT) ||
- !cxl_memdev_has_poison_cmd(cxlmd, CXL_POISON_ENABLED_CLEAR)) {
- poison_supported = false;
- break;
- }
- }
-
- if (poison_supported) {
- struct dentry *dentry;
-
- dentry = cxl_debugfs_create_dir(dev_name(dev));
- debugfs_create_file("inject_poison", 0200, dentry, cxlr,
- &cxl_poison_inject_fops);
- debugfs_create_file("clear_poison", 0200, dentry, cxlr,
- &cxl_poison_clear_fops);
- rc = devm_add_action_or_reset(dev, remove_debugfs, dentry);
- if (rc)
- return rc;
- }
+ rc = cxl_region_setup_poison(cxlr);
+ if (rc)
+ return rc;
switch (cxlr->mode) {
case CXL_PARTMODE_PMEM:
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 478d8803b464..04c673e7cdb0 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -332,7 +332,7 @@ int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport);
#define CXL_DECODER_F_TYPE3 BIT(3)
#define CXL_DECODER_F_LOCK BIT(4)
#define CXL_DECODER_F_ENABLE BIT(5)
-#define CXL_DECODER_F_MASK GENMASK(5, 0)
+#define CXL_DECODER_F_NORMALIZED_ADDRESSING BIT(6)
enum cxl_decoder_type {
CXL_DECODER_DEVMEM = 2,
@@ -525,10 +525,19 @@ enum cxl_partition_mode {
*/
#define CXL_REGION_F_LOCK 2
+/*
+ * Indicate Normalized Addressing. Use it to disable SPA conversion if
+ * HPA != SPA and an address translation callback handler does not
+ * exist. Flag is needed by AMD Zen5 platforms.
+ */
+#define CXL_REGION_F_NORMALIZED_ADDRESSING 3
+
/**
* struct cxl_region - CXL region
* @dev: This region's device
* @id: This region's id. Id is globally unique across all regions
+ * @cxlrd: Region's root decoder
+ * @hpa_range: Address range occupied by the region
* @mode: Operational mode of the mapped capacity
* @type: Endpoint decoder target type
* @cxl_nvb: nvdimm bridge for coordinating @cxlr_pmem setup / shutdown
@@ -542,6 +551,8 @@ enum cxl_partition_mode {
struct cxl_region {
struct device dev;
int id;
+ struct cxl_root_decoder *cxlrd;
+ struct range hpa_range;
enum cxl_partition_mode mode;
enum cxl_decoder_type type;
struct cxl_nvdimm_bridge *cxl_nvb;
@@ -644,6 +655,15 @@ struct cxl_port {
resource_size_t component_reg_phys;
};
+struct cxl_root;
+
+struct cxl_root_ops {
+ int (*qos_class)(struct cxl_root *cxl_root,
+ struct access_coordinate *coord, int entries,
+ int *qos_class);
+ int (*translation_setup_root)(struct cxl_root *cxl_root, void *data);
+};
+
/**
* struct cxl_root - logical collection of root cxl_port items
*
@@ -652,7 +672,7 @@ struct cxl_port {
*/
struct cxl_root {
struct cxl_port port;
- const struct cxl_root_ops *ops;
+ struct cxl_root_ops ops;
};
static inline struct cxl_root *
@@ -661,12 +681,6 @@ to_cxl_root(const struct cxl_port *port)
return container_of(port, struct cxl_root, port);
}
-struct cxl_root_ops {
- int (*qos_class)(struct cxl_root *cxl_root,
- struct access_coordinate *coord, int entries,
- int *qos_class);
-};
-
static inline struct cxl_dport *
cxl_find_dport_by_dev(struct cxl_port *port, const struct device *dport_dev)
{
@@ -780,8 +794,7 @@ struct cxl_port *devm_cxl_add_port(struct device *host,
struct device *uport_dev,
resource_size_t component_reg_phys,
struct cxl_dport *parent_dport);
-struct cxl_root *devm_cxl_add_root(struct device *host,
- const struct cxl_root_ops *ops);
+struct cxl_root *devm_cxl_add_root(struct device *host);
int devm_cxl_add_endpoint(struct device *host, struct cxl_memdev *cxlmd,
struct cxl_dport *parent_dport);
struct cxl_root *find_cxl_root(struct cxl_port *port);
@@ -807,6 +820,13 @@ struct cxl_dport *devm_cxl_add_rch_dport(struct cxl_port *port,
struct device *dport_dev, int port_id,
resource_size_t rcrb);
+#ifdef CONFIG_CXL_ATL
+void cxl_setup_prm_address_translation(struct cxl_root *cxl_root);
+#else
+static inline
+void cxl_setup_prm_address_translation(struct cxl_root *cxl_root) {}
+#endif
+
struct cxl_decoder *to_cxl_decoder(struct device *dev);
struct cxl_root_decoder *to_cxl_root_decoder(struct device *dev);
struct cxl_switch_decoder *to_cxl_switch_decoder(struct device *dev);