From: Qiuxu Zhuo Date: Fri, 3 Apr 2026 05:40:28 +0000 (+0800) Subject: EDAC/igen6: Fix memory topology parsing for Panther Lake-H SoCs X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=114bfa24eacb688488caa2e459358a1b9b89b16d;p=thirdparty%2Flinux.git EDAC/igen6: Fix memory topology parsing for Panther Lake-H SoCs 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 Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260403054029.3950383-3-qiuxu.zhuo@intel.com --- diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 0bf9cf349d0b4..f849e3299593f 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); + res_cfg = (struct res_config *)ent->driver_data; + + igen6_pvt = igen6_pvt_setup(pdev); if (!igen6_pvt) return -ENOMEM; - res_cfg = (struct res_config *)ent->driver_data; - rc = igen6_pci_setup(pdev, &mchbar); if (rc) goto fail; @@ -1609,7 +1844,7 @@ fail3: 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 deba46b3ee25d..e6b4e51130e5f 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)