[PATCH v10 3/7] RAS/AMD/ATL: Add unified UMC address translation interface
From: Yazen Ghannam
Date: Tue Sep 22 2026 - 16:29:24 EST
The library exposes a single function for UMC address translation. Adding a
new translation would mean adding and plumbing another export. Callers also
have no way to request more than one representation of an address in a
single call.
Add a single entry point, amd_translate_umc_mca_addr(), that operates
entirely through struct atl_err. Expand the struct with a pair of
operation bitmaps and the per-operation output fields.
The caller provides the error inputs and requests one or more operations.
Each operation is attempted independently. The library sets a validation
bit and fills the matching output for each operation that succeeds. The
caller must check the validation bit before consuming an output value.
Group the error identifiers (normalized address, socket, and UMC bank ID)
into struct atl_umc_addr. Lay it out to match the PRM parameter buffer
inputs that every handler shares, and embed it in the buffer. A caller can
then build the buffer straight from its own struct atl_err. Move the buffer
definition to internal.h so callers outside prm.c can do so.
Route the interface through the always-built RAS core so the library
remains optional. The core provides the entry point and a
register/unregister pair. The library registers its implementation on load.
The entry point does nothing (no validation bits set) when the library is
absent.
Start with a single operation, ATL_OP_SPA, that produces the System
Physical Address. Try the PRM handler first. Fall back to the native Data
Fabric translation if PRM is unavailable and the Data Fabric revision
supports it.
Leave the existing amd_convert_umc_mca_addr_to_sys_addr() interface in
place for now.
Assisted-by: LLM
Signed-off-by: Yazen Ghannam <yazen.ghannam@xxxxxxx>
---
drivers/ras/amd/atl/core.c | 2 ++
drivers/ras/amd/atl/internal.h | 8 +++++++
drivers/ras/amd/atl/prm.c | 18 +++++----------
drivers/ras/amd/atl/umc.c | 42 ++++++++++++++++++++++++++++++++++
drivers/ras/ras.c | 28 +++++++++++++++++++++++
include/linux/ras.h | 28 +++++++++++++++++++++--
6 files changed, 112 insertions(+), 14 deletions(-)
diff --git a/drivers/ras/amd/atl/core.c b/drivers/ras/amd/atl/core.c
index d77dacdd4f56..b754eaef8585 100644
--- a/drivers/ras/amd/atl/core.c
+++ b/drivers/ras/amd/atl/core.c
@@ -210,6 +210,7 @@ static int __init amd_atl_init(void)
/* Increment this module's recount so that it can't be easily unloaded. */
__module_get(THIS_MODULE);
amd_atl_register_decoder(convert_umc_mca_addr_to_sys_addr);
+ amd_atl_register_umc_translator(amd_atl_umc_translate_addr);
pr_info("AMD Address Translation Library initialized\n");
return 0;
@@ -222,6 +223,7 @@ static int __init amd_atl_init(void)
static void __exit amd_atl_exit(void)
{
amd_atl_unregister_decoder();
+ amd_atl_unregister_umc_translator();
}
module_init(amd_atl_init);
diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
index 4fc4bc3c3500..0c86f40a094b 100644
--- a/drivers/ras/amd/atl/internal.h
+++ b/drivers/ras/amd/atl/internal.h
@@ -280,6 +280,7 @@ int dehash_address(struct addr_ctx *ctx);
unsigned long norm_to_sys_addr(u8 socket_id, u8 die_id, u8 coh_st_inst_id, unsigned long addr);
unsigned long convert_umc_mca_addr_to_sys_addr(struct atl_err *err);
+void amd_atl_umc_translate_addr(struct atl_err *err);
u64 add_base_and_hole(struct addr_ctx *ctx, u64 addr);
u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr);
@@ -287,6 +288,13 @@ u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr);
/* GUIDs for PRM handlers */
extern const guid_t norm_to_sys_guid;
+/* See "PRM Parameter Buffer" in the AMD ACPI Porting Guide. */
+struct param_buf {
+ struct atl_umc_addr addr;
+ void *out_buf;
+} __packed;
+
+int prm_umc_norm_to_addr(guid_t guid, struct param_buf *p_buf);
unsigned long prm_umc_norm_to_sys_addr(u8 socket_id, u64 umc_bank_inst_id, unsigned long addr);
/*
diff --git a/drivers/ras/amd/atl/prm.c b/drivers/ras/amd/atl/prm.c
index b3ff33a668c2..0635215f220a 100644
--- a/drivers/ras/amd/atl/prm.c
+++ b/drivers/ras/amd/atl/prm.c
@@ -18,15 +18,7 @@
#include <linux/prmt.h>
-/* See "PRM Parameter Buffer" in the AMD ACPI Porting Guide. */
-struct param_buf {
- u64 norm_addr;
- u8 socket;
- u64 bank_id;
- void *out_buf;
-} __packed;
-
-static int prm_umc_norm_to_addr(guid_t guid, struct param_buf *p_buf)
+int prm_umc_norm_to_addr(guid_t guid, struct param_buf *p_buf)
{
int ret;
@@ -46,9 +38,11 @@ unsigned long prm_umc_norm_to_sys_addr(u8 socket_id, u64 bank_id, unsigned long
{
unsigned long sys_addr;
struct param_buf p_buf = {
- .norm_addr = addr,
- .socket = socket_id,
- .bank_id = bank_id,
+ .addr = {
+ .addr = addr,
+ .socket_id = socket_id,
+ .ipid = bank_id,
+ },
.out_buf = &sys_addr,
};
int ret;
diff --git a/drivers/ras/amd/atl/umc.c b/drivers/ras/amd/atl/umc.c
index befc616d5e8a..91c02ef5dc11 100644
--- a/drivers/ras/amd/atl/umc.c
+++ b/drivers/ras/amd/atl/umc.c
@@ -416,3 +416,45 @@ unsigned long convert_umc_mca_addr_to_sys_addr(struct atl_err *err)
return norm_to_sys_addr(socket_id, die_id, coh_st_inst_id, addr);
}
+
+/*
+ * Translate a UMC MCA error address into one or more representations as
+ * requested by the caller.
+ *
+ * The caller sets the input values and requests one or more operations
+ * through @err->requested. @err->valid is cleared on entry. Each operation
+ * is attempted independently. The corresponding bit in @err->valid is set
+ * and the related output field is filled for each operation that succeeds.
+ * The caller must check @err->valid before consuming an output value.
+ *
+ * The PRM handlers consume @err->umc_addr directly. Only MI300 needs its
+ * MCA_ADDR value converted to a normalized address first; see get_addr().
+ * MI300 platforms provide no PRM handlers, so the conversion is left to the
+ * native fallback path.
+ *
+ * Registered with the RAS core as the UMC address translator; see
+ * amd_translate_umc_mca_addr().
+ */
+void amd_atl_umc_translate_addr(struct atl_err *err)
+{
+ err->socket_id = topology_physical_package_id(err->cpu);
+ err->valid = 0;
+
+ if (err->requested & ATL_OP_SPA) {
+ struct param_buf p_buf = {
+ .addr = err->umc_addr,
+ .out_buf = &err->spa,
+ };
+
+ if (!prm_umc_norm_to_addr(norm_to_sys_guid, &p_buf)) {
+ err->valid |= ATL_OP_SPA;
+ } else if (!df_cfg.flags.prm_only) {
+ unsigned long spa = convert_umc_mca_addr_to_sys_addr(err);
+
+ if (!IS_ERR_VALUE(spa)) {
+ err->spa = spa;
+ err->valid |= ATL_OP_SPA;
+ }
+ }
+ }
+}
diff --git a/drivers/ras/ras.c b/drivers/ras/ras.c
index 03df3db62334..20e23560dee5 100644
--- a/drivers/ras/ras.c
+++ b/drivers/ras/ras.c
@@ -39,6 +39,34 @@ unsigned long amd_convert_umc_mca_addr_to_sys_addr(struct atl_err *err)
return amd_atl_umc_na_to_spa(err);
}
EXPORT_SYMBOL_GPL(amd_convert_umc_mca_addr_to_sys_addr);
+
+/*
+ * Set by the library module when it loads. Left registered while the module is
+ * resident; consumers keep no direct dependency on the library, so translation
+ * is simply skipped when it is not loaded.
+ */
+static void (*amd_atl_umc_translate)(struct atl_err *err);
+
+void amd_atl_register_umc_translator(void (*f)(struct atl_err *))
+{
+ amd_atl_umc_translate = f;
+}
+EXPORT_SYMBOL_GPL(amd_atl_register_umc_translator);
+
+void amd_atl_unregister_umc_translator(void)
+{
+ amd_atl_umc_translate = NULL;
+}
+EXPORT_SYMBOL_GPL(amd_atl_unregister_umc_translator);
+
+void amd_translate_umc_mca_addr(struct atl_err *err)
+{
+ err->valid = 0;
+
+ if (amd_atl_umc_translate)
+ amd_atl_umc_translate(err);
+}
+EXPORT_SYMBOL_GPL(amd_translate_umc_mca_addr);
#endif /* CONFIG_AMD_ATL */
#define CREATE_TRACE_POINTS
diff --git a/include/linux/ras.h b/include/linux/ras.h
index 468941bfe855..eac8cf39ddd5 100644
--- a/include/linux/ras.h
+++ b/include/linux/ras.h
@@ -3,6 +3,8 @@
#define __RAS_H__
#include <asm/errno.h>
+#include <linux/bits.h>
+#include <linux/stddef.h>
#include <linux/uuid.h>
#include <linux/cper.h>
@@ -35,10 +37,26 @@ static inline void
log_arm_hw_error(struct cper_sec_proc_arm *err, const u8 sev) { return; }
#endif
+/* Operations requested and completed through amd_translate_umc_mca_addr(). */
+#define ATL_OP_SPA BIT(0) /* System Physical Address */
+
struct atl_err {
- u64 addr;
- u64 ipid;
+ /* Identifiers; layout mirrors the PRM parameter buffer inputs */
+ __struct_group(atl_umc_addr, umc_addr, __packed,
+ u64 addr;
+ u8 socket_id; /* Filled by the library from @cpu */
+ u64 ipid;
+ );
u32 cpu;
+
+ /* Requested operations (input) */
+ u8 requested;
+
+ /* Completed operations (output) */
+ u8 valid;
+
+ /* Outputs */
+ u64 spa; /* Valid if (@valid & ATL_OP_SPA) */
};
#if IS_ENABLED(CONFIG_AMD_ATL)
@@ -46,10 +64,16 @@ void amd_atl_register_decoder(unsigned long (*f)(struct atl_err *));
void amd_atl_unregister_decoder(void);
void amd_retire_dram_row(struct atl_err *err);
unsigned long amd_convert_umc_mca_addr_to_sys_addr(struct atl_err *err);
+
+void amd_atl_register_umc_translator(void (*f)(struct atl_err *));
+void amd_atl_unregister_umc_translator(void);
+void amd_translate_umc_mca_addr(struct atl_err *err);
#else
static inline void amd_retire_dram_row(struct atl_err *err) { }
static inline unsigned long
amd_convert_umc_mca_addr_to_sys_addr(struct atl_err *err) { return -EINVAL; }
+static inline void
+amd_translate_umc_mca_addr(struct atl_err *err) { err->valid = 0; }
#endif /* CONFIG_AMD_ATL */
#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
--
2.43.0