| // 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 <linux/kernel.h> | |
| #include <linux/io.h> | |
| #include <asm/cpu_device_id.h> | |
| #include <asm/intel-family.h> | |
| #include <asm/mce.h> | |
| #include <asm/cpu.h> | |
| #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) | |
| /* Home Agent (HA) */ | |
| #define NMCACHING(reg) GET_BITFIELD(reg, 8, 8) | |
| /** | |
| * 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 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; | |
| r->val = skx_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_obj(*dev); | |
| 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_flex(*d, imc, imc_num); | |
| 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; | |
| } | |
| 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; | |
| } | |
| static u32 read_imc_mcmtr(struct res_config *cfg, struct skx_imc *imc, int chan) | |
| { | |
| 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)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) | |
| { | |
| 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, | |
| .ha_base = 0x3eb000, | |
| .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[] = { | |
| 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); | |
| 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; | |
| skx_set_mem_cfg(imh_2lm_enabled(cfg, edac_list)); | |
| 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); | |
| 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; | |
| fail: | |
| skx_remove(); | |
| return rc; | |
| } | |
| 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(); | |
| skx_remove(); | |
| } | |
| 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"); |