| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- // 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 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_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;
- }
- /* 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,
- .ha_base = 0x3eb000,
- .ha_size = 0x1000,
- .ha_reg_mode_offset = 0x4a0,
- .ha_reg_mode_width = 4,
- };
- 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;
- 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");
- 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");
- skx_teardown_debug();
- 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");
|