From 76239f441c9455edd5a0784a3117de03d55d1b17 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 31 Jul 2025 22:55:28 +0800 Subject: [PATCH 01/19] EDAC/{skx_common,skx}: Use configuration data, not global macros commit 219af5dfce98fe254335ea79c2bf98428c38370c upstream. Use model-specific configuration data for the number of memory controllers per socket, channels per memory controller, and DIMMs per channel as intended, instead of relying on global macros for maximum values. No functional changes intended. Intel-SIG: commit 219af5dfce98 EDAC/{skx_common,skx}: Use configuration data, not global macros Backport EDAC for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://lore.kernel.org/r/20250731145534.2759334-2-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/skx_base.c | 33 ++++++++++++++++++++------------- drivers/edac/skx_common.c | 16 +++++++++------- drivers/edac/skx_common.h | 1 + 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/drivers/edac/skx_base.c b/drivers/edac/skx_base.c index 67634d3af4617..78282c8e3444e 100644 --- a/drivers/edac/skx_base.c +++ b/drivers/edac/skx_base.c @@ -33,6 +33,15 @@ static unsigned int nvdimm_count; #define MASK26 0x3FFFFFF /* Mask for 2^26 */ #define MASK29 0x1FFFFFFF /* Mask for 2^29 */ +static struct res_config skx_cfg = { + .type = SKX, + .decs_did = 0x2016, + .busno_cfg_offset = 0xcc, + .ddr_imc_num = 2, + .ddr_chan_num = 3, + .ddr_dimm_num = 2, +}; + static struct skx_dev *get_skx_dev(struct pci_bus *bus, u8 idx) { struct skx_dev *d; @@ -52,7 +61,7 @@ enum munittype { struct munit { u16 did; - u16 devfn[SKX_NUM_IMC]; + u16 devfn[2]; u8 busidx; u8 per_socket; enum munittype mtype; @@ -89,11 +98,11 @@ static int get_all_munits(const struct munit *m) if (!pdev) break; ndev++; - if (m->per_socket == SKX_NUM_IMC) { - for (i = 0; i < SKX_NUM_IMC; i++) + if (m->per_socket == skx_cfg.ddr_imc_num) { + for (i = 0; i < skx_cfg.ddr_imc_num; i++) if (m->devfn[i] == pdev->devfn) break; - if (i == SKX_NUM_IMC) + if (i == skx_cfg.ddr_imc_num) goto fail; } d = get_skx_dev(pdev->bus, m->busidx); @@ -157,12 +166,6 @@ static int get_all_munits(const struct munit *m) return -ENODEV; } -static struct res_config skx_cfg = { - .type = SKX, - .decs_did = 0x2016, - .busno_cfg_offset = 0xcc, -}; - static const struct x86_cpu_id skx_cpuids[] = { X86_MATCH_INTEL_FAM6_MODEL_STEPPINGS(SKYLAKE_X, X86_STEPPINGS(0x0, 0xf), &skx_cfg), { } @@ -186,11 +189,11 @@ static int skx_get_dimm_config(struct mem_ctl_info *mci, struct res_config *cfg) /* Only the mcmtr on the first channel is effective */ pci_read_config_dword(imc->chan[0].cdev, 0x87c, &mcmtr); - for (i = 0; i < SKX_NUM_CHANNELS; i++) { + for (i = 0; i < cfg->ddr_chan_num; i++) { ndimms = 0; pci_read_config_dword(imc->chan[i].cdev, 0x8C, &amap); pci_read_config_dword(imc->chan[i].cdev, 0x400, &mcddrtcfg); - for (j = 0; j < SKX_NUM_DIMMS; j++) { + for (j = 0; j < cfg->ddr_dimm_num; j++) { dimm = edac_get_dimm(mci, i, j, 0); pci_read_config_dword(imc->chan[i].cdev, 0x80 + 4 * j, &mtr); @@ -620,6 +623,7 @@ static int __init skx_init(void) return -ENODEV; cfg = (struct res_config *)id->driver_data; + skx_set_res_cfg(cfg); rc = skx_get_hi_lo(0x2034, off, &skx_tolm, &skx_tohm); if (rc) @@ -652,10 +656,13 @@ static int __init skx_init(void) goto fail; edac_dbg(2, "src_id = %d\n", src_id); - for (i = 0; i < SKX_NUM_IMC; i++) { + for (i = 0; i < cfg->ddr_imc_num; i++) { d->imc[i].mc = mc++; d->imc[i].lmc = i; d->imc[i].src_id = src_id; + d->imc[i].num_channels = cfg->ddr_chan_num; + d->imc[i].num_dimms = cfg->ddr_dimm_num; + rc = skx_register_mci(&d->imc[i], d->imc[i].chan[0].cdev, "Skylake Socket", EDAC_MOD_STR, skx_get_dimm_config, cfg); diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index d9cd4a3b27736..24cdf57b20e37 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -322,10 +322,10 @@ static int get_width(u32 mtr) */ int skx_get_all_bus_mappings(struct res_config *cfg, struct list_head **list) { + int ndev = 0, imc_num = cfg->ddr_imc_num + cfg->hbm_imc_num; struct pci_dev *pdev, *prev; struct skx_dev *d; u32 reg; - int ndev = 0; prev = NULL; for (;;) { @@ -356,8 +356,10 @@ int skx_get_all_bus_mappings(struct res_config *cfg, struct list_head **list) d->seg = GET_BITFIELD(reg, 16, 23); } - edac_dbg(2, "busses: 0x%x, 0x%x, 0x%x, 0x%x\n", - d->bus[0], d->bus[1], d->bus[2], d->bus[3]); + d->num_imc = imc_num; + + edac_dbg(2, "busses: 0x%x, 0x%x, 0x%x, 0x%x, imcs %d\n", + d->bus[0], d->bus[1], d->bus[2], d->bus[3], imc_num); list_add_tail(&d->list, &dev_edac_list); prev = pdev; @@ -546,10 +548,10 @@ int skx_register_mci(struct skx_imc *imc, struct pci_dev *pdev, /* Allocate a new MC control structure */ layers[0].type = EDAC_MC_LAYER_CHANNEL; - layers[0].size = NUM_CHANNELS; + layers[0].size = imc->num_channels; layers[0].is_virt_csrow = false; layers[1].type = EDAC_MC_LAYER_SLOT; - layers[1].size = NUM_DIMMS; + layers[1].size = imc->num_dimms; layers[1].is_virt_csrow = true; mci = edac_mc_alloc(imc->mc, ARRAY_SIZE(layers), layers, sizeof(struct skx_pvt)); @@ -789,7 +791,7 @@ void skx_remove(void) list_for_each_entry_safe(d, tmp, &dev_edac_list, list) { list_del(&d->list); - for (i = 0; i < NUM_IMC; i++) { + for (i = 0; i < d->num_imc; i++) { if (d->imc[i].mci) skx_unregister_mci(&d->imc[i]); @@ -799,7 +801,7 @@ void skx_remove(void) if (d->imc[i].mbase) iounmap(d->imc[i].mbase); - for (j = 0; j < NUM_CHANNELS; j++) { + for (j = 0; j < d->imc[i].num_channels; j++) { if (d->imc[i].chan[j].cdev) pci_dev_put(d->imc[i].chan[j].cdev); } diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index ec4966f7ea40b..3f6007a972679 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -134,6 +134,7 @@ struct skx_dev { struct pci_dev *uracu; /* for i10nm CPU */ struct pci_dev *pcu_cr3; /* for HBM memory detection */ u32 mcroute; + int num_imc; /* * Some server BIOS may hide certain memory controllers, and the * EDAC driver skips those hidden memory controllers. However, the From 88cf6f3a824e2900bd005e658bd775028ca5a1b3 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 31 Jul 2025 22:55:29 +0800 Subject: [PATCH 02/19] EDAC/skx_common: Move mc_mapping to be a field inside struct skx_imc commit 59cfc06a874e01633865d0b05eba47b09b8b3a8f upstream. The mc_mapping and imc fields of struct skx_dev have the same size, NUM_IMC. Move mc_mapping to be a field inside struct skx_imc to prepare for making the imc array of memory controller instances a flexible array. No functional changes intended. Intel-SIG: commit 59cfc06a874e EDAC/skx_common: Move mc_mapping to be a field inside struct skx_imc Backport EDAC for DMR Suggested-by: Tony Luck Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://lore.kernel.org/r/20250731145534.2759334-3-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/skx_common.c | 8 ++++---- drivers/edac/skx_common.h | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index 24cdf57b20e37..c3cd478d02f26 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -133,7 +133,7 @@ static void skx_init_mc_mapping(struct skx_dev *d) * EDAC driver. */ for (int i = 0; i < NUM_IMC; i++) - d->mc_mapping[i] = i; + d->imc[i].mc_mapping = i; } void skx_set_mc_mapping(struct skx_dev *d, u8 pmc, u8 lmc) @@ -141,16 +141,16 @@ void skx_set_mc_mapping(struct skx_dev *d, u8 pmc, u8 lmc) edac_dbg(0, "Set the mapping of mc phy idx to logical idx: %02d -> %02d\n", pmc, lmc); - d->mc_mapping[pmc] = lmc; + d->imc[pmc].mc_mapping = lmc; } EXPORT_SYMBOL_GPL(skx_set_mc_mapping); static u8 skx_get_mc_mapping(struct skx_dev *d, u8 pmc) { edac_dbg(0, "Get the mapping of mc phy idx to logical idx: %02d -> %02d\n", - pmc, d->mc_mapping[pmc]); + pmc, d->imc[pmc].mc_mapping); - return d->mc_mapping[pmc]; + return d->imc[pmc].mc_mapping; } static bool skx_adxl_decode(struct decoded_addr *res, enum error_source err_src) diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index 3f6007a972679..95d61d23f89ef 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -135,16 +135,6 @@ struct skx_dev { struct pci_dev *pcu_cr3; /* for HBM memory detection */ u32 mcroute; int num_imc; - /* - * Some server BIOS may hide certain memory controllers, and the - * EDAC driver skips those hidden memory controllers. However, the - * ADXL still decodes memory error address using physical memory - * controller indices. The mapping table is used to convert the - * physical indices (reported by ADXL) to the logical indices - * (used the EDAC driver) of present memory controllers during the - * error handling process. - */ - u8 mc_mapping[NUM_IMC]; struct skx_imc { struct mem_ctl_info *mci; struct pci_dev *mdev; /* for i10nm CPU */ @@ -156,6 +146,16 @@ struct skx_dev { u8 mc; /* system wide mc# */ u8 lmc; /* socket relative mc# */ u8 src_id; + /* + * Some server BIOS may hide certain memory controllers, and the + * EDAC driver skips those hidden memory controllers. However, the + * ADXL still decodes memory error address using physical memory + * controller indices. The mapping table is used to convert the + * physical indices (reported by ADXL) to the logical indices + * (used the EDAC driver) of present memory controllers during the + * error handling process. + */ + u8 mc_mapping; struct skx_channel { struct pci_dev *cdev; struct pci_dev *edev; From 9b293522ebbcde4b75a0146a9174999730bfe821 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 31 Jul 2025 22:55:30 +0800 Subject: [PATCH 03/19] EDAC/skx_common: Swap memory controller index mapping commit 30b47b71fdc0091db5a4fa9503ad630aeb0ab3eb upstream. The current mapping of memory controller indices is from physical index [1] to logical index [2], as show below: skx_dev->imc[pmc].mc_mapping = lmc Since skx_dev->imc[] is an array of present memory controller instances, mapping memory controller indices from logical index to physical index, as show below, is more reasonable. This is also a preparatory step for making skx_dev->imc[] a flexible array. skx_dev->imc[lmc].mc_mapping = pmc Both mappings are equivalent. No functional changes intended. [1] Indices for memory controllers include both those present to the OS and those disabled by BIOS. [2] Indices for memory controllers present to the OS. Intel-SIG: commit 30b47b71fdc0 EDAC/skx_common: Swap memory controller index mapping Backport EDAC for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://lore.kernel.org/r/20250731145534.2759334-4-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/skx_common.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index c3cd478d02f26..e9af4f716a60e 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -132,7 +132,7 @@ static void skx_init_mc_mapping(struct skx_dev *d) * the logical indices of the memory controllers enumerated by the * EDAC driver. */ - for (int i = 0; i < NUM_IMC; i++) + for (int i = 0; i < d->num_imc; i++) d->imc[i].mc_mapping = i; } @@ -141,22 +141,28 @@ void skx_set_mc_mapping(struct skx_dev *d, u8 pmc, u8 lmc) edac_dbg(0, "Set the mapping of mc phy idx to logical idx: %02d -> %02d\n", pmc, lmc); - d->imc[pmc].mc_mapping = lmc; + d->imc[lmc].mc_mapping = pmc; } EXPORT_SYMBOL_GPL(skx_set_mc_mapping); -static u8 skx_get_mc_mapping(struct skx_dev *d, u8 pmc) +static int skx_get_mc_mapping(struct skx_dev *d, u8 pmc) { - edac_dbg(0, "Get the mapping of mc phy idx to logical idx: %02d -> %02d\n", - pmc, d->imc[pmc].mc_mapping); + for (int lmc = 0; lmc < d->num_imc; lmc++) { + if (d->imc[lmc].mc_mapping == pmc) { + edac_dbg(0, "Get the mapping of mc phy idx to logical idx: %02d -> %02d\n", + pmc, lmc); - return d->imc[pmc].mc_mapping; + return lmc; + } + } + + return -1; } static bool skx_adxl_decode(struct decoded_addr *res, enum error_source err_src) { + int i, lmc, len = 0; struct skx_dev *d; - int i, len = 0; if (res->addr >= skx_tohm || (res->addr >= skx_tolm && res->addr < BIT_ULL(32))) { @@ -220,7 +226,13 @@ static bool skx_adxl_decode(struct decoded_addr *res, enum error_source err_src) return false; } - res->imc = skx_get_mc_mapping(d, res->imc); + lmc = skx_get_mc_mapping(d, res->imc); + if (lmc < 0) { + skx_printk(KERN_ERR, "No lmc for imc %d\n", res->imc); + return false; + } + + res->imc = lmc; for (i = 0; i < adxl_component_count; i++) { if (adxl_values[i] == ~0x0ull) From 7c8adb70a4315c698830212876db58fcb1654155 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 31 Jul 2025 22:55:31 +0800 Subject: [PATCH 04/19] EDAC/skx_common: Make skx_dev->imc[] a flexible array commit 43060ca5332462903f57c2ff9b49b69124618070 upstream. The current skx->imc[NUM_IMC] array of memory controller instances is sized using the macro NUM_IMC. Each time EDAC support is added for a new CPU, NUM_IMC needs to be updated to ensure it is greater than or equal to the number of memory controllers for the new CPU. This approach is inconvenient and results in memory waste for older CPUs with fewer memory controllers. To address this, make skx->imc[] a flexible array and determine its size from configuration data or at runtime. Intel-SIG: commit 43060ca53324 EDAC/skx_common: Make skx_dev->imc[] a flexible array Backport EDAC for DMR Suggested-by: Tony Luck Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://lore.kernel.org/r/20250731145534.2759334-5-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/skx_common.c | 3 ++- drivers/edac/skx_common.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index e9af4f716a60e..e7851b92f1c20 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -345,7 +346,7 @@ int skx_get_all_bus_mappings(struct res_config *cfg, struct list_head **list) if (!pdev) break; ndev++; - d = kzalloc(sizeof(*d), GFP_KERNEL); + d = kzalloc(struct_size(d, imc, imc_num), GFP_KERNEL); if (!d) { pci_dev_put(pdev); return -ENOMEM; diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index 95d61d23f89ef..e7038fd45d063 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -172,7 +172,7 @@ struct skx_dev { u8 colbits; } dimms[NUM_DIMMS]; } chan[NUM_CHANNELS]; - } imc[NUM_IMC]; + } imc[]; }; struct skx_pvt { From 38a3a68d09e98d6a4a6b0a8077b97d0b9ec6edec Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Wed, 19 Nov 2025 12:11:40 -0800 Subject: [PATCH 05/19] EDAC/{skx_common,skx,i10nm}: Make skx_register_mci() independent of pci_dev commit b3d70059cbb262eef12927dbcf37ba8586d4a3ab upstream. Memory controllers in the new Intel server CPUs, such as Diamond Rapids, are presented as MMIO-based devices rather than PCI devices. Modify skx_register_mci() to be independent of 'pci_dev' and use a generic 'dev' of 'struct device' to prepare for support of such MMIO-based memory controllers. Intel-SIG: commit b3d70059cbb2 EDAC/{skx_common,skx,i10nm}: Make skx_register_mci() independent of pci_dev Backport EDAC for DMR Tested-by: Yi Lai Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20251119134132.2389472-2-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/i10nm_base.c | 3 ++- drivers/edac/skx_base.c | 4 ++-- drivers/edac/skx_common.c | 10 +++++----- drivers/edac/skx_common.h | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c index ab4d3e483618d..efcb910015ec0 100644 --- a/drivers/edac/i10nm_base.c +++ b/drivers/edac/i10nm_base.c @@ -1197,7 +1197,8 @@ static int __init i10nm_init(void) d->imc[i].num_dimms = cfg->ddr_dimm_num; } - rc = skx_register_mci(&d->imc[i], d->imc[i].mdev, + rc = skx_register_mci(&d->imc[i], &d->imc[i].mdev->dev, + pci_name(d->imc[i].mdev), "Intel_10nm Socket", EDAC_MOD_STR, i10nm_get_dimm_config, cfg); if (rc < 0) diff --git a/drivers/edac/skx_base.c b/drivers/edac/skx_base.c index 78282c8e3444e..8246e5dd2b61c 100644 --- a/drivers/edac/skx_base.c +++ b/drivers/edac/skx_base.c @@ -662,8 +662,8 @@ static int __init skx_init(void) d->imc[i].src_id = src_id; d->imc[i].num_channels = cfg->ddr_chan_num; d->imc[i].num_dimms = cfg->ddr_dimm_num; - - rc = skx_register_mci(&d->imc[i], d->imc[i].chan[0].cdev, + rc = skx_register_mci(&d->imc[i], &d->imc[i].chan[0].cdev->dev, + pci_name(d->imc[i].chan[0].cdev), "Skylake Socket", EDAC_MOD_STR, skx_get_dimm_config, cfg); if (rc < 0) diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index e7851b92f1c20..ef1b50d151d55 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -549,9 +549,9 @@ int skx_get_nvdimm_info(struct dimm_info *dimm, struct skx_imc *imc, } EXPORT_SYMBOL_GPL(skx_get_nvdimm_info); -int skx_register_mci(struct skx_imc *imc, struct pci_dev *pdev, - const char *ctl_name, const char *mod_str, - get_dimm_config_f get_dimm_config, +int skx_register_mci(struct skx_imc *imc, struct device *dev, + const char *dev_name, const char *ctl_name, + const char *mod_str, get_dimm_config_f get_dimm_config, struct res_config *cfg) { struct mem_ctl_info *mci; @@ -592,7 +592,7 @@ int skx_register_mci(struct skx_imc *imc, struct pci_dev *pdev, mci->edac_ctl_cap = EDAC_FLAG_NONE; mci->edac_cap = EDAC_FLAG_NONE; mci->mod_name = mod_str; - mci->dev_name = pci_name(pdev); + mci->dev_name = dev_name; mci->ctl_page_to_phys = NULL; rc = get_dimm_config(mci, cfg); @@ -600,7 +600,7 @@ int skx_register_mci(struct skx_imc *imc, struct pci_dev *pdev, goto fail; /* Record ptr to the generic device */ - mci->pdev = &pdev->dev; + mci->pdev = dev; /* Add this new MC control structure to EDAC's list of MCs */ if (unlikely(edac_mc_add_mc(mci))) { diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index e7038fd45d063..24e1a93bcd982 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -307,7 +307,7 @@ int skx_get_dimm_info(u32 mtr, u32 mcmtr, u32 amap, struct dimm_info *dimm, int skx_get_nvdimm_info(struct dimm_info *dimm, struct skx_imc *imc, int chan, int dimmno, const char *mod_str); -int skx_register_mci(struct skx_imc *imc, struct pci_dev *pdev, +int skx_register_mci(struct skx_imc *imc, struct device *dev, const char *dev_name, const char *ctl_name, const char *mod_str, get_dimm_config_f get_dimm_config, struct res_config *cfg); From 878af15f92a6def7b16cb5fbcdf7d061ebfa91b9 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Wed, 19 Nov 2025 12:11:40 -0800 Subject: [PATCH 06/19] EDAC/skx_common: Prepare for skx_get_edac_list() commit 9529e697739e2d4dc8c4129bbe91576f744f79f6 upstream. The Intel EDAC library 'skx_common' maintains the Intel server EDAC device list for {skx, i10nm}_edac drivers, which use skx_get_all_bus_mappings() to build and retrieve the EDAC device list. However, the upcoming Intel EDAC driver, imh_edac, for Diamond Rapids servers is designed for memory controllers that are MMIO-based devices rather than PCI devices. Consequently, it can't use skx_get_all_bus_mappings() due to the absence of a PCI bus. To accommodate this, prepare skx_get_edac_list() to enable the upcoming imh_edac driver to obtain the EDAC device list from the skx_common library and build the EDAC device list independently. Intel-SIG: commit 9529e697739e EDAC/skx_common: Prepare for skx_get_edac_list() Backport EDAC for DMR Tested-by: Yi Lai Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20251119134132.2389472-3-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/skx_common.c | 6 ++++++ drivers/edac/skx_common.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index ef1b50d151d55..f70666a19dffa 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -385,6 +385,12 @@ int skx_get_all_bus_mappings(struct res_config *cfg, struct list_head **list) } EXPORT_SYMBOL_GPL(skx_get_all_bus_mappings); +struct list_head *skx_get_edac_list(void) +{ + return &dev_edac_list; +} +EXPORT_SYMBOL_GPL(skx_get_edac_list); + int skx_get_hi_lo(unsigned int did, int off[], u64 *tolm, u64 *tohm) { struct pci_dev *pdev; diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index 24e1a93bcd982..cf364a7936f9d 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -298,6 +298,8 @@ int skx_get_src_id(struct skx_dev *d, int off, u8 *id); int skx_get_all_bus_mappings(struct res_config *cfg, struct list_head **list); +struct list_head *skx_get_edac_list(void); + int skx_get_hi_lo(unsigned int did, int off[], u64 *tolm, u64 *tohm); int skx_get_dimm_info(u32 mtr, u32 mcmtr, u32 amap, struct dimm_info *dimm, From 27666c837805fc18990fe0739c4aa8ea1698c7a7 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Wed, 19 Nov 2025 12:11:40 -0800 Subject: [PATCH 07/19] EDAC/skx_common: Prepare for skx_set_hi_lo() commit d4839582bc7002095b013acb4a2dcaa1438c41aa upstream. The upcoming imh_edac driver for Intel Diamond Rapids servers cannot use skx_get_hi_lo() in skx_common to retrieve the TOHM (Top of High Memory) and TOLM (Top of Low Memory) parameters. Instead, it obtains these parameters within its own EDAC driver. To accommodate this, prepare skx_set_hi_lo() to allow the driver to notify skx_common of these parameters. Intel-SIG: commit d4839582bc70 EDAC/skx_common: Prepare for skx_set_hi_lo() Backport EDAC for DMR Tested-by: Yi Lai Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20251119134132.2389472-4-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/skx_common.c | 7 +++++++ drivers/edac/skx_common.h | 1 + 2 files changed, 8 insertions(+) diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index f70666a19dffa..38603509efab0 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -431,6 +431,13 @@ int skx_get_hi_lo(unsigned int did, int off[], u64 *tolm, u64 *tohm) } EXPORT_SYMBOL_GPL(skx_get_hi_lo); +void skx_set_hi_lo(u64 tolm, u64 tohm) +{ + skx_tolm = tolm; + skx_tohm = tohm; +} +EXPORT_SYMBOL_GPL(skx_set_hi_lo); + static int skx_get_dimm_attr(u32 reg, int lobit, int hibit, int add, int minval, int maxval, const char *name) { diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index cf364a7936f9d..93f50ab4153f9 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -301,6 +301,7 @@ int skx_get_all_bus_mappings(struct res_config *cfg, struct list_head **list); struct list_head *skx_get_edac_list(void); int skx_get_hi_lo(unsigned int did, int off[], u64 *tolm, u64 *tohm); +void skx_set_hi_lo(u64 tolm, u64 tohm); int skx_get_dimm_info(u32 mtr, u32 mcmtr, u32 amap, struct dimm_info *dimm, struct skx_imc *imc, int chan, int dimmno, From bf4a949ec2c906bd70d33eec29a486fafbfea6be Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Wed, 19 Nov 2025 12:11:40 -0800 Subject: [PATCH 08/19] EDAC/{skx_common,imh}: Add EDAC driver for Intel Diamond Rapids servers commit 9fc67b11703fe9d8a5617ccacec2a452e455fa52 upstream. Intel Diamond Rapids CPUs include Integrated Memory and I/O Hubs (IMH). The memory controllers within the IMHs provide memory stacks to the processor. Create a new driver for this IMH-based memory controllers rather than applying additional patches to the existing i10nm_edac.c for the following reasons: 1) The memory controllers are not presented as PCI devices; instead, the detection and all their registers have been transitioned to MMIO-based memory spaces. 2) Validation processes are costly. Modifications to i10nm_edac would require extensive validation checks against multiple platforms, including Ice Lake, Sapphire Rapids, Emerald Rapids, Granite Rapids, Sierra Forest, and Grand Ridge. 3) Future Intel CPUs will likely only need patches on top of this new EDAC driver. Validation can be limited to Diamond Rapids servers and future Intel CPU generations. [Tony: Fix kerneldoc for struct local_reg] [randconfig: Added dependencies on NFIT and DMI] Intel-SIG: commit 9fc67b11703f EDAC/{skx_common,imh}: Add EDAC driver for Intel Diamond Rapids servers Backport EDAC for DMR Tested-by: Yi Lai Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20251119134132.2389472-5-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/Kconfig | 12 + drivers/edac/Makefile | 3 + drivers/edac/imh_base.c | 562 ++++++++++++++++++++++++++++++++++++++ drivers/edac/skx_common.c | 8 +- drivers/edac/skx_common.h | 89 ++++-- 5 files changed, 648 insertions(+), 26 deletions(-) create mode 100644 drivers/edac/imh_base.c diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index 41a215f230f0f..ab43465e19a7e 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -263,6 +263,18 @@ config EDAC_I10NM system has non-volatile DIMMs you should also manually select CONFIG_ACPI_NFIT. +config EDAC_IMH + tristate "Intel Integrated Memory/IO Hub MC" + depends on X86_64 && X86_MCE_INTEL && ACPI + depends on ACPI_NFIT || !ACPI_NFIT # if ACPI_NFIT=m, EDAC_IMH can't be y + select DMI + select ACPI_ADXL + help + Support for error detection and correction the Intel + Integrated Memory/IO Hub Memory Controller. This MC IP is + first used on the Diamond Rapids servers but may appear on + others in the future. + config EDAC_PND2 tristate "Intel Pondicherry2" depends on PCI && X86_64 && X86_MCE_INTEL diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index 446364264e2b1..b0a121bf4e1e8 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -67,6 +67,9 @@ obj-$(CONFIG_EDAC_PPC4XX) += ppc4xx_edac.o obj-$(CONFIG_EDAC_AMD8111) += amd8111_edac.o obj-$(CONFIG_EDAC_AMD8131) += amd8131_edac.o +imh_edac-y := imh_base.o +obj-$(CONFIG_EDAC_IMH) += imh_edac.o skx_edac_common.o + obj-$(CONFIG_EDAC_HIGHBANK_MC) += highbank_mc_edac.o obj-$(CONFIG_EDAC_HIGHBANK_L2) += highbank_l2_edac.o diff --git a/drivers/edac/imh_base.c b/drivers/edac/imh_base.c new file mode 100644 index 0000000000000..2be27be783907 --- /dev/null +++ b/drivers/edac/imh_base.c @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Intel(R) servers with Integrated Memory/IO Hub-based memory controller. + * Copyright (c) 2025, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include "edac_module.h" +#include "skx_common.h" + +#define IMH_REVISION "v0.0.1" +#define EDAC_MOD_STR "imh_edac" + +/* Debug macros */ +#define imh_printk(level, fmt, arg...) \ + edac_printk(level, "imh", fmt, ##arg) + +/* Configuration Agent(Ubox) */ +#define MMIO_BASE_H(reg) (((u64)GET_BITFIELD(reg, 0, 29)) << 23) +#define SOCKET_ID(reg) GET_BITFIELD(reg, 0, 3) + +/* PUNIT */ +#define DDR_IMC_BITMAP(reg) GET_BITFIELD(reg, 23, 30) + +/* Memory Controller */ +#define ECC_ENABLED(reg) GET_BITFIELD(reg, 2, 2) +#define DIMM_POPULATED(reg) GET_BITFIELD(reg, 15, 15) + +/* System Cache Agent(SCA) */ +#define TOLM(reg) (((u64)GET_BITFIELD(reg, 16, 31)) << 16) +#define TOHM(reg) (((u64)GET_BITFIELD(reg, 16, 51)) << 16) + +/** + * struct local_reg - A register as described in the local package view. + * + * @pkg: (input) The package where the register is located. + * @pbase: (input) The IP MMIO base physical address in the local package view. + * @size: (input) The IP MMIO size. + * @offset: (input) The register offset from the IP MMIO base @pbase. + * @width: (input) The register width in byte. + * @vbase: (internal) The IP MMIO base virtual address. + * @val: (output) The register value. + */ +struct local_reg { + int pkg; + u64 pbase; + u32 size; + u32 offset; + u8 width; + void __iomem *vbase; + u64 val; +}; + +#define DEFINE_LOCAL_REG(name, cfg, package, north, ip_name, ip_idx, reg_name) \ + struct local_reg name = { \ + .pkg = package, \ + .pbase = (north ? (cfg)->mmio_base_l_north : \ + (cfg)->mmio_base_l_south) + \ + (cfg)->ip_name##_base + \ + (cfg)->ip_name##_size * (ip_idx), \ + .size = (cfg)->ip_name##_size, \ + .offset = (cfg)->ip_name##_reg_##reg_name##_offset, \ + .width = (cfg)->ip_name##_reg_##reg_name##_width, \ + } + +static u64 readx(void __iomem *addr, u8 width) +{ + switch (width) { + case 1: + return readb(addr); + case 2: + return readw(addr); + case 4: + return readl(addr); + case 8: + return readq(addr); + default: + imh_printk(KERN_ERR, "Invalid reg 0x%p width %d\n", addr, width); + return 0; + } +} + +static void __read_local_reg(void *reg) +{ + struct local_reg *r = (struct local_reg *)reg; + + r->val = readx(r->vbase + r->offset, r->width); +} + +/* Read a local-view register. */ +static bool read_local_reg(struct local_reg *reg) +{ + int cpu; + + /* Get the target CPU in the package @reg->pkg. */ + for_each_online_cpu(cpu) { + if (reg->pkg == topology_physical_package_id(cpu)) + break; + } + + if (cpu >= nr_cpu_ids) + return false; + + reg->vbase = ioremap(reg->pbase, reg->size); + if (!reg->vbase) { + imh_printk(KERN_ERR, "Failed to ioremap 0x%llx\n", reg->pbase); + return false; + } + + /* Get the target CPU to read the register. */ + smp_call_function_single(cpu, __read_local_reg, reg, 1); + iounmap(reg->vbase); + + return true; +} + +/* Get the bitmap of memory controller instances in package @pkg. */ +static u32 get_imc_bitmap(struct res_config *cfg, int pkg, bool north) +{ + DEFINE_LOCAL_REG(reg, cfg, pkg, north, pcu, 0, capid3); + + if (!read_local_reg(®)) + return 0; + + edac_dbg(2, "Pkg%d %s mc instances bitmap 0x%llx (reg 0x%llx)\n", + pkg, north ? "north" : "south", + DDR_IMC_BITMAP(reg.val), reg.val); + + return DDR_IMC_BITMAP(reg.val); +} + +static void imc_release(struct device *dev) +{ + edac_dbg(2, "imc device %s released\n", dev_name(dev)); + kfree(dev); +} + +static int __get_ddr_munits(struct res_config *cfg, struct skx_dev *d, + bool north, int lmc) +{ + unsigned long size = cfg->ddr_chan_mmio_sz * cfg->ddr_chan_num; + unsigned long bitmap = get_imc_bitmap(cfg, d->pkg, north); + void __iomem *mbase; + struct device *dev; + int i, rc, pmc; + u64 base; + + for_each_set_bit(i, &bitmap, sizeof(bitmap) * 8) { + base = north ? d->mmio_base_h_north : d->mmio_base_h_south; + base += cfg->ddr_imc_base + size * i; + + edac_dbg(2, "Pkg%d mc%d mmio base 0x%llx size 0x%lx\n", + d->pkg, lmc, base, size); + + /* Set up the imc MMIO. */ + mbase = ioremap(base, size); + if (!mbase) { + imh_printk(KERN_ERR, "Failed to ioremap 0x%llx\n", base); + return -ENOMEM; + } + + d->imc[lmc].mbase = mbase; + d->imc[lmc].lmc = lmc; + + /* Create the imc device instance. */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->release = imc_release; + device_initialize(dev); + rc = dev_set_name(dev, "0x%llx", base); + if (rc) { + imh_printk(KERN_ERR, "Failed to set dev name\n"); + put_device(dev); + return rc; + } + + d->imc[lmc].dev = dev; + + /* Set up the imc index mapping. */ + pmc = north ? i : 8 + i; + skx_set_mc_mapping(d, pmc, lmc); + + lmc++; + } + + return lmc; +} + +static bool get_ddr_munits(struct res_config *cfg, struct skx_dev *d) +{ + int lmc = __get_ddr_munits(cfg, d, true, 0); + + if (lmc < 0) + return false; + + lmc = __get_ddr_munits(cfg, d, false, lmc); + if (lmc <= 0) + return false; + + return true; +} + +static bool get_socket_id(struct res_config *cfg, struct skx_dev *d) +{ + DEFINE_LOCAL_REG(reg, cfg, d->pkg, true, ubox, 0, socket_id); + u8 src_id; + int i; + + if (!read_local_reg(®)) + return false; + + src_id = SOCKET_ID(reg.val); + edac_dbg(2, "socket id 0x%x (reg 0x%llx)\n", src_id, reg.val); + + for (i = 0; i < cfg->ddr_imc_num; i++) + d->imc[i].src_id = src_id; + + return true; +} + +/* Get TOLM (Top Of Low Memory) and TOHM (Top Of High Memory) parameters. */ +static bool imh_get_tolm_tohm(struct res_config *cfg, u64 *tolm, u64 *tohm) +{ + DEFINE_LOCAL_REG(reg, cfg, 0, true, sca, 0, tolm); + + if (!read_local_reg(®)) + return false; + + *tolm = TOLM(reg.val); + edac_dbg(2, "tolm 0x%llx (reg 0x%llx)\n", *tolm, reg.val); + + DEFINE_LOCAL_REG(reg2, cfg, 0, true, sca, 0, tohm); + + if (!read_local_reg(®2)) + return false; + + *tohm = TOHM(reg2.val); + edac_dbg(2, "tohm 0x%llx (reg 0x%llx)\n", *tohm, reg2.val); + + return true; +} + +/* Get the system-view MMIO_BASE_H for {north,south}-IMH. */ +static int imh_get_all_mmio_base_h(struct res_config *cfg, struct list_head *edac_list) +{ + int i, n = topology_max_packages(), imc_num = cfg->ddr_imc_num + cfg->hbm_imc_num; + struct skx_dev *d; + + for (i = 0; i < n; i++) { + d = kzalloc(struct_size(d, imc, imc_num), GFP_KERNEL); + if (!d) + return -ENOMEM; + + DEFINE_LOCAL_REG(reg, cfg, i, true, ubox, 0, mmio_base); + + /* Get MMIO_BASE_H for the north-IMH. */ + if (!read_local_reg(®) || !reg.val) { + kfree(d); + imh_printk(KERN_ERR, "Pkg%d has no north mmio_base_h\n", i); + return -ENODEV; + } + + d->mmio_base_h_north = MMIO_BASE_H(reg.val); + edac_dbg(2, "Pkg%d north mmio_base_h 0x%llx (reg 0x%llx)\n", + i, d->mmio_base_h_north, reg.val); + + /* Get MMIO_BASE_H for the south-IMH (optional). */ + DEFINE_LOCAL_REG(reg2, cfg, i, false, ubox, 0, mmio_base); + + if (read_local_reg(®2)) { + d->mmio_base_h_south = MMIO_BASE_H(reg2.val); + edac_dbg(2, "Pkg%d south mmio_base_h 0x%llx (reg 0x%llx)\n", + i, d->mmio_base_h_south, reg2.val); + } + + d->pkg = i; + d->num_imc = imc_num; + skx_init_mc_mapping(d); + list_add_tail(&d->list, edac_list); + } + + return 0; +} + +/* Get the number of per-package memory controllers. */ +static int imh_get_imc_num(struct res_config *cfg) +{ + int imc_num = hweight32(get_imc_bitmap(cfg, 0, true)) + + hweight32(get_imc_bitmap(cfg, 0, false)); + + if (!imc_num) { + imh_printk(KERN_ERR, "Invalid mc number\n"); + return -ENODEV; + } + + if (cfg->ddr_imc_num != imc_num) { + /* + * Update the configuration data to reflect the number of + * present DDR memory controllers. + */ + cfg->ddr_imc_num = imc_num; + edac_dbg(2, "Set ddr mc number %d\n", imc_num); + } + + return 0; +} + +/* Get all memory controllers' parameters. */ +static int imh_get_munits(struct res_config *cfg, struct list_head *edac_list) +{ + struct skx_imc *imc; + struct skx_dev *d; + u8 mc = 0; + int i; + + list_for_each_entry(d, edac_list, list) { + if (!get_ddr_munits(cfg, d)) { + imh_printk(KERN_ERR, "No mc found\n"); + return -ENODEV; + } + + if (!get_socket_id(cfg, d)) { + imh_printk(KERN_ERR, "Failed to get socket id\n"); + return -ENODEV; + } + + for (i = 0; i < cfg->ddr_imc_num; i++) { + imc = &d->imc[i]; + if (!imc->mbase) + continue; + + imc->chan_mmio_sz = cfg->ddr_chan_mmio_sz; + imc->num_channels = cfg->ddr_chan_num; + imc->num_dimms = cfg->ddr_dimm_num; + imc->mc = mc++; + } + } + + return 0; +} + +/* Helpers to read memory controller registers */ +static u64 read_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width) +{ + return readx(imc->mbase + imc->chan_mmio_sz * chan + offset, width); +} + +static u32 read_imc_mcmtr(struct res_config *cfg, struct skx_imc *imc, int chan) +{ + return (u32)read_imc_reg(imc, chan, cfg->ddr_reg_mcmtr_offset, cfg->ddr_reg_mcmtr_width); +} + +static u32 read_imc_dimmmtr(struct res_config *cfg, struct skx_imc *imc, int chan, int dimm) +{ + return (u32)read_imc_reg(imc, chan, cfg->ddr_reg_dimmmtr_offset + + cfg->ddr_reg_dimmmtr_width * dimm, + cfg->ddr_reg_dimmmtr_width); +} + +static bool ecc_enabled(u32 mcmtr) +{ + return (bool)ECC_ENABLED(mcmtr); +} + +static bool dimm_populated(u32 dimmmtr) +{ + return (bool)DIMM_POPULATED(dimmmtr); +} + +/* Get each DIMM's configurations of the memory controller @mci. */ +static int imh_get_dimm_config(struct mem_ctl_info *mci, struct res_config *cfg) +{ + struct skx_pvt *pvt = mci->pvt_info; + struct skx_imc *imc = pvt->imc; + struct dimm_info *dimm; + u32 mcmtr, dimmmtr; + int i, j, ndimms; + + for (i = 0; i < imc->num_channels; i++) { + if (!imc->mbase) + continue; + + mcmtr = read_imc_mcmtr(cfg, imc, i); + + for (ndimms = 0, j = 0; j < imc->num_dimms; j++) { + dimmmtr = read_imc_dimmmtr(cfg, imc, i, j); + edac_dbg(1, "mcmtr 0x%x dimmmtr 0x%x (mc%d ch%d dimm%d)\n", + mcmtr, dimmmtr, imc->mc, i, j); + + if (!dimm_populated(dimmmtr)) + continue; + + dimm = edac_get_dimm(mci, i, j, 0); + ndimms += skx_get_dimm_info(dimmmtr, 0, 0, dimm, + imc, i, j, cfg); + } + + if (ndimms && !ecc_enabled(mcmtr)) { + imh_printk(KERN_ERR, "ECC is disabled on mc%d ch%d\n", + imc->mc, i); + return -ENODEV; + } + } + + return 0; +} + +/* Register all memory controllers to the EDAC core. */ +static int imh_register_mci(struct res_config *cfg, struct list_head *edac_list) +{ + struct skx_imc *imc; + struct skx_dev *d; + int i, rc; + + list_for_each_entry(d, edac_list, list) { + for (i = 0; i < cfg->ddr_imc_num; i++) { + imc = &d->imc[i]; + if (!imc->mbase) + continue; + + rc = skx_register_mci(imc, imc->dev, + dev_name(imc->dev), + "Intel IMH-based Socket", + EDAC_MOD_STR, + imh_get_dimm_config, cfg); + if (rc) + return rc; + } + } + + return 0; +} + +static struct res_config dmr_cfg = { + .type = DMR, + .support_ddr5 = true, + .mmio_base_l_north = 0xf6800000, + .mmio_base_l_south = 0xf6000000, + .ddr_chan_num = 1, + .ddr_dimm_num = 2, + .ddr_imc_base = 0x39b000, + .ddr_chan_mmio_sz = 0x8000, + .ddr_reg_mcmtr_offset = 0x360, + .ddr_reg_mcmtr_width = 4, + .ddr_reg_dimmmtr_offset = 0x370, + .ddr_reg_dimmmtr_width = 4, + .ubox_base = 0x0, + .ubox_size = 0x2000, + .ubox_reg_mmio_base_offset = 0x580, + .ubox_reg_mmio_base_width = 4, + .ubox_reg_socket_id_offset = 0x1080, + .ubox_reg_socket_id_width = 4, + .pcu_base = 0x3000, + .pcu_size = 0x10000, + .pcu_reg_capid3_offset = 0x290, + .pcu_reg_capid3_width = 4, + .sca_base = 0x24c000, + .sca_size = 0x2500, + .sca_reg_tolm_offset = 0x2100, + .sca_reg_tolm_width = 8, + .sca_reg_tohm_offset = 0x2108, + .sca_reg_tohm_width = 8, +}; + +static const struct x86_cpu_id imh_cpuids[] = { + X86_MATCH_VFM(INTEL_DIAMONDRAPIDS_X, &dmr_cfg), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, imh_cpuids); + +static struct notifier_block imh_mce_dec = { + .notifier_call = skx_mce_check_error, + .priority = MCE_PRIO_EDAC, +}; + +static int __init imh_init(void) +{ + const struct x86_cpu_id *id; + struct list_head *edac_list; + struct res_config *cfg; + const char *owner; + u64 tolm, tohm; + int rc; + + edac_dbg(2, "\n"); + + if (ghes_get_devices()) + return -EBUSY; + + owner = edac_get_owner(); + if (owner && strncmp(owner, EDAC_MOD_STR, sizeof(EDAC_MOD_STR))) + return -EBUSY; + + if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) + return -ENODEV; + + id = x86_match_cpu(imh_cpuids); + if (!id) + return -ENODEV; + cfg = (struct res_config *)id->driver_data; + skx_set_res_cfg(cfg); + + if (!imh_get_tolm_tohm(cfg, &tolm, &tohm)) + return -ENODEV; + + skx_set_hi_lo(tolm, tohm); + + rc = imh_get_imc_num(cfg); + if (rc < 0) + goto fail; + + edac_list = skx_get_edac_list(); + + rc = imh_get_all_mmio_base_h(cfg, edac_list); + if (rc) + goto fail; + + rc = imh_get_munits(cfg, edac_list); + if (rc) + goto fail; + + rc = imh_register_mci(cfg, edac_list); + if (rc) + goto fail; + + rc = skx_adxl_get(); + if (rc) + goto fail; + + opstate_init(); + mce_register_decode_chain(&imh_mce_dec); + + imh_printk(KERN_INFO, "%s\n", IMH_REVISION); + + return 0; +fail: + skx_remove(); + return rc; +} + +static void __exit imh_exit(void) +{ + edac_dbg(2, "\n"); + + mce_unregister_decode_chain(&imh_mce_dec); + skx_adxl_put(); + skx_remove(); +} + +module_init(imh_init); +module_exit(imh_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Qiuxu Zhuo"); +MODULE_DESCRIPTION("MC Driver for Intel servers using IMH-based memory controller"); diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index 38603509efab0..3f2b6c4e16e6b 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -125,7 +125,7 @@ void skx_adxl_put(void) } EXPORT_SYMBOL_GPL(skx_adxl_put); -static void skx_init_mc_mapping(struct skx_dev *d) +void skx_init_mc_mapping(struct skx_dev *d) { /* * By default, the BIOS presents all memory controllers within each @@ -136,6 +136,7 @@ static void skx_init_mc_mapping(struct skx_dev *d) for (int i = 0; i < d->num_imc; i++) d->imc[i].mc_mapping = i; } +EXPORT_SYMBOL_GPL(skx_init_mc_mapping); void skx_set_mc_mapping(struct skx_dev *d, u8 pmc, u8 lmc) { @@ -827,6 +828,9 @@ void skx_remove(void) if (d->imc[i].mbase) iounmap(d->imc[i].mbase); + if (d->imc[i].dev) + put_device(d->imc[i].dev); + for (j = 0; j < d->imc[i].num_channels; j++) { if (d->imc[i].chan[j].cdev) pci_dev_put(d->imc[i].chan[j].cdev); @@ -850,7 +854,7 @@ EXPORT_SYMBOL_GPL(skx_remove); /* * Debug feature. * Exercise the address decode logic by writing an address to - * /sys/kernel/debug/edac/{skx,i10nm}_test/addr. + * /sys/kernel/debug/edac/{skx,i10nm,imh}_test/addr. */ static struct dentry *skx_test; diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index 93f50ab4153f9..b970e8f4680d8 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -126,20 +126,33 @@ struct reg_rrl { * memory controllers on the die. */ struct skx_dev { - struct list_head list; + /* {skx,i10nm}_edac */ u8 bus[4]; int seg; struct pci_dev *sad_all; struct pci_dev *util_all; - struct pci_dev *uracu; /* for i10nm CPU */ - struct pci_dev *pcu_cr3; /* for HBM memory detection */ + struct pci_dev *uracu; + struct pci_dev *pcu_cr3; u32 mcroute; + + /* imh_edac */ + /* System-view MMIO base physical addresses. */ + u64 mmio_base_h_north; + u64 mmio_base_h_south; + int pkg; + int num_imc; + struct list_head list; struct skx_imc { + /* i10nm_edac */ + struct pci_dev *mdev; + + /* imh_edac */ + struct device *dev; + struct mem_ctl_info *mci; - struct pci_dev *mdev; /* for i10nm CPU */ - void __iomem *mbase; /* for i10nm CPU */ - int chan_mmio_sz; /* for i10nm CPU */ + void __iomem *mbase; + int chan_mmio_sz; int num_channels; /* channels per memory controller */ int num_dimms; /* dimms per channel */ bool hbm_mc; @@ -183,7 +196,8 @@ enum type { SKX, I10NM, SPR, - GNR + GNR, + DMR, }; enum { @@ -242,10 +256,6 @@ struct pci_bdf { struct res_config { enum type type; - /* Configuration agent device ID */ - unsigned int decs_did; - /* Default bus number configuration register offset */ - int busno_cfg_offset; /* DDR memory controllers per socket */ int ddr_imc_num; /* DDR channels per DDR memory controller */ @@ -263,23 +273,53 @@ struct res_config { /* Per HBM channel memory-mapped I/O size */ int hbm_chan_mmio_sz; bool support_ddr5; - /* SAD device BDF */ - struct pci_bdf sad_all_bdf; - /* PCU device BDF */ - struct pci_bdf pcu_cr3_bdf; - /* UTIL device BDF */ - struct pci_bdf util_all_bdf; - /* URACU device BDF */ - struct pci_bdf uracu_bdf; - /* DDR mdev device BDF */ - struct pci_bdf ddr_mdev_bdf; - /* HBM mdev device BDF */ - struct pci_bdf hbm_mdev_bdf; - int sad_all_offset; /* RRL register sets per DDR channel */ struct reg_rrl *reg_rrl_ddr; /* RRL register sets per HBM channel */ struct reg_rrl *reg_rrl_hbm[2]; + union { + /* {skx,i10nm}_edac */ + struct { + /* Configuration agent device ID */ + unsigned int decs_did; + /* Default bus number configuration register offset */ + int busno_cfg_offset; + struct pci_bdf sad_all_bdf; + struct pci_bdf pcu_cr3_bdf; + struct pci_bdf util_all_bdf; + struct pci_bdf uracu_bdf; + struct pci_bdf ddr_mdev_bdf; + struct pci_bdf hbm_mdev_bdf; + int sad_all_offset; + }; + /* imh_edac */ + struct { + /* MMIO base physical address in local package view */ + u64 mmio_base_l_north; + u64 mmio_base_l_south; + u64 ddr_imc_base; + u64 ddr_reg_mcmtr_offset; + u8 ddr_reg_mcmtr_width; + u64 ddr_reg_dimmmtr_offset; + u8 ddr_reg_dimmmtr_width; + u64 ubox_base; + u32 ubox_size; + u32 ubox_reg_mmio_base_offset; + u8 ubox_reg_mmio_base_width; + u32 ubox_reg_socket_id_offset; + u8 ubox_reg_socket_id_width; + u64 pcu_base; + u32 pcu_size; + u32 pcu_reg_capid3_offset; + u8 pcu_reg_capid3_width; + u64 sca_base; + u32 sca_size; + u32 sca_reg_tolm_offset; + u8 sca_reg_tolm_width; + u32 sca_reg_tohm_offset; + u8 sca_reg_tohm_width; + }; + }; }; typedef int (*get_dimm_config_f)(struct mem_ctl_info *mci, @@ -292,6 +332,7 @@ void skx_adxl_put(void); void skx_set_decode(skx_decode_f decode, skx_show_retry_log_f show_retry_log); void skx_set_mem_cfg(bool mem_cfg_2lm); void skx_set_res_cfg(struct res_config *cfg); +void skx_init_mc_mapping(struct skx_dev *d); void skx_set_mc_mapping(struct skx_dev *d, u8 pmc, u8 lmc); int skx_get_src_id(struct skx_dev *d, int off, u8 *id); From 7f44b87483914a2809befd53630c91d4cbc4c42e Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Wed, 19 Nov 2025 12:11:40 -0800 Subject: [PATCH 09/19] EDAC/skx_common: Extend the maximum number of DRAM chip row bits commit 39abdcbdad597b1ac3dabd44a757de91b87c683a upstream. The allowed maximum number of row bits for DRAM chips in the Diamond Rapids server processor is up to 19. Extend the current maximum row bits from 18 to 19. Intel-SIG: commit 39abdcbdad59 EDAC/skx_common: Extend the maximum number of DRAM chip row bits Backport EDAC for DMR Tested-by: Yi Lai Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20251119134132.2389472-6-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/skx_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index 3f2b6c4e16e6b..4ed4f5a465e08 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -452,7 +452,7 @@ static int skx_get_dimm_attr(u32 reg, int lobit, int hibit, int add, } #define numrank(reg) skx_get_dimm_attr(reg, 12, 13, 0, 0, 2, "ranks") -#define numrow(reg) skx_get_dimm_attr(reg, 2, 4, 12, 1, 6, "rows") +#define numrow(reg) skx_get_dimm_attr(reg, 2, 4, 12, 1, 7, "rows") #define numcol(reg) skx_get_dimm_attr(reg, 0, 1, 10, 0, 2, "cols") int skx_get_dimm_info(u32 mtr, u32 mcmtr, u32 amap, struct dimm_info *dimm, From cc44e58bb48bf4f334b30e3635da05a03566a860 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Wed, 19 Nov 2025 12:11:40 -0800 Subject: [PATCH 10/19] EDAC/{skx_comm,imh}: Detect 2-level memory configuration commit f619613f3058dee38f50b116ec774c5295c8b08b upstream. Detect 2-level memory configurations and notify the 'skx_common' library to enable ADXL 2-level memory error decoding. Intel-SIG: commit f619613f3058 EDAC/{skx_comm,imh}: Detect 2-level memory configuration Backport EDAC for DMR Tested-by: Yi Lai Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20251119134132.2389472-7-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/imh_base.c | 38 ++++++++++++++++++++++++++++++++++++++ drivers/edac/skx_common.h | 4 ++++ 2 files changed, 42 insertions(+) diff --git a/drivers/edac/imh_base.c b/drivers/edac/imh_base.c index 2be27be783907..2b0db6321da0a 100644 --- a/drivers/edac/imh_base.c +++ b/drivers/edac/imh_base.c @@ -35,6 +35,9 @@ #define TOLM(reg) (((u64)GET_BITFIELD(reg, 16, 31)) << 16) #define TOHM(reg) (((u64)GET_BITFIELD(reg, 16, 51)) << 16) +/* Home Agent (HA) */ +#define NMCACHING(reg) GET_BITFIELD(reg, 8, 8) + /** * struct local_reg - A register as described in the local package view. * @@ -346,6 +349,35 @@ static int imh_get_munits(struct res_config *cfg, struct list_head *edac_list) return 0; } +static bool check_2lm_enabled(struct res_config *cfg, struct skx_dev *d, int ha_idx) +{ + DEFINE_LOCAL_REG(reg, cfg, d->pkg, true, ha, ha_idx, mode); + + if (!read_local_reg(®)) + return false; + + if (!NMCACHING(reg.val)) + return false; + + edac_dbg(2, "2-level memory configuration (reg 0x%llx, ha idx %d)\n", reg.val, ha_idx); + return true; +} + +/* Check whether the system has a 2-level memory configuration. */ +static bool imh_2lm_enabled(struct res_config *cfg, struct list_head *head) +{ + struct skx_dev *d; + int i; + + list_for_each_entry(d, head, list) { + for (i = 0; i < cfg->ddr_imc_num; i++) + if (check_2lm_enabled(cfg, d, i)) + return true; + } + + return false; +} + /* Helpers to read memory controller registers */ static u64 read_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width) { @@ -467,6 +499,10 @@ static struct res_config dmr_cfg = { .sca_reg_tolm_width = 8, .sca_reg_tohm_offset = 0x2108, .sca_reg_tohm_width = 8, + .ha_base = 0x3eb000, + .ha_size = 0x1000, + .ha_reg_mode_offset = 0x4a0, + .ha_reg_mode_width = 4, }; static const struct x86_cpu_id imh_cpuids[] = { @@ -526,6 +562,8 @@ static int __init imh_init(void) if (rc) goto fail; + skx_set_mem_cfg(imh_2lm_enabled(cfg, edac_list)); + rc = imh_register_mci(cfg, edac_list); if (rc) goto fail; diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index b970e8f4680d8..0ca6c9cd46a9d 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -318,6 +318,10 @@ struct res_config { u8 sca_reg_tolm_width; u32 sca_reg_tohm_offset; u8 sca_reg_tohm_width; + u64 ha_base; + u32 ha_size; + u32 ha_reg_mode_offset; + u8 ha_reg_mode_width; }; }; }; From 7c142fc14591fbd1bd189205dbbe8a5d9a1f99c8 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Wed, 19 Nov 2025 12:11:40 -0800 Subject: [PATCH 11/19] EDAC/imh: Setup 'imh_test' debugfs testing node commit 5f40ea7f41773d996d92db8bde600199200adc11 upstream. Setup the following debugfs testing node to enable fake memory error address decoding tests for the imh_edac driver. /sys/kernel/debug/edac/imh_test/addr Intel-SIG: commit 5f40ea7f4177 EDAC/imh: Setup 'imh_test' debugfs testing node Backport EDAC for DMR Tested-by: Yi Lai Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20251119134132.2389472-8-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/imh_base.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/edac/imh_base.c b/drivers/edac/imh_base.c index 2b0db6321da0a..4348b3883b457 100644 --- a/drivers/edac/imh_base.c +++ b/drivers/edac/imh_base.c @@ -574,6 +574,7 @@ static int __init imh_init(void) opstate_init(); mce_register_decode_chain(&imh_mce_dec); + skx_setup_debug("imh_test"); imh_printk(KERN_INFO, "%s\n", IMH_REVISION); @@ -587,6 +588,7 @@ static void __exit imh_exit(void) { edac_dbg(2, "\n"); + skx_teardown_debug(); mce_unregister_decode_chain(&imh_mce_dec); skx_adxl_put(); skx_remove(); From 03ea62c5c117a6b7cbb42d19baa5a2c393b38c9f Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 21 May 2026 15:31:05 +0800 Subject: [PATCH 12/19] EDAC/{skx_common,i10nm,imh}: Move MC register access helpers to skx_common commit bebda0aba2cf64271ded37746c48f9ab6652ca31 upstream. Both i10nm_basic.c and imh_basic.c use identical helpers for accessing memory controller MMIO-based registers. Move these helpers to skx_common.c to eliminate code duplication. This change also prepares for an upcoming patch that will move RRL(retry_rd_err_log) code from i10nm_basic.c to skx_common.c, which requires these helpers to be available in skx_common.c. Additionally, prefix these function names with 'skx_' to maintain naming consistency within the file. No functional changes intended. Intel-SIG: commit bebda0aba2cf EDAC/{skx_common,i10nm,imh}: Move MC register access helpers to skx_common Backport EDAC retry_rd_error_log support for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Yi Lai Link: https://patch.msgid.link/20260521073112.3881223-2-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/i10nm_base.c | 39 ++++-------------------------- drivers/edac/imh_base.c | 33 ++++---------------------- drivers/edac/skx_common.c | 50 +++++++++++++++++++++++++++++++++++++++ drivers/edac/skx_common.h | 3 +++ 4 files changed, 63 insertions(+), 62 deletions(-) diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c index efcb910015ec0..df719700632a3 100644 --- a/drivers/edac/i10nm_base.c +++ b/drivers/edac/i10nm_base.c @@ -47,12 +47,6 @@ readl((m)->mbase + ((m)->hbm_mc ? 0xef8 : \ (res_cfg->type == GNR ? 0xaf8 : 0x20ef8)) + \ (i) * (m)->chan_mmio_sz) -#define I10NM_GET_REG32(m, i, offset) \ - readl((m)->mbase + (i) * (m)->chan_mmio_sz + (offset)) -#define I10NM_GET_REG64(m, i, offset) \ - readq((m)->mbase + (i) * (m)->chan_mmio_sz + (offset)) -#define I10NM_SET_REG32(m, i, offset, v) \ - writel(v, (m)->mbase + (i) * (m)->chan_mmio_sz + (offset)) #define I10NM_GET_SCK_MMIO_BASE(reg) (GET_BITFIELD(reg, 0, 28) << 23) #define I10NM_GET_IMC_MMIO_OFFSET(reg) (GET_BITFIELD(reg, 0, 10) << 12) @@ -189,29 +183,6 @@ static struct reg_rrl gnr_reg_rrl_ddr = { .cecnt_widths = {4, 4, 4, 4, 4, 4, 4, 4}, }; -static u64 read_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width) -{ - switch (width) { - case 4: - return I10NM_GET_REG32(imc, chan, offset); - case 8: - return I10NM_GET_REG64(imc, chan, offset); - default: - i10nm_printk(KERN_ERR, "Invalid readd RRL 0x%x width %d\n", offset, width); - return 0; - } -} - -static void write_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width, u64 val) -{ - switch (width) { - case 4: - return I10NM_SET_REG32(imc, chan, offset, (u32)val); - default: - i10nm_printk(KERN_ERR, "Invalid write RRL 0x%x width %d\n", offset, width); - } -} - static void enable_rrl(struct skx_imc *imc, int chan, struct reg_rrl *rrl, int rrl_set, bool enable, u32 *rrl_ctl) { @@ -225,7 +196,7 @@ static void enable_rrl(struct skx_imc *imc, int chan, struct reg_rrl *rrl, /* Patrol scrub or on-demand read error. */ scrub = (mode == FRE_SCRUB || mode == LRE_SCRUB); - v = read_imc_reg(imc, chan, offset, width); + v = skx_read_imc_reg(imc, chan, offset, width); if (enable) { /* Save default configurations. */ @@ -268,7 +239,7 @@ static void enable_rrl(struct skx_imc *imc, int chan, struct reg_rrl *rrl, v &= ~rrl->en_mask; } - write_imc_reg(imc, chan, offset, width, v); + skx_write_imc_reg(imc, chan, offset, width, v); } static void enable_rrls(struct skx_imc *imc, int chan, struct reg_rrl *rrl, @@ -354,7 +325,7 @@ static void show_retry_rd_err_log(struct decoded_addr *res, char *msg, for (j = 0; j < rrl->reg_num && len - n > 0; j++) { offset = rrl->offsets[i][j]; width = rrl->widths[j]; - log = read_imc_reg(imc, ch, offset, width); + log = skx_read_imc_reg(imc, ch, offset, width); if (width == 4) n += scnprintf(msg + n, len - n, "%.8llx ", log); @@ -363,7 +334,7 @@ static void show_retry_rd_err_log(struct decoded_addr *res, char *msg, /* Clear RRL status if RRL in Linux control mode. */ if (retry_rd_err_log == 2 && !j && (log & status_mask)) - write_imc_reg(imc, ch, offset, width, log & ~status_mask); + skx_write_imc_reg(imc, ch, offset, width, log & ~status_mask); } } @@ -376,7 +347,7 @@ static void show_retry_rd_err_log(struct decoded_addr *res, char *msg, for (i = 0; i < rrl->cecnt_num && len - n > 0; i++) { offset = rrl->cecnt_offsets[i]; width = rrl->cecnt_widths[i]; - corr = read_imc_reg(imc, ch, offset, width); + corr = skx_read_imc_reg(imc, ch, offset, width); /* CPUs {ICX,SPR} encode two counters per 4-byte CORRERRCNT register. */ if (res_cfg->type <= SPR) { diff --git a/drivers/edac/imh_base.c b/drivers/edac/imh_base.c index 4348b3883b457..e0ce408a53323 100644 --- a/drivers/edac/imh_base.c +++ b/drivers/edac/imh_base.c @@ -71,28 +71,11 @@ struct local_reg { .width = (cfg)->ip_name##_reg_##reg_name##_width, \ } -static u64 readx(void __iomem *addr, u8 width) -{ - switch (width) { - case 1: - return readb(addr); - case 2: - return readw(addr); - case 4: - return readl(addr); - case 8: - return readq(addr); - default: - imh_printk(KERN_ERR, "Invalid reg 0x%p width %d\n", addr, width); - return 0; - } -} - static void __read_local_reg(void *reg) { struct local_reg *r = (struct local_reg *)reg; - r->val = readx(r->vbase + r->offset, r->width); + r->val = skx_readx(r->vbase + r->offset, r->width); } /* Read a local-view register. */ @@ -378,22 +361,16 @@ static bool imh_2lm_enabled(struct res_config *cfg, struct list_head *head) return false; } -/* Helpers to read memory controller registers */ -static u64 read_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width) -{ - return readx(imc->mbase + imc->chan_mmio_sz * chan + offset, width); -} - static u32 read_imc_mcmtr(struct res_config *cfg, struct skx_imc *imc, int chan) { - return (u32)read_imc_reg(imc, chan, cfg->ddr_reg_mcmtr_offset, cfg->ddr_reg_mcmtr_width); + return (u32)skx_read_imc_reg(imc, chan, cfg->ddr_reg_mcmtr_offset, cfg->ddr_reg_mcmtr_width); } static u32 read_imc_dimmmtr(struct res_config *cfg, struct skx_imc *imc, int chan, int dimm) { - return (u32)read_imc_reg(imc, chan, cfg->ddr_reg_dimmmtr_offset + - cfg->ddr_reg_dimmmtr_width * dimm, - cfg->ddr_reg_dimmmtr_width); + return (u32)skx_read_imc_reg(imc, chan, cfg->ddr_reg_dimmmtr_offset + + cfg->ddr_reg_dimmmtr_width * dimm, + cfg->ddr_reg_dimmmtr_width); } static bool ecc_enabled(u32 mcmtr) diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index 4ed4f5a465e08..70d5afffc3242 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -51,6 +51,56 @@ static LIST_HEAD(dev_edac_list); static bool skx_mem_cfg_2lm; static struct res_config *skx_res_cfg; +u64 skx_readx(void __iomem *addr, u8 width) +{ + switch (width) { + case 1: + return readb(addr); + case 2: + return readw(addr); + case 4: + return readl(addr); + case 8: + return readq(addr); + default: + skx_printk(KERN_ERR, "Invalid reg 0x%p width %u to read.\n", addr, width); + return 0; + } +} +EXPORT_SYMBOL_GPL(skx_readx); + +static void skx_writex(void __iomem *addr, u8 width, u64 val) +{ + switch (width) { + case 1: + writeb((u8)val, addr); + return; + case 2: + writew((u16)val, addr); + return; + case 4: + writel((u32)val, addr); + return; + case 8: + writeq(val, addr); + return; + default: + skx_printk(KERN_ERR, "Invalid reg 0x%p width %u to write 0x%llx.\n", addr, width, val); + } +} + +u64 skx_read_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width) +{ + return skx_readx(imc->mbase + imc->chan_mmio_sz * chan + offset, width); +} +EXPORT_SYMBOL_GPL(skx_read_imc_reg); + +void skx_write_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width, u64 val) +{ + skx_writex(imc->mbase + imc->chan_mmio_sz * chan + offset, width, val); +} +EXPORT_SYMBOL_GPL(skx_write_imc_reg); + int skx_adxl_get(void) { const char * const *names; diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index 0ca6c9cd46a9d..b9d0cac0b29e3 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -331,6 +331,9 @@ typedef int (*get_dimm_config_f)(struct mem_ctl_info *mci, typedef bool (*skx_decode_f)(struct decoded_addr *res); typedef void (*skx_show_retry_log_f)(struct decoded_addr *res, char *msg, int len, bool scrub_err); +u64 skx_readx(void __iomem *addr, u8 width); +u64 skx_read_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width); +void skx_write_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width, u64 val); int skx_adxl_get(void); void skx_adxl_put(void); void skx_set_decode(skx_decode_f decode, skx_show_retry_log_f show_retry_log); From 4556fedb349d1de4ff09c440620bd7025ac31d2b Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 21 May 2026 15:31:06 +0800 Subject: [PATCH 13/19] EDAC/{skx_common,skx,i10nm}: Split skx_set_decode() commit bb7902db79dd423085474febb1f2aaeb105b9447 upstream. skx_set_decode() currently handles both address decoding and Retry Read error Log (RRL) reporting, coupling two independent functions in a single API. This complicates setup/teardown and forces callers to update unrelated state. Introduce skx_set_show_rrl() and keep skx_set_decode() focused on decode setup, allowing decode and RRL handling to be managed independently. Also rename the callback type and variable to skx_show_rrl_f and show_rrl for clearer RRL terminology and consistency. No functional changes intended. Intel-SIG: commit bb7902db79dd EDAC/{skx_common,skx,i10nm}: Split skx_set_decode() Backport EDAC retry_rd_error_log support for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Yi Lai Link: https://patch.msgid.link/20260521073112.3881223-3-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/i10nm_base.c | 10 ++++++---- drivers/edac/skx_base.c | 6 +++--- drivers/edac/skx_common.c | 15 ++++++++++----- drivers/edac/skx_common.h | 5 +++-- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c index df719700632a3..3696fcaf0fc01 100644 --- a/drivers/edac/i10nm_base.c +++ b/drivers/edac/i10nm_base.c @@ -1192,13 +1192,13 @@ static int __init i10nm_init(void) skx_setup_debug("i10nm_test"); if (retry_rd_err_log && res_cfg->reg_rrl_ddr) { - skx_set_decode(i10nm_mc_decode, show_retry_rd_err_log); + skx_set_show_rrl(show_retry_rd_err_log); if (retry_rd_err_log == 2) enable_retry_rd_err_log(true); - } else { - skx_set_decode(i10nm_mc_decode, NULL); } + skx_set_decode(i10nm_mc_decode); + i10nm_printk(KERN_INFO, "%s\n", I10NM_REVISION); return 0; @@ -1211,10 +1211,12 @@ static void __exit i10nm_exit(void) { edac_dbg(2, "\n"); + skx_set_decode(NULL); + if (retry_rd_err_log && res_cfg->reg_rrl_ddr) { - skx_set_decode(NULL, NULL); if (retry_rd_err_log == 2) enable_retry_rd_err_log(false); + skx_set_show_rrl(NULL); } skx_teardown_debug(); diff --git a/drivers/edac/skx_base.c b/drivers/edac/skx_base.c index 8246e5dd2b61c..d6ce5dc6fd816 100644 --- a/drivers/edac/skx_base.c +++ b/drivers/edac/skx_base.c @@ -671,14 +671,14 @@ static int __init skx_init(void) } } - skx_set_decode(skx_decode, skx_show_retry_rd_err_log); + skx_set_show_rrl(skx_show_retry_rd_err_log); if (nvdimm_count && skx_adxl_get() != -ENODEV) { - skx_set_decode(NULL, skx_show_retry_rd_err_log); + skx_set_decode(NULL); } else { if (nvdimm_count) skx_printk(KERN_NOTICE, "Only decoding DDR4 address!\n"); - skx_set_decode(skx_decode, skx_show_retry_rd_err_log); + skx_set_decode(skx_decode); } /* Ensure that the OPSTATE is set correctly for POLL or NMI */ diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index 70d5afffc3242..b11d44e806981 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -45,7 +45,7 @@ static unsigned long adxl_nm_bitmap; static char skx_msg[MSG_SIZE]; static skx_decode_f driver_decode; -static skx_show_retry_log_f skx_show_retry_rd_err_log; +static skx_show_rrl_f show_rrl; static u64 skx_tolm, skx_tohm; static LIST_HEAD(dev_edac_list); static bool skx_mem_cfg_2lm; @@ -313,13 +313,18 @@ void skx_set_res_cfg(struct res_config *cfg) } EXPORT_SYMBOL_GPL(skx_set_res_cfg); -void skx_set_decode(skx_decode_f decode, skx_show_retry_log_f show_retry_log) +void skx_set_decode(skx_decode_f decode) { driver_decode = decode; - skx_show_retry_rd_err_log = show_retry_log; } EXPORT_SYMBOL_GPL(skx_set_decode); +void skx_set_show_rrl(skx_show_rrl_f rrl) +{ + show_rrl = rrl; +} +EXPORT_SYMBOL_GPL(skx_set_show_rrl); + static int skx_get_pkg_id(struct skx_dev *d, u8 *id) { int node; @@ -768,8 +773,8 @@ static void skx_mce_output_error(struct mem_ctl_info *mci, res->row, res->column, res->bank_address, res->bank_group); } - if (skx_show_retry_rd_err_log) - skx_show_retry_rd_err_log(res, skx_msg + len, MSG_SIZE - len, scrub_err); + if (show_rrl) + show_rrl(res, skx_msg + len, MSG_SIZE - len, scrub_err); edac_dbg(0, "%s\n", skx_msg); diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index b9d0cac0b29e3..acda40b186530 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -329,14 +329,15 @@ struct res_config { typedef int (*get_dimm_config_f)(struct mem_ctl_info *mci, struct res_config *cfg); typedef bool (*skx_decode_f)(struct decoded_addr *res); -typedef void (*skx_show_retry_log_f)(struct decoded_addr *res, char *msg, int len, bool scrub_err); +typedef void (*skx_show_rrl_f)(struct decoded_addr *res, char *msg, int len, bool scrub_err); u64 skx_readx(void __iomem *addr, u8 width); u64 skx_read_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width); void skx_write_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width, u64 val); int skx_adxl_get(void); void skx_adxl_put(void); -void skx_set_decode(skx_decode_f decode, skx_show_retry_log_f show_retry_log); +void skx_set_decode(skx_decode_f decode); +void skx_set_show_rrl(skx_show_rrl_f rrl); void skx_set_mem_cfg(bool mem_cfg_2lm); void skx_set_res_cfg(struct res_config *cfg); void skx_init_mc_mapping(struct skx_dev *d); From fb451d1d4432321a763d70b2a63e233154d7fed4 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 21 May 2026 15:31:07 +0800 Subject: [PATCH 14/19] EDAC/{skx_common,i10nm}: Rename rrl_mode to rrl_source_type commit 579f40db12f75eb15b2517b150814564857dd8c7 upstream. The RRL (Retry Read error Log) values describe where an error was logged from (first/last read and scrub/demand), not an operating mode. Rename rrl_mode to rrl_source_type and "modes" to "sources" to better reflect their meaning and improve code readability. No functional changes intended. Intel-SIG: commit 579f40db12f7 EDAC/{skx_common,i10nm}: Rename rrl_mode to rrl_source_type Backport EDAC retry_rd_error_log support for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Yi Lai Link: https://patch.msgid.link/20260521073112.3881223-4-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/i10nm_base.c | 18 +++++++++--------- drivers/edac/skx_common.h | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c index 3696fcaf0fc01..238a9c402b4e2 100644 --- a/drivers/edac/i10nm_base.c +++ b/drivers/edac/i10nm_base.c @@ -78,7 +78,7 @@ static bool no_adxl; static struct reg_rrl icx_reg_rrl_ddr = { .set_num = 2, .reg_num = 6, - .modes = {LRE_SCRUB, LRE_DEMAND}, + .sources = {RRL_SRC_LRE_SCRUB, RRL_SRC_LRE_DEMAND}, .offsets = { {0x22c60, 0x22c54, 0x22c5c, 0x22c58, 0x22c28, 0x20ed8}, {0x22e54, 0x22e60, 0x22e64, 0x22e58, 0x22e5c, 0x20ee0}, @@ -99,7 +99,7 @@ static struct reg_rrl icx_reg_rrl_ddr = { static struct reg_rrl spr_reg_rrl_ddr = { .set_num = 3, .reg_num = 6, - .modes = {LRE_SCRUB, LRE_DEMAND, FRE_DEMAND}, + .sources = {RRL_SRC_LRE_SCRUB, RRL_SRC_LRE_DEMAND, RRL_SRC_FRE_DEMAND}, .offsets = { {0x22c60, 0x22c54, 0x22f08, 0x22c58, 0x22c28, 0x20ed8}, {0x22e54, 0x22e60, 0x22f10, 0x22e58, 0x22e5c, 0x20ee0}, @@ -121,7 +121,7 @@ static struct reg_rrl spr_reg_rrl_ddr = { static struct reg_rrl spr_reg_rrl_hbm_pch0 = { .set_num = 2, .reg_num = 6, - .modes = {LRE_SCRUB, LRE_DEMAND}, + .sources = {RRL_SRC_LRE_SCRUB, RRL_SRC_LRE_DEMAND}, .offsets = { {0x2860, 0x2854, 0x2b08, 0x2858, 0x2828, 0x0ed8}, {0x2a54, 0x2a60, 0x2b10, 0x2a58, 0x2a5c, 0x0ee0}, @@ -142,7 +142,7 @@ static struct reg_rrl spr_reg_rrl_hbm_pch0 = { static struct reg_rrl spr_reg_rrl_hbm_pch1 = { .set_num = 2, .reg_num = 6, - .modes = {LRE_SCRUB, LRE_DEMAND}, + .sources = {RRL_SRC_LRE_SCRUB, RRL_SRC_LRE_DEMAND}, .offsets = { {0x2c60, 0x2c54, 0x2f08, 0x2c58, 0x2c28, 0x0fa8}, {0x2e54, 0x2e60, 0x2f10, 0x2e58, 0x2e5c, 0x0fb0}, @@ -163,7 +163,7 @@ static struct reg_rrl spr_reg_rrl_hbm_pch1 = { static struct reg_rrl gnr_reg_rrl_ddr = { .set_num = 4, .reg_num = 6, - .modes = {FRE_SCRUB, FRE_DEMAND, LRE_SCRUB, LRE_DEMAND}, + .sources = {RRL_SRC_FRE_SCRUB, RRL_SRC_FRE_DEMAND, RRL_SRC_LRE_SCRUB, RRL_SRC_LRE_DEMAND}, .offsets = { {0x2f10, 0x2f20, 0x2f30, 0x2f50, 0x2f60, 0xba0}, {0x2f14, 0x2f24, 0x2f38, 0x2f54, 0x2f64, 0xba8}, @@ -186,15 +186,15 @@ static struct reg_rrl gnr_reg_rrl_ddr = { static void enable_rrl(struct skx_imc *imc, int chan, struct reg_rrl *rrl, int rrl_set, bool enable, u32 *rrl_ctl) { - enum rrl_mode mode = rrl->modes[rrl_set]; + enum rrl_source_type source = rrl->sources[rrl_set]; u32 offset = rrl->offsets[rrl_set][0], v; u8 width = rrl->widths[0]; bool first, scrub; /* First or last read error. */ - first = (mode == FRE_SCRUB || mode == FRE_DEMAND); + first = (source == RRL_SRC_FRE_SCRUB || source == RRL_SRC_FRE_DEMAND); /* Patrol scrub or on-demand read error. */ - scrub = (mode == FRE_SCRUB || mode == LRE_SCRUB); + scrub = (source == RRL_SRC_FRE_SCRUB || source == RRL_SRC_LRE_SCRUB); v = skx_read_imc_reg(imc, chan, offset, width); @@ -318,7 +318,7 @@ static void show_retry_rd_err_log(struct decoded_addr *res, char *msg, n = scnprintf(msg, len, " retry_rd_err_log["); for (i = 0; i < rrl->set_num; i++) { - scrub = (rrl->modes[i] == FRE_SCRUB || rrl->modes[i] == LRE_SCRUB); + scrub = (rrl->sources[i] == RRL_SRC_FRE_SCRUB || rrl->sources[i] == RRL_SRC_LRE_SCRUB); if (scrub_err != scrub) continue; diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index acda40b186530..4f453d07c93cf 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -86,23 +86,23 @@ /* Max correctable error count registers. */ #define NUM_CECNT_REG 8 -/* Modes of RRL register set. */ -enum rrl_mode { +/* Error source from which the RRL registers log errors. */ +enum rrl_source_type { /* Last read error from patrol scrub. */ - LRE_SCRUB, + RRL_SRC_LRE_SCRUB, /* Last read error from demand. */ - LRE_DEMAND, + RRL_SRC_LRE_DEMAND, /* First read error from patrol scrub. */ - FRE_SCRUB, + RRL_SRC_FRE_SCRUB, /* First read error from demand. */ - FRE_DEMAND, + RRL_SRC_FRE_DEMAND, }; /* RRL registers per {,sub-,pseudo-}channel. */ struct reg_rrl { /* RRL register parts. */ int set_num, reg_num; - enum rrl_mode modes[NUM_RRL_SET]; + enum rrl_source_type sources[NUM_RRL_SET]; u32 offsets[NUM_RRL_SET][NUM_RRL_REG]; /* RRL register widths in byte per set. */ u8 widths[NUM_RRL_REG]; From 3ea6746f8a81e1f42c90c923b453a3313f079913 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 21 May 2026 15:31:08 +0800 Subject: [PATCH 15/19] EDAC/{skx_common,i10nm}: Introduce rrl_ctrl_mode commit 1286fc30cc08a12c2b6d2cf4a1d5dbb0b6bc76d9 upstream. RRL (Retry Read error Log) ownership is currently inferred from retry_rd_err_log magic values, making control semantics implicit and harder to understand. Introduce rrl_ctrl_mode to explicitly describe whether RRL is controlled by none, BIOS, or Linux, and replace direct checks with named control states to improve readability and maintainability. No functional change intended. Intel-SIG: commit 1286fc30cc08 EDAC/{skx_common,i10nm}: Introduce rrl_ctrl_mode Backport EDAC retry_rd_error_log support for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Yi Lai Link: https://patch.msgid.link/20260521073112.3881223-5-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/i10nm_base.c | 7 ++++--- drivers/edac/skx_common.h | 11 +++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c index 238a9c402b4e2..a4341d0f90dde 100644 --- a/drivers/edac/i10nm_base.c +++ b/drivers/edac/i10nm_base.c @@ -333,7 +333,7 @@ static void show_retry_rd_err_log(struct decoded_addr *res, char *msg, n += scnprintf(msg + n, len - n, "%.16llx ", log); /* Clear RRL status if RRL in Linux control mode. */ - if (retry_rd_err_log == 2 && !j && (log & status_mask)) + if (res_cfg->rrl_ctrl_mode == RRL_CTRL_LINUX && !j && (log & status_mask)) skx_write_imc_reg(imc, ch, offset, width, log & ~status_mask); } } @@ -1191,9 +1191,10 @@ static int __init i10nm_init(void) mce_register_decode_chain(&i10nm_mce_dec); skx_setup_debug("i10nm_test"); + res_cfg->rrl_ctrl_mode = retry_rd_err_log; if (retry_rd_err_log && res_cfg->reg_rrl_ddr) { skx_set_show_rrl(show_retry_rd_err_log); - if (retry_rd_err_log == 2) + if (retry_rd_err_log == RRL_CTRL_LINUX) enable_retry_rd_err_log(true); } @@ -1214,7 +1215,7 @@ static void __exit i10nm_exit(void) skx_set_decode(NULL); if (retry_rd_err_log && res_cfg->reg_rrl_ddr) { - if (retry_rd_err_log == 2) + if (retry_rd_err_log == RRL_CTRL_LINUX) enable_retry_rd_err_log(false); skx_set_show_rrl(NULL); } diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index 4f453d07c93cf..f0e0360a9464a 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -98,6 +98,15 @@ enum rrl_source_type { RRL_SRC_FRE_DEMAND, }; +enum rrl_ctrl_mode { + /* Linux does not control RRL or reports values. */ + RRL_CTRL_NONE, + /* Firmware retains control. Linux only reports values. */ + RRL_CTRL_BIOS, + /* Linux takes control, resets mode bits, and clears valid/UC bits; reports values. */ + RRL_CTRL_LINUX, +}; + /* RRL registers per {,sub-,pseudo-}channel. */ struct reg_rrl { /* RRL register parts. */ @@ -277,6 +286,8 @@ struct res_config { struct reg_rrl *reg_rrl_ddr; /* RRL register sets per HBM channel */ struct reg_rrl *reg_rrl_hbm[2]; + /* RRL control mode */ + enum rrl_ctrl_mode rrl_ctrl_mode; union { /* {skx,i10nm}_edac */ struct { From 250f42110d66587eb60b1efe8616d6e00c8fe7f7 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 21 May 2026 15:31:09 +0800 Subject: [PATCH 16/19] EDAC/{skx_common,i10nm}: Move RRL handling to common code commit 8998a4a646471dc218f6499c5580e252891ccaa4 upstream. Move RRL (Retry Read error Log) handling from i10nm_edac to skx_common so it can be shared across EDAC drivers (e.g. imh_edac). - Move RRL enable/disable and log dumping helpers to skx_common to avoid code duplication and enable reuse by other drivers. - Export skx_enable_rrl() and skx_show_rrl() so common RRL handling can be used by i10nm_edac and imh_edac. No functional change intended. Intel-SIG: commit 8998a4a64647 EDAC/{skx_common,i10nm}: Move RRL handling to common code Backport EDAC retry_rd_error_log support for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Yi Lai Link: https://patch.msgid.link/20260521073112.3881223-6-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/i10nm_base.c | 191 +------------------------------------- drivers/edac/skx_common.c | 186 +++++++++++++++++++++++++++++++++++++ drivers/edac/skx_common.h | 2 + 3 files changed, 191 insertions(+), 188 deletions(-) diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c index a4341d0f90dde..d55bfb53bb38c 100644 --- a/drivers/edac/i10nm_base.c +++ b/drivers/edac/i10nm_base.c @@ -183,191 +183,6 @@ static struct reg_rrl gnr_reg_rrl_ddr = { .cecnt_widths = {4, 4, 4, 4, 4, 4, 4, 4}, }; -static void enable_rrl(struct skx_imc *imc, int chan, struct reg_rrl *rrl, - int rrl_set, bool enable, u32 *rrl_ctl) -{ - enum rrl_source_type source = rrl->sources[rrl_set]; - u32 offset = rrl->offsets[rrl_set][0], v; - u8 width = rrl->widths[0]; - bool first, scrub; - - /* First or last read error. */ - first = (source == RRL_SRC_FRE_SCRUB || source == RRL_SRC_FRE_DEMAND); - /* Patrol scrub or on-demand read error. */ - scrub = (source == RRL_SRC_FRE_SCRUB || source == RRL_SRC_LRE_SCRUB); - - v = skx_read_imc_reg(imc, chan, offset, width); - - if (enable) { - /* Save default configurations. */ - *rrl_ctl = v; - v &= ~rrl->uc_mask; - - if (first) - v |= rrl->noover_mask; - else - v &= ~rrl->noover_mask; - - if (scrub) - v |= rrl->en_patspr_mask; - else - v &= ~rrl->en_patspr_mask; - - v |= rrl->en_mask; - } else { - /* Restore default configurations. */ - if (*rrl_ctl & rrl->uc_mask) - v |= rrl->uc_mask; - - if (first) { - if (!(*rrl_ctl & rrl->noover_mask)) - v &= ~rrl->noover_mask; - } else { - if (*rrl_ctl & rrl->noover_mask) - v |= rrl->noover_mask; - } - - if (scrub) { - if (!(*rrl_ctl & rrl->en_patspr_mask)) - v &= ~rrl->en_patspr_mask; - } else { - if (*rrl_ctl & rrl->en_patspr_mask) - v |= rrl->en_patspr_mask; - } - - if (!(*rrl_ctl & rrl->en_mask)) - v &= ~rrl->en_mask; - } - - skx_write_imc_reg(imc, chan, offset, width, v); -} - -static void enable_rrls(struct skx_imc *imc, int chan, struct reg_rrl *rrl, - bool enable, u32 *rrl_ctl) -{ - for (int i = 0; i < rrl->set_num; i++) - enable_rrl(imc, chan, rrl, i, enable, rrl_ctl + i); -} - -static void enable_rrls_ddr(struct skx_imc *imc, bool enable) -{ - struct reg_rrl *rrl_ddr = res_cfg->reg_rrl_ddr; - int i, chan_num = res_cfg->ddr_chan_num; - struct skx_channel *chan = imc->chan; - - if (!imc->mbase) - return; - - for (i = 0; i < chan_num; i++) - enable_rrls(imc, i, rrl_ddr, enable, chan[i].rrl_ctl[0]); -} - -static void enable_rrls_hbm(struct skx_imc *imc, bool enable) -{ - struct reg_rrl **rrl_hbm = res_cfg->reg_rrl_hbm; - int i, chan_num = res_cfg->hbm_chan_num; - struct skx_channel *chan = imc->chan; - - if (!imc->mbase || !imc->hbm_mc || !rrl_hbm[0] || !rrl_hbm[1]) - return; - - for (i = 0; i < chan_num; i++) { - enable_rrls(imc, i, rrl_hbm[0], enable, chan[i].rrl_ctl[0]); - enable_rrls(imc, i, rrl_hbm[1], enable, chan[i].rrl_ctl[1]); - } -} - -static void enable_retry_rd_err_log(bool enable) -{ - struct skx_dev *d; - int i, imc_num; - - edac_dbg(2, "\n"); - - list_for_each_entry(d, i10nm_edac_list, list) { - imc_num = res_cfg->ddr_imc_num; - for (i = 0; i < imc_num; i++) - enable_rrls_ddr(&d->imc[i], enable); - - imc_num += res_cfg->hbm_imc_num; - for (; i < imc_num; i++) - enable_rrls_hbm(&d->imc[i], enable); - } -} - -static void show_retry_rd_err_log(struct decoded_addr *res, char *msg, - int len, bool scrub_err) -{ - int i, j, n, ch = res->channel, pch = res->cs & 1; - struct skx_imc *imc = &res->dev->imc[res->imc]; - u64 log, corr, status_mask; - struct reg_rrl *rrl; - bool scrub; - u32 offset; - u8 width; - - if (!imc->mbase) - return; - - rrl = imc->hbm_mc ? res_cfg->reg_rrl_hbm[pch] : res_cfg->reg_rrl_ddr; - - if (!rrl) - return; - - status_mask = rrl->over_mask | rrl->uc_mask | rrl->v_mask; - - n = scnprintf(msg, len, " retry_rd_err_log["); - for (i = 0; i < rrl->set_num; i++) { - scrub = (rrl->sources[i] == RRL_SRC_FRE_SCRUB || rrl->sources[i] == RRL_SRC_LRE_SCRUB); - if (scrub_err != scrub) - continue; - - for (j = 0; j < rrl->reg_num && len - n > 0; j++) { - offset = rrl->offsets[i][j]; - width = rrl->widths[j]; - log = skx_read_imc_reg(imc, ch, offset, width); - - if (width == 4) - n += scnprintf(msg + n, len - n, "%.8llx ", log); - else - n += scnprintf(msg + n, len - n, "%.16llx ", log); - - /* Clear RRL status if RRL in Linux control mode. */ - if (res_cfg->rrl_ctrl_mode == RRL_CTRL_LINUX && !j && (log & status_mask)) - skx_write_imc_reg(imc, ch, offset, width, log & ~status_mask); - } - } - - /* Move back one space. */ - n--; - n += scnprintf(msg + n, len - n, "]"); - - if (len - n > 0) { - n += scnprintf(msg + n, len - n, " correrrcnt["); - for (i = 0; i < rrl->cecnt_num && len - n > 0; i++) { - offset = rrl->cecnt_offsets[i]; - width = rrl->cecnt_widths[i]; - corr = skx_read_imc_reg(imc, ch, offset, width); - - /* CPUs {ICX,SPR} encode two counters per 4-byte CORRERRCNT register. */ - if (res_cfg->type <= SPR) { - n += scnprintf(msg + n, len - n, "%.4llx %.4llx ", - corr & 0xffff, corr >> 16); - } else { - /* CPUs {GNR} encode one counter per CORRERRCNT register. */ - if (width == 4) - n += scnprintf(msg + n, len - n, "%.8llx ", corr); - else - n += scnprintf(msg + n, len - n, "%.16llx ", corr); - } - } - - /* Move back one space. */ - n--; - n += scnprintf(msg + n, len - n, "]"); - } -} - static struct pci_dev *pci_get_dev_wrapper(int dom, unsigned int bus, unsigned int dev, unsigned int fun) { @@ -1193,9 +1008,9 @@ static int __init i10nm_init(void) res_cfg->rrl_ctrl_mode = retry_rd_err_log; if (retry_rd_err_log && res_cfg->reg_rrl_ddr) { - skx_set_show_rrl(show_retry_rd_err_log); + skx_set_show_rrl(skx_show_rrl); if (retry_rd_err_log == RRL_CTRL_LINUX) - enable_retry_rd_err_log(true); + skx_enable_rrl(true); } skx_set_decode(i10nm_mc_decode); @@ -1216,7 +1031,7 @@ static void __exit i10nm_exit(void) if (retry_rd_err_log && res_cfg->reg_rrl_ddr) { if (retry_rd_err_log == RRL_CTRL_LINUX) - enable_retry_rd_err_log(false); + skx_enable_rrl(false); skx_set_show_rrl(NULL); } diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index b11d44e806981..9b49cab82bd1e 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -101,6 +101,192 @@ void skx_write_imc_reg(struct skx_imc *imc, int chan, u32 offset, u8 width, u64 } EXPORT_SYMBOL_GPL(skx_write_imc_reg); +static void enable_rrl(struct skx_imc *imc, int chan, struct reg_rrl *rrl, + int rrl_set, bool enable, u32 *rrl_ctl) +{ + enum rrl_source_type source = rrl->sources[rrl_set]; + u32 offset = rrl->offsets[rrl_set][0], v; + u8 width = rrl->widths[0]; + bool first, scrub; + + /* First or last read error. */ + first = (source == RRL_SRC_FRE_SCRUB || source == RRL_SRC_FRE_DEMAND); + /* Patrol scrub or on-demand read error. */ + scrub = (source == RRL_SRC_FRE_SCRUB || source == RRL_SRC_LRE_SCRUB); + + v = skx_read_imc_reg(imc, chan, offset, width); + + if (enable) { + /* Save default configurations. */ + *rrl_ctl = v; + v &= ~rrl->uc_mask; + + if (first) + v |= rrl->noover_mask; + else + v &= ~rrl->noover_mask; + + if (scrub) + v |= rrl->en_patspr_mask; + else + v &= ~rrl->en_patspr_mask; + + v |= rrl->en_mask; + } else { + /* Restore default configurations. */ + if (*rrl_ctl & rrl->uc_mask) + v |= rrl->uc_mask; + + if (first) { + if (!(*rrl_ctl & rrl->noover_mask)) + v &= ~rrl->noover_mask; + } else { + if (*rrl_ctl & rrl->noover_mask) + v |= rrl->noover_mask; + } + + if (scrub) { + if (!(*rrl_ctl & rrl->en_patspr_mask)) + v &= ~rrl->en_patspr_mask; + } else { + if (*rrl_ctl & rrl->en_patspr_mask) + v |= rrl->en_patspr_mask; + } + + if (!(*rrl_ctl & rrl->en_mask)) + v &= ~rrl->en_mask; + } + + skx_write_imc_reg(imc, chan, offset, width, v); +} + +static void enable_rrls(struct skx_imc *imc, int chan, struct reg_rrl *rrl, + bool enable, u32 *rrl_ctl) +{ + for (int i = 0; i < rrl->set_num; i++) + enable_rrl(imc, chan, rrl, i, enable, rrl_ctl + i); +} + +static void enable_rrls_ddr(struct skx_imc *imc, bool enable) +{ + struct reg_rrl *rrl_ddr = skx_res_cfg->reg_rrl_ddr; + int i, chan_num = skx_res_cfg->ddr_chan_num; + struct skx_channel *chan = imc->chan; + + if (!imc->mbase) + return; + + for (i = 0; i < chan_num; i++) + enable_rrls(imc, i, rrl_ddr, enable, chan[i].rrl_ctl[0]); +} + +static void enable_rrls_hbm(struct skx_imc *imc, bool enable) +{ + struct reg_rrl **rrl_hbm = skx_res_cfg->reg_rrl_hbm; + int i, chan_num = skx_res_cfg->hbm_chan_num; + struct skx_channel *chan = imc->chan; + + if (!imc->mbase || !imc->hbm_mc || !rrl_hbm[0] || !rrl_hbm[1]) + return; + + for (i = 0; i < chan_num; i++) { + enable_rrls(imc, i, rrl_hbm[0], enable, chan[i].rrl_ctl[0]); + enable_rrls(imc, i, rrl_hbm[1], enable, chan[i].rrl_ctl[1]); + } +} + +void skx_enable_rrl(bool enable) +{ + struct skx_dev *d; + int i, imc_num; + + edac_dbg(2, "\n"); + + list_for_each_entry(d, &dev_edac_list, list) { + imc_num = skx_res_cfg->ddr_imc_num; + for (i = 0; i < imc_num; i++) + enable_rrls_ddr(&d->imc[i], enable); + + imc_num += skx_res_cfg->hbm_imc_num; + for (; i < imc_num; i++) + enable_rrls_hbm(&d->imc[i], enable); + } +} +EXPORT_SYMBOL_GPL(skx_enable_rrl); + +void skx_show_rrl(struct decoded_addr *res, char *msg, int len, bool scrub_err) +{ + int i, j, n, ch = res->channel, pch = res->cs & 1; + struct skx_imc *imc = &res->dev->imc[res->imc]; + u64 log, corr, status_mask; + struct reg_rrl *rrl; + bool scrub; + u32 offset; + u8 width; + + if (!imc->mbase) + return; + + rrl = imc->hbm_mc ? skx_res_cfg->reg_rrl_hbm[pch] : skx_res_cfg->reg_rrl_ddr; + + if (!rrl) + return; + + status_mask = rrl->over_mask | rrl->uc_mask | rrl->v_mask; + + n = scnprintf(msg, len, " retry_rd_err_log["); + for (i = 0; i < rrl->set_num; i++) { + scrub = (rrl->sources[i] == RRL_SRC_FRE_SCRUB || rrl->sources[i] == RRL_SRC_LRE_SCRUB); + if (scrub_err != scrub) + continue; + + for (j = 0; j < rrl->reg_num && len - n > 0; j++) { + offset = rrl->offsets[i][j]; + width = rrl->widths[j]; + log = skx_read_imc_reg(imc, ch, offset, width); + + if (width == 4) + n += scnprintf(msg + n, len - n, "%.8llx ", log); + else + n += scnprintf(msg + n, len - n, "%.16llx ", log); + + /* Clear RRL status if RRL in Linux control mode. */ + if (skx_res_cfg->rrl_ctrl_mode == RRL_CTRL_LINUX && !j && (log & status_mask)) + skx_write_imc_reg(imc, ch, offset, width, log & ~status_mask); + } + } + + /* Move back one space. */ + n--; + n += scnprintf(msg + n, len - n, "]"); + + if (len - n > 0) { + n += scnprintf(msg + n, len - n, " correrrcnt["); + for (i = 0; i < rrl->cecnt_num && len - n > 0; i++) { + offset = rrl->cecnt_offsets[i]; + width = rrl->cecnt_widths[i]; + corr = skx_read_imc_reg(imc, ch, offset, width); + + /* CPUs {ICX,SPR} encode two counters per 4-byte CORRERRCNT register. */ + if (skx_res_cfg->type <= SPR) { + n += scnprintf(msg + n, len - n, "%.4llx %.4llx ", + corr & 0xffff, corr >> 16); + } else { + /* CPUs {GNR} encode one counter per CORRERRCNT register. */ + if (width == 4) + n += scnprintf(msg + n, len - n, "%.8llx ", corr); + else + n += scnprintf(msg + n, len - n, "%.16llx ", corr); + } + } + + /* Move back one space. */ + n--; + n += scnprintf(msg + n, len - n, "]"); + } +} +EXPORT_SYMBOL_GPL(skx_show_rrl); + int skx_adxl_get(void) { const char * const *names; diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index f0e0360a9464a..079bde09028be 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -349,6 +349,8 @@ int skx_adxl_get(void); void skx_adxl_put(void); void skx_set_decode(skx_decode_f decode); void skx_set_show_rrl(skx_show_rrl_f rrl); +void skx_show_rrl(struct decoded_addr *res, char *msg, int len, bool scrub_err); +void skx_enable_rrl(bool enable); void skx_set_mem_cfg(bool mem_cfg_2lm); void skx_set_res_cfg(struct res_config *cfg); void skx_init_mc_mapping(struct skx_dev *d); From 321864cac4841e99523b7cd1a3a3f22582ea8ef8 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 21 May 2026 15:31:10 +0800 Subject: [PATCH 17/19] EDAC/skx_common: Add SubChannel support to ADXL decode commit 5b33d5a7be01000014f81e426ff20c3a20b5d9b0 upstream. Diamond Rapids server RRL (Retry Read error Log) operates at sub-channel granularity. Add SubChannel support to ADXL decoding in preparation for enabling this feature. Also introduce adxl_component_required() to validate mandatory ADXL components to improve code readability. Intel-SIG: commit 5b33d5a7be01 EDAC/skx_common: Add SubChannel support to ADXL decode Backport EDAC retry_rd_error_log support for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Yi Lai Link: https://patch.msgid.link/20260521073112.3881223-7-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/skx_common.c | 20 +++++++++++++++++++- drivers/edac/skx_common.h | 5 +++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index 9b49cab82bd1e..3389826a3c419 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -30,10 +30,12 @@ static const char * const component_names[] = { [INDEX_CHANNEL] = "ChannelId", [INDEX_DIMM] = "DimmSlotId", [INDEX_CS] = "ChipSelect", + [INDEX_SUBCH] = "SubChId", [INDEX_NM_MEMCTRL] = "NmMemoryControllerId", [INDEX_NM_CHANNEL] = "NmChannelId", [INDEX_NM_DIMM] = "NmDimmSlotId", [INDEX_NM_CS] = "NmChipSelect", + [INDEX_NM_SUBCH] = "NmSubChId", }; static int component_indices[ARRAY_SIZE(component_names)]; @@ -42,6 +44,7 @@ static const char * const *adxl_component_names; static u64 *adxl_values; static char *adxl_msg; static unsigned long adxl_nm_bitmap; +static unsigned long adxl_bitmap; static char skx_msg[MSG_SIZE]; static skx_decode_f driver_decode; @@ -287,6 +290,15 @@ void skx_show_rrl(struct decoded_addr *res, char *msg, int len, bool scrub_err) } EXPORT_SYMBOL_GPL(skx_show_rrl); +static bool adxl_component_required(int idx) +{ + return idx == INDEX_SOCKET || + idx == INDEX_MEMCTRL || + idx == INDEX_CHANNEL || + idx == INDEX_DIMM || + idx == INDEX_CS; +} + int skx_adxl_get(void) { const char * const *names; @@ -305,12 +317,14 @@ int skx_adxl_get(void) if (i >= INDEX_NM_FIRST) adxl_nm_bitmap |= 1 << i; + else + adxl_bitmap |= 1 << i; break; } } - if (!names[j] && i < INDEX_NM_FIRST) + if (!names[j] && adxl_component_required(i)) goto err; } @@ -439,11 +453,15 @@ static bool skx_adxl_decode(struct decoded_addr *res, enum error_source err_src) (int)adxl_values[component_indices[INDEX_NM_DIMM]] : -1; res->cs = (adxl_nm_bitmap & BIT_NM_CS) ? (int)adxl_values[component_indices[INDEX_NM_CS]] : -1; + res->subch = (adxl_nm_bitmap & BIT_NM_SUBCH) ? + (int)adxl_values[component_indices[INDEX_NM_SUBCH]] : -1; } else { res->imc = (int)adxl_values[component_indices[INDEX_MEMCTRL]]; res->channel = (int)adxl_values[component_indices[INDEX_CHANNEL]]; res->dimm = (int)adxl_values[component_indices[INDEX_DIMM]]; res->cs = (int)adxl_values[component_indices[INDEX_CS]]; + res->subch = (adxl_bitmap & BIT_SUBCH) ? + (int)adxl_values[component_indices[INDEX_SUBCH]] : -1; } if (res->imc > NUM_IMC - 1 || res->imc < 0) { diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index 079bde09028be..9819542444dde 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -215,11 +215,13 @@ enum { INDEX_CHANNEL, INDEX_DIMM, INDEX_CS, + INDEX_SUBCH, INDEX_NM_FIRST, INDEX_NM_MEMCTRL = INDEX_NM_FIRST, INDEX_NM_CHANNEL, INDEX_NM_DIMM, INDEX_NM_CS, + INDEX_NM_SUBCH, INDEX_MAX }; @@ -230,10 +232,12 @@ enum error_source { ERR_SRC_NOT_MEMORY, }; +#define BIT_SUBCH BIT_ULL(INDEX_SUBCH) #define BIT_NM_MEMCTRL BIT_ULL(INDEX_NM_MEMCTRL) #define BIT_NM_CHANNEL BIT_ULL(INDEX_NM_CHANNEL) #define BIT_NM_DIMM BIT_ULL(INDEX_NM_DIMM) #define BIT_NM_CS BIT_ULL(INDEX_NM_CS) +#define BIT_NM_SUBCH BIT_ULL(INDEX_NM_SUBCH) struct decoded_addr { struct mce *mce; @@ -247,6 +251,7 @@ struct decoded_addr { int chanways; int dimm; int cs; + int subch; int rank; int channel_rank; u64 rank_address; From 19d68215540f45366d8e862e7dfea28d2e52dc39 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 21 May 2026 15:31:11 +0800 Subject: [PATCH 18/19] EDAC/{skx_common,i10nm}: Prepare RRL for sub-channel granularity commit 4742ae4d82454af4eb21c9eefb17c5e80a08d392 upstream. To prepare for enabling Diamond Rapids server RRL (Retry Read error Log), which operates at sub-channel granularity by converting struct res_config::reg_rrl_ddr from a single pointer to an array (reg_rrl_ddr[2]) and updating all users in i10nm_edac and skx_common accordingly. Initialize only reg_rrl_ddr[0] for existing platforms and prepare for supporting two RRL set groups per DDR channel (one per sub-channel) when present. Intel-SIG: commit 4742ae4d8245 EDAC/{skx_common,i10nm}: Prepare RRL for sub-channel granularity Backport EDAC retry_rd_error_log support for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Yi Lai Link: https://patch.msgid.link/20260521073112.3881223-8-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/i10nm_base.c | 12 ++++++------ drivers/edac/skx_common.c | 35 +++++++++++++++++++++++++++++------ drivers/edac/skx_common.h | 2 +- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c index d55bfb53bb38c..f6837cf13f9ca 100644 --- a/drivers/edac/i10nm_base.c +++ b/drivers/edac/i10nm_base.c @@ -752,7 +752,7 @@ static struct res_config i10nm_cfg0 = { .ddr_mdev_bdf = {0, 12, 0}, .hbm_mdev_bdf = {0, 12, 1}, .sad_all_offset = 0x108, - .reg_rrl_ddr = &icx_reg_rrl_ddr, + .reg_rrl_ddr[0] = &icx_reg_rrl_ddr, }; static struct res_config i10nm_cfg1 = { @@ -770,7 +770,7 @@ static struct res_config i10nm_cfg1 = { .ddr_mdev_bdf = {0, 12, 0}, .hbm_mdev_bdf = {0, 12, 1}, .sad_all_offset = 0x108, - .reg_rrl_ddr = &icx_reg_rrl_ddr, + .reg_rrl_ddr[0] = &icx_reg_rrl_ddr, }; static struct res_config spr_cfg = { @@ -793,7 +793,7 @@ static struct res_config spr_cfg = { .ddr_mdev_bdf = {0, 12, 0}, .hbm_mdev_bdf = {0, 12, 1}, .sad_all_offset = 0x300, - .reg_rrl_ddr = &spr_reg_rrl_ddr, + .reg_rrl_ddr[0] = &spr_reg_rrl_ddr, .reg_rrl_hbm[0] = &spr_reg_rrl_hbm_pch0, .reg_rrl_hbm[1] = &spr_reg_rrl_hbm_pch1, }; @@ -813,7 +813,7 @@ static struct res_config gnr_cfg = { .uracu_bdf = {0, 0, 1}, .ddr_mdev_bdf = {0, 5, 1}, .sad_all_offset = 0x300, - .reg_rrl_ddr = &gnr_reg_rrl_ddr, + .reg_rrl_ddr[0] = &gnr_reg_rrl_ddr, }; static const struct x86_cpu_id i10nm_cpuids[] = { @@ -1007,7 +1007,7 @@ static int __init i10nm_init(void) skx_setup_debug("i10nm_test"); res_cfg->rrl_ctrl_mode = retry_rd_err_log; - if (retry_rd_err_log && res_cfg->reg_rrl_ddr) { + if (retry_rd_err_log && res_cfg->reg_rrl_ddr[0]) { skx_set_show_rrl(skx_show_rrl); if (retry_rd_err_log == RRL_CTRL_LINUX) skx_enable_rrl(true); @@ -1029,7 +1029,7 @@ static void __exit i10nm_exit(void) skx_set_decode(NULL); - if (retry_rd_err_log && res_cfg->reg_rrl_ddr) { + if (retry_rd_err_log && res_cfg->reg_rrl_ddr[0]) { if (retry_rd_err_log == RRL_CTRL_LINUX) skx_enable_rrl(false); skx_set_show_rrl(NULL); diff --git a/drivers/edac/skx_common.c b/drivers/edac/skx_common.c index 3389826a3c419..41c50ccc7d849 100644 --- a/drivers/edac/skx_common.c +++ b/drivers/edac/skx_common.c @@ -172,15 +172,18 @@ static void enable_rrls(struct skx_imc *imc, int chan, struct reg_rrl *rrl, static void enable_rrls_ddr(struct skx_imc *imc, bool enable) { - struct reg_rrl *rrl_ddr = skx_res_cfg->reg_rrl_ddr; + struct reg_rrl **rrl_ddr = skx_res_cfg->reg_rrl_ddr; int i, chan_num = skx_res_cfg->ddr_chan_num; struct skx_channel *chan = imc->chan; if (!imc->mbase) return; - for (i = 0; i < chan_num; i++) - enable_rrls(imc, i, rrl_ddr, enable, chan[i].rrl_ctl[0]); + for (i = 0; i < chan_num; i++) { + enable_rrls(imc, i, rrl_ddr[0], enable, chan[i].rrl_ctl[0]); + if (rrl_ddr[1]) + enable_rrls(imc, i, rrl_ddr[1], enable, chan[i].rrl_ctl[1]); + } } static void enable_rrls_hbm(struct skx_imc *imc, bool enable) @@ -217,10 +220,31 @@ void skx_enable_rrl(bool enable) } EXPORT_SYMBOL_GPL(skx_enable_rrl); +static struct reg_rrl *get_rrl_reg(struct decoded_addr *res, struct res_config *cfg) +{ + struct skx_imc *imc = &res->dev->imc[res->imc]; + + /* HBM has two groups of RRL sets, one per pseudo-channel. */ + if (imc->hbm_mc) + return cfg->reg_rrl_hbm[res->cs & 1]; + + /* One group of RRL sets per DDR channel. */ + if (!cfg->reg_rrl_ddr[1]) + return cfg->reg_rrl_ddr[0]; + + if (res->subch == -1) { + skx_printk(KERN_ERR, "Invalid sub-channel id (-1), possibly missing %s ADXL component.\n", component_names[INDEX_SUBCH]); + return NULL; + } + + /* Two groups of RRL sets per DDR channel (e.g., DMR: one group per sub-channel). */ + return cfg->reg_rrl_ddr[res->subch & 1]; +} + void skx_show_rrl(struct decoded_addr *res, char *msg, int len, bool scrub_err) { - int i, j, n, ch = res->channel, pch = res->cs & 1; struct skx_imc *imc = &res->dev->imc[res->imc]; + int i, j, n, ch = res->channel; u64 log, corr, status_mask; struct reg_rrl *rrl; bool scrub; @@ -230,8 +254,7 @@ void skx_show_rrl(struct decoded_addr *res, char *msg, int len, bool scrub_err) if (!imc->mbase) return; - rrl = imc->hbm_mc ? skx_res_cfg->reg_rrl_hbm[pch] : skx_res_cfg->reg_rrl_ddr; - + rrl = get_rrl_reg(res, skx_res_cfg); if (!rrl) return; diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index 9819542444dde..1b93de78233bb 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -288,7 +288,7 @@ struct res_config { int hbm_chan_mmio_sz; bool support_ddr5; /* RRL register sets per DDR channel */ - struct reg_rrl *reg_rrl_ddr; + struct reg_rrl *reg_rrl_ddr[2]; /* RRL register sets per HBM channel */ struct reg_rrl *reg_rrl_hbm[2]; /* RRL control mode */ From 946aaa6345a4339deb4da5610cb131c9512ceb9c Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 21 May 2026 15:31:12 +0800 Subject: [PATCH 19/19] EDAC/imh: Add RRL support for Intel Diamond Rapids server commit bdfc4367e3f516479e0a68c731bea5c6638a6c7e upstream. Compared to previous generations, Diamond Rapids RRL (Retry Read error Log) operates at DDR sub-channel granularity and adds an extra register per set. It also increases the CORRERRCNT register width from 4 to 8 bytes while reducing the number of registers from 8 to 4. Add the Diamond Rapids RRL register configuration table and enable support. Intel-SIG: commit bdfc4367e3f5 EDAC/imh: Add RRL support for Intel Diamond Rapids server Backport EDAC retry_rd_error_log support for DMR Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Yi Lai Link: https://patch.msgid.link/20260521073112.3881223-9-qiuxu.zhuo@intel.com [ Zhang Rui: amend change log ] Signed-off-by: Zhang Rui --- drivers/edac/imh_base.c | 52 +++++++++++++++++++++++++++++++++++++++ drivers/edac/skx_common.h | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/drivers/edac/imh_base.c b/drivers/edac/imh_base.c index e0ce408a53323..c5906e8d540dc 100644 --- a/drivers/edac/imh_base.c +++ b/drivers/edac/imh_base.c @@ -71,6 +71,39 @@ struct local_reg { .width = (cfg)->ip_name##_reg_##reg_name##_width, \ } +static struct res_config *res_cfg; +static int retry_rd_err_log; + +#define REG_RRL_DEFINE(a0, a1, a2, a3, a4, a5, a6, b0, b1, b2, b3) \ + { \ + .set_num = 4, \ + .reg_num = 7, \ + .sources = {RRL_SRC_FRE_SCRUB, RRL_SRC_FRE_DEMAND, RRL_SRC_LRE_SCRUB, RRL_SRC_LRE_DEMAND}, \ + .offsets = { \ + {a0, a1, a2, a3, a4, a5, a6}, \ + {a0 + 4, a1 + 4, a2 + 8, a3 + 4, a4 + 4, a5 + 8, a6 + 8}, \ + {a0 + 8, a1 + 8, a2 + 16, a3 + 8, a4 + 8, a5 + 16, a6 + 16}, \ + {a0 + 12, a1 + 12, a2 + 24, a3 + 12, a4 + 12, a5 + 24, a6 + 24}, \ + }, \ + .widths = {4, 4, 8, 4, 4, 8, 8}, \ + .v_mask = BIT(0), \ + .uc_mask = BIT(1), \ + .over_mask = BIT(2), \ + .en_mask = BIT(12), \ + .en_patspr_mask = BIT(14), \ + .noover_mask = BIT(15), \ + .cecnt_num = 4, \ + .cecnt_offsets = {b0, b1, b2, b3}, \ + .cecnt_widths = {8, 8, 8, 8}, \ +} + +static struct reg_rrl dmr_reg_rrl_ddr_subch0 = REG_RRL_DEFINE( + 0x2dc0, 0x2dd0, 0x2de0, 0x2e00, 0x2e10, 0x2f70, 0x0200, + 0x2c10, 0x2c18, 0x2c20, 0x2c28); +static struct reg_rrl dmr_reg_rrl_ddr_subch1 = REG_RRL_DEFINE( + 0x6dc0, 0x6dd0, 0x6de0, 0x6e00, 0x6e10, 0x6f70, 0x4200, + 0x6c10, 0x6c18, 0x6c20, 0x6c28); + static void __read_local_reg(void *reg) { struct local_reg *r = (struct local_reg *)reg; @@ -480,6 +513,8 @@ static struct res_config dmr_cfg = { .ha_size = 0x1000, .ha_reg_mode_offset = 0x4a0, .ha_reg_mode_width = 4, + .reg_rrl_ddr[0] = &dmr_reg_rrl_ddr_subch0, + .reg_rrl_ddr[1] = &dmr_reg_rrl_ddr_subch1, }; static const struct x86_cpu_id imh_cpuids[] = { @@ -519,6 +554,7 @@ static int __init imh_init(void) return -ENODEV; cfg = (struct res_config *)id->driver_data; skx_set_res_cfg(cfg); + res_cfg = cfg; if (!imh_get_tolm_tohm(cfg, &tolm, &tohm)) return -ENODEV; @@ -553,6 +589,13 @@ static int __init imh_init(void) mce_register_decode_chain(&imh_mce_dec); skx_setup_debug("imh_test"); + cfg->rrl_ctrl_mode = retry_rd_err_log; + if (retry_rd_err_log && cfg->reg_rrl_ddr[0]) { + skx_set_show_rrl(skx_show_rrl); + if (retry_rd_err_log == RRL_CTRL_LINUX) + skx_enable_rrl(true); + } + imh_printk(KERN_INFO, "%s\n", IMH_REVISION); return 0; @@ -565,6 +608,12 @@ static void __exit imh_exit(void) { edac_dbg(2, "\n"); + if (retry_rd_err_log && res_cfg->reg_rrl_ddr[0]) { + if (retry_rd_err_log == RRL_CTRL_LINUX) + skx_enable_rrl(false); + skx_set_show_rrl(NULL); + } + skx_teardown_debug(); mce_unregister_decode_chain(&imh_mce_dec); skx_adxl_put(); @@ -574,6 +623,9 @@ static void __exit imh_exit(void) module_init(imh_init); module_exit(imh_exit); +module_param(retry_rd_err_log, int, 0444); +MODULE_PARM_DESC(retry_rd_err_log, "retry_rd_err_log: 0=off(default), 1=bios(Linux doesn't reset any control bits, but just reports values.), 2=linux(Linux tries to take control and resets mode bits, clear valid/UC bits after reading.)"); + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Qiuxu Zhuo"); MODULE_DESCRIPTION("MC Driver for Intel servers using IMH-based memory controller"); diff --git a/drivers/edac/skx_common.h b/drivers/edac/skx_common.h index 1b93de78233bb..2bd56abb3f787 100644 --- a/drivers/edac/skx_common.h +++ b/drivers/edac/skx_common.h @@ -82,7 +82,7 @@ /* Max RRL register sets per {,sub-,pseudo-}channel. */ #define NUM_RRL_SET 4 /* Max RRL registers per set. */ -#define NUM_RRL_REG 6 +#define NUM_RRL_REG 7 /* Max correctable error count registers. */ #define NUM_CECNT_REG 8