[PATCH v9 6/7] RAS/AMD/ATL: Add DRAM address translation via PRM

Yazen Ghannam <[email protected]> Thu, 30 Jul 2026 15:48:33 -0400
Newsgroups org.kernel.vger.linux-edac,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
From: Avadhut Naik <[email protected]>

On AMD's Zen-based SoCs, a Unified Memory Controller (UMC) relative
"normalized" address is reported through the MCA_ADDR of UMC SMCA banks on
occurrence of a DRAM ECC error. This address can be converted into a DRAM
address to decode additional information about the error.

Add support to convert a normalized address into a DRAM address using the
appropriate PRM handler. Expose it as a new operation, ATL_OP_DA, on the
unified interface.

Link: https://bugzilla.kernel.org/show_bug.cgi?id=220577
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Avadhut Naik <[email protected]>
Co-developed-by: Yazen Ghannam <[email protected]>
Signed-off-by: Yazen Ghannam <[email protected]>
---
 drivers/ras/amd/atl/internal.h |  1 +
 drivers/ras/amd/atl/system.c   |  4 ++++
 drivers/ras/amd/atl/umc.c      |  4 ++++
 include/linux/ras.h            | 13 +++++++++++++
 4 files changed, 22 insertions(+)

diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
index 0ebab3732886..86879f5632c1 100644
--- a/drivers/ras/amd/atl/internal.h
+++ b/drivers/ras/amd/atl/internal.h
@@ -286,6 +286,7 @@ u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr);
 
 /* GUIDs for PRM handlers */
 extern const guid_t norm_to_sys_guid;
+extern const guid_t norm_to_dram_guid;
 
 int prm_umc_norm_to_addr(guid_t guid, struct atl_umc_addr *addr, void *out_buf);
 
diff --git a/drivers/ras/amd/atl/system.c b/drivers/ras/amd/atl/system.c
index 812a30e21d3a..1315bcc63e30 100644
--- a/drivers/ras/amd/atl/system.c
+++ b/drivers/ras/amd/atl/system.c
@@ -18,6 +18,10 @@ const guid_t norm_to_sys_guid = GUID_INIT(0xE7180659, 0xA65D, 0x451D,
 					  0x92, 0xCD, 0x2B, 0x56, 0xF1,
 					  0x2B, 0xEB, 0xA6);
 
+const guid_t norm_to_dram_guid = GUID_INIT(0x7626C6AE, 0xF973, 0x429C,
+					   0xA9, 0x1C, 0x10, 0x7D, 0x7B,
+					   0xE2, 0x98, 0xB0);
+
 int determine_node_id(struct addr_ctx *ctx, u8 socket_id, u8 die_id)
 {
 	u16 socket_id_bits, die_id_bits;
diff --git a/drivers/ras/amd/atl/umc.c b/drivers/ras/amd/atl/umc.c
index a3f611a4a9f8..1ab321a04cf1 100644
--- a/drivers/ras/amd/atl/umc.c
+++ b/drivers/ras/amd/atl/umc.c
@@ -449,4 +449,8 @@ void amd_atl_umc_translate_addr(struct atl_err *err)
 			}
 		}
 	}
+
+	if ((err->requested & ATL_OP_DA) &&
+	    !prm_umc_norm_to_addr(norm_to_dram_guid, &err->umc_addr, &err->da))
+		err->valid |= ATL_OP_DA;
 }
diff --git a/include/linux/ras.h b/include/linux/ras.h
index f0afacdcebcf..6589b4e3ff0c 100644
--- a/include/linux/ras.h
+++ b/include/linux/ras.h
@@ -37,8 +37,20 @@ static inline void
 log_arm_hw_error(struct cper_sec_proc_arm *err, const u8 sev) { return; }
 #endif
 
+/* See "NA to DA Output Buffer" in the AMD ACPI Porting Guide. */
+struct atl_dram_addr {
+	u8 chip_select;
+	u8 bank_group;
+	u8 bank_addr;
+	u32 row_addr;
+	u16 col_addr;
+	u8 rank_mul;
+	u8 sub_ch;
+} __packed;
+
 /* Operations requested and completed through amd_translate_umc_mca_addr(). */
 #define ATL_OP_SPA		BIT(0)	/* System Physical Address */
+#define ATL_OP_DA		BIT(1)	/* DRAM address components */
 
 struct atl_err {
 	/* Identifiers; layout mirrors the PRM parameter buffer inputs */
@@ -57,6 +69,7 @@ struct atl_err {
 
 	/* Outputs */
 	u64 spa;			/* Valid if (@valid & ATL_OP_SPA) */
+	struct atl_dram_addr da;	/* Valid if (@valid & ATL_OP_DA) */
 };
 
 #if IS_ENABLED(CONFIG_AMD_ATL)
-- 
2.53.0