[PATCH 2/3] EDAC/igen6: Fix memory topology parsing for Panther Lake-H SoCs
From: Qiuxu Zhuo
Date: Fri Apr 03 2026 - 01:42:39 EST
Panther Lake-H SoC memory controller registers for memory topology have
been updated, but the current igen6_edac driver still uses old generation
ones to incorrectly parse memory topology.
Fix the issue by adding memory topology parsing function pointers to the
'struct res_config' and creating a new configuration structure for Panther
Lake-H SoCs to enable igen6_edac to parse memory correctly.
Fixes: 0be9f1af3902 ("EDAC/igen6: Add Intel Panther Lake-H SoCs support")
Fixes: 4c36e6106997 ("EDAC/igen6: Add more Intel Panther Lake-H SoCs support")
Signed-off-by: Qiuxu Zhuo <qiuxu.zhuo@xxxxxxxxx>
---
drivers/edac/igen6_edac.c | 375 +++++++++++++++++++++++++++++++-------
include/linux/edac.h | 3 +
2 files changed, 308 insertions(+), 70 deletions(-)
diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c
index 0bf9cf349d0b..f849e3299593 100644
--- a/drivers/edac/igen6_edac.c
+++ b/drivers/edac/igen6_edac.c
@@ -122,6 +122,20 @@
#define MEM_SLICE_HASH_MASK(v) (GET_BITFIELD(v, 6, 19) << 6)
#define MEM_SLICE_HASH_LSB_MASK_BIT(v) GET_BITFIELD(v, 24, 26)
+struct igen6_imc {
+ int mc;
+ struct mem_ctl_info *mci;
+ struct pci_dev *pdev;
+ struct device dev;
+ void __iomem *window;
+ u64 size;
+ u64 ch_s_size;
+ int ch_l_map;
+ u64 dimm_s_size[NUM_CHANNELS];
+ u64 dimm_l_size[NUM_CHANNELS];
+ int dimm_l_map[NUM_CHANNELS];
+};
+
static struct res_config {
bool machine_check;
/* The number of present memory controllers. */
@@ -134,12 +148,29 @@ static struct res_config {
u64 reg_touud_mask;
/* IBECC error log */
u64 reg_eccerrlog_addr_mask;
+ /* MEMSS_PMA_CR registers. */
+ u32 reg_mem_config_offset;
+ u32 reg_mem_config_ddr_type_mask;
+ /* Memory controller registers. */
+ u32 reg_mad_inter_size_mask[NUM_CHANNELS];
+ u64 reg_mad_inter_size_granularity;
+ u32 reg_mad_intra_rank_mask[NUM_DIMMS];
+ u32 reg_mad_intra_width_mask[NUM_DIMMS];
+ u32 reg_mad_intra_density_mask[NUM_DIMMS];
u32 imc_base;
u32 cmf_base;
u32 cmf_size;
u32 ms_hash_offset;
u32 ibecc_base;
u32 ibecc_error_log_offset;
+ /* Get memory type. */
+ enum mem_type (*get_mem_type)(struct igen6_imc *imc);
+ /* Get DRAM chip type. */
+ enum dev_type (*get_dev_type)(struct igen6_imc *imc, int chan, int dimm_l);
+ /* Set imc->ch_{s_size,l_map}. */
+ void (*set_chan_params)(struct igen6_imc *imc);
+ /* Set imc->dimm_{l_size,s_size,l_map}[chan]. */
+ void (*set_dimm_params)(struct igen6_imc *imc, int chan);
bool (*ibecc_available)(struct pci_dev *pdev);
/* Extract error address logged in IBECC */
u64 (*err_addr)(u64 ecclog);
@@ -149,22 +180,9 @@ static struct res_config {
u64 (*err_addr_to_imc_addr)(u64 eaddr, int mc);
} *res_cfg;
-struct igen6_imc {
- int mc;
- struct mem_ctl_info *mci;
- struct pci_dev *pdev;
- struct device dev;
- void __iomem *window;
- u64 size;
- u64 ch_s_size;
- int ch_l_map;
- u64 dimm_s_size[NUM_CHANNELS];
- u64 dimm_l_size[NUM_CHANNELS];
- int dimm_l_map[NUM_CHANNELS];
-};
-
static struct igen6_pvt {
struct igen6_imc imc[NUM_IMC];
+ void __iomem *memss_pma_cr;
u64 ms_hash;
u64 ms_s_size;
int ms_l_map;
@@ -500,6 +518,119 @@ static u64 rpl_p_err_addr(u64 ecclog)
return field_get(res_cfg->reg_eccerrlog_addr_mask, ecclog);
}
+static enum mem_type ptl_h_get_mem_type(struct igen6_imc *imc)
+{
+ u32 mtype, val;
+
+ val = readl(igen6_pvt->memss_pma_cr + res_cfg->reg_mem_config_offset);
+ mtype = field_get(res_cfg->reg_mem_config_ddr_type_mask, val);
+
+ edac_dbg(2, "mtype %u (reg 0x%x)\n", mtype, val);
+
+ switch (mtype) {
+ case 1:
+ return MEM_DDR5;
+ case 2:
+ return MEM_LPDDR5;
+ case 3:
+ return MEM_LPDDR4;
+ default:
+ return MEM_UNKNOWN;
+ }
+}
+
+static enum dev_type ptl_h_get_dev_type(struct igen6_imc *imc, int chan, int dimm)
+{
+ u32 width, val;
+
+ val = readl(imc->window + MAD_INTRA_CH0_OFFSET + chan * 4);
+ width = field_get(res_cfg->reg_mad_intra_width_mask[dimm], val);
+
+ switch (width) {
+ case 1:
+ return DEV_X8;
+ default:
+ return DEV_X16;
+ }
+}
+
+static u64 ptl_h_get_chan_size(struct igen6_imc *imc, int chan)
+{
+ u32 val = readl(imc->window + MAD_INTER_CHANNEL_OFFSET);
+
+ return field_get(res_cfg->reg_mad_inter_size_mask[chan], val) *
+ res_cfg->reg_mad_inter_size_granularity;
+}
+
+static u64 ptl_h_get_dimm_size(struct igen6_imc *imc, int chan, int dimm)
+{
+ u32 val = readl(imc->window + MAD_INTRA_CH0_OFFSET + chan * 4);
+ u32 ranks = 1 << field_get(res_cfg->reg_mad_intra_rank_mask[dimm], val);
+ /* DRAM device density in Gb */
+ u64 density = field_get(res_cfg->reg_mad_intra_density_mask[dimm], val) * 4;
+
+ enum mem_type mtype = ptl_h_get_mem_type(imc);
+ enum dev_type dtype = ptl_h_get_dev_type(imc, chan, dimm);
+ u64 sub_ch_width, dev_num;
+
+ switch (mtype) {
+ case MEM_DDR5:
+ sub_ch_width = 32;
+ break;
+ case MEM_LPDDR5:
+ case MEM_LPDDR4:
+ sub_ch_width = 16;
+ break;
+ default:
+ sub_ch_width = 0;
+ }
+
+ switch (dtype) {
+ case DEV_X8:
+ dev_num = sub_ch_width / 8;
+ break;
+ case DEV_X16:
+ dev_num = sub_ch_width / 16;
+ break;
+ default:
+ dev_num = 0;
+ }
+
+ edac_dbg(2, "ranks %d, density %lluGb, sub_ch_width %llu, dev_num %llu (reg 0x%x)\n", ranks, density, sub_ch_width, dev_num, val);
+
+ return ((dev_num * density / 8) * ranks) << 30;
+}
+
+static void ptl_h_set_chan_params(struct igen6_imc *imc)
+{
+ u64 ch0_size = ptl_h_get_chan_size(imc, 0);
+ u64 ch1_size = ptl_h_get_chan_size(imc, 1);
+
+ if (ch0_size <= ch1_size) {
+ imc->ch_s_size = ch0_size;
+ imc->ch_l_map = 1;
+ } else {
+ imc->ch_s_size = ch1_size;
+ imc->ch_l_map = 0;
+ }
+}
+
+static void ptl_h_set_dimm_params(struct igen6_imc *imc, int chan)
+{
+ u64 dimm0_size = ptl_h_get_dimm_size(imc, chan, 0);
+ u64 dimm1_size = ptl_h_get_dimm_size(imc, chan, 1);
+
+ if (dimm0_size <= dimm1_size) {
+ imc->dimm_s_size[chan] = dimm0_size;
+ imc->dimm_l_size[chan] = dimm1_size;
+ imc->dimm_l_map[chan] = 1;
+ } else {
+ imc->dimm_s_size[chan] = dimm1_size;
+ imc->dimm_l_size[chan] = dimm0_size;
+ imc->dimm_l_map[chan] = 0;
+ }
+}
+
static struct res_config ehl_cfg = {
.num_imc = 1,
.reg_mchbar_mask = GENMASK_ULL(38, 16),
@@ -622,6 +753,36 @@ static struct res_config mtl_p_cfg = {
.err_addr_to_imc_addr = adl_err_addr_to_imc_addr,
};
+static struct res_config ptl_h_cfg = {
+ .machine_check = true,
+ .num_imc = 2,
+ .reg_mchbar_mask = GENMASK_ULL(41, 17),
+ .reg_tom_mask = GENMASK_ULL(41, 20),
+ .reg_touud_mask = GENMASK_ULL(41, 20),
+ .reg_eccerrlog_addr_mask = GENMASK_ULL(38, 5),
+ .reg_mem_config_offset = 0x13d04,
+ .reg_mem_config_ddr_type_mask = GENMASK(8, 6),
+ .reg_mad_inter_size_mask[0] = GENMASK(15, 8),
+ .reg_mad_inter_size_mask[1] = GENMASK(23, 16),
+ .reg_mad_inter_size_granularity = BIT_ULL(29),
+ .reg_mad_intra_rank_mask[0] = BIT(7),
+ .reg_mad_intra_rank_mask[1] = BIT(15),
+ .reg_mad_intra_width_mask[0] = BIT(6),
+ .reg_mad_intra_width_mask[1] = BIT(14),
+ .reg_mad_intra_density_mask[0] = GENMASK(3, 0),
+ .reg_mad_intra_density_mask[1] = GENMASK(11, 8),
+ .imc_base = 0xd800,
+ .ibecc_base = 0xd400,
+ .ibecc_error_log_offset = 0x170,
+ .get_mem_type = ptl_h_get_mem_type,
+ .get_dev_type = ptl_h_get_dev_type,
+ .set_chan_params = ptl_h_set_chan_params,
+ .set_dimm_params = ptl_h_set_dimm_params,
+ .ibecc_available = mtl_p_ibecc_available,
+ .err_addr_to_sys_addr = adl_err_addr_to_sys_addr,
+ .err_addr_to_imc_addr = adl_err_addr_to_imc_addr,
+};
+
static struct res_config wcl_cfg = {
.machine_check = true,
.num_imc = 1,
@@ -689,46 +850,34 @@ static struct pci_device_id igen6_pci_tbl[] = {
{ PCI_VDEVICE(INTEL, DID_ARL_UH_SKU1), (kernel_ulong_t)&mtl_p_cfg },
{ PCI_VDEVICE(INTEL, DID_ARL_UH_SKU2), (kernel_ulong_t)&mtl_p_cfg },
{ PCI_VDEVICE(INTEL, DID_ARL_UH_SKU3), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU1), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU2), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU3), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU4), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU5), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU6), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU7), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU8), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU9), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU10), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU11), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU12), (kernel_ulong_t)&mtl_p_cfg },
- { PCI_VDEVICE(INTEL, DID_PTL_H_SKU13), (kernel_ulong_t)&mtl_p_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU1), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU2), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU3), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU4), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU5), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU6), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU7), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU8), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU9), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU10), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU11), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU12), (kernel_ulong_t)&ptl_h_cfg },
+ { PCI_VDEVICE(INTEL, DID_PTL_H_SKU13), (kernel_ulong_t)&ptl_h_cfg },
{ PCI_VDEVICE(INTEL, DID_WCL_SKU1), (kernel_ulong_t)&wcl_cfg },
{ },
};
MODULE_DEVICE_TABLE(pci, igen6_pci_tbl);
-static enum dev_type get_width(int dimm_l, u32 mad_dimm)
+static enum mem_type get_mem_type(struct igen6_imc *imc)
{
- u32 w = dimm_l ? MAD_DIMM_CH_DLW(mad_dimm) :
- MAD_DIMM_CH_DSW(mad_dimm);
+ u32 val;
- switch (w) {
- case 0:
- return DEV_X8;
- case 1:
- return DEV_X16;
- case 2:
- return DEV_X32;
- default:
- return DEV_UNKNOWN;
- }
-}
+ if (res_cfg->get_mem_type)
+ return res_cfg->get_mem_type(imc);
-static enum mem_type get_memory_type(u32 mad_inter)
-{
- u32 t = MAD_INTER_CHANNEL_DDR_TYPE(mad_inter);
+ val = readl(imc->window + MAD_INTER_CHANNEL_OFFSET);
- switch (t) {
+ switch (MAD_INTER_CHANNEL_DDR_TYPE(val)) {
case 0:
return MEM_DDR4;
case 1:
@@ -744,6 +893,73 @@ static enum mem_type get_memory_type(u32 mad_inter)
}
}
+static bool large_dimm(struct igen6_imc *imc, int chan, int dimm)
+{
+ return dimm == imc->dimm_l_map[chan];
+}
+
+static enum dev_type get_dev_type(struct igen6_imc *imc, int chan, int dimm)
+{
+ u32 width, val;
+
+ if (res_cfg->get_dev_type)
+ return res_cfg->get_dev_type(imc, chan, dimm);
+
+ val = readl(imc->window + MAD_DIMM_CH0_OFFSET + chan * 4);
+ width = large_dimm(imc, chan, dimm) ? MAD_DIMM_CH_DLW(val) :
+ MAD_DIMM_CH_DSW(val);
+
+ switch (width) {
+ case 0:
+ return DEV_X8;
+ case 1:
+ return DEV_X16;
+ case 2:
+ return DEV_X32;
+ default:
+ return DEV_UNKNOWN;
+ }
+}
+
+static u64 get_dimm_size(struct igen6_imc *imc, int chan, int dimm)
+{
+ if (large_dimm(imc, chan, dimm))
+ return imc->dimm_l_size[chan];
+
+ return imc->dimm_s_size[chan];
+}
+
+static void set_chan_params(struct igen6_imc *imc)
+{
+ u32 val;
+
+ if (res_cfg->set_chan_params) {
+ res_cfg->set_chan_params(imc);
+ return;
+ }
+
+ val = readl(imc->window + MAD_INTER_CHANNEL_OFFSET);
+ imc->ch_s_size = MAD_INTER_CHANNEL_CH_S_SIZE(val);
+ imc->ch_l_map = MAD_INTER_CHANNEL_CH_L_MAP(val);
+}
+
+static void set_dimm_params(struct igen6_imc *imc, int chan)
+{
+ u32 val;
+
+ if (res_cfg->set_dimm_params) {
+ res_cfg->set_dimm_params(imc, chan);
+ return;
+ }
+
+ val = readl(imc->window + MAD_INTRA_CH0_OFFSET + chan * 4);
+ imc->dimm_l_map[chan] = MAD_INTRA_CH_DIMM_L_MAP(val);
+
+ val = readl(imc->window + MAD_DIMM_CH0_OFFSET + chan * 4);
+ imc->dimm_l_size[chan] = MAD_DIMM_CH_DIMM_L_SIZE(val);
+ imc->dimm_s_size[chan] = MAD_DIMM_CH_DIMM_S_SIZE(val);
+}
+
static int decode_chan_idx(u64 addr, u64 mask, int intlv_bit)
{
u64 hash_addr = addr & mask, hash = 0;
@@ -1084,7 +1300,6 @@ static bool igen6_check_ecc(struct igen6_imc *imc)
static int igen6_get_dimm_config(struct mem_ctl_info *mci)
{
struct igen6_imc *imc = mci->pvt_info;
- u32 mad_inter, mad_intra, mad_dimm;
int i, j, ndimms, mc = imc->mc;
struct dimm_info *dimm;
enum mem_type mtype;
@@ -1094,33 +1309,20 @@ static int igen6_get_dimm_config(struct mem_ctl_info *mci)
edac_dbg(2, "\n");
- mad_inter = readl(imc->window + MAD_INTER_CHANNEL_OFFSET);
- mtype = get_memory_type(mad_inter);
+ mtype = get_mem_type(imc);
ecc = igen6_check_ecc(imc);
- imc->ch_s_size = MAD_INTER_CHANNEL_CH_S_SIZE(mad_inter);
- imc->ch_l_map = MAD_INTER_CHANNEL_CH_L_MAP(mad_inter);
+ set_chan_params(imc);
for (i = 0; i < NUM_CHANNELS; i++) {
- mad_intra = readl(imc->window + MAD_INTRA_CH0_OFFSET + i * 4);
- mad_dimm = readl(imc->window + MAD_DIMM_CH0_OFFSET + i * 4);
-
- imc->dimm_l_size[i] = MAD_DIMM_CH_DIMM_L_SIZE(mad_dimm);
- imc->dimm_s_size[i] = MAD_DIMM_CH_DIMM_S_SIZE(mad_dimm);
- imc->dimm_l_map[i] = MAD_INTRA_CH_DIMM_L_MAP(mad_intra);
+ set_dimm_params(imc, i);
imc->size += imc->dimm_s_size[i];
imc->size += imc->dimm_l_size[i];
ndimms = 0;
for (j = 0; j < NUM_DIMMS; j++) {
dimm = edac_get_dimm(mci, i, j, 0);
-
- if (j ^ imc->dimm_l_map[i]) {
- dtype = get_width(0, mad_dimm);
- dsize = imc->dimm_s_size[i];
- } else {
- dtype = get_width(1, mad_dimm);
- dsize = imc->dimm_l_size[i];
- }
+ dtype = get_dev_type(imc, i, j);
+ dsize = get_dimm_size(imc, i, j);
if (!dsize)
continue;
@@ -1223,6 +1425,39 @@ static void igen6_debug_setup(void) {}
static void igen6_debug_teardown(void) {}
#endif
+static struct igen6_pvt *igen6_pvt_setup(struct pci_dev *pdev)
+{
+ void __iomem *memss_pma_cr;
+ struct igen6_pvt *pvt;
+ u64 mchbar;
+ int rc;
+
+ pvt = kzalloc_obj(*igen6_pvt);
+ if (!pvt)
+ return NULL;
+
+ rc = get_mchbar(pdev, &mchbar);
+ if (rc) {
+ kfree(pvt);
+ return NULL;
+ }
+
+ memss_pma_cr = ioremap(mchbar, MCHBAR_SIZE * 2);
+ if (!memss_pma_cr) {
+ kfree(pvt);
+ return NULL;
+ }
+ pvt->memss_pma_cr = memss_pma_cr;
+
+ return pvt;
+}
+
+static void igen6_pvt_release(struct igen6_pvt *pvt)
+{
+ iounmap(pvt->memss_pma_cr);
+ kfree(pvt);
+}
+
static int igen6_pci_setup(struct pci_dev *pdev, u64 *mchbar)
{
union {
@@ -1555,12 +1790,12 @@ static int igen6_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
edac_dbg(2, "\n");
- igen6_pvt = kzalloc_obj(*igen6_pvt);
- if (!igen6_pvt)
- return -ENOMEM;
-
res_cfg = (struct res_config *)ent->driver_data;
+ igen6_pvt = igen6_pvt_setup(pdev);
+ if (!igen6_pvt)
+ return -ENOMEM;
+
rc = igen6_pci_setup(pdev, &mchbar);
if (rc)
goto fail;
@@ -1609,7 +1844,7 @@ static int igen6_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
fail2:
igen6_unregister_mcis();
fail:
- kfree(igen6_pvt);
+ igen6_pvt_release(igen6_pvt);
return rc;
}
@@ -1624,7 +1859,7 @@ static void igen6_remove(struct pci_dev *pdev)
flush_work(&ecclog_work);
gen_pool_destroy(ecclog_pool);
igen6_unregister_mcis();
- kfree(igen6_pvt);
+ igen6_pvt_release(igen6_pvt);
}
static struct pci_driver igen6_driver = {
diff --git a/include/linux/edac.h b/include/linux/edac.h
index fa32f2aca22f..2a3511b0ad1c 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -184,6 +184,7 @@ static inline char *mc_event_error_type(const unsigned int err_type)
* @MEM_DDR5: Unbuffered DDR5 RAM
* @MEM_RDDR5: Registered DDR5 RAM
* @MEM_LRDDR5: Load-Reduced DDR5 memory.
+ * @MEM_LPDDR5: Low-Power DDR5 memory.
* @MEM_NVDIMM: Non-volatile RAM
* @MEM_WIO2: Wide I/O 2.
* @MEM_HBM2: High bandwidth Memory Gen 2.
@@ -216,6 +217,7 @@ enum mem_type {
MEM_DDR5,
MEM_RDDR5,
MEM_LRDDR5,
+ MEM_LPDDR5,
MEM_NVDIMM,
MEM_WIO2,
MEM_HBM2,
@@ -247,6 +249,7 @@ enum mem_type {
#define MEM_FLAG_DDR5 BIT(MEM_DDR5)
#define MEM_FLAG_RDDR5 BIT(MEM_RDDR5)
#define MEM_FLAG_LRDDR5 BIT(MEM_LRDDR5)
+#define MEM_FLAG_LPDDR5 BIT(MEM_LPDDR5)
#define MEM_FLAG_NVDIMM BIT(MEM_NVDIMM)
#define MEM_FLAG_WIO2 BIT(MEM_WIO2)
#define MEM_FLAG_HBM2 BIT(MEM_HBM2)
--
2.43.0