[PATCH v8 3/7] RAS/AMD/ATL: Add unified UMC address translation interface
From: Yazen Ghannam
Date: Mon Jul 27 2026 - 17:22: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 struct atl_err 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. Reuse that struct for the parameter
buffer. A caller-populated atl_err can then be handed to a PRM handler
directly.
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. Use the PRM handler on PRM-only platforms, where the Data
Fabric revision has no native translation support, and use the native Data
Fabric translation otherwise. The existing
amd_convert_umc_mca_addr_to_sys_addr() interface is left in place for now.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Yazen Ghannam <yazen.ghannam@xxxxxxx>
---
drivers/ras/amd/atl/core.c | 2 ++
drivers/ras/amd/atl/internal.h | 4 ++--
drivers/ras/amd/atl/prm.c | 20 ++++++++++----------
drivers/ras/amd/atl/umc.c | 31 +++++++++++++++++++++++++++++++
drivers/ras/ras.c | 26 ++++++++++++++++++++++++++
include/linux/ras.h | 27 +++++++++++++++++++++++++--
6 files changed, 96 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 9f4d2a5b6c14..0086bf0ff24f 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,8 +288,7 @@ u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr);
/* GUIDs for PRM handlers */
extern const guid_t norm_to_sys_guid;
-int prm_umc_norm_to_addr(guid_t guid, u8 socket_id, u64 umc_bank_inst_id,
- unsigned long addr, void *out_buf);
+int prm_umc_norm_to_addr(guid_t guid, struct atl_umc_addr *addr, void *out_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 0934f890745d..5d9f0cb9eadb 100644
--- a/drivers/ras/amd/atl/prm.c
+++ b/drivers/ras/amd/atl/prm.c
@@ -20,22 +20,17 @@
/* See "PRM Parameter Buffer" in the AMD ACPI Porting Guide. */
struct param_buf {
- u64 norm_addr;
- u8 socket;
- u64 bank_id;
+ struct atl_umc_addr addr;
void *out_buf;
} __packed;
-int prm_umc_norm_to_addr(guid_t guid, u8 socket_id, u64 bank_id,
- unsigned long addr, void *out_buf)
+int prm_umc_norm_to_addr(guid_t guid, struct atl_umc_addr *addr, void *out_buf)
{
struct param_buf p_buf;
int ret;
- p_buf.norm_addr = addr;
- p_buf.socket = socket_id;
- p_buf.bank_id = bank_id;
- p_buf.out_buf = out_buf;
+ p_buf.addr = *addr;
+ p_buf.out_buf = out_buf;
ret = acpi_call_prm_handler(guid, &p_buf);
if (!ret)
@@ -51,10 +46,15 @@ int prm_umc_norm_to_addr(guid_t guid, u8 socket_id, u64 bank_id,
unsigned long prm_umc_norm_to_sys_addr(u8 socket_id, u64 bank_id, unsigned long addr)
{
+ struct atl_umc_addr uaddr = {
+ .addr = addr,
+ .socket_id = socket_id,
+ .ipid = bank_id,
+ };
unsigned long sys_addr;
int ret;
- ret = prm_umc_norm_to_addr(norm_to_sys_guid, socket_id, bank_id, addr, &sys_addr);
+ ret = prm_umc_norm_to_addr(norm_to_sys_guid, &uaddr, &sys_addr);
if (ret)
return ret;
diff --git a/drivers/ras/amd/atl/umc.c b/drivers/ras/amd/atl/umc.c
index befc616d5e8a..c22bc98104ee 100644
--- a/drivers/ras/amd/atl/umc.c
+++ b/drivers/ras/amd/atl/umc.c
@@ -416,3 +416,34 @@ 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. 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.
+ *
+ * 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);
+
+ if (err->requested & ATL_OP_SPA) {
+ if (!prm_umc_norm_to_addr(norm_to_sys_guid, &err->umc_addr, &err->spa)) {
+ 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..6032f1cfce91 100644
--- a/drivers/ras/ras.c
+++ b/drivers/ras/ras.c
@@ -39,6 +39,32 @@ 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)
+{
+ 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..df1a1485ea16 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; /* Socket of @cpu */
+ u64 ipid;
+ );
u32 cpu;
+
+ /* Requested operations (input) */
+ u32 requested;
+
+ /* Completed operations (output) */
+ u32 valid;
+
+ /* Outputs */
+ u64 spa; /* Valid if (@valid & ATL_OP_SPA) */
};
#if IS_ENABLED(CONFIG_AMD_ATL)
@@ -46,10 +64,15 @@ 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) { }
#endif /* CONFIG_AMD_ATL */
#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
--
2.53.0