* [RFC PATCH 01/10] ras/amd/atl: Add Hygon DF1 Data Fabric system information helper
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 02/10] ras/amd/atl: Add Hygon DF1 DRAM address map decoding helper Aichun Shi
` (9 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Add Hygon Family 0x18 model 0x4/0x5 (Hygon DF1) support as the start.
Add Hygon DF1 Data Fabric system information that Hygon address
translation function depends on later.
Prepare hygon_get_df_system_info() to be called by amd_atl_init() later.
- Add hygon/reg_fields.h for Hygon-specific register field definitions
with Hygon DF1 System Fabric ID Mask defined first.
- Add hygon/system.c for Hygon-specific implementation similar to
system.c for AMD.
- Add HYGON_DF1 to enum df_revisions in internal.h.
- Add hygon_determine_df_rev() to detect models 0x4/0x5 as Hygon DF1 and
get system information.
- Use get_dram_hole_base() and dump_df_cfg() shared from system.c and
exported in internal.h to avoid duplicate code.
- Build hygon/system.c from the Makefile as an amd_atl object.
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/ras/amd/atl/Makefile | 2 +
drivers/ras/amd/atl/hygon/reg_fields.h | 60 +++++++++++++++++++++
drivers/ras/amd/atl/hygon/system.c | 73 ++++++++++++++++++++++++++
drivers/ras/amd/atl/internal.h | 7 +++
drivers/ras/amd/atl/system.c | 4 +-
5 files changed, 144 insertions(+), 2 deletions(-)
create mode 100644 drivers/ras/amd/atl/hygon/reg_fields.h
create mode 100644 drivers/ras/amd/atl/hygon/system.c
diff --git a/drivers/ras/amd/atl/Makefile b/drivers/ras/amd/atl/Makefile
index b56892c0c0d9..3716fe068eb6 100644
--- a/drivers/ras/amd/atl/Makefile
+++ b/drivers/ras/amd/atl/Makefile
@@ -15,6 +15,8 @@ amd_atl-y += map.o
amd_atl-y += system.o
amd_atl-y += umc.o
+amd_atl-y += hygon/system.o
+
amd_atl-$(CONFIG_AMD_ATL_PRM) += prm.o
obj-$(CONFIG_AMD_ATL) += amd_atl.o
diff --git a/drivers/ras/amd/atl/hygon/reg_fields.h b/drivers/ras/amd/atl/hygon/reg_fields.h
new file mode 100644
index 000000000000..2c1193639c6c
--- /dev/null
+++ b/drivers/ras/amd/atl/hygon/reg_fields.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD Address Translation Library
+ *
+ * reg_fields.h : Register field definitions for Hygon
+ *
+ * Author: AichunShi <shiaichun@open-hieco.net>
+ */
+
+/*
+ * Die ID Mask
+ *
+ * Access type: Broadcast
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F1x208 [System Fabric ID Mask]
+ * HYGON_DF1 DieIdMask [9:0]
+ */
+#define HYGON_DF1_DIE_ID_MASK GENMASK(9, 0)
+
+/*
+ * Die ID Shift
+ *
+ * Access type: Broadcast
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F1x208 [System Fabric ID Mask]
+ * HYGON_DF1 DieIdShift [15:12]
+ */
+#define HYGON_DF1_DIE_ID_SHIFT GENMASK(15, 12)
+
+/*
+ * Socket ID Mask
+ *
+ * Access type: Broadcast
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F1x208 [System Fabric ID Mask]
+ * HYGON_DF1 SocketIdMask [25:16]
+ */
+#define HYGON_DF1_SOCKET_ID_MASK GENMASK(25, 16)
+
+/*
+ * Socket ID Shift
+ *
+ * Access type: Broadcast
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F1x208 [System Fabric ID Mask]
+ * HYGON_DF1 SocketIdShift [31:28]
+ */
+#define HYGON_DF1_SOCKET_ID_SHIFT GENMASK(31, 28)
diff --git a/drivers/ras/amd/atl/hygon/system.c b/drivers/ras/amd/atl/hygon/system.c
new file mode 100644
index 000000000000..2a149cfc34c1
--- /dev/null
+++ b/drivers/ras/amd/atl/hygon/system.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Address Translation Library
+ *
+ * system.c : Functions to read and save system-wide data for Hygon
+ *
+ * Author: AichunShi <shiaichun@open-hieco.net>
+ */
+
+#include "../internal.h"
+
+static void hygon_df_get_masks_shifts(u32 mask0)
+{
+ switch (df_cfg.rev) {
+ case HYGON_DF1:
+ df_cfg.socket_id_shift = FIELD_GET(HYGON_DF1_SOCKET_ID_SHIFT, mask0);
+ df_cfg.socket_id_mask = FIELD_GET(HYGON_DF1_SOCKET_ID_MASK, mask0);
+ df_cfg.die_id_shift = FIELD_GET(HYGON_DF1_DIE_ID_SHIFT, mask0);
+ df_cfg.die_id_mask = FIELD_GET(HYGON_DF1_DIE_ID_MASK, mask0);
+ break;
+ default:
+ atl_debug_on_bad_df_rev();
+ return;
+ }
+}
+
+static int hygon_determine_df_rev(void)
+{
+ u32 fabric_id_mask0;
+
+ if (boot_cpu_data.x86_model == 0x4 || boot_cpu_data.x86_model == 0x5)
+ df_cfg.rev = HYGON_DF1;
+
+ /* Read D18F1x208 (SystemFabricIdMask). */
+ if (df_indirect_read_broadcast(0, 1, 0x208, &fabric_id_mask0))
+ return -EINVAL;
+
+ hygon_df_get_masks_shifts(fabric_id_mask0);
+
+ return 0;
+}
+
+static void hygon_get_num_maps(void)
+{
+ switch (df_cfg.rev) {
+ case HYGON_DF1:
+ df_cfg.num_coh_st_maps = 2;
+ break;
+ default:
+ atl_debug_on_bad_df_rev();
+ }
+}
+
+int hygon_get_df_system_info(void)
+{
+ int ret;
+
+ ret = hygon_determine_df_rev();
+ if (ret) {
+ pr_warn("Failed to determine DF Revision");
+ df_cfg.rev = UNKNOWN;
+ return ret;
+ }
+
+ hygon_get_num_maps();
+
+ if (get_dram_hole_base())
+ pr_warn("Failed to read DRAM hole base");
+
+ dump_df_cfg();
+
+ return 0;
+}
diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
index 82a56d9c2be1..19651cf9e978 100644
--- a/drivers/ras/amd/atl/internal.h
+++ b/drivers/ras/amd/atl/internal.h
@@ -21,6 +21,7 @@
#include <asm/amd/node.h>
#include "reg_fields.h"
+#include "hygon/reg_fields.h"
#undef pr_fmt
#define pr_fmt(fmt) "amd_atl: " fmt
@@ -42,6 +43,7 @@
enum df_revisions {
UNKNOWN,
+ HYGON_DF1,
DF2,
DF3,
DF3p5,
@@ -284,6 +286,11 @@ unsigned long convert_umc_mca_addr_to_sys_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);
+int get_dram_hole_base(void);
+void dump_df_cfg(void);
+
+int hygon_get_df_system_info(void);
+
/* GUIDs for PRM handlers */
extern const guid_t norm_to_sys_guid;
diff --git a/drivers/ras/amd/atl/system.c b/drivers/ras/amd/atl/system.c
index 812a30e21d3a..eb420978febb 100644
--- a/drivers/ras/amd/atl/system.c
+++ b/drivers/ras/amd/atl/system.c
@@ -231,7 +231,7 @@ static int determine_df_rev(void)
return 0;
}
-static int get_dram_hole_base(void)
+int get_dram_hole_base(void)
{
u8 func = 0;
@@ -274,7 +274,7 @@ static void apply_node_id_shift(void)
df_cfg.socket_id_shift += df_cfg.node_id_shift;
}
-static void dump_df_cfg(void)
+void dump_df_cfg(void)
{
pr_debug("rev=0x%x", df_cfg.rev);
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 02/10] ras/amd/atl: Add Hygon DF1 DRAM address map decoding helper
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 01/10] ras/amd/atl: Add Hygon DF1 Data Fabric system information helper Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 03/10] ras/amd/atl: Add Hygon DF1 normalized address denormalization helper Aichun Shi
` (8 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Add Hygon DF1 DRAM address map decoding function that Hygon address
translation pipeline depends on later.
Prepare hygon_get_address_map() to be called by hygon/core.c later.
- Add struct hygon_chan_intlv in addr_ctx and HYGON_DF1_3CHAN in enum
intlv_modes to internal.h for Hygon DF1 three-channel interleave mode.
- Add hygon/map.c for Hygon-specific implementation similar to map.c for
AMD.
- Get the coherent-station fabric ID, handle normalized offsets, read
Hygon DF1 specific channel interleave state, and derive global
interleave metadata (modes, bit positions, die/socket/channel counts).
- Use df2_get_dram_addr_map(), valid_map(), and dump_address_map()
shared from map.c and exported in internal.h to avoid duplicate code.
- Build hygon/map.c from the Makefile as an amd_atl object.
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/ras/amd/atl/Makefile | 1 +
drivers/ras/amd/atl/hygon/map.c | 337 +++++++++++++++++++++++++
drivers/ras/amd/atl/hygon/reg_fields.h | 78 ++++++
drivers/ras/amd/atl/internal.h | 17 ++
drivers/ras/amd/atl/map.c | 6 +-
5 files changed, 436 insertions(+), 3 deletions(-)
create mode 100644 drivers/ras/amd/atl/hygon/map.c
diff --git a/drivers/ras/amd/atl/Makefile b/drivers/ras/amd/atl/Makefile
index 3716fe068eb6..105c11015f4c 100644
--- a/drivers/ras/amd/atl/Makefile
+++ b/drivers/ras/amd/atl/Makefile
@@ -15,6 +15,7 @@ amd_atl-y += map.o
amd_atl-y += system.o
amd_atl-y += umc.o
+amd_atl-y += hygon/map.o
amd_atl-y += hygon/system.o
amd_atl-$(CONFIG_AMD_ATL_PRM) += prm.o
diff --git a/drivers/ras/amd/atl/hygon/map.c b/drivers/ras/amd/atl/hygon/map.c
new file mode 100644
index 000000000000..2194c10ff587
--- /dev/null
+++ b/drivers/ras/amd/atl/hygon/map.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Address Translation Library
+ *
+ * map.c : Functions to read and decode DRAM address maps for Hygon
+ *
+ * Author: AichunShi <shiaichun@open-hieco.net>
+ */
+
+#include "../internal.h"
+
+static int hygon_df_get_intlv_mode(struct addr_ctx *ctx)
+{
+ ctx->map.intlv_mode = FIELD_GET(HYGON_DF1_INTLV_NUM_CHAN, ctx->map.base);
+
+ if (df_cfg.rev == HYGON_DF1 && ctx->map.intlv_mode == 2)
+ ctx->map.intlv_mode = HYGON_DF1_3CHAN;
+
+ if (ctx->map.intlv_mode == 8)
+ ctx->map.intlv_mode = DF2_2CHAN_HASH;
+
+ if (ctx->map.intlv_mode != NONE &&
+ ctx->map.intlv_mode != NOHASH_2CHAN &&
+ ctx->map.intlv_mode != HYGON_DF1_3CHAN &&
+ ctx->map.intlv_mode != NOHASH_4CHAN &&
+ ctx->map.intlv_mode != NOHASH_8CHAN &&
+ ctx->map.intlv_mode != NOHASH_16CHAN &&
+ ctx->map.intlv_mode != DF2_2CHAN_HASH)
+ return -EINVAL;
+
+ return 0;
+}
+
+static u64 hygon_get_hi_addr_offset(u32 reg_dram_offset)
+{
+ u8 shift = DF_DRAM_BASE_LIMIT_LSB;
+ u64 hi_addr_offset;
+
+ switch (df_cfg.rev) {
+ case HYGON_DF1:
+ hi_addr_offset = FIELD_GET(HYGON_DF1_HI_ADDR_OFFSET, reg_dram_offset);
+ break;
+ default:
+ hi_addr_offset = 0;
+ atl_debug_on_bad_df_rev();
+ }
+
+ return hi_addr_offset << shift;
+}
+
+/*
+ * Returns: 0 if offset is disabled.
+ * 1 if offset is enabled.
+ * -EINVAL on error.
+ */
+static int hygon_get_dram_offset(struct addr_ctx *ctx, u64 *norm_offset)
+{
+ u32 reg_dram_offset;
+ u8 map_num;
+
+ /* Should not be called for map 0. */
+ if (!ctx->map.num) {
+ atl_debug(ctx, "Trying to find DRAM offset for map 0");
+ return -EINVAL;
+ }
+
+ /*
+ * DramOffset registers don't exist for map 0, so the base register
+ * actually refers to map 1.
+ * Adjust the map_num for the register offsets.
+ */
+ map_num = ctx->map.num - 1;
+
+ if (df_cfg.rev >= HYGON_DF1) {
+ /* Read D18F0x214 (DramOffset) */
+ if (df_indirect_read_instance(ctx->node_id, 0, 0x214 + (4 * map_num),
+ ctx->inst_id, ®_dram_offset))
+ return -EINVAL;
+ } else {
+ return -EINVAL;
+ }
+
+ if (!FIELD_GET(DF_HI_ADDR_OFFSET_EN, reg_dram_offset))
+ return 0;
+
+ *norm_offset = hygon_get_hi_addr_offset(reg_dram_offset);
+
+ return 1;
+}
+
+#define HYGON_DF1_CHAN_ADDR_SEL BIT(24)
+#define HYGON_DF1_CHAN_HASH_ENABLE BIT(23)
+#define HYGON_DF1_DDR5_ENABLE BIT(19)
+static int hygon_df1_get_dram_addr_map(struct addr_ctx *ctx)
+{
+ if (df2_get_dram_addr_map(ctx))
+ return -EINVAL;
+
+ /* Read D18F2x48 */
+ if (df_indirect_read_instance(ctx->node_id, 2, 0x48,
+ ctx->inst_id, &ctx->map.intlv))
+ return -EINVAL;
+
+ ctx->chan_intlv_hygon.chan_addr_sel =
+ FIELD_GET(HYGON_DF1_CHAN_ADDR_SEL, ctx->map.intlv);
+ ctx->chan_intlv_hygon.chan_hash_enable =
+ FIELD_GET(HYGON_DF1_CHAN_HASH_ENABLE, ctx->map.intlv);
+ ctx->chan_intlv_hygon.ddr5_enable =
+ FIELD_GET(HYGON_DF1_DDR5_ENABLE, ctx->map.intlv);
+
+ if (ctx->chan_intlv_hygon.ddr5_enable) {
+ u64 low_addr, high_addr;
+
+ ctx->chan_intlv_hygon.start_bit = ctx->chan_intlv_hygon.chan_addr_sel ? 8 : 7;
+ low_addr = ctx->ret_addr & GENMASK_ULL(ctx->chan_intlv_hygon.start_bit - 1, 0);
+ high_addr = (ctx->ret_addr & GENMASK_ULL(63, ctx->chan_intlv_hygon.start_bit)) << 1;
+ ctx->ret_addr = high_addr | low_addr;
+ }
+
+ return 0;
+}
+
+static int hygon_get_dram_addr_map(struct addr_ctx *ctx)
+{
+ switch (df_cfg.rev) {
+ case HYGON_DF1: return hygon_df1_get_dram_addr_map(ctx);
+ default:
+ atl_debug_on_bad_df_rev();
+ return -EINVAL;
+ }
+}
+
+static int hygon_get_coh_st_fabric_id(struct addr_ctx *ctx)
+{
+ u32 reg;
+
+ /* Read D18F0x50 (FabricBlockInstanceInformation3). */
+ if (df_indirect_read_instance(ctx->node_id, 0, 0x50, ctx->inst_id, ®))
+ return -EINVAL;
+
+ switch (df_cfg.rev) {
+ case HYGON_DF1:
+ ctx->coh_st_fabric_id = FIELD_GET(HYGON_DF1_COH_ST_FABRIC_ID, reg);
+ break;
+ default:
+ atl_debug_on_bad_df_rev();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hygon_find_normalized_offset(struct addr_ctx *ctx, u64 *norm_offset)
+{
+ u64 last_offset = 0;
+ int ret;
+
+ for (ctx->map.num = 1; ctx->map.num < df_cfg.num_coh_st_maps; ctx->map.num++) {
+ ret = hygon_get_dram_offset(ctx, norm_offset);
+ if (ret < 0)
+ return ret;
+
+ /* Continue search if this map's offset is not enabled. */
+ if (!ret)
+ continue;
+
+ /* Enabled offsets should never be 0. */
+ if (*norm_offset == 0) {
+ atl_debug(ctx, "Enabled map %u offset is 0", ctx->map.num);
+ return -EINVAL;
+ }
+
+ /* Offsets should always increase from one map to the next. */
+ if (*norm_offset <= last_offset) {
+ atl_debug(ctx, "Map %u offset (0x%016llx) <= previous (0x%016llx)",
+ ctx->map.num, *norm_offset, last_offset);
+ return -EINVAL;
+ }
+
+ /* Match if this map's offset is less than the current calculated address. */
+ if (ctx->ret_addr >= *norm_offset)
+ break;
+
+ last_offset = *norm_offset;
+ }
+
+ /*
+ * Finished search without finding a match.
+ * Reset to map 0 and no offset.
+ */
+ if (ctx->map.num >= df_cfg.num_coh_st_maps) {
+ ctx->map.num = 0;
+ *norm_offset = 0;
+ }
+
+ return 0;
+}
+
+static int hygon_get_address_map_common(struct addr_ctx *ctx)
+{
+ u64 norm_offset = 0;
+
+ if (hygon_get_coh_st_fabric_id(ctx))
+ return -EINVAL;
+
+ if (hygon_find_normalized_offset(ctx, &norm_offset))
+ return -EINVAL;
+
+ ctx->ret_addr -= norm_offset;
+
+ if (hygon_get_dram_addr_map(ctx))
+ return -EINVAL;
+
+ if (!valid_map(ctx))
+ return -EINVAL;
+
+ return 0;
+}
+
+static u8 hygon_get_num_intlv_chan(struct addr_ctx *ctx)
+{
+ switch (ctx->map.intlv_mode) {
+ case NONE:
+ return 1;
+ case NOHASH_2CHAN:
+ return 2;
+ case HYGON_DF1_3CHAN:
+ return 3;
+ case NOHASH_4CHAN:
+ return 4;
+ case NOHASH_8CHAN:
+ return 8;
+ case NOHASH_16CHAN:
+ return 16;
+ case NOHASH_32CHAN:
+ return 32;
+ default:
+ atl_debug_on_bad_intlv_mode(ctx);
+ return 0;
+ }
+}
+
+static void hygon_calculate_intlv_bits(struct addr_ctx *ctx)
+{
+ ctx->map.num_intlv_chan = hygon_get_num_intlv_chan(ctx);
+
+ ctx->map.total_intlv_chan = ctx->map.num_intlv_chan;
+ ctx->map.total_intlv_chan *= ctx->map.num_intlv_dies;
+ ctx->map.total_intlv_chan *= ctx->map.num_intlv_sockets;
+
+ /*
+ * Get the number of bits needed to cover this many channels.
+ * order_base_2() rounds up automatically.
+ */
+ ctx->map.total_intlv_bits = order_base_2(ctx->map.total_intlv_chan);
+}
+
+static u8 hygon_get_intlv_bit_pos(struct addr_ctx *ctx)
+{
+ u8 addr_sel = 0;
+
+ switch (df_cfg.rev) {
+ case HYGON_DF1:
+ addr_sel = FIELD_GET(HYGON_DF1_INTLV_ADDR_SEL, ctx->map.base);
+ break;
+ default:
+ atl_debug_on_bad_df_rev();
+ break;
+ }
+
+ /* Add '8' to get the 'interleave bit position'. */
+ return addr_sel + 8;
+}
+
+static u8 hygon_get_num_intlv_dies(struct addr_ctx *ctx)
+{
+ u8 dies = 0;
+
+ switch (df_cfg.rev) {
+ case HYGON_DF1:
+ dies = FIELD_GET(HYGON_DF1_INTLV_NUM_DIES, ctx->map.limit);
+ break;
+ default:
+ atl_debug_on_bad_df_rev();
+ break;
+ }
+
+ /* Register value is log2, e.g. 0 -> 1 die, 1 -> 2 dies, etc. */
+ return 1 << dies;
+}
+
+static u8 hygon_get_num_intlv_sockets(struct addr_ctx *ctx)
+{
+ u8 sockets = 0;
+
+ switch (df_cfg.rev) {
+ case HYGON_DF1:
+ sockets = FIELD_GET(HYGON_DF1_INTLV_NUM_SOCKETS, ctx->map.base);
+ break;
+ default:
+ atl_debug_on_bad_df_rev();
+ break;
+ }
+
+ /* Register value is log2, e.g. 0 -> 1 sockets, 1 -> 2 sockets, etc. */
+ return 1 << sockets;
+}
+
+static int hygon_get_global_map_data(struct addr_ctx *ctx)
+{
+ if (hygon_df_get_intlv_mode(ctx))
+ return -EINVAL;
+
+ ctx->map.intlv_bit_pos = hygon_get_intlv_bit_pos(ctx);
+ ctx->map.num_intlv_dies = hygon_get_num_intlv_dies(ctx);
+ ctx->map.num_intlv_sockets = hygon_get_num_intlv_sockets(ctx);
+ hygon_calculate_intlv_bits(ctx);
+
+ return 0;
+}
+
+int hygon_get_address_map(struct addr_ctx *ctx)
+{
+ int ret;
+
+ ret = hygon_get_address_map_common(ctx);
+ if (ret)
+ return ret;
+
+ ret = hygon_get_global_map_data(ctx);
+ if (ret)
+ return ret;
+
+ dump_address_map(&ctx->map);
+
+ return ret;
+}
diff --git a/drivers/ras/amd/atl/hygon/reg_fields.h b/drivers/ras/amd/atl/hygon/reg_fields.h
index 2c1193639c6c..44b24593175d 100644
--- a/drivers/ras/amd/atl/hygon/reg_fields.h
+++ b/drivers/ras/amd/atl/hygon/reg_fields.h
@@ -7,6 +7,84 @@
* Author: AichunShi <shiaichun@open-hieco.net>
*/
+/*
+ * Coherent Station Fabric ID
+ *
+ * Access type: Instance
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F0x50 [Fabric Block Instance Information 3]
+ * HYGON_DF1 BlockFabricId [17:8]
+ */
+#define HYGON_DF1_COH_ST_FABRIC_ID GENMASK(17, 8)
+
+/*
+ * Interleave Number of Sockets
+ *
+ * Access type: Instance
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F0x110 [DRAM Base Address]
+ * HYGON_DF1 IntLvNumSockets [3:2]
+ */
+#define HYGON_DF1_INTLV_NUM_SOCKETS GENMASK(3, 2)
+
+/*
+ * Interleave Number of Channels
+ *
+ * Access type: Instance
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F0x110 [DRAM Base Address]
+ * HYGON_DF1 IntLvNumChan [7:4]
+ */
+#define HYGON_DF1_INTLV_NUM_CHAN GENMASK(7, 4)
+
+/*
+ * Interleave Address Select
+ *
+ * Access type: Instance
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F0x110 [DRAM Base Address]
+ * HYGON_DF1 IntLvAddrSel [10:8]
+ */
+#define HYGON_DF1_INTLV_ADDR_SEL GENMASK(10, 8)
+
+/*
+ * Interleave Number of Dies
+ *
+ * Access type: Instance
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F0x114 [DRAM Limit Address]
+ * HYGON_DF1 IntLvNumDies [11:10]
+ */
+#define HYGON_DF1_INTLV_NUM_DIES GENMASK(11, 10)
+
+/*
+ * High Address Offset
+ *
+ * Access type: Instance
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F0x214 [DRAM Offset]
+ * HYGON_DF1 HiAddrOffset [31:19]
+ */
+#define HYGON_DF1_HI_ADDR_OFFSET GENMASK(31, 19)
+
/*
* Die ID Mask
*
diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
index 19651cf9e978..d149ec6e7da9 100644
--- a/drivers/ras/amd/atl/internal.h
+++ b/drivers/ras/amd/atl/internal.h
@@ -55,6 +55,7 @@ enum df_revisions {
enum intlv_modes {
NONE = 0x00,
NOHASH_2CHAN = 0x01,
+ HYGON_DF1_3CHAN = 0x02,
NOHASH_4CHAN = 0x03,
NOHASH_8CHAN = 0x05,
DF3_6CHAN = 0x06,
@@ -246,6 +247,14 @@ struct addr_ctx_inputs {
u8 coh_st_inst_id;
};
+struct hygon_chan_intlv {
+ u8 sub_channel;
+ u8 chan_addr_sel;
+ u8 chan_hash_enable;
+ u8 ddr5_enable;
+ u8 start_bit;
+};
+
struct addr_ctx {
u64 ret_addr;
@@ -266,6 +275,9 @@ struct addr_ctx {
* System-wide ID that includes 'node' bits.
*/
u16 coh_st_fabric_id;
+
+ /* Hygon Channel Interleave */
+ struct hygon_chan_intlv chan_intlv_hygon;
};
int df_indirect_read_instance(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo);
@@ -289,7 +301,12 @@ u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr);
int get_dram_hole_base(void);
void dump_df_cfg(void);
+int df2_get_dram_addr_map(struct addr_ctx *ctx);
+bool valid_map(struct addr_ctx *ctx);
+void dump_address_map(struct dram_addr_map *map);
+
int hygon_get_df_system_info(void);
+int hygon_get_address_map(struct addr_ctx *ctx);
/* GUIDs for PRM handlers */
extern const guid_t norm_to_sys_guid;
diff --git a/drivers/ras/amd/atl/map.c b/drivers/ras/amd/atl/map.c
index 24a05af747d5..acfd0a636160 100644
--- a/drivers/ras/amd/atl/map.c
+++ b/drivers/ras/amd/atl/map.c
@@ -209,7 +209,7 @@ static int df3_6ch_get_dram_addr_map(struct addr_ctx *ctx)
return 0;
}
-static int df2_get_dram_addr_map(struct addr_ctx *ctx)
+int df2_get_dram_addr_map(struct addr_ctx *ctx)
{
/* Read D18F0x110 (DramBaseAddress). */
if (df_indirect_read_instance(ctx->node_id, 0, 0x110 + (8 * ctx->map.num),
@@ -441,7 +441,7 @@ static int find_normalized_offset(struct addr_ctx *ctx, u64 *norm_offset)
return 0;
}
-static bool valid_map(struct addr_ctx *ctx)
+bool valid_map(struct addr_ctx *ctx)
{
if (df_cfg.rev >= DF4)
return FIELD_GET(DF_ADDR_RANGE_VAL, ctx->map.ctl);
@@ -735,7 +735,7 @@ static int validate_address_map(struct addr_ctx *ctx)
return -EINVAL;
}
-static void dump_address_map(struct dram_addr_map *map)
+void dump_address_map(struct dram_addr_map *map)
{
u8 i;
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 03/10] ras/amd/atl: Add Hygon DF1 normalized address denormalization helper
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 01/10] ras/amd/atl: Add Hygon DF1 Data Fabric system information helper Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 02/10] ras/amd/atl: Add Hygon DF1 DRAM address map decoding helper Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 04/10] ras/amd/atl: Add Hygon DF1 address dehash helper Aichun Shi
` (7 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Add Hygon DF1 normalized address denormalization function that Hygon
address translation pipeline depends on later.
Prepare hygon_denormalize_address() to be called by hygon/core.c later.
- Add hygon/denormalize.c for Hygon-specific implementation similar to
denormalize.c for AMD.
- Add hygon_denormalize_address() to handle non-hashed and hashed
interleave modes, including the Hygon DF1 three-channel interleave mode.
- Use make_space_for_coh_st_id_at_intlv_bit() and
insert_coh_st_id_at_intlv_bit() shared from denormalize.c and exported
in internal.h to avoid duplicate code.
- Build hygon/denormalize.c from the Makefile as an amd_atl object.
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/ras/amd/atl/Makefile | 1 +
drivers/ras/amd/atl/denormalize.c | 4 +-
drivers/ras/amd/atl/hygon/denormalize.c | 176 ++++++++++++++++++++++++
drivers/ras/amd/atl/hygon/reg_fields.h | 13 ++
drivers/ras/amd/atl/internal.h | 4 +
5 files changed, 196 insertions(+), 2 deletions(-)
create mode 100644 drivers/ras/amd/atl/hygon/denormalize.c
diff --git a/drivers/ras/amd/atl/Makefile b/drivers/ras/amd/atl/Makefile
index 105c11015f4c..c4e01f3ecc7b 100644
--- a/drivers/ras/amd/atl/Makefile
+++ b/drivers/ras/amd/atl/Makefile
@@ -15,6 +15,7 @@ amd_atl-y += map.o
amd_atl-y += system.o
amd_atl-y += umc.o
+amd_atl-y += hygon/denormalize.o
amd_atl-y += hygon/map.o
amd_atl-y += hygon/system.o
diff --git a/drivers/ras/amd/atl/denormalize.c b/drivers/ras/amd/atl/denormalize.c
index d45ce753e636..cf58cfa7ab3a 100644
--- a/drivers/ras/amd/atl/denormalize.c
+++ b/drivers/ras/amd/atl/denormalize.c
@@ -41,7 +41,7 @@ static u16 get_dst_fabric_id(struct addr_ctx *ctx)
* expanded address bits: [20+n : n+p][n+p-1 : p][p-1 : 0]
* [23 : 11][10 : 8][7 : 0]
*/
-static u64 make_space_for_coh_st_id_at_intlv_bit(struct addr_ctx *ctx)
+u64 make_space_for_coh_st_id_at_intlv_bit(struct addr_ctx *ctx)
{
return expand_bits(ctx->map.intlv_bit_pos,
ctx->map.total_intlv_bits,
@@ -320,7 +320,7 @@ static u16 calculate_coh_st_id(struct addr_ctx *ctx)
}
}
-static u64 insert_coh_st_id_at_intlv_bit(struct addr_ctx *ctx, u64 denorm_addr, u16 coh_st_id)
+u64 insert_coh_st_id_at_intlv_bit(struct addr_ctx *ctx, u64 denorm_addr, u16 coh_st_id)
{
return denorm_addr | (coh_st_id << ctx->map.intlv_bit_pos);
}
diff --git a/drivers/ras/amd/atl/hygon/denormalize.c b/drivers/ras/amd/atl/hygon/denormalize.c
new file mode 100644
index 000000000000..d189799bd183
--- /dev/null
+++ b/drivers/ras/amd/atl/hygon/denormalize.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Address Translation Library
+ *
+ * denormalize.c : Functions to account for interleaving bits for Hygon
+ *
+ * Author: AichunShi <shiaichun@open-hieco.net>
+ */
+
+#include "../internal.h"
+
+static u16 hygon_get_dst_fabric_id(struct addr_ctx *ctx)
+{
+ switch (df_cfg.rev) {
+ case HYGON_DF1:
+ return FIELD_GET(HYGON_DF1_DST_FABRIC_ID, ctx->map.limit);
+ default:
+ atl_debug_on_bad_df_rev();
+ return 0;
+ }
+}
+
+static u64 hygon_make_space_for_coh_st_id_df1_3chan(struct addr_ctx *ctx)
+{
+ return ctx->ret_addr & GENMASK_ULL(ctx->map.intlv_bit_pos - 1, 0);
+}
+
+static u64 hygon_make_space_for_coh_st_id(struct addr_ctx *ctx)
+{
+ switch (ctx->map.intlv_mode) {
+ case NOHASH_2CHAN:
+ case NOHASH_4CHAN:
+ case NOHASH_8CHAN:
+ case NOHASH_16CHAN:
+ case NOHASH_32CHAN:
+ case DF2_2CHAN_HASH:
+ return make_space_for_coh_st_id_at_intlv_bit(ctx);
+
+ case HYGON_DF1_3CHAN:
+ return hygon_make_space_for_coh_st_id_df1_3chan(ctx);
+
+ default:
+ atl_debug_on_bad_intlv_mode(ctx);
+ return ~0ULL;
+ }
+}
+
+static u16 hygon_get_coh_st_id_df(struct addr_ctx *ctx)
+{
+ u8 num_socket_intlv_bits = ilog2(ctx->map.num_intlv_sockets);
+ u8 num_die_intlv_bits = ilog2(ctx->map.num_intlv_dies);
+ u16 coh_st_id = ctx->coh_st_fabric_id;
+ u16 mask;
+ u8 intlv_bit_pos = ctx->map.intlv_bit_pos;
+ u8 num_intlv_bits = order_base_2(ctx->map.num_intlv_chan);
+
+ if (ctx->map.intlv_mode == HYGON_DF1_3CHAN) {
+ u8 cs_offset;
+ u16 dst_fabric_id = hygon_get_dst_fabric_id(ctx);
+ u64 temp_addr_x;
+
+ cs_offset = (ctx->coh_st_fabric_id & 0x3) - (dst_fabric_id & 0x3);
+ if (cs_offset > 3) {
+ atl_debug(ctx,
+ "Invalid cs_offset: 0x%x coh_st_fabric_id: 0x%x dst_fabric_id: 0x%x.\n",
+ cs_offset, ctx->coh_st_fabric_id, dst_fabric_id);
+ return ~0;
+ }
+
+ temp_addr_x = (ctx->ret_addr & GENMASK_ULL(63, intlv_bit_pos)) >>
+ intlv_bit_pos;
+ temp_addr_x = temp_addr_x * 3 + cs_offset;
+ ctx->ret_addr = temp_addr_x;
+
+ coh_st_id = temp_addr_x & GENMASK_ULL(num_intlv_bits - 1, 0);
+ } else {
+ mask = GENMASK(num_intlv_bits - 1, 0);
+ coh_st_id &= mask;
+ }
+
+ /* Die interleave bits */
+ if (num_die_intlv_bits) {
+ u16 die_bits;
+
+ mask = GENMASK(num_die_intlv_bits - 1, 0);
+ die_bits = ctx->coh_st_fabric_id & df_cfg.die_id_mask;
+ die_bits >>= df_cfg.die_id_shift;
+ die_bits -= 4;
+
+ coh_st_id |= (die_bits & mask) << num_intlv_bits;
+ num_intlv_bits += num_die_intlv_bits;
+ }
+
+ /* Socket interleave bits */
+ if (num_socket_intlv_bits) {
+ u16 socket_bits;
+
+ mask = GENMASK(num_socket_intlv_bits - 1, 0);
+ socket_bits = ctx->coh_st_fabric_id & df_cfg.socket_id_mask;
+ socket_bits >>= df_cfg.socket_id_shift;
+
+ coh_st_id |= (socket_bits & mask) << num_intlv_bits;
+ }
+
+ ctx->coh_st_fabric_id = coh_st_id;
+
+ return coh_st_id;
+}
+
+static u16 hygon_calculate_coh_st_id(struct addr_ctx *ctx)
+{
+ switch (ctx->map.intlv_mode) {
+ case NOHASH_2CHAN:
+ case HYGON_DF1_3CHAN:
+ case NOHASH_4CHAN:
+ case NOHASH_8CHAN:
+ case NOHASH_16CHAN:
+ case NOHASH_32CHAN:
+ case DF2_2CHAN_HASH:
+ return hygon_get_coh_st_id_df(ctx);
+
+ default:
+ atl_debug_on_bad_intlv_mode(ctx);
+ return ~0;
+ }
+}
+
+static u64 hygon_insert_coh_st_id_df1_3chan(struct addr_ctx *ctx, u64 denorm_addr, u16 coh_st_id)
+{
+ u8 num_intlv_bits_chan = order_base_2(ctx->map.num_intlv_chan);
+ u64 temp_addr = ((ctx->ret_addr >> num_intlv_bits_chan) << num_intlv_bits_chan) | coh_st_id;
+
+ return denorm_addr | (temp_addr << ctx->map.intlv_bit_pos);
+}
+
+static u64 hygon_insert_coh_st_id(struct addr_ctx *ctx, u64 denorm_addr, u16 coh_st_id)
+{
+ switch (ctx->map.intlv_mode) {
+ case NOHASH_2CHAN:
+ case NOHASH_4CHAN:
+ case NOHASH_8CHAN:
+ case NOHASH_16CHAN:
+ case NOHASH_32CHAN:
+ case DF2_2CHAN_HASH:
+ return insert_coh_st_id_at_intlv_bit(ctx, denorm_addr, coh_st_id);
+
+ case HYGON_DF1_3CHAN:
+ return hygon_insert_coh_st_id_df1_3chan(ctx, denorm_addr, coh_st_id);
+
+ default:
+ atl_debug_on_bad_intlv_mode(ctx);
+ return ~0ULL;
+ }
+}
+
+static int hygon_denorm_addr_common(struct addr_ctx *ctx)
+{
+ u64 denorm_addr;
+ u16 coh_st_id;
+
+ denorm_addr = hygon_make_space_for_coh_st_id(ctx);
+ coh_st_id = hygon_calculate_coh_st_id(ctx);
+ ctx->ret_addr = hygon_insert_coh_st_id(ctx, denorm_addr, coh_st_id);
+ return 0;
+}
+
+int hygon_denormalize_address(struct addr_ctx *ctx)
+{
+ switch (ctx->map.intlv_mode) {
+ case NONE:
+ return 0;
+
+ default:
+ return hygon_denorm_addr_common(ctx);
+ }
+}
diff --git a/drivers/ras/amd/atl/hygon/reg_fields.h b/drivers/ras/amd/atl/hygon/reg_fields.h
index 44b24593175d..69be2d5d2240 100644
--- a/drivers/ras/amd/atl/hygon/reg_fields.h
+++ b/drivers/ras/amd/atl/hygon/reg_fields.h
@@ -59,6 +59,19 @@
*/
#define HYGON_DF1_INTLV_ADDR_SEL GENMASK(10, 8)
+/*
+ * Destination Fabric ID
+ *
+ * Access type: Instance
+ *
+ * Register
+ * Rev Fieldname Bits
+ *
+ * D18F0x114 [DRAM Limit Address]
+ * HYGON_DF1 DstFabricID [9:0]
+ */
+#define HYGON_DF1_DST_FABRIC_ID GENMASK(9, 0)
+
/*
* Interleave Number of Dies
*
diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
index d149ec6e7da9..8f9fe6710c1e 100644
--- a/drivers/ras/amd/atl/internal.h
+++ b/drivers/ras/amd/atl/internal.h
@@ -305,8 +305,12 @@ int df2_get_dram_addr_map(struct addr_ctx *ctx);
bool valid_map(struct addr_ctx *ctx);
void dump_address_map(struct dram_addr_map *map);
+u64 make_space_for_coh_st_id_at_intlv_bit(struct addr_ctx *ctx);
+u64 insert_coh_st_id_at_intlv_bit(struct addr_ctx *ctx, u64 denorm_addr, u16 coh_st_id);
+
int hygon_get_df_system_info(void);
int hygon_get_address_map(struct addr_ctx *ctx);
+int hygon_denormalize_address(struct addr_ctx *ctx);
/* GUIDs for PRM handlers */
extern const guid_t norm_to_sys_guid;
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 04/10] ras/amd/atl: Add Hygon DF1 address dehash helper
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
` (2 preceding siblings ...)
2026-04-03 10:57 ` [RFC PATCH 03/10] ras/amd/atl: Add Hygon DF1 normalized address denormalization helper Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 05/10] ras/amd/atl: Add Hygon DF1 normalized-to-system address translation Aichun Shi
` (6 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Add Hygon DF1 address dehash function that Hygon address translation
pipeline depends on later.
Prepare hygon_dehash_address() to be called by hygon/core.c later.
- Add hygon/dehash.c for Hygon-specific implementation similar to dehash.c
for AMD.
- Add hygon_dehash_address() to do address dehashing where the DRAM map
uses hashed interleave modes for Hygon DF1 including two-channel hash
interleave mode and DDR5 case.
- Build hygon/dehash.c from the Makefile as an amd_atl object.
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/ras/amd/atl/Makefile | 1 +
drivers/ras/amd/atl/hygon/dehash.c | 77 ++++++++++++++++++++++++++++++
drivers/ras/amd/atl/internal.h | 1 +
3 files changed, 79 insertions(+)
create mode 100644 drivers/ras/amd/atl/hygon/dehash.c
diff --git a/drivers/ras/amd/atl/Makefile b/drivers/ras/amd/atl/Makefile
index c4e01f3ecc7b..fc12013de21f 100644
--- a/drivers/ras/amd/atl/Makefile
+++ b/drivers/ras/amd/atl/Makefile
@@ -15,6 +15,7 @@ amd_atl-y += map.o
amd_atl-y += system.o
amd_atl-y += umc.o
+amd_atl-y += hygon/dehash.o
amd_atl-y += hygon/denormalize.o
amd_atl-y += hygon/map.o
amd_atl-y += hygon/system.o
diff --git a/drivers/ras/amd/atl/hygon/dehash.c b/drivers/ras/amd/atl/hygon/dehash.c
new file mode 100644
index 000000000000..43f1bdea1eeb
--- /dev/null
+++ b/drivers/ras/amd/atl/hygon/dehash.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Address Translation Library
+ *
+ * dehash.c : Functions to account for hashing bits for Hygon
+ *
+ * Author: AichunShi <shiaichun@open-hieco.net>
+ */
+
+#include "../internal.h"
+
+static int hygon_df1_ddr5_dehash_addr(struct addr_ctx *ctx)
+{
+ u8 chan_hash_enable = ctx->chan_intlv_hygon.chan_hash_enable;
+ u8 sub_channel = ctx->chan_intlv_hygon.sub_channel;
+ u8 start_bit = ctx->chan_intlv_hygon.start_bit;
+ u8 hashed_bit;
+
+ if (chan_hash_enable) {
+ hashed_bit = (ctx->ret_addr >> 12) ^
+ (ctx->ret_addr >> 21) ^
+ (ctx->ret_addr >> 30) ^
+ sub_channel;
+ hashed_bit &= BIT(0);
+ ctx->ret_addr |= hashed_bit << start_bit;
+ } else {
+ ctx->ret_addr |= sub_channel << start_bit;
+ }
+
+ return 0;
+}
+
+static int hygon_df_2chan_dehash_addr(struct addr_ctx *ctx)
+{
+ u8 hashed_bit;
+ u8 intlv_bit_pos = ctx->map.intlv_bit_pos;
+
+ hashed_bit = (ctx->ret_addr >> 12) ^
+ (ctx->ret_addr >> 18) ^
+ (ctx->ret_addr >> 21) ^
+ (ctx->ret_addr >> 30) ^
+ ctx->coh_st_fabric_id;
+
+ hashed_bit &= BIT(0);
+ if (hashed_bit != ((ctx->ret_addr >> intlv_bit_pos) & BIT(0)))
+ ctx->ret_addr ^= BIT(intlv_bit_pos);
+
+ return 0;
+}
+
+int hygon_dehash_address(struct addr_ctx *ctx)
+{
+ switch (ctx->map.intlv_mode) {
+ /* No hashing cases. */
+ case NONE:
+ case NOHASH_2CHAN:
+ case HYGON_DF1_3CHAN:
+ case NOHASH_4CHAN:
+ case NOHASH_8CHAN:
+ case NOHASH_16CHAN:
+ case NOHASH_32CHAN:
+ break;
+
+ case DF2_2CHAN_HASH:
+ hygon_df_2chan_dehash_addr(ctx);
+ break;
+
+ default:
+ atl_debug_on_bad_intlv_mode(ctx);
+ return -EINVAL;
+ }
+
+ if (df_cfg.rev == HYGON_DF1 && ctx->chan_intlv_hygon.ddr5_enable)
+ hygon_df1_ddr5_dehash_addr(ctx);
+
+ return 0;
+}
diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
index 8f9fe6710c1e..a5624599c91e 100644
--- a/drivers/ras/amd/atl/internal.h
+++ b/drivers/ras/amd/atl/internal.h
@@ -311,6 +311,7 @@ u64 insert_coh_st_id_at_intlv_bit(struct addr_ctx *ctx, u64 denorm_addr, u16 coh
int hygon_get_df_system_info(void);
int hygon_get_address_map(struct addr_ctx *ctx);
int hygon_denormalize_address(struct addr_ctx *ctx);
+int hygon_dehash_address(struct addr_ctx *ctx);
/* GUIDs for PRM handlers */
extern const guid_t norm_to_sys_guid;
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 05/10] ras/amd/atl: Add Hygon DF1 normalized-to-system address translation
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
` (3 preceding siblings ...)
2026-04-03 10:57 ` [RFC PATCH 04/10] ras/amd/atl: Add Hygon DF1 address dehash helper Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 06/10] ras/amd/atl: Add Hygon UMC MCA to system address conversion support Aichun Shi
` (5 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Translate a normalized UMC MCA address into a system physical address for
Hygon DF1 with the following pipeline.
Pipeline:
- Initialize address context with socket, die, coherent-station instance,
sub-channel, and normalized address.
- Enforce legacy hole requirements when the map enables the hole without
a configured hole base.
- Run Hygon DRAM map decoding, denormalization, DRAM base plus
legacy hole, dehash, and DRAM limit checks.
Prepare hygon_norm_to_sys_addr() to be called by hygon/umc.c later.
- Add hygon/core.c for Hygon-specific implementation similar to core.c for
AMD.
- Call hygon_get_address_map(), hygon_denormalize_address(),
hygon_dehash_address() prepared previously in hygon/map.c,
hygon/denormalize.c and hygon/dehash.c.
- Use legacy hole_en() and addr_over_limit() shared from core.c and
exported in internal.h to avoid duplicate code.
- Build hygon/core.c from the Makefile as an amd_atl object.
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/ras/amd/atl/Makefile | 1 +
drivers/ras/amd/atl/core.c | 4 +--
drivers/ras/amd/atl/hygon/core.c | 52 ++++++++++++++++++++++++++++++++
drivers/ras/amd/atl/internal.h | 6 ++++
4 files changed, 61 insertions(+), 2 deletions(-)
create mode 100644 drivers/ras/amd/atl/hygon/core.c
diff --git a/drivers/ras/amd/atl/Makefile b/drivers/ras/amd/atl/Makefile
index fc12013de21f..0a42f2cbabc4 100644
--- a/drivers/ras/amd/atl/Makefile
+++ b/drivers/ras/amd/atl/Makefile
@@ -15,6 +15,7 @@ amd_atl-y += map.o
amd_atl-y += system.o
amd_atl-y += umc.o
+amd_atl-y += hygon/core.o
amd_atl-y += hygon/dehash.o
amd_atl-y += hygon/denormalize.o
amd_atl-y += hygon/map.o
diff --git a/drivers/ras/amd/atl/core.c b/drivers/ras/amd/atl/core.c
index 0f7cd6dab0b0..b7083588b46e 100644
--- a/drivers/ras/amd/atl/core.c
+++ b/drivers/ras/amd/atl/core.c
@@ -17,7 +17,7 @@
struct df_config df_cfg __read_mostly;
-static int addr_over_limit(struct addr_ctx *ctx)
+int addr_over_limit(struct addr_ctx *ctx)
{
u64 dram_limit_addr;
@@ -39,7 +39,7 @@ static int addr_over_limit(struct addr_ctx *ctx)
return 0;
}
-static bool legacy_hole_en(struct addr_ctx *ctx)
+bool legacy_hole_en(struct addr_ctx *ctx)
{
u32 reg = ctx->map.base;
diff --git a/drivers/ras/amd/atl/hygon/core.c b/drivers/ras/amd/atl/hygon/core.c
new file mode 100644
index 000000000000..ef064e61805b
--- /dev/null
+++ b/drivers/ras/amd/atl/hygon/core.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Address Translation Library
+ *
+ * core.c : Base translation functions for Hygon
+ *
+ * Author: AichunShi <shiaichun@open-hieco.net>
+ */
+
+#include "../internal.h"
+
+unsigned long hygon_norm_to_sys_addr(u8 socket_id, u8 die_id, u8 coh_st_inst_id,
+ u8 sub_channel, unsigned long addr)
+{
+ struct addr_ctx ctx;
+
+ if (df_cfg.rev == UNKNOWN)
+ return -EINVAL;
+
+ memset(&ctx, 0, sizeof(ctx));
+
+ /* Start from the normalized address */
+ ctx.ret_addr = addr;
+ ctx.node_id = die_id;
+ ctx.inst_id = coh_st_inst_id;
+
+ ctx.inputs.norm_addr = addr;
+ ctx.inputs.socket_id = socket_id;
+ ctx.inputs.die_id = die_id;
+ ctx.inputs.coh_st_inst_id = coh_st_inst_id;
+
+ ctx.chan_intlv_hygon.sub_channel = sub_channel;
+
+ if (legacy_hole_en(&ctx) && !df_cfg.dram_hole_base)
+ return -EINVAL;
+
+ if (hygon_get_address_map(&ctx))
+ return -EINVAL;
+
+ if (hygon_denormalize_address(&ctx))
+ return -EINVAL;
+
+ ctx.ret_addr = add_base_and_hole(&ctx, ctx.ret_addr);
+
+ if (hygon_dehash_address(&ctx))
+ return -EINVAL;
+
+ if (addr_over_limit(&ctx))
+ return -EINVAL;
+
+ return ctx.ret_addr;
+}
diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
index a5624599c91e..f943cc33f82e 100644
--- a/drivers/ras/amd/atl/internal.h
+++ b/drivers/ras/amd/atl/internal.h
@@ -297,6 +297,8 @@ unsigned long convert_umc_mca_addr_to_sys_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);
+bool legacy_hole_en(struct addr_ctx *ctx);
+int addr_over_limit(struct addr_ctx *ctx);
int get_dram_hole_base(void);
void dump_df_cfg(void);
@@ -313,6 +315,10 @@ int hygon_get_address_map(struct addr_ctx *ctx);
int hygon_denormalize_address(struct addr_ctx *ctx);
int hygon_dehash_address(struct addr_ctx *ctx);
+unsigned long hygon_norm_to_sys_addr(u8 socket_id, u8 die_id, u8 coh_st_inst_id,
+ u8 sub_channel, unsigned long addr);
+unsigned long hygon_convert_umc_mca_addr_to_sys_addr(struct atl_err *err);
+
/* GUIDs for PRM handlers */
extern const guid_t norm_to_sys_guid;
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 06/10] ras/amd/atl: Add Hygon UMC MCA to system address conversion support
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
` (4 preceding siblings ...)
2026-04-03 10:57 ` [RFC PATCH 05/10] ras/amd/atl: Add Hygon DF1 normalized-to-system address translation Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 07/10] ras/amd/atl: Add Hygon DF discovery and MCA decode at initialization Aichun Shi
` (4 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Prepare hygon_convert_umc_mca_addr_to_sys_addr() to be registered by
amd_atl_register_decoder() in amd_atl_init() later.
- Add hygon/umc.c for Hygon-specific implementation similar to umc.c for
AMD.
- Add hygon_convert_umc_mca_addr_to_sys_addr() to call
hygon_norm_to_sys_addr() to do the address translation process.
- Build hygon/umc.c from the Makefile as an amd_atl object.
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/ras/amd/atl/Makefile | 1 +
drivers/ras/amd/atl/hygon/umc.c | 46 +++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+)
create mode 100644 drivers/ras/amd/atl/hygon/umc.c
diff --git a/drivers/ras/amd/atl/Makefile b/drivers/ras/amd/atl/Makefile
index 0a42f2cbabc4..9d65e215b87a 100644
--- a/drivers/ras/amd/atl/Makefile
+++ b/drivers/ras/amd/atl/Makefile
@@ -20,6 +20,7 @@ amd_atl-y += hygon/dehash.o
amd_atl-y += hygon/denormalize.o
amd_atl-y += hygon/map.o
amd_atl-y += hygon/system.o
+amd_atl-y += hygon/umc.o
amd_atl-$(CONFIG_AMD_ATL_PRM) += prm.o
diff --git a/drivers/ras/amd/atl/hygon/umc.c b/drivers/ras/amd/atl/hygon/umc.c
new file mode 100644
index 000000000000..e2339db3f575
--- /dev/null
+++ b/drivers/ras/amd/atl/hygon/umc.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Address Translation Library
+ *
+ * umc.c : Unified Memory Controller (UMC) topology helpers for Hygon
+ *
+ * Author: AichunShi <shiaichun@open-hieco.net>
+ */
+
+#include "../internal.h"
+
+static u8 hygon_get_die_id(struct atl_err *err)
+{
+ /*
+ * For Hygon CPUs, amd_node_id can be non-contiguous, so get the Die
+ * ID from the logical Die ID.
+ */
+ return topology_logical_die_id(err->cpu);
+}
+
+#define HYGON_IPID_SUB_CHANNEL_MASK BIT(13)
+static u8 hygon_get_ipid_sub_channel(struct atl_err *err)
+{
+ return FIELD_GET(HYGON_IPID_SUB_CHANNEL_MASK, err->ipid);
+}
+
+#define HYGON_UMC_CHANNEL_NUM GENMASK(23, 20)
+static u8 hygon_get_coh_st_inst_id(struct atl_err *err)
+{
+ return FIELD_GET(HYGON_UMC_CHANNEL_NUM, err->ipid);
+}
+
+unsigned long hygon_convert_umc_mca_addr_to_sys_addr(struct atl_err *err)
+{
+ u8 socket_id = topology_physical_package_id(err->cpu);
+ u8 coh_st_inst_id = hygon_get_coh_st_inst_id(err);
+ u8 die_id = hygon_get_die_id(err);
+
+ pr_debug("socket_id=0x%x die_id=0x%x coh_st_inst_id=0x%x addr=0x%016llx",
+ socket_id, die_id, coh_st_inst_id, err->addr);
+
+ u8 sub_channel = hygon_get_ipid_sub_channel(err);
+
+ return hygon_norm_to_sys_addr(socket_id, die_id, coh_st_inst_id,
+ sub_channel, err->addr);
+}
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 07/10] ras/amd/atl: Add Hygon DF discovery and MCA decode at initialization
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
` (5 preceding siblings ...)
2026-04-03 10:57 ` [RFC PATCH 06/10] ras/amd/atl: Add Hygon UMC MCA to system address conversion support Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 08/10] ras/amd/atl: Add Hygon DF2 address translation support Aichun Shi
` (3 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Add node number bounds, Data Fabric discovery and UMC MCA to system
address decoder for Hygon while keeping the AMD path unchanged.
Address translation function is exported by
amd_convert_umc_mca_addr_to_sys_addr() and used by users such as EDAC.
- Add Hygon node number support in amd_atl_init(),
amd_atl_exit() and __df_indirect_read().
- Call hygon_get_df_system_info() to get Hygon-specific Data Fabric
system information.
- Register hygon_convert_umc_mca_addr_to_sys_addr() as the address
translation decoder for Hygon.
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/ras/amd/atl/access.c | 9 ++++++++-
drivers/ras/amd/atl/core.c | 21 ++++++++++++++++++---
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/drivers/ras/amd/atl/access.c b/drivers/ras/amd/atl/access.c
index c2334f8f9add..6d75bcb930d4 100644
--- a/drivers/ras/amd/atl/access.c
+++ b/drivers/ras/amd/atl/access.c
@@ -68,9 +68,16 @@ static int __df_indirect_read(u16 node, u8 func, u16 reg, u8 instance_id, u32 *l
struct pci_dev *F4;
int err = -ENODEV;
u32 ficaa = 0;
+ u16 num_nodes;
+ bool is_hygon = boot_cpu_data.x86_vendor == X86_VENDOR_HYGON;
node = get_accessible_node(node);
- if (node >= amd_nb_num()) {
+ if (is_hygon)
+ num_nodes = amd_num_nodes();
+ else
+ num_nodes = amd_nb_num();
+
+ if (node >= num_nodes) {
pr_debug("Node %u is out of bounds\n", node);
goto out;
}
diff --git a/drivers/ras/amd/atl/core.c b/drivers/ras/amd/atl/core.c
index b7083588b46e..bbbbff4d43a3 100644
--- a/drivers/ras/amd/atl/core.c
+++ b/drivers/ras/amd/atl/core.c
@@ -195,22 +195,37 @@ MODULE_DEVICE_TABLE(x86cpu, amd_atl_cpuids);
static int __init amd_atl_init(void)
{
int ret;
+ u16 num_nodes;
+ bool is_hygon = boot_cpu_data.x86_vendor == X86_VENDOR_HYGON;
if (!x86_match_cpu(amd_atl_cpuids))
return -ENODEV;
- if (!amd_nb_num())
+ if (is_hygon)
+ num_nodes = amd_num_nodes();
+ else
+ num_nodes = amd_nb_num();
+
+ if (!num_nodes)
return -ENODEV;
check_for_legacy_df_access();
- ret = get_df_system_info();
+ if (is_hygon)
+ ret = hygon_get_df_system_info();
+ else
+ ret = get_df_system_info();
+
if (ret)
return ret;
/* 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);
+
+ if (is_hygon)
+ amd_atl_register_decoder(hygon_convert_umc_mca_addr_to_sys_addr);
+ else
+ amd_atl_register_decoder(convert_umc_mca_addr_to_sys_addr);
pr_info("AMD Address Translation Library initialized\n");
return 0;
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 08/10] ras/amd/atl: Add Hygon DF2 address translation support
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
` (6 preceding siblings ...)
2026-04-03 10:57 ` [RFC PATCH 07/10] ras/amd/atl: Add Hygon DF discovery and MCA decode at initialization Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 09/10] ras/amd/atl: Add Hygon DF3 " Aichun Shi
` (2 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Extend address translation support for Hygon Family 0x18 model 0x6/0x8
(Hygon DF2).
System detection:
- Add register field definitions for Hygon DF2 in hygon/reg_fields.h.
- Add HYGON_DF2 to enum df_revisions and interleave mode
HYGON_DF2_4CHAN_HASH for four-channel hash for Hygon DF2 in internal.h.
- Discover Hygon DF2 socket/die mask widths and shifts; detect model
0x6/0x8 as Hygon DF2.
DRAM map:
- Add hygon_df2_get_dram_addr_map(): use shared AMD DF2 DRAM base/limit
via df2_get_dram_addr_map(), and read Hygon-specific interleave fields.
- Map Hygon-specific HYGON_DF2_4CHAN_HASH interleave mode instead of
the AMD DF2 two-channel hash mode.
- Get die/socket interleave count for Hygon DF2.
Denormalize/dehash:
- Get coherent-station fabric ID for Hygon DF2 including
HYGON_DF2_4CHAN_HASH case.
- Add hygon_df2_4chan_dehash_addr() to support dehash function for
HYGON_DF2_4CHAN_HASH interleave mode.
UMC:
- Form the coherent-station instance ID from the channel field and
sub-channel bit from MCA IPID to match Hygon DF2 UMC addressing.
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/ras/amd/atl/hygon/dehash.c | 23 +++++++++++++++++
drivers/ras/amd/atl/hygon/denormalize.c | 3 +++
drivers/ras/amd/atl/hygon/map.c | 33 +++++++++++++++++++++++--
drivers/ras/amd/atl/hygon/reg_fields.h | 18 ++++++++++++++
drivers/ras/amd/atl/hygon/system.c | 9 +++++++
drivers/ras/amd/atl/hygon/umc.c | 8 +++++-
drivers/ras/amd/atl/internal.h | 2 ++
7 files changed, 93 insertions(+), 3 deletions(-)
diff --git a/drivers/ras/amd/atl/hygon/dehash.c b/drivers/ras/amd/atl/hygon/dehash.c
index 43f1bdea1eeb..e0546ea70900 100644
--- a/drivers/ras/amd/atl/hygon/dehash.c
+++ b/drivers/ras/amd/atl/hygon/dehash.c
@@ -48,6 +48,25 @@ static int hygon_df_2chan_dehash_addr(struct addr_ctx *ctx)
return 0;
}
+static int hygon_df2_4chan_dehash_addr(struct addr_ctx *ctx)
+{
+ u8 hashed_bit;
+ u8 intlv_bit_pos = ctx->map.intlv_bit_pos;
+
+ hashed_bit = (ctx->ret_addr >> 12) ^
+ (ctx->ret_addr >> 18) ^
+ (ctx->ret_addr >> 21) ^
+ (ctx->ret_addr >> 30) ^
+ ctx->coh_st_fabric_id;
+
+ hashed_bit &= 0x3;
+ if (hashed_bit != ((ctx->ret_addr >> intlv_bit_pos) & 0x3))
+ ctx->ret_addr = (ctx->ret_addr & ~((u64)3 << intlv_bit_pos)) |
+ (hashed_bit << intlv_bit_pos);
+
+ return 0;
+}
+
int hygon_dehash_address(struct addr_ctx *ctx)
{
switch (ctx->map.intlv_mode) {
@@ -65,6 +84,10 @@ int hygon_dehash_address(struct addr_ctx *ctx)
hygon_df_2chan_dehash_addr(ctx);
break;
+ case HYGON_DF2_4CHAN_HASH:
+ hygon_df2_4chan_dehash_addr(ctx);
+ break;
+
default:
atl_debug_on_bad_intlv_mode(ctx);
return -EINVAL;
diff --git a/drivers/ras/amd/atl/hygon/denormalize.c b/drivers/ras/amd/atl/hygon/denormalize.c
index d189799bd183..82c5982893ba 100644
--- a/drivers/ras/amd/atl/hygon/denormalize.c
+++ b/drivers/ras/amd/atl/hygon/denormalize.c
@@ -14,6 +14,8 @@ static u16 hygon_get_dst_fabric_id(struct addr_ctx *ctx)
switch (df_cfg.rev) {
case HYGON_DF1:
return FIELD_GET(HYGON_DF1_DST_FABRIC_ID, ctx->map.limit);
+ case HYGON_DF2:
+ return FIELD_GET(HYGON_DF2_DST_FABRIC_ID, ctx->map.limit);
default:
atl_debug_on_bad_df_rev();
return 0;
@@ -117,6 +119,7 @@ static u16 hygon_calculate_coh_st_id(struct addr_ctx *ctx)
case NOHASH_16CHAN:
case NOHASH_32CHAN:
case DF2_2CHAN_HASH:
+ case HYGON_DF2_4CHAN_HASH:
return hygon_get_coh_st_id_df(ctx);
default:
diff --git a/drivers/ras/amd/atl/hygon/map.c b/drivers/ras/amd/atl/hygon/map.c
index 2194c10ff587..b0c05532b744 100644
--- a/drivers/ras/amd/atl/hygon/map.c
+++ b/drivers/ras/amd/atl/hygon/map.c
@@ -16,8 +16,12 @@ static int hygon_df_get_intlv_mode(struct addr_ctx *ctx)
if (df_cfg.rev == HYGON_DF1 && ctx->map.intlv_mode == 2)
ctx->map.intlv_mode = HYGON_DF1_3CHAN;
- if (ctx->map.intlv_mode == 8)
- ctx->map.intlv_mode = DF2_2CHAN_HASH;
+ if (ctx->map.intlv_mode == 8) {
+ if (df_cfg.rev == HYGON_DF2)
+ ctx->map.intlv_mode = HYGON_DF2_4CHAN_HASH;
+ else
+ ctx->map.intlv_mode = DF2_2CHAN_HASH;
+ }
if (ctx->map.intlv_mode != NONE &&
ctx->map.intlv_mode != NOHASH_2CHAN &&
@@ -25,6 +29,7 @@ static int hygon_df_get_intlv_mode(struct addr_ctx *ctx)
ctx->map.intlv_mode != NOHASH_4CHAN &&
ctx->map.intlv_mode != NOHASH_8CHAN &&
ctx->map.intlv_mode != NOHASH_16CHAN &&
+ ctx->map.intlv_mode != HYGON_DF2_4CHAN_HASH &&
ctx->map.intlv_mode != DF2_2CHAN_HASH)
return -EINVAL;
@@ -38,6 +43,7 @@ static u64 hygon_get_hi_addr_offset(u32 reg_dram_offset)
switch (df_cfg.rev) {
case HYGON_DF1:
+ case HYGON_DF2:
hi_addr_offset = FIELD_GET(HYGON_DF1_HI_ADDR_OFFSET, reg_dram_offset);
break;
default:
@@ -120,10 +126,24 @@ static int hygon_df1_get_dram_addr_map(struct addr_ctx *ctx)
return 0;
}
+static int hygon_df2_get_dram_addr_map(struct addr_ctx *ctx)
+{
+ if (df2_get_dram_addr_map(ctx))
+ return -EINVAL;
+
+ /* Read D18F0x60 */
+ if (df_indirect_read_instance(ctx->node_id, 0, 0x60,
+ ctx->inst_id, &ctx->map.intlv))
+ return -EINVAL;
+
+ return 0;
+}
+
static int hygon_get_dram_addr_map(struct addr_ctx *ctx)
{
switch (df_cfg.rev) {
case HYGON_DF1: return hygon_df1_get_dram_addr_map(ctx);
+ case HYGON_DF2: return hygon_df2_get_dram_addr_map(ctx);
default:
atl_debug_on_bad_df_rev();
return -EINVAL;
@@ -142,6 +162,9 @@ static int hygon_get_coh_st_fabric_id(struct addr_ctx *ctx)
case HYGON_DF1:
ctx->coh_st_fabric_id = FIELD_GET(HYGON_DF1_COH_ST_FABRIC_ID, reg);
break;
+ case HYGON_DF2:
+ ctx->coh_st_fabric_id = FIELD_GET(HYGON_DF2_COH_ST_FABRIC_ID, reg);
+ break;
default:
atl_debug_on_bad_df_rev();
return -EINVAL;
@@ -226,6 +249,7 @@ static u8 hygon_get_num_intlv_chan(struct addr_ctx *ctx)
return 2;
case HYGON_DF1_3CHAN:
return 3;
+ case HYGON_DF2_4CHAN_HASH:
case NOHASH_4CHAN:
return 4;
case NOHASH_8CHAN:
@@ -261,6 +285,7 @@ static u8 hygon_get_intlv_bit_pos(struct addr_ctx *ctx)
switch (df_cfg.rev) {
case HYGON_DF1:
+ case HYGON_DF2:
addr_sel = FIELD_GET(HYGON_DF1_INTLV_ADDR_SEL, ctx->map.base);
break;
default:
@@ -280,6 +305,9 @@ static u8 hygon_get_num_intlv_dies(struct addr_ctx *ctx)
case HYGON_DF1:
dies = FIELD_GET(HYGON_DF1_INTLV_NUM_DIES, ctx->map.limit);
break;
+ case HYGON_DF2:
+ dies = FIELD_GET(HYGON_DF2_INTLV_NUM_DIES, ctx->map.intlv);
+ break;
default:
atl_debug_on_bad_df_rev();
break;
@@ -295,6 +323,7 @@ static u8 hygon_get_num_intlv_sockets(struct addr_ctx *ctx)
switch (df_cfg.rev) {
case HYGON_DF1:
+ case HYGON_DF2:
sockets = FIELD_GET(HYGON_DF1_INTLV_NUM_SOCKETS, ctx->map.base);
break;
default:
diff --git a/drivers/ras/amd/atl/hygon/reg_fields.h b/drivers/ras/amd/atl/hygon/reg_fields.h
index 69be2d5d2240..eaf1d628fd0e 100644
--- a/drivers/ras/amd/atl/hygon/reg_fields.h
+++ b/drivers/ras/amd/atl/hygon/reg_fields.h
@@ -17,8 +17,10 @@
*
* D18F0x50 [Fabric Block Instance Information 3]
* HYGON_DF1 BlockFabricId [17:8]
+ * HYGON_DF2 BlockFabricId [18:8]
*/
#define HYGON_DF1_COH_ST_FABRIC_ID GENMASK(17, 8)
+#define HYGON_DF2_COH_ST_FABRIC_ID GENMASK(18, 8)
/*
* Interleave Number of Sockets
@@ -30,6 +32,7 @@
*
* D18F0x110 [DRAM Base Address]
* HYGON_DF1 IntLvNumSockets [3:2]
+ * HYGON_DF2 IntLvNumSockets [3:2]
*/
#define HYGON_DF1_INTLV_NUM_SOCKETS GENMASK(3, 2)
@@ -43,6 +46,7 @@
*
* D18F0x110 [DRAM Base Address]
* HYGON_DF1 IntLvNumChan [7:4]
+ * HYGON_DF2 IntLvNumChan [7:4]
*/
#define HYGON_DF1_INTLV_NUM_CHAN GENMASK(7, 4)
@@ -56,6 +60,7 @@
*
* D18F0x110 [DRAM Base Address]
* HYGON_DF1 IntLvAddrSel [10:8]
+ * HYGON_DF2 IntLvAddrSel [10:8]
*/
#define HYGON_DF1_INTLV_ADDR_SEL GENMASK(10, 8)
@@ -69,8 +74,10 @@
*
* D18F0x114 [DRAM Limit Address]
* HYGON_DF1 DstFabricID [9:0]
+ * HYGON_DF2 DstFabricID [10:0]
*/
#define HYGON_DF1_DST_FABRIC_ID GENMASK(9, 0)
+#define HYGON_DF2_DST_FABRIC_ID GENMASK(10, 0)
/*
* Interleave Number of Dies
@@ -82,8 +89,12 @@
*
* D18F0x114 [DRAM Limit Address]
* HYGON_DF1 IntLvNumDies [11:10]
+ *
+ * D18F0x60
+ * HYGON_DF2 IntLvNumDies [1:0]
*/
#define HYGON_DF1_INTLV_NUM_DIES GENMASK(11, 10)
+#define HYGON_DF2_INTLV_NUM_DIES GENMASK(1, 0)
/*
* High Address Offset
@@ -95,6 +106,7 @@
*
* D18F0x214 [DRAM Offset]
* HYGON_DF1 HiAddrOffset [31:19]
+ * HYGON_DF2 HiAddrOffset [31:19]
*/
#define HYGON_DF1_HI_ADDR_OFFSET GENMASK(31, 19)
@@ -108,8 +120,10 @@
*
* D18F1x208 [System Fabric ID Mask]
* HYGON_DF1 DieIdMask [9:0]
+ * HYGON_DF2 DieIdMask [10:0]
*/
#define HYGON_DF1_DIE_ID_MASK GENMASK(9, 0)
+#define HYGON_DF2_DIE_ID_MASK GENMASK(10, 0)
/*
* Die ID Shift
@@ -121,6 +135,7 @@
*
* D18F1x208 [System Fabric ID Mask]
* HYGON_DF1 DieIdShift [15:12]
+ * HYGON_DF2 DieIdShift [15:12]
*/
#define HYGON_DF1_DIE_ID_SHIFT GENMASK(15, 12)
@@ -134,8 +149,10 @@
*
* D18F1x208 [System Fabric ID Mask]
* HYGON_DF1 SocketIdMask [25:16]
+ * HYGON_DF2 SocketIdMask [26:16]
*/
#define HYGON_DF1_SOCKET_ID_MASK GENMASK(25, 16)
+#define HYGON_DF2_SOCKET_ID_MASK GENMASK(26, 16)
/*
* Socket ID Shift
@@ -147,5 +164,6 @@
*
* D18F1x208 [System Fabric ID Mask]
* HYGON_DF1 SocketIdShift [31:28]
+ * HYGON_DF2 SocketIdShift [31:28]
*/
#define HYGON_DF1_SOCKET_ID_SHIFT GENMASK(31, 28)
diff --git a/drivers/ras/amd/atl/hygon/system.c b/drivers/ras/amd/atl/hygon/system.c
index 2a149cfc34c1..a06a39bc4527 100644
--- a/drivers/ras/amd/atl/hygon/system.c
+++ b/drivers/ras/amd/atl/hygon/system.c
@@ -18,6 +18,12 @@ static void hygon_df_get_masks_shifts(u32 mask0)
df_cfg.die_id_shift = FIELD_GET(HYGON_DF1_DIE_ID_SHIFT, mask0);
df_cfg.die_id_mask = FIELD_GET(HYGON_DF1_DIE_ID_MASK, mask0);
break;
+ case HYGON_DF2:
+ df_cfg.socket_id_shift = FIELD_GET(HYGON_DF1_SOCKET_ID_SHIFT, mask0);
+ df_cfg.socket_id_mask = FIELD_GET(HYGON_DF2_SOCKET_ID_MASK, mask0);
+ df_cfg.die_id_shift = FIELD_GET(HYGON_DF1_DIE_ID_SHIFT, mask0);
+ df_cfg.die_id_mask = FIELD_GET(HYGON_DF2_DIE_ID_MASK, mask0);
+ break;
default:
atl_debug_on_bad_df_rev();
return;
@@ -30,6 +36,8 @@ static int hygon_determine_df_rev(void)
if (boot_cpu_data.x86_model == 0x4 || boot_cpu_data.x86_model == 0x5)
df_cfg.rev = HYGON_DF1;
+ else if (boot_cpu_data.x86_model == 0x6 || boot_cpu_data.x86_model == 0x8)
+ df_cfg.rev = HYGON_DF2;
/* Read D18F1x208 (SystemFabricIdMask). */
if (df_indirect_read_broadcast(0, 1, 0x208, &fabric_id_mask0))
@@ -44,6 +52,7 @@ static void hygon_get_num_maps(void)
{
switch (df_cfg.rev) {
case HYGON_DF1:
+ case HYGON_DF2:
df_cfg.num_coh_st_maps = 2;
break;
default:
diff --git a/drivers/ras/amd/atl/hygon/umc.c b/drivers/ras/amd/atl/hygon/umc.c
index e2339db3f575..b912dbdebee4 100644
--- a/drivers/ras/amd/atl/hygon/umc.c
+++ b/drivers/ras/amd/atl/hygon/umc.c
@@ -27,7 +27,13 @@ static u8 hygon_get_ipid_sub_channel(struct atl_err *err)
#define HYGON_UMC_CHANNEL_NUM GENMASK(23, 20)
static u8 hygon_get_coh_st_inst_id(struct atl_err *err)
{
- return FIELD_GET(HYGON_UMC_CHANNEL_NUM, err->ipid);
+ u8 sub_channel = hygon_get_ipid_sub_channel(err);
+ u8 coh_st_inst_id = FIELD_GET(HYGON_UMC_CHANNEL_NUM, err->ipid);
+
+ if (df_cfg.rev == HYGON_DF2)
+ coh_st_inst_id = (coh_st_inst_id << 1) + sub_channel;
+
+ return coh_st_inst_id;
}
unsigned long hygon_convert_umc_mca_addr_to_sys_addr(struct atl_err *err)
diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
index f943cc33f82e..c368559ca2ae 100644
--- a/drivers/ras/amd/atl/internal.h
+++ b/drivers/ras/amd/atl/internal.h
@@ -44,6 +44,7 @@
enum df_revisions {
UNKNOWN,
HYGON_DF1,
+ HYGON_DF2,
DF2,
DF3,
DF3p5,
@@ -61,6 +62,7 @@ enum intlv_modes {
DF3_6CHAN = 0x06,
NOHASH_16CHAN = 0x07,
NOHASH_32CHAN = 0x08,
+ HYGON_DF2_4CHAN_HASH = 0x09,
DF3_COD4_2CHAN_HASH = 0x0C,
DF3_COD2_4CHAN_HASH = 0x0D,
DF3_COD1_8CHAN_HASH = 0x0E,
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 09/10] ras/amd/atl: Add Hygon DF3 address translation support
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
` (7 preceding siblings ...)
2026-04-03 10:57 ` [RFC PATCH 08/10] ras/amd/atl: Add Hygon DF2 address translation support Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-04-03 10:57 ` [RFC PATCH 10/10] EDAC/amd64: Add Hygon Family 0x18 models 0x4-0x8 support Aichun Shi
2026-05-14 15:08 ` [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Borislav Petkov
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Extend address translation support for Hygon Family 0x18 model 0x7
(Hygon DF3).
System detection:
- Add register field definitions for Hygon DF3 in hygon/reg_fields.h.
- Add HYGON_DF3 to enum df_revisions in internal.h.
- Discover Hygon DF3 socket/die mask widths and shifts; detect model
0x7 as Hygon DF3.
DRAM map:
- Use shared AMD DF2 DRAM base/limit via df2_get_dram_addr_map().
- Map Hygon-specific HYGON_DF2_4CHAN_HASH interleave mode for Hygon DF3.
- Get die/socket interleave count for Hygon DF3.
- Get DRAM offset and high address offset for Hygon DF3.
Denormalize:
- Get destination fabric ID for Hygon DF3.
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/ras/amd/atl/hygon/denormalize.c | 2 ++
drivers/ras/amd/atl/hygon/map.c | 21 +++++++++++++++++++--
drivers/ras/amd/atl/hygon/reg_fields.h | 22 ++++++++++++++++++++++
drivers/ras/amd/atl/hygon/system.c | 9 +++++++++
drivers/ras/amd/atl/internal.h | 1 +
5 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/drivers/ras/amd/atl/hygon/denormalize.c b/drivers/ras/amd/atl/hygon/denormalize.c
index 82c5982893ba..6e882a6b7b55 100644
--- a/drivers/ras/amd/atl/hygon/denormalize.c
+++ b/drivers/ras/amd/atl/hygon/denormalize.c
@@ -16,6 +16,8 @@ static u16 hygon_get_dst_fabric_id(struct addr_ctx *ctx)
return FIELD_GET(HYGON_DF1_DST_FABRIC_ID, ctx->map.limit);
case HYGON_DF2:
return FIELD_GET(HYGON_DF2_DST_FABRIC_ID, ctx->map.limit);
+ case HYGON_DF3:
+ return FIELD_GET(HYGON_DF3_DST_FABRIC_ID, ctx->map.limit);
default:
atl_debug_on_bad_df_rev();
return 0;
diff --git a/drivers/ras/amd/atl/hygon/map.c b/drivers/ras/amd/atl/hygon/map.c
index b0c05532b744..2a3d962be083 100644
--- a/drivers/ras/amd/atl/hygon/map.c
+++ b/drivers/ras/amd/atl/hygon/map.c
@@ -17,7 +17,7 @@ static int hygon_df_get_intlv_mode(struct addr_ctx *ctx)
ctx->map.intlv_mode = HYGON_DF1_3CHAN;
if (ctx->map.intlv_mode == 8) {
- if (df_cfg.rev == HYGON_DF2)
+ if (df_cfg.rev == HYGON_DF2 || df_cfg.rev == HYGON_DF3)
ctx->map.intlv_mode = HYGON_DF2_4CHAN_HASH;
else
ctx->map.intlv_mode = DF2_2CHAN_HASH;
@@ -46,6 +46,9 @@ static u64 hygon_get_hi_addr_offset(u32 reg_dram_offset)
case HYGON_DF2:
hi_addr_offset = FIELD_GET(HYGON_DF1_HI_ADDR_OFFSET, reg_dram_offset);
break;
+ case HYGON_DF3:
+ hi_addr_offset = FIELD_GET(HYGON_DF3_HI_ADDR_OFFSET, reg_dram_offset);
+ break;
default:
hi_addr_offset = 0;
atl_debug_on_bad_df_rev();
@@ -77,7 +80,12 @@ static int hygon_get_dram_offset(struct addr_ctx *ctx, u64 *norm_offset)
*/
map_num = ctx->map.num - 1;
- if (df_cfg.rev >= HYGON_DF1) {
+ if (df_cfg.rev >= HYGON_DF3) {
+ /* Read D18F0x1B4 (DramOffset) */
+ if (df_indirect_read_instance(ctx->node_id, 0, 0x1B4 + (4 * map_num),
+ ctx->inst_id, ®_dram_offset))
+ return -EINVAL;
+ } else if (df_cfg.rev >= HYGON_DF1) {
/* Read D18F0x214 (DramOffset) */
if (df_indirect_read_instance(ctx->node_id, 0, 0x214 + (4 * map_num),
ctx->inst_id, ®_dram_offset))
@@ -144,6 +152,7 @@ static int hygon_get_dram_addr_map(struct addr_ctx *ctx)
switch (df_cfg.rev) {
case HYGON_DF1: return hygon_df1_get_dram_addr_map(ctx);
case HYGON_DF2: return hygon_df2_get_dram_addr_map(ctx);
+ case HYGON_DF3: return df2_get_dram_addr_map(ctx);
default:
atl_debug_on_bad_df_rev();
return -EINVAL;
@@ -165,6 +174,9 @@ static int hygon_get_coh_st_fabric_id(struct addr_ctx *ctx)
case HYGON_DF2:
ctx->coh_st_fabric_id = FIELD_GET(HYGON_DF2_COH_ST_FABRIC_ID, reg);
break;
+ case HYGON_DF3:
+ ctx->coh_st_fabric_id = FIELD_GET(HYGON_DF3_COH_ST_FABRIC_ID, reg);
+ break;
default:
atl_debug_on_bad_df_rev();
return -EINVAL;
@@ -286,6 +298,7 @@ static u8 hygon_get_intlv_bit_pos(struct addr_ctx *ctx)
switch (df_cfg.rev) {
case HYGON_DF1:
case HYGON_DF2:
+ case HYGON_DF3:
addr_sel = FIELD_GET(HYGON_DF1_INTLV_ADDR_SEL, ctx->map.base);
break;
default:
@@ -303,6 +316,7 @@ static u8 hygon_get_num_intlv_dies(struct addr_ctx *ctx)
switch (df_cfg.rev) {
case HYGON_DF1:
+ case HYGON_DF3:
dies = FIELD_GET(HYGON_DF1_INTLV_NUM_DIES, ctx->map.limit);
break;
case HYGON_DF2:
@@ -326,6 +340,9 @@ static u8 hygon_get_num_intlv_sockets(struct addr_ctx *ctx)
case HYGON_DF2:
sockets = FIELD_GET(HYGON_DF1_INTLV_NUM_SOCKETS, ctx->map.base);
break;
+ case HYGON_DF3:
+ sockets = FIELD_GET(HYGON_DF3_INTLV_NUM_SOCKETS, ctx->map.limit);
+ break;
default:
atl_debug_on_bad_df_rev();
break;
diff --git a/drivers/ras/amd/atl/hygon/reg_fields.h b/drivers/ras/amd/atl/hygon/reg_fields.h
index eaf1d628fd0e..1ee9980d7834 100644
--- a/drivers/ras/amd/atl/hygon/reg_fields.h
+++ b/drivers/ras/amd/atl/hygon/reg_fields.h
@@ -18,9 +18,11 @@
* D18F0x50 [Fabric Block Instance Information 3]
* HYGON_DF1 BlockFabricId [17:8]
* HYGON_DF2 BlockFabricId [18:8]
+ * HYGON_DF3 BlockFabricId [15:8]
*/
#define HYGON_DF1_COH_ST_FABRIC_ID GENMASK(17, 8)
#define HYGON_DF2_COH_ST_FABRIC_ID GENMASK(18, 8)
+#define HYGON_DF3_COH_ST_FABRIC_ID GENMASK(15, 8)
/*
* Interleave Number of Sockets
@@ -33,8 +35,12 @@
* D18F0x110 [DRAM Base Address]
* HYGON_DF1 IntLvNumSockets [3:2]
* HYGON_DF2 IntLvNumSockets [3:2]
+ *
+ * D18F0x114 [DRAM Limit Address]
+ * HYGON_DF3 IntLvNumSockets [8]
*/
#define HYGON_DF1_INTLV_NUM_SOCKETS GENMASK(3, 2)
+#define HYGON_DF3_INTLV_NUM_SOCKETS BIT(8)
/*
* Interleave Number of Channels
@@ -47,6 +53,7 @@
* D18F0x110 [DRAM Base Address]
* HYGON_DF1 IntLvNumChan [7:4]
* HYGON_DF2 IntLvNumChan [7:4]
+ * HYGON_DF3 IntLvNumChan [7:4]
*/
#define HYGON_DF1_INTLV_NUM_CHAN GENMASK(7, 4)
@@ -61,6 +68,7 @@
* D18F0x110 [DRAM Base Address]
* HYGON_DF1 IntLvAddrSel [10:8]
* HYGON_DF2 IntLvAddrSel [10:8]
+ * HYGON_DF3 IntLvAddrSel [10:8]
*/
#define HYGON_DF1_INTLV_ADDR_SEL GENMASK(10, 8)
@@ -75,9 +83,11 @@
* D18F0x114 [DRAM Limit Address]
* HYGON_DF1 DstFabricID [9:0]
* HYGON_DF2 DstFabricID [10:0]
+ * HYGON_DF3 DstFabricID [7:0]
*/
#define HYGON_DF1_DST_FABRIC_ID GENMASK(9, 0)
#define HYGON_DF2_DST_FABRIC_ID GENMASK(10, 0)
+#define HYGON_DF3_DST_FABRIC_ID GENMASK(7, 0)
/*
* Interleave Number of Dies
@@ -89,6 +99,7 @@
*
* D18F0x114 [DRAM Limit Address]
* HYGON_DF1 IntLvNumDies [11:10]
+ * HYGON_DF3 IntLvNumDies [11:10]
*
* D18F0x60
* HYGON_DF2 IntLvNumDies [1:0]
@@ -107,8 +118,12 @@
* D18F0x214 [DRAM Offset]
* HYGON_DF1 HiAddrOffset [31:19]
* HYGON_DF2 HiAddrOffset [31:19]
+ *
+ * D18F0x1B4 [DRAM Offset]
+ * HYGON_DF3 HiAddrOffset [31:20]
*/
#define HYGON_DF1_HI_ADDR_OFFSET GENMASK(31, 19)
+#define HYGON_DF3_HI_ADDR_OFFSET GENMASK(31, 20)
/*
* Die ID Mask
@@ -121,9 +136,11 @@
* D18F1x208 [System Fabric ID Mask]
* HYGON_DF1 DieIdMask [9:0]
* HYGON_DF2 DieIdMask [10:0]
+ * HYGON_DF3 DieIdMask [15:8]
*/
#define HYGON_DF1_DIE_ID_MASK GENMASK(9, 0)
#define HYGON_DF2_DIE_ID_MASK GENMASK(10, 0)
+#define HYGON_DF3_DIE_ID_MASK GENMASK(15, 8)
/*
* Die ID Shift
@@ -136,8 +153,10 @@
* D18F1x208 [System Fabric ID Mask]
* HYGON_DF1 DieIdShift [15:12]
* HYGON_DF2 DieIdShift [15:12]
+ * HYGON_DF3 DieIdShift [27:24]
*/
#define HYGON_DF1_DIE_ID_SHIFT GENMASK(15, 12)
+#define HYGON_DF3_DIE_ID_SHIFT GENMASK(27, 24)
/*
* Socket ID Mask
@@ -150,9 +169,11 @@
* D18F1x208 [System Fabric ID Mask]
* HYGON_DF1 SocketIdMask [25:16]
* HYGON_DF2 SocketIdMask [26:16]
+ * HYGON_DF3 SocketIdMask [23:16]
*/
#define HYGON_DF1_SOCKET_ID_MASK GENMASK(25, 16)
#define HYGON_DF2_SOCKET_ID_MASK GENMASK(26, 16)
+#define HYGON_DF3_SOCKET_ID_MASK GENMASK(23, 16)
/*
* Socket ID Shift
@@ -165,5 +186,6 @@
* D18F1x208 [System Fabric ID Mask]
* HYGON_DF1 SocketIdShift [31:28]
* HYGON_DF2 SocketIdShift [31:28]
+ * HYGON_DF3 SocketIdShift [31:28]
*/
#define HYGON_DF1_SOCKET_ID_SHIFT GENMASK(31, 28)
diff --git a/drivers/ras/amd/atl/hygon/system.c b/drivers/ras/amd/atl/hygon/system.c
index a06a39bc4527..f3ca6f58d00f 100644
--- a/drivers/ras/amd/atl/hygon/system.c
+++ b/drivers/ras/amd/atl/hygon/system.c
@@ -24,6 +24,12 @@ static void hygon_df_get_masks_shifts(u32 mask0)
df_cfg.die_id_shift = FIELD_GET(HYGON_DF1_DIE_ID_SHIFT, mask0);
df_cfg.die_id_mask = FIELD_GET(HYGON_DF2_DIE_ID_MASK, mask0);
break;
+ case HYGON_DF3:
+ df_cfg.socket_id_shift = FIELD_GET(HYGON_DF1_SOCKET_ID_SHIFT, mask0);
+ df_cfg.socket_id_mask = FIELD_GET(HYGON_DF3_SOCKET_ID_MASK, mask0);
+ df_cfg.die_id_shift = FIELD_GET(HYGON_DF3_DIE_ID_SHIFT, mask0);
+ df_cfg.die_id_mask = FIELD_GET(HYGON_DF3_DIE_ID_MASK, mask0);
+ break;
default:
atl_debug_on_bad_df_rev();
return;
@@ -38,6 +44,8 @@ static int hygon_determine_df_rev(void)
df_cfg.rev = HYGON_DF1;
else if (boot_cpu_data.x86_model == 0x6 || boot_cpu_data.x86_model == 0x8)
df_cfg.rev = HYGON_DF2;
+ else if (boot_cpu_data.x86_model == 0x7)
+ df_cfg.rev = HYGON_DF3;
/* Read D18F1x208 (SystemFabricIdMask). */
if (df_indirect_read_broadcast(0, 1, 0x208, &fabric_id_mask0))
@@ -53,6 +61,7 @@ static void hygon_get_num_maps(void)
switch (df_cfg.rev) {
case HYGON_DF1:
case HYGON_DF2:
+ case HYGON_DF3:
df_cfg.num_coh_st_maps = 2;
break;
default:
diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
index c368559ca2ae..5e95d598c89e 100644
--- a/drivers/ras/amd/atl/internal.h
+++ b/drivers/ras/amd/atl/internal.h
@@ -45,6 +45,7 @@ enum df_revisions {
UNKNOWN,
HYGON_DF1,
HYGON_DF2,
+ HYGON_DF3,
DF2,
DF3,
DF3p5,
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 10/10] EDAC/amd64: Add Hygon Family 0x18 models 0x4-0x8 support
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
` (8 preceding siblings ...)
2026-04-03 10:57 ` [RFC PATCH 09/10] ras/amd/atl: Add Hygon DF3 " Aichun Shi
@ 2026-04-03 10:57 ` Aichun Shi
2026-05-14 15:08 ` [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Borislav Petkov
10 siblings, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-04-03 10:57 UTC (permalink / raw)
To: yazen.ghannam; +Cc: tglx, linux-edac, linux-kernel, shiaichun
Add UMC base support for Hygon Family 0x18 models 0x4-0x8.
Add error channel fields from MCA IPID for Hygon.
Add distinct memory-controller counts for Hygon models 0x4/0x5.
Add Hygon node number support in amd64_edac_init() and amd64_edac_exit().
Signed-off-by: Aichun Shi <shiaichun@open-hieco.net>
---
drivers/edac/amd64_edac.c | 68 ++++++++++++++++++++++++++++++++-------
1 file changed, 56 insertions(+), 12 deletions(-)
diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c
index 8908ab881c85..4a24cf28e543 100644
--- a/drivers/edac/amd64_edac.c
+++ b/drivers/edac/amd64_edac.c
@@ -4,6 +4,7 @@
#include "amd64_edac.h"
#include <asm/amd/nb.h>
#include <asm/amd/node.h>
+#include <asm/hygon/node.h>
static struct edac_pci_ctl_info *pci_ctl;
@@ -98,6 +99,29 @@ int __amd64_write_pci_cfg_dword(struct pci_dev *pdev, int offset,
return pcibios_err_to_errno(err);
}
+static u32 get_umc_base_addr(struct amd64_pvt *pvt, u8 channel)
+{
+ if (hygon_f18h_m4h()) {
+ struct pci_dev *dev = node_to_amd_nb(pvt->mc_node_id)->misc;
+ u8 df_id;
+
+ hygon_get_dfid(dev, &df_id);
+
+ return get_umc_base(channel) + (0x80000000 + (0x10000000 * (df_id - 4)));
+ }
+
+ return get_umc_base(channel);
+}
+
+static u16 get_num_nodes(void)
+{
+ if (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON &&
+ boot_cpu_data.x86 == 0x18)
+ return amd_num_nodes();
+
+ return amd_nb_num();
+}
+
/*
* Select DCT to which PCI cfg accesses are routed
*/
@@ -1452,12 +1476,15 @@ static void umc_read_base_mask(struct amd64_pvt *pvt)
u32 mask_reg, mask_reg_sec;
u32 *base, *base_sec;
u32 *mask, *mask_sec;
+ u32 umc_base;
int cs, umc;
u32 tmp;
for_each_umc(umc) {
- umc_base_reg = get_umc_base(umc) + UMCCH_BASE_ADDR;
- umc_base_reg_sec = get_umc_base(umc) + UMCCH_BASE_ADDR_SEC;
+ umc_base = get_umc_base_addr(pvt, umc);
+
+ umc_base_reg = umc_base + UMCCH_BASE_ADDR;
+ umc_base_reg_sec = umc_base + UMCCH_BASE_ADDR_SEC;
for_each_chip_select(cs, umc, pvt) {
base = &pvt->csels[umc].csbases[cs];
@@ -1479,8 +1506,8 @@ static void umc_read_base_mask(struct amd64_pvt *pvt)
}
}
- umc_mask_reg = get_umc_base(umc) + UMCCH_ADDR_MASK;
- umc_mask_reg_sec = get_umc_base(umc) + get_umc_reg(pvt, UMCCH_ADDR_MASK_SEC);
+ umc_mask_reg = umc_base + UMCCH_ADDR_MASK;
+ umc_mask_reg_sec = umc_base + get_umc_reg(pvt, UMCCH_ADDR_MASK_SEC);
for_each_chip_select_mask(cs, umc, pvt) {
mask = &pvt->csels[umc].csmasks[cs];
@@ -1567,7 +1594,8 @@ static void umc_determine_memory_type(struct amd64_pvt *pvt)
* Check if the system supports the "DDR Type" field in UMC Config
* and has DDR5 DIMMs in use.
*/
- if (pvt->flags.zn_regs_v2 && ((umc->umc_cfg & GENMASK(2, 0)) == 0x1)) {
+ if ((pvt->flags.zn_regs_v2 || hygon_f18h_m4h()) &&
+ ((umc->umc_cfg & GENMASK(2, 0)) == 0x1)) {
if (umc->dimm_cfg & BIT(5))
umc->dram_type = MEM_LRDDR5;
else if (umc->dimm_cfg & BIT(4))
@@ -2801,7 +2829,11 @@ static inline void decode_bus_error(int node_id, struct mce *m)
*/
static void umc_get_err_info(struct mce *m, struct err_info *err)
{
- err->channel = (m->ipid & GENMASK(31, 0)) >> 20;
+ if (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON &&
+ boot_cpu_data.x86 == 0x18)
+ err->channel = FIELD_GET(GENMASK(23, 20), m->ipid);
+ else
+ err->channel = FIELD_GET(GENMASK(31, 20), m->ipid);
err->csrow = m->synd & 0x7;
}
@@ -2922,8 +2954,7 @@ static void umc_read_mc_regs(struct amd64_pvt *pvt)
/* Read registers from each UMC */
for_each_umc(i) {
-
- umc_base = get_umc_base(i);
+ umc_base = get_umc_base_addr(pvt, i);
umc = &pvt->umc[i];
if (!amd_smn_read(nid, umc_base + get_umc_reg(pvt, UMCCH_DIMM_CFG), &tmp))
@@ -3841,6 +3872,16 @@ static int per_family_init(struct amd64_pvt *pvt)
break;
case 0x18:
+ switch (pvt->model) {
+ case 0x4:
+ pvt->max_mcs = 3;
+ break;
+ case 0x5:
+ pvt->max_mcs = 1;
+ break;
+ default:
+ break;
+ }
break;
case 0x19:
@@ -4128,6 +4169,7 @@ static int __init amd64_edac_init(void)
{
const char *owner;
int err = -ENODEV;
+ u16 node_num;
int i;
if (ghes_get_devices())
@@ -4140,13 +4182,15 @@ static int __init amd64_edac_init(void)
if (!x86_match_cpu(amd64_cpuids))
return -ENODEV;
- if (!amd_nb_num())
+ node_num = get_num_nodes();
+
+ if (!node_num)
return -ENODEV;
opstate_init();
err = -ENOMEM;
- ecc_stngs = kzalloc_objs(ecc_stngs[0], amd_nb_num());
+ ecc_stngs = kzalloc_objs(ecc_stngs[0], node_num);
if (!ecc_stngs)
goto err_free;
@@ -4154,7 +4198,7 @@ static int __init amd64_edac_init(void)
if (!msrs)
goto err_free;
- for (i = 0; i < amd_nb_num(); i++) {
+ for (i = 0; i < node_num; i++) {
err = probe_one_instance(i);
if (err) {
/* unwind properly */
@@ -4210,7 +4254,7 @@ static void __exit amd64_edac_exit(void)
else
amd_unregister_ecc_decoder(decode_bus_error);
- for (i = 0; i < amd_nb_num(); i++)
+ for (i = 0; i < get_num_nodes(); i++)
remove_one_instance(i);
kfree(ecc_stngs);
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation
2026-04-03 10:57 [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Aichun Shi
` (9 preceding siblings ...)
2026-04-03 10:57 ` [RFC PATCH 10/10] EDAC/amd64: Add Hygon Family 0x18 models 0x4-0x8 support Aichun Shi
@ 2026-05-14 15:08 ` Borislav Petkov
2026-05-16 5:42 ` Aichun Shi
2026-05-21 1:46 ` Aichun Shi
10 siblings, 2 replies; 14+ messages in thread
From: Borislav Petkov @ 2026-05-14 15:08 UTC (permalink / raw)
To: Aichun Shi; +Cc: yazen.ghannam, tglx, linux-edac, linux-kernel
On Fri, Apr 03, 2026 at 06:57:24PM +0800, Aichun Shi wrote:
> Hi all,
>
> This is an RFC (Request For Comments) for the patch series that proposes
> Hygon Family 0x18 (models 0x4-0x8) support for UMC ECC error address
> translation, aligned with the existing AMD EDAC + AMD ATL layering in
> mainline.
Please go over all review comments and address:
https://sashiko.dev/#/patchset/cover.1775213147.git.shiaichun%40open-hieco.net
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation
2026-05-14 15:08 ` [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Borislav Petkov
@ 2026-05-16 5:42 ` Aichun Shi
2026-05-21 1:46 ` Aichun Shi
1 sibling, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-05-16 5:42 UTC (permalink / raw)
To: Borislav Petkov; +Cc: yazen.ghannam, tglx, linux-edac, linux-kernel
On 5/14/2026 11:08 PM, Borislav Petkov wrote:
> On Fri, Apr 03, 2026 at 06:57:24PM +0800, Aichun Shi wrote:
>> Hi all,
>>
>> This is an RFC (Request For Comments) for the patch series that proposes
>> Hygon Family 0x18 (models 0x4-0x8) support for UMC ECC error address
>> translation, aligned with the existing AMD EDAC + AMD ATL layering in
>> mainline.
>
> Please go over all review comments and address:
>
> https://sashiko.dev/#/patchset/cover.1775213147.git.shiaichun%40open-hieco.net
>
Hi Boris,
Thanks for the review. I will go over all of them and address them soon.
Best Regards,
Aichun
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation
2026-05-14 15:08 ` [RFC PATCH 00/10] EDAC/RAS: Hygon Family 0x18 UMC ECC address translation Borislav Petkov
2026-05-16 5:42 ` Aichun Shi
@ 2026-05-21 1:46 ` Aichun Shi
1 sibling, 0 replies; 14+ messages in thread
From: Aichun Shi @ 2026-05-21 1:46 UTC (permalink / raw)
To: Borislav Petkov; +Cc: yazen.ghannam, tglx, linux-edac, linux-kernel
On 5/14/2026 11:08 PM, Borislav Petkov wrote:
> On Fri, Apr 03, 2026 at 06:57:24PM +0800, Aichun Shi wrote:
>> Hi all,
>>
>> This is an RFC (Request For Comments) for the patch series that proposes
>> Hygon Family 0x18 (models 0x4-0x8) support for UMC ECC error address
>> translation, aligned with the existing AMD EDAC + AMD ATL layering in
>> mainline.
>
> Please go over all review comments and address:
>
> https://sashiko.dev/#/patchset/cover.1775213147.git.shiaichun%40open-hieco.net
>
Hi Boris,
Please see my comments below, and I will send RFC V2 soon.
> Patch 2: [RFC PATCH 02/10] ras/amd/atl: Add Hygon DF1 DRAM address map decoding helper
> Result:
> Critical: 0 · High: 1 · Medium: 0 · Low: 0
>> +static u8 hygon_get_num_intlv_chan(struct addr_ctx *ctx)
>> +{
>> + switch (ctx->map.intlv_mode) {
>> + case NONE:
>> + return 1;
>> + case NOHASH_2CHAN:
>> + return 2;
>> + case HYGON_DF1_3CHAN:
>> + return 3;
>> + case NOHASH_4CHAN:
>> + return 4;
>> + case NOHASH_8CHAN:
>> + return 8;
>> + case NOHASH_16CHAN:
>> + return 16;
>> + case NOHASH_32CHAN:
>> + return 32;
>> + default:
>> + atl_debug_on_bad_intlv_mode(ctx);
>> + return 0;
>> + }
>> +}
> In hygon_df_get_intlv_mode(), an initial value of 8 is explicitly mapped to
> DF2_2CHAN_HASH. However, the switch statement in hygon_get_num_intlv_chan()
> checks for NOHASH_32CHAN (which is enum value 0x08) but does not have a case
> for DF2_2CHAN_HASH.
> Does this code fail to handle the DF2_2CHAN_HASH interleave mode?
> If DF2_2CHAN_HASH is used, the switch will fall through to the default case
> and return 0 channels. This 0 propagates into ctx->map.total_intlv_chan
> and ctx->map.total_intlv_bits.
> Later in the address translation pipeline, calculating the coherent station
> ID uses order_base_2(ctx->map.num_intlv_chan). Because order_base_2(0)
> returns 0, this causes GENMASK(num_intlv_bits - 1, 0) to evaluate to
> GENMASK(-1, 0), producing ~0UL.
> Can this corrupt the translated system physical address by mangling the
> subsequent die/socket bit shifts and ORing the invalid station ID directly
> over the address bits?
> Should DF2_2CHAN_HASH be included in this switch block?
Will fix in RFC V2.
> Patch 3: [RFC PATCH 03/10] ras/amd/atl: Add Hygon DF1 normalized address denormalization helper
> Result:
> Critical: 0 · High: 3 · Medium: 0 · Low: 0
>
>> +static u16 hygon_get_coh_st_id_df(struct addr_ctx *ctx)
>> +{
>> + u8 num_socket_intlv_bits = ilog2(ctx->map.num_intlv_sockets);
>> + u8 num_die_intlv_bits = ilog2(ctx->map.num_intlv_dies);
>> + u16 coh_st_id = ctx->coh_st_fabric_id;
>> + u16 mask;
>> + u8 intlv_bit_pos = ctx->map.intlv_bit_pos;
>> + u8 num_intlv_bits = order_base_2(ctx->map.num_intlv_chan);
>> +
>> + if (ctx->map.intlv_mode == HYGON_DF1_3CHAN) {
>> + u8 cs_offset;
>> + u16 dst_fabric_id = hygon_get_dst_fabric_id(ctx);
>> + u64 temp_addr_x;
>> +
>> + cs_offset = (ctx->coh_st_fabric_id & 0x3) - (dst_fabric_id & 0x3);
> If the base and target IDs cross a multiple-of-4 boundary (for example, if
> the base ID is 2 and the target ID is 4), does this subtraction underflow?
> Since cs_offset is a u8, (4 & 3) - (2 & 3) would evaluate to 0 - 2, wrapping
> around to 254.
>> + if (cs_offset > 3) {
> And cause this check to falsely evaluate to true, rejecting a valid topology?
As the valid values of ctx->coh_st_fabric_id and dst_fabric_id are
between 0 and 3, "cs_offset > 3" check should be enough for invalid
values of ctx->coh_st_fabric_id and dst_fabric_id.
>> + atl_debug(ctx,
>> + "Invalid cs_offset: 0x%x coh_st_fabric_id: 0x%x dst_fabric_id: 0x%x.\n",
>> + cs_offset, ctx->coh_st_fabric_id, dst_fabric_id);
>> + return ~0;
>> + }
>> +
>> + temp_addr_x = (ctx->ret_addr & GENMASK_ULL(63, intlv_bit_pos)) >>
>> + intlv_bit_pos;
>> + temp_addr_x = temp_addr_x * 3 + cs_offset;
>> + ctx->ret_addr = temp_addr_x;
>> +
>> + coh_st_id = temp_addr_x & GENMASK_ULL(num_intlv_bits - 1, 0);
>> + } else {
>> + mask = GENMASK(num_intlv_bits - 1, 0);
>> + coh_st_id &= mask;
>> + }
>> +
>> + /* Die interleave bits */
>> + if (num_die_intlv_bits) {
>> + u16 die_bits;
>> +
>> + mask = GENMASK(num_die_intlv_bits - 1, 0);
>> + die_bits = ctx->coh_st_fabric_id & df_cfg.die_id_mask;
>> + die_bits >>= df_cfg.die_id_shift;
>> + die_bits -= 4;
>> +
>> + coh_st_id |= (die_bits & mask) << num_intlv_bits;
>> + num_intlv_bits += num_die_intlv_bits;
>> + }
>> +
>> + /* Socket interleave bits */
>> + if (num_socket_intlv_bits) {
>> + u16 socket_bits;
>> +
>> + mask = GENMASK(num_socket_intlv_bits - 1, 0);
>> + socket_bits = ctx->coh_st_fabric_id & df_cfg.socket_id_mask;
>> + socket_bits >>= df_cfg.socket_id_shift;
>> +
>> + coh_st_id |= (socket_bits & mask) << num_intlv_bits;
>> + }
> If die or socket interleaving is enabled alongside HYGON_DF1_3CHAN mode, does
> coh_st_id bitwise overlap with the uncleared physical address? The upper bits
> of coh_st_id will be set here, but the physical address was multiplied by 3
> and never shifted left to make space for these additional bits.
> [ ... ]
>> +static u64 hygon_insert_coh_st_id_df1_3chan(struct addr_ctx *ctx, u64 denorm_addr, u16 coh_st_id)
>> +{
>> + u8 num_intlv_bits_chan = order_base_2(ctx->map.num_intlv_chan);
>> + u64 temp_addr = ((ctx->ret_addr >> num_intlv_bits_chan) << num_intlv_bits_chan) | coh_st_id;
> Since num_intlv_bits_chan is 2 for 3-channel mode, are we missing the space
> needed for the die and socket bits when we apply | coh_st_id here?
As HYGON_DF1_3CHAN mode will not co-exist with die interleave or socket
interleave mode, the above comments should not be an issue.
>> +static int hygon_denorm_addr_common(struct addr_ctx *ctx)
>> +{
>> + u64 denorm_addr;
>> + u16 coh_st_id;
>> +
>> + denorm_addr = hygon_make_space_for_coh_st_id(ctx);
>> + coh_st_id = hygon_calculate_coh_st_id(ctx);
>> + ctx->ret_addr = hygon_insert_coh_st_id(ctx, denorm_addr, coh_st_id);
>> + return 0;
>> +}
> If hygon_calculate_coh_st_id() encounters an error and returns ~0, is it safe
> to pass this directly into hygon_insert_coh_st_id() and then return 0 for
> success?
> It looks like the ~0 will be truncated to 0xFFFF as a u16, bitwise OR'd
> directly into the physical address, and then the caller will trust the
> success return code, leading to incorrect page offlining operations.
Will fix in RFC V2.
> Patch 4: [RFC PATCH 04/10] ras/amd/atl: Add Hygon DF1 address dehash helper
> Result:
> Critical: 0 · High: 0 · Medium: 0 · Low: 1
>> +static int hygon_df_2chan_dehash_addr(struct addr_ctx *ctx)
>> +{
>> + u8 hashed_bit;
>> + u8 intlv_bit_pos = ctx->map.intlv_bit_pos;
>> +
>> + hashed_bit = (ctx->ret_addr >> 12) ^
>> + (ctx->ret_addr >> 18) ^
>> + (ctx->ret_addr >> 21) ^
>> + (ctx->ret_addr >> 30) ^
>> + ctx->coh_st_fabric_id;
>> +
>> + hashed_bit &= BIT(0);
>> + if (hashed_bit != ((ctx->ret_addr >> intlv_bit_pos) & BIT(0)))
>> + ctx->ret_addr ^= BIT(intlv_bit_pos);
> Should this code use BIT_ULL() instead of BIT()?
> While BIT() expands to an unsigned long (which happens to be 64-bit on x86_64),
> ctx->ret_addr is explicitly defined as a u64 variable. Using BIT_ULL() here
> would ensure the mask is always explicitly 64-bit, which safely guards against
> future changes and maintains consistency with the rest of the AMD ATL
> subsystem.
Will fix in RFC V2.
> Patch 5: [RFC PATCH 05/10] ras/amd/atl: Add Hygon DF1 normalized-to-system address translation
> Result:
> Critical: 0 · High: 1 · Medium: 0 · Low: 0
>> +unsigned long hygon_norm_to_sys_addr(u8 socket_id, u8 die_id, u8 coh_st_inst_id,
>> + u8 sub_channel, unsigned long addr)
>> +{
>> + struct addr_ctx ctx;
>> +
>> + if (df_cfg.rev == UNKNOWN)
>> + return -EINVAL;
>> +
>> + memset(&ctx, 0, sizeof(ctx));
>> +
>> + /* Start from the normalized address */
>> + ctx.ret_addr = addr;
>> + ctx.node_id = die_id;
>> + ctx.inst_id = coh_st_inst_id;
>> +
>> + ctx.inputs.norm_addr = addr;
>> + ctx.inputs.socket_id = socket_id;
>> + ctx.inputs.die_id = die_id;
>> + ctx.inputs.coh_st_inst_id = coh_st_inst_id;
>> +
>> + ctx.chan_intlv_hygon.sub_channel = sub_channel;
>> +
>> + if (legacy_hole_en(&ctx) && !df_cfg.dram_hole_base)
>> + return -EINVAL;
> Since ctx was just initialized with memset(), ctx.map hasn't been populated
> yet. Will legacy_hole_en() always read 0 for ctx.map.base and return false?
> If so, does this bypass the misconfiguration check?
>> +
>> + if (hygon_get_address_map(&ctx))
>> + return -EINVAL;
>> +
>> + if (hygon_denormalize_address(&ctx))
>> + return -EINVAL;
>> +
>> + ctx.ret_addr = add_base_and_hole(&ctx, ctx.ret_addr);
> Later, when ctx.map is populated, if the legacy hole is actually enabled in
> hardware but left unconfigured (df_cfg.dram_hole_base is 0), could
> add_base_and_hole() incorrectly shift the physical address by 4GB?
> If that happens, can this lead to a corrupted system physical address being
> returned, causing the machine check handler to offline a healthy memory page
> and leave the faulty page active?
> Would it be better to move the legacy hole validation check after
> hygon_get_address_map() has populated ctx.map?
Will fix in RFC V2.
> Patch 7: [RFC PATCH 07/10] ras/amd/atl: Add Hygon DF discovery and MCA decode at initialization
> Result:
> Critical: 0 · High: 1 · Medium: 1 · Low: 0
> commit 4b3cc464e5cc696659d9b1d2ba8977f4028f70ca
> Author: Aichun Shi <shiaichun@open-hieco.net>
> ras/amd/atl: Add Hygon DF discovery and MCA decode at initialization
> This commit adds node number bounds, Data Fabric discovery, and UMC MCA address
> translation support for Hygon systems in the AMD Address Translation Library.
>> - Add Hygon node number support in amd_atl_init(),
>> amd_atl_exit() and __df_indirect_read().
> The commit message mentions adding support in amd_atl_exit(), but there do not
> appear to be any modifications to that function in this patch. Is this bullet
> point out of date?
Will fix in RFC V2.
>> struct pci_dev *F4;
>> int err = -ENODEV;
>> u32 ficaa = 0;
>> + u16 num_nodes;
>> + bool is_hygon = boot_cpu_data.x86_vendor == X86_VENDOR_HYGON;
>>
>> node = get_accessible_node(node);
>> - if (node >= amd_nb_num()) {
>> + if (is_hygon)
>> + num_nodes = amd_num_nodes();
>> + else
>> + num_nodes = amd_nb_num();
>> +
>> + if (node >= num_nodes) {
>> pr_debug("Node %u is out of bounds\n", node);
>> goto out;
>> }
> Can this result in a NULL pointer dereference just after this check?
> The code immediately follows this bounds check with:
> F4 = node_to_amd_nb(node)->link;
> If northbridge initialization partially or fully fails, amd_num_nodes() can
> return a value greater than amd_nb_num(). If node is greater than or equal to
> amd_nb_num(), node_to_amd_nb(node) will return NULL, causing the ->link access
> to crash.
> [ ... ]
>> diff --git a/drivers/ras/amd/atl/core.c b/drivers/ras/amd/atl/core.c
>> index b7083588b46ea..bbbbff4d43a3a 100644
>> --- a/drivers/ras/amd/atl/core.c
>> +++ b/drivers/ras/amd/atl/core.c
>> @@ -195,22 +195,37 @@ MODULE_DEVICE_TABLE(x86cpu, amd_atl_cpuids);
>> static int __init amd_atl_init(void)
>> {
>> int ret;
>> + u16 num_nodes;
>> + bool is_hygon = boot_cpu_data.x86_vendor == X86_VENDOR_HYGON;
>>
>> if (!x86_match_cpu(amd_atl_cpuids))
>> return -ENODEV;
>>
>> - if (!amd_nb_num())
>> + if (is_hygon)
>> + num_nodes = amd_num_nodes();
>> + else
>> + num_nodes = amd_nb_num();
>> +
>> + if (!num_nodes)
>> return -ENODEV;
> Does this allow the module to incorrectly initialize when no northbridges are
> found?
> Previously, if amd_nb_num() was 0, it would safely return -ENODEV. By checking
> amd_num_nodes() instead for Hygon systems, the initialization could proceed
> even if northbridge initialization failed, potentially leading to similar
> NULL pointer dereferences when node_to_amd_nb() is called later in the code.
Will fix it in RFC V2 with hygon_cdd_num() instead of amd_num_nodes()
exported from Lin's Hygon Node RFC V2:
https://lore.kernel.org/lkml/20260423060420.1785357-3-wanglin@open-hieco.net/
> Patch 8: [RFC PATCH 08/10] ras/amd/atl: Add Hygon DF2 address translation support
> Result:
> Critical: 0 · High: 2 · Medium: 0 · Low: 1
>> @@ -117,6 +119,7 @@ static u16 hygon_calculate_coh_st_id(struct addr_ctx *ctx)
>> case NOHASH_16CHAN:
>> case NOHASH_32CHAN:
>> case DF2_2CHAN_HASH:
>> + case HYGON_DF2_4CHAN_HASH:
>> return hygon_get_coh_st_id_df(ctx);
>>
>> default:
> If HYGON_DF2_4CHAN_HASH is added here, does it also need to be handled in
> hygon_make_space_for_coh_st_id() and hygon_insert_coh_st_id() which also use
> a switch on ctx->map.intlv_mode?
> Without adding it to those functions, the switch will fall through to the
> default case, logging an "Unrecognized interleave mode" debug message and
> returning ~0ULL. This propagates into ctx->ret_addr causing subsequent bounds
> checks like addr_over_limit() to fail, which prevents translation of the MCA
> error address.
Will fix in RFC V2.
>
>> diff --git a/drivers/ras/amd/atl/hygon/map.c b/drivers/ras/amd/atl/hygon/map.c
>> index 2194c10ff5877..b0c05532b7449 100644
>> --- a/drivers/ras/amd/atl/hygon/map.c
>> +++ b/drivers/ras/amd/atl/hygon/map.c
>> @@ -16,8 +16,12 @@ static int hygon_df_get_intlv_mode(struct addr_ctx *ctx)
>> if (df_cfg.rev == HYGON_DF1 && ctx->map.intlv_mode == 2)
>> ctx->map.intlv_mode = HYGON_DF1_3CHAN;
>>
>> - if (ctx->map.intlv_mode == 8)
>> - ctx->map.intlv_mode = DF2_2CHAN_HASH;
>> + if (ctx->map.intlv_mode == 8) {
>> + if (df_cfg.rev == HYGON_DF2)
>> + ctx->map.intlv_mode = HYGON_DF2_4CHAN_HASH;
>> + else
>> + ctx->map.intlv_mode = DF2_2CHAN_HASH;
>> + }
> This isn't a bug introduced by this patch, but does the pre-existing
> DF2_2CHAN_HASH mode map to 0 channels in hygon_get_num_intlv_chan() due to
> being missing from its switch statement?
> If it falls through to the default case and returns 0, order_base_2(0) will
> evaluate to 0 in hygon_get_coh_st_id_df(), resulting in GENMASK(-1, 0).
> This can trigger undefined behavior and break Coherent Station ID
> calculations entirely for these systems.
Will fix in Patch 2 in RFC V2.
>> if (ctx->map.intlv_mode != NONE &&
>> ctx->map.intlv_mode != NOHASH_2CHAN &&
>> @@ -25,6 +29,7 @@ static int hygon_df_get_intlv_mode(struct addr_ctx *ctx)
>> ctx->map.intlv_mode != NOHASH_4CHAN &&
>> ctx->map.intlv_mode != NOHASH_8CHAN &&
>> ctx->map.intlv_mode != NOHASH_16CHAN &&
>> + ctx->map.intlv_mode != HYGON_DF2_4CHAN_HASH &&
>> ctx->map.intlv_mode != DF2_2CHAN_HASH)
>> return -EINVAL;
> If the hardware register reads a raw value of 9, could it bypass this
> validation?
> Since HYGON_DF1_INTLV_NUM_CHAN is a 4-bit hardware field, a value of 9 would
> skip the remapping block above. But because HYGON_DF2_4CHAN_HASH is defined
> as 0x09 in the intlv_modes enum, the validation check here will evaluate to
> false, mistakenly accepting an invalid hardware state as 4-channel hashing.
>> diff --git a/drivers/ras/amd/atl/internal.h b/drivers/ras/amd/atl/internal.h
>> index f943cc33f82e0..c368559ca2ae5 100644
>> --- a/drivers/ras/amd/atl/internal.h
>> +++ b/drivers/ras/amd/atl/internal.h
> [ ... ]
>> @@ -61,6 +62,7 @@ enum intlv_modes {
>> DF3_6CHAN = 0x06,
>> NOHASH_16CHAN = 0x07,
>> NOHASH_32CHAN = 0x08,
>> + HYGON_DF2_4CHAN_HASH = 0x09,
>> DF3_COD4_2CHAN_HASH = 0x0C,
> Could this enum value be moved to a value like those > 0x20 to prevent the
> aliasing with raw hardware field values described above?
Will fix in RFC V2.
> Patch 9: [RFC PATCH 09/10] ras/amd/atl: Add Hygon DF3 address translation support
> Result:
> Critical: 0 · High: 1 · Medium: 1 · Low: 0
>> diff --git a/drivers/ras/amd/atl/hygon/map.c b/drivers/ras/amd/atl/hygon/map.c
>> index b0c05532b7449..2a3d962be083e 100644
>> --- a/drivers/ras/amd/atl/hygon/map.c
>> +++ b/drivers/ras/amd/atl/hygon/map.c
>> @@ -17,7 +17,7 @@ static int hygon_df_get_intlv_mode(struct addr_ctx *ctx)
>> ctx->map.intlv_mode = HYGON_DF1_3CHAN;
>>
>> if (ctx->map.intlv_mode == 8) {
>> - if (df_cfg.rev == HYGON_DF2)
>> + if (df_cfg.rev == HYGON_DF2 || df_cfg.rev == HYGON_DF3)
>> ctx->map.intlv_mode = HYGON_DF2_4CHAN_HASH;
> Since HYGON_DF3 explicitly maps to the HYGON_DF2_4CHAN_HASH interleave
> mode here, does it also share the 4-channel hashing and sub-channel
> architecture of DF2?
> If so, does hygon_get_coh_st_inst_id() in drivers/ras/amd/atl/hygon/umc.c
> need to be updated?
> Currently, it only checks df_cfg.rev == HYGON_DF2 to shift the
> coh_st_inst_id and add the sub_channel. Without an update there, could
> MCA physical address translation fail or return an incorrect address for
> HYGON_DF3?
Will fix in RFC V2.
>> diff --git a/drivers/ras/amd/atl/hygon/system.c b/drivers/ras/amd/atl/hygon/system.c
>> index a06a39bc45276..f3ca6f58d00f5 100644
>> --- a/drivers/ras/amd/atl/hygon/system.c
>> +++ b/drivers/ras/amd/atl/hygon/system.c
> [ ... ]
>> @@ -38,6 +44,8 @@ static int hygon_determine_df_rev(void)
>> df_cfg.rev = HYGON_DF1;
>> else if (boot_cpu_data.x86_model == 0x6 || boot_cpu_data.x86_model == 0x8)
>> df_cfg.rev = HYGON_DF2;
>> + else if (boot_cpu_data.x86_model == 0x7)
>> + df_cfg.rev = HYGON_DF3;
> Does this logic need to verify the CPU family (boot_cpu_data.x86 == 0x18)
> alongside the model number?
> While Hygon might only have Family 0x18 right now, if a future CPU
> family introduces a model 0x7, could it falsely be detected as HYGON_DF3
> since model numbers can overlap across different families?
Will fix in RFC V2.
> Patch 10: [RFC PATCH 10/10] EDAC/amd64: Add Hygon Family 0x18 models 0x4-0x8 support
> Result:
> Critical: 1 · High: 2 · Medium: 1 · Low: 1
>> @@ -98,6 +99,29 @@ int __amd64_write_pci_cfg_dword(struct pci_dev *pdev, int offset,
>> return pcibios_err_to_errno(err);
>> }
>>
>> +static u32 get_umc_base_addr(struct amd64_pvt *pvt, u8 channel)
>> +{
>> + if (hygon_f18h_m4h()) {
>> + struct pci_dev *dev = node_to_amd_nb(pvt->mc_node_id)->misc;
> Could this use the already stored misc device pointer pvt->F3 instead of
> looking it up globally again?
Will fix in RFC V2.
>> + u8 df_id;
>> +
>> + hygon_get_dfid(dev, &df_id);
> If hygon_get_dfid() encounters an error and returns without populating the
> out-parameter, does this leave df_id uninitialized, leading to a regression
> during the umc base calculation?
Will fix in RFC V2.
>> +
>> + return get_umc_base(channel) + (0x80000000 + (0x10000000 * (df_id - 4)));
> Could this cause a signed integer overflow regression if df_id is 12 or
> greater?
> The literal 0x10000000 is treated as a signed integer. Multiplying it by 8
> or more evaluates to 2,147,483,648, which exceeds the maximum signed 32-bit
> integer limit.
> Would adding an unsigned suffix like 0x10000000U prevent this?
Will fix in RFC V2.
>> @@ -4143,13 +4185,15 @@ static int __init amd64_edac_init(void)
>> if (!x86_match_cpu(amd64_cpuids))
>> return -ENODEV;
>>
>> - if (!amd_nb_num())
>> + node_num = get_num_nodes();
>> +
>> + if (!node_num)
>> return -ENODEV;
> If northbridge caching fails during boot, amd_northbridges.num is 0, and
> amd_nb_num() correctly returns 0 to abort the driver load. By returning
> amd_num_nodes() for Hygon systems here, does this bypass the failure state
> check and force execution into the probe_one_instance() loop?
> If execution proceeds when initialization has failed, node_to_amd_nb(nid)
> will evaluate to null since nid >= amd_northbridges.num. Can this cause a
> null pointer dereference regression when the driver subsequently accesses
> the misc field?
>> opstate_init();
>>
>> err = -ENOMEM;
>> - ecc_stngs = kzalloc_objs(ecc_stngs[0], amd_nb_num());
>> + ecc_stngs = kzalloc_objs(ecc_stngs[0], node_num);
>> if (!ecc_stngs)
>> goto err_free;
Will fix it in RFC V2 with hygon_cdd_num() instead of amd_num_nodes()
exported from Lin's Hygon Node RFC V2:
https://lore.kernel.org/lkml/20260423060420.1785357-3-wanglin@open-hieco.net/
>> @@ -4213,7 +4257,7 @@ static void __exit amd64_edac_exit(void)
>> else
>> amd_unregister_ecc_decoder(decode_bus_error);
>>
>> - for (i = 0; i < amd_nb_num(); i++)
>> + for (i = 0; i < get_num_nodes(); i++)
>> remove_one_instance(i);
> This isn't a bug introduced by this commit, but is there a race condition
> during module unload?
> When amd_unregister_ecc_decoder() clears the callback pointer, could
> concurrently executing exception handlers still be running? If so,
> remove_one_instance() might free the memory controller instances while
> the handler is actively reading from them, potentially resulting in a
> use-after-free regression.
May consider it in RFC V2.
Best Regards,
Aichun
^ permalink raw reply [flat|nested] 14+ messages in thread