// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2025 Advanced Micro Devices, Inc. */ #include #include #include #include #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");