#include <linux/prmt.h>
#include <linux/pci.h>
#include <linux/acpi.h>
#include <cxlmem.h>
#include "core.h"
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;
if (hpa_range.start != cxled->dpa_res->start)
return 0;
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);
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;
}
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;
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;
}
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;
if (!acpi_match_device(host->driver->acpi_match_table, host))
return;
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");