[PATCH v9 3/7] RAS/AMD/ATL: Add unified UMC address translation interface
Yazen Ghannam <[email protected]> Thu, 30 Jul 2026 15:48:30 -0400
| Newsgroups | org.kernel.vger.linux-edac,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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. 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. 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: Claude:claude-opus-4-8 Signed-off-by: Yazen Ghannam <[email protected]> --- 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 | 37 ++++++++++++++++++++++++++++++++++ drivers/ras/ras.c | 28 +++++++++++++++++++++++++ include/linux/ras.h | 28 +++++++++++++++++++++++-- 6 files changed, 105 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 3d5a6f5ae0a9..a951c00f940c 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..2d61f890e4b8 100644 --- a/drivers/ras/amd/atl/umc.c +++ b/drivers/ras/amd/atl/umc.c @@ -416,3 +416,40 @@ 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) { + 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..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.53.0