2019-01-30 22:15:19 +03:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
|
|
* Driver for Intel(R) 10nm server memory controller.
|
|
|
|
* Copyright (c) 2019, Intel Corporation.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
2020-11-17 15:49:51 +03:00
|
|
|
#include <linux/io.h>
|
2019-01-30 22:15:19 +03:00
|
|
|
#include <asm/cpu_device_id.h>
|
|
|
|
#include <asm/intel-family.h>
|
|
|
|
#include <asm/mce.h>
|
|
|
|
#include "edac_module.h"
|
|
|
|
#include "skx_common.h"
|
|
|
|
|
2021-06-11 20:01:20 +03:00
|
|
|
#define I10NM_REVISION "v0.0.5"
|
2019-01-30 22:15:19 +03:00
|
|
|
#define EDAC_MOD_STR "i10nm_edac"
|
|
|
|
|
|
|
|
/* Debug macros */
|
|
|
|
#define i10nm_printk(level, fmt, arg...) \
|
|
|
|
edac_printk(level, "i10nm", fmt, ##arg)
|
|
|
|
|
2020-11-17 15:49:51 +03:00
|
|
|
#define I10NM_GET_SCK_BAR(d, reg) \
|
2019-01-30 22:15:19 +03:00
|
|
|
pci_read_config_dword((d)->uracu, 0xd0, &(reg))
|
|
|
|
#define I10NM_GET_IMC_BAR(d, i, reg) \
|
|
|
|
pci_read_config_dword((d)->uracu, 0xd8 + (i) * 4, &(reg))
|
2021-06-11 20:01:19 +03:00
|
|
|
#define I10NM_GET_SAD(d, offset, i, reg)\
|
|
|
|
pci_read_config_dword((d)->sad_all, (offset) + (i) * 8, &(reg))
|
2021-06-11 20:01:20 +03:00
|
|
|
#define I10NM_GET_HBM_IMC_BAR(d, reg) \
|
|
|
|
pci_read_config_dword((d)->uracu, 0xd4, &(reg))
|
|
|
|
#define I10NM_GET_CAPID3_CFG(d, reg) \
|
|
|
|
pci_read_config_dword((d)->pcu_cr3, 0x90, &(reg))
|
2019-01-30 22:15:19 +03:00
|
|
|
#define I10NM_GET_DIMMMTR(m, i, j) \
|
2021-06-11 20:01:20 +03:00
|
|
|
readl((m)->mbase + ((m)->hbm_mc ? 0x80c : 0x2080c) + \
|
|
|
|
(i) * (m)->chan_mmio_sz + (j) * 4)
|
2019-01-30 22:15:19 +03:00
|
|
|
#define I10NM_GET_MCDDRTCFG(m, i, j) \
|
2021-06-11 20:01:20 +03:00
|
|
|
readl((m)->mbase + ((m)->hbm_mc ? 0x970 : 0x20970) + \
|
|
|
|
(i) * (m)->chan_mmio_sz + (j) * 4)
|
2020-11-17 15:49:51 +03:00
|
|
|
#define I10NM_GET_MCMTR(m, i) \
|
2021-06-11 20:01:20 +03:00
|
|
|
readl((m)->mbase + ((m)->hbm_mc ? 0xef8 : 0x20ef8) + \
|
|
|
|
(i) * (m)->chan_mmio_sz)
|
2020-11-17 15:49:53 +03:00
|
|
|
#define I10NM_GET_AMAP(m, i) \
|
2021-06-11 20:01:20 +03:00
|
|
|
readl((m)->mbase + ((m)->hbm_mc ? 0x814 : 0x20814) + \
|
|
|
|
(i) * (m)->chan_mmio_sz)
|
2019-01-30 22:15:19 +03:00
|
|
|
|
|
|
|
#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)
|
|
|
|
#define I10NM_GET_IMC_MMIO_SIZE(reg) ((GET_BITFIELD(reg, 13, 23) - \
|
|
|
|
GET_BITFIELD(reg, 0, 10) + 1) << 12)
|
2021-06-11 20:01:20 +03:00
|
|
|
#define I10NM_GET_HBM_IMC_MMIO_OFFSET(reg) \
|
|
|
|
((GET_BITFIELD(reg, 0, 10) << 12) + 0x140000)
|
|
|
|
|
|
|
|
#define I10NM_HBM_IMC_MMIO_SIZE 0x9000
|
|
|
|
#define I10NM_IS_HBM_PRESENT(reg) GET_BITFIELD(reg, 27, 30)
|
|
|
|
#define I10NM_IS_HBM_IMC(reg) GET_BITFIELD(reg, 29, 29)
|
2019-01-30 22:15:19 +03:00
|
|
|
|
2021-06-11 20:01:19 +03:00
|
|
|
#define I10NM_MAX_SAD 16
|
|
|
|
#define I10NM_SAD_ENABLE(reg) GET_BITFIELD(reg, 0, 0)
|
|
|
|
#define I10NM_SAD_NM_CACHEABLE(reg) GET_BITFIELD(reg, 5, 5)
|
|
|
|
|
2019-01-30 22:15:19 +03:00
|
|
|
static struct list_head *i10nm_edac_list;
|
|
|
|
|
|
|
|
static struct pci_dev *pci_get_dev_wrapper(int dom, unsigned int bus,
|
|
|
|
unsigned int dev, unsigned int fun)
|
|
|
|
{
|
|
|
|
struct pci_dev *pdev;
|
|
|
|
|
|
|
|
pdev = pci_get_domain_bus_and_slot(dom, bus, PCI_DEVFN(dev, fun));
|
|
|
|
if (!pdev) {
|
|
|
|
edac_dbg(2, "No device %02x:%02x.%x\n",
|
|
|
|
bus, dev, fun);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unlikely(pci_enable_device(pdev) < 0)) {
|
|
|
|
edac_dbg(2, "Failed to enable device %02x:%02x.%x\n",
|
|
|
|
bus, dev, fun);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pci_dev_get(pdev);
|
|
|
|
|
|
|
|
return pdev;
|
|
|
|
}
|
|
|
|
|
2021-06-11 20:01:19 +03:00
|
|
|
static bool i10nm_check_2lm(struct res_config *cfg)
|
|
|
|
{
|
|
|
|
struct skx_dev *d;
|
|
|
|
u32 reg;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
list_for_each_entry(d, i10nm_edac_list, list) {
|
|
|
|
d->sad_all = pci_get_dev_wrapper(d->seg, d->bus[1],
|
|
|
|
PCI_SLOT(cfg->sad_all_devfn),
|
|
|
|
PCI_FUNC(cfg->sad_all_devfn));
|
|
|
|
if (!d->sad_all)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (i = 0; i < I10NM_MAX_SAD; i++) {
|
|
|
|
I10NM_GET_SAD(d, cfg->sad_all_offset, i, reg);
|
|
|
|
if (I10NM_SAD_ENABLE(reg) && I10NM_SAD_NM_CACHEABLE(reg)) {
|
|
|
|
edac_dbg(2, "2-level memory configuration.\n");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-11 20:01:20 +03:00
|
|
|
static int i10nm_get_ddr_munits(void)
|
2019-01-30 22:15:19 +03:00
|
|
|
{
|
|
|
|
struct pci_dev *mdev;
|
|
|
|
void __iomem *mbase;
|
|
|
|
unsigned long size;
|
|
|
|
struct skx_dev *d;
|
|
|
|
int i, j = 0;
|
|
|
|
u32 reg, off;
|
|
|
|
u64 base;
|
|
|
|
|
|
|
|
list_for_each_entry(d, i10nm_edac_list, list) {
|
|
|
|
d->util_all = pci_get_dev_wrapper(d->seg, d->bus[1], 29, 1);
|
|
|
|
if (!d->util_all)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
d->uracu = pci_get_dev_wrapper(d->seg, d->bus[0], 0, 1);
|
|
|
|
if (!d->uracu)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (I10NM_GET_SCK_BAR(d, reg)) {
|
|
|
|
i10nm_printk(KERN_ERR, "Failed to socket bar\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
base = I10NM_GET_SCK_MMIO_BASE(reg);
|
|
|
|
edac_dbg(2, "socket%d mmio base 0x%llx (reg 0x%x)\n",
|
|
|
|
j++, base, reg);
|
|
|
|
|
2021-06-11 20:01:20 +03:00
|
|
|
for (i = 0; i < I10NM_NUM_DDR_IMC; i++) {
|
2019-01-30 22:15:19 +03:00
|
|
|
mdev = pci_get_dev_wrapper(d->seg, d->bus[0],
|
|
|
|
12 + i, 0);
|
|
|
|
if (i == 0 && !mdev) {
|
|
|
|
i10nm_printk(KERN_ERR, "No IMC found\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
if (!mdev)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
d->imc[i].mdev = mdev;
|
|
|
|
|
|
|
|
if (I10NM_GET_IMC_BAR(d, i, reg)) {
|
|
|
|
i10nm_printk(KERN_ERR, "Failed to get mc bar\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
off = I10NM_GET_IMC_MMIO_OFFSET(reg);
|
|
|
|
size = I10NM_GET_IMC_MMIO_SIZE(reg);
|
|
|
|
edac_dbg(2, "mc%d mmio base 0x%llx size 0x%lx (reg 0x%x)\n",
|
|
|
|
i, base + off, size, reg);
|
|
|
|
|
|
|
|
mbase = ioremap(base + off, size);
|
|
|
|
if (!mbase) {
|
|
|
|
i10nm_printk(KERN_ERR, "Failed to ioremap 0x%llx\n",
|
|
|
|
base + off);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
d->imc[i].mbase = mbase;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-06-11 20:01:20 +03:00
|
|
|
static bool i10nm_check_hbm_imc(struct skx_dev *d)
|
|
|
|
{
|
|
|
|
u32 reg;
|
|
|
|
|
|
|
|
if (I10NM_GET_CAPID3_CFG(d, reg)) {
|
|
|
|
i10nm_printk(KERN_ERR, "Failed to get capid3_cfg\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return I10NM_IS_HBM_PRESENT(reg) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int i10nm_get_hbm_munits(void)
|
|
|
|
{
|
|
|
|
struct pci_dev *mdev;
|
|
|
|
void __iomem *mbase;
|
|
|
|
u32 reg, off, mcmtr;
|
|
|
|
struct skx_dev *d;
|
|
|
|
int i, lmc;
|
|
|
|
u64 base;
|
|
|
|
|
|
|
|
list_for_each_entry(d, i10nm_edac_list, list) {
|
|
|
|
d->pcu_cr3 = pci_get_dev_wrapper(d->seg, d->bus[1], 30, 3);
|
|
|
|
if (!d->pcu_cr3)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (!i10nm_check_hbm_imc(d)) {
|
|
|
|
i10nm_printk(KERN_DEBUG, "No hbm memory\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (I10NM_GET_SCK_BAR(d, reg)) {
|
|
|
|
i10nm_printk(KERN_ERR, "Failed to get socket bar\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
base = I10NM_GET_SCK_MMIO_BASE(reg);
|
|
|
|
|
|
|
|
if (I10NM_GET_HBM_IMC_BAR(d, reg)) {
|
|
|
|
i10nm_printk(KERN_ERR, "Failed to get hbm mc bar\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
base += I10NM_GET_HBM_IMC_MMIO_OFFSET(reg);
|
|
|
|
|
|
|
|
lmc = I10NM_NUM_DDR_IMC;
|
|
|
|
|
|
|
|
for (i = 0; i < I10NM_NUM_HBM_IMC; i++) {
|
|
|
|
mdev = pci_get_dev_wrapper(d->seg, d->bus[0],
|
|
|
|
12 + i / 4, 1 + i % 4);
|
|
|
|
if (i == 0 && !mdev) {
|
|
|
|
i10nm_printk(KERN_ERR, "No hbm mc found\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
if (!mdev)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
d->imc[lmc].mdev = mdev;
|
|
|
|
off = i * I10NM_HBM_IMC_MMIO_SIZE;
|
|
|
|
|
|
|
|
edac_dbg(2, "hbm mc%d mmio base 0x%llx size 0x%x\n",
|
|
|
|
lmc, base + off, I10NM_HBM_IMC_MMIO_SIZE);
|
|
|
|
|
|
|
|
mbase = ioremap(base + off, I10NM_HBM_IMC_MMIO_SIZE);
|
|
|
|
if (!mbase) {
|
|
|
|
i10nm_printk(KERN_ERR, "Failed to ioremap for hbm mc 0x%llx\n",
|
|
|
|
base + off);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
d->imc[lmc].mbase = mbase;
|
|
|
|
d->imc[lmc].hbm_mc = true;
|
|
|
|
|
|
|
|
mcmtr = I10NM_GET_MCMTR(&d->imc[lmc], 0);
|
|
|
|
if (!I10NM_IS_HBM_IMC(mcmtr)) {
|
|
|
|
i10nm_printk(KERN_ERR, "This isn't an hbm mc!\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
lmc++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-04-24 15:18:33 +03:00
|
|
|
static struct res_config i10nm_cfg0 = {
|
2020-04-24 15:18:14 +03:00
|
|
|
.type = I10NM,
|
|
|
|
.decs_did = 0x3452,
|
|
|
|
.busno_cfg_offset = 0xcc,
|
2020-11-17 15:49:53 +03:00
|
|
|
.ddr_chan_mmio_sz = 0x4000,
|
2021-06-11 20:01:19 +03:00
|
|
|
.sad_all_devfn = PCI_DEVFN(29, 0),
|
|
|
|
.sad_all_offset = 0x108,
|
2020-04-24 15:18:14 +03:00
|
|
|
};
|
|
|
|
|
2020-04-24 15:18:33 +03:00
|
|
|
static struct res_config i10nm_cfg1 = {
|
|
|
|
.type = I10NM,
|
|
|
|
.decs_did = 0x3452,
|
|
|
|
.busno_cfg_offset = 0xd0,
|
2020-11-17 15:49:53 +03:00
|
|
|
.ddr_chan_mmio_sz = 0x4000,
|
2021-06-11 20:01:19 +03:00
|
|
|
.sad_all_devfn = PCI_DEVFN(29, 0),
|
|
|
|
.sad_all_offset = 0x108,
|
2020-11-17 15:49:53 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct res_config spr_cfg = {
|
|
|
|
.type = SPR,
|
|
|
|
.decs_did = 0x3252,
|
|
|
|
.busno_cfg_offset = 0xd0,
|
|
|
|
.ddr_chan_mmio_sz = 0x8000,
|
2021-06-11 20:01:20 +03:00
|
|
|
.hbm_chan_mmio_sz = 0x4000,
|
2020-11-17 15:49:53 +03:00
|
|
|
.support_ddr5 = true,
|
2021-06-11 20:01:19 +03:00
|
|
|
.sad_all_devfn = PCI_DEVFN(10, 0),
|
|
|
|
.sad_all_offset = 0x300,
|
2020-04-24 15:18:33 +03:00
|
|
|
};
|
|
|
|
|
2019-01-30 22:15:19 +03:00
|
|
|
static const struct x86_cpu_id i10nm_cpuids[] = {
|
2020-05-09 04:08:22 +03:00
|
|
|
X86_MATCH_INTEL_FAM6_MODEL_STEPPINGS(ATOM_TREMONT_D, X86_STEPPINGS(0x0, 0x3), &i10nm_cfg0),
|
|
|
|
X86_MATCH_INTEL_FAM6_MODEL_STEPPINGS(ATOM_TREMONT_D, X86_STEPPINGS(0x4, 0xf), &i10nm_cfg1),
|
|
|
|
X86_MATCH_INTEL_FAM6_MODEL_STEPPINGS(ICELAKE_X, X86_STEPPINGS(0x0, 0x3), &i10nm_cfg0),
|
|
|
|
X86_MATCH_INTEL_FAM6_MODEL_STEPPINGS(ICELAKE_X, X86_STEPPINGS(0x4, 0xf), &i10nm_cfg1),
|
|
|
|
X86_MATCH_INTEL_FAM6_MODEL_STEPPINGS(ICELAKE_D, X86_STEPPINGS(0x0, 0xf), &i10nm_cfg1),
|
2020-11-17 15:49:53 +03:00
|
|
|
X86_MATCH_INTEL_FAM6_MODEL_STEPPINGS(SAPPHIRERAPIDS_X, X86_STEPPINGS(0x0, 0xf), &spr_cfg),
|
2020-03-20 16:13:55 +03:00
|
|
|
{}
|
2019-01-30 22:15:19 +03:00
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(x86cpu, i10nm_cpuids);
|
|
|
|
|
|
|
|
static bool i10nm_check_ecc(struct skx_imc *imc, int chan)
|
|
|
|
{
|
|
|
|
u32 mcmtr;
|
|
|
|
|
2020-11-17 15:49:51 +03:00
|
|
|
mcmtr = I10NM_GET_MCMTR(imc, chan);
|
2019-01-30 22:15:19 +03:00
|
|
|
edac_dbg(1, "ch%d mcmtr reg %x\n", chan, mcmtr);
|
|
|
|
|
|
|
|
return !!GET_BITFIELD(mcmtr, 2, 2);
|
|
|
|
}
|
|
|
|
|
2020-11-17 15:49:53 +03:00
|
|
|
static int i10nm_get_dimm_config(struct mem_ctl_info *mci,
|
|
|
|
struct res_config *cfg)
|
2019-01-30 22:15:19 +03:00
|
|
|
{
|
|
|
|
struct skx_pvt *pvt = mci->pvt_info;
|
|
|
|
struct skx_imc *imc = pvt->imc;
|
2020-11-17 15:49:53 +03:00
|
|
|
u32 mtr, amap, mcddrtcfg;
|
2019-01-30 22:15:19 +03:00
|
|
|
struct dimm_info *dimm;
|
|
|
|
int i, j, ndimms;
|
|
|
|
|
2021-06-11 20:01:20 +03:00
|
|
|
for (i = 0; i < imc->num_channels; i++) {
|
2019-01-30 22:15:19 +03:00
|
|
|
if (!imc->mbase)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ndimms = 0;
|
2020-11-17 15:49:53 +03:00
|
|
|
amap = I10NM_GET_AMAP(imc, i);
|
2021-06-11 20:01:20 +03:00
|
|
|
for (j = 0; j < imc->num_dimms; j++) {
|
EDAC: Replace EDAC_DIMM_PTR() macro with edac_get_dimm() function
The EDAC_DIMM_PTR() macro takes 3 arguments from struct mem_ctl_info.
Clean up this interface to only pass the mci struct and replace this
macro with a new function edac_get_dimm().
Also introduce an edac_get_dimm_by_index() function for later use.
This allows it to get a DIMM pointer only by a given index. This can
be useful if the DIMM's position within the layers of the memory
controller or the exact size of the layers are unknown.
Small style changes made for some hunks after applying the semantic
patch.
Semantic patch used:
@@ expression mci, a, b,c; @@
-EDAC_DIMM_PTR(mci->layers, mci->dimms, mci->n_layers, a, b, c)
+edac_get_dimm(mci, a, b, c)
[ bp: Touchups. ]
Signed-off-by: Robert Richter <rrichter@marvell.com>
Signed-off-by: Borislav Petkov <bp@suse.de>
Reviewed-by: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: "linux-edac@vger.kernel.org" <linux-edac@vger.kernel.org>
Cc: James Morse <james.morse@arm.com>
Cc: Jason Baron <jbaron@akamai.com>
Cc: Qiuxu Zhuo <qiuxu.zhuo@intel.com>
Cc: Tero Kristo <t-kristo@ti.com>
Cc: Tony Luck <tony.luck@intel.com>
Link: https://lkml.kernel.org/r/20191106093239.25517-2-rrichter@marvell.com
2019-11-06 12:33:02 +03:00
|
|
|
dimm = edac_get_dimm(mci, i, j, 0);
|
2019-01-30 22:15:19 +03:00
|
|
|
mtr = I10NM_GET_DIMMMTR(imc, i, j);
|
|
|
|
mcddrtcfg = I10NM_GET_MCDDRTCFG(imc, i, j);
|
|
|
|
edac_dbg(1, "dimmmtr 0x%x mcddrtcfg 0x%x (mc%d ch%d dimm%d)\n",
|
|
|
|
mtr, mcddrtcfg, imc->mc, i, j);
|
|
|
|
|
|
|
|
if (IS_DIMM_PRESENT(mtr))
|
2020-11-17 15:49:53 +03:00
|
|
|
ndimms += skx_get_dimm_info(mtr, 0, amap, dimm,
|
|
|
|
imc, i, j, cfg);
|
2019-01-30 22:15:19 +03:00
|
|
|
else if (IS_NVDIMM_PRESENT(mcddrtcfg, j))
|
|
|
|
ndimms += skx_get_nvdimm_info(dimm, imc, i, j,
|
|
|
|
EDAC_MOD_STR);
|
|
|
|
}
|
2019-06-26 09:16:38 +03:00
|
|
|
if (ndimms && !i10nm_check_ecc(imc, i)) {
|
|
|
|
i10nm_printk(KERN_ERR, "ECC is disabled on imc %d channel %d\n",
|
|
|
|
imc->mc, i);
|
2019-01-30 22:15:19 +03:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct notifier_block i10nm_mce_dec = {
|
|
|
|
.notifier_call = skx_mce_check_error,
|
|
|
|
.priority = MCE_PRIO_EDAC,
|
|
|
|
};
|
|
|
|
|
2019-03-22 01:13:39 +03:00
|
|
|
#ifdef CONFIG_EDAC_DEBUG
|
|
|
|
/*
|
|
|
|
* Debug feature.
|
|
|
|
* Exercise the address decode logic by writing an address to
|
|
|
|
* /sys/kernel/debug/edac/i10nm_test/addr.
|
|
|
|
*/
|
|
|
|
static struct dentry *i10nm_test;
|
|
|
|
|
|
|
|
static int debugfs_u64_set(void *data, u64 val)
|
|
|
|
{
|
|
|
|
struct mce m;
|
|
|
|
|
|
|
|
pr_warn_once("Fake error to 0x%llx injected via debugfs\n", val);
|
|
|
|
|
|
|
|
memset(&m, 0, sizeof(m));
|
|
|
|
/* ADDRV + MemRd + Unknown channel */
|
|
|
|
m.status = MCI_STATUS_ADDRV + 0x90;
|
|
|
|
/* One corrected error */
|
|
|
|
m.status |= BIT_ULL(MCI_STATUS_CEC_SHIFT);
|
|
|
|
m.addr = val;
|
|
|
|
skx_mce_check_error(NULL, 0, &m);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_u64_wo, NULL, debugfs_u64_set, "%llu\n");
|
|
|
|
|
|
|
|
static void setup_i10nm_debug(void)
|
|
|
|
{
|
|
|
|
i10nm_test = edac_debugfs_create_dir("i10nm_test");
|
|
|
|
if (!i10nm_test)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!edac_debugfs_create_file("addr", 0200, i10nm_test,
|
|
|
|
NULL, &fops_u64_wo)) {
|
|
|
|
debugfs_remove(i10nm_test);
|
|
|
|
i10nm_test = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void teardown_i10nm_debug(void)
|
|
|
|
{
|
|
|
|
debugfs_remove_recursive(i10nm_test);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void setup_i10nm_debug(void) {}
|
|
|
|
static inline void teardown_i10nm_debug(void) {}
|
|
|
|
#endif /*CONFIG_EDAC_DEBUG*/
|
|
|
|
|
2019-01-30 22:15:19 +03:00
|
|
|
static int __init i10nm_init(void)
|
|
|
|
{
|
|
|
|
u8 mc = 0, src_id = 0, node_id = 0;
|
|
|
|
const struct x86_cpu_id *id;
|
2020-04-24 15:18:14 +03:00
|
|
|
struct res_config *cfg;
|
2019-01-30 22:15:19 +03:00
|
|
|
const char *owner;
|
|
|
|
struct skx_dev *d;
|
|
|
|
int rc, i, off[3] = {0xd0, 0xc8, 0xcc};
|
|
|
|
u64 tolm, tohm;
|
|
|
|
|
|
|
|
edac_dbg(2, "\n");
|
|
|
|
|
|
|
|
owner = edac_get_owner();
|
|
|
|
if (owner && strncmp(owner, EDAC_MOD_STR, sizeof(EDAC_MOD_STR)))
|
|
|
|
return -EBUSY;
|
|
|
|
|
2021-06-15 20:44:19 +03:00
|
|
|
if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR))
|
|
|
|
return -ENODEV;
|
|
|
|
|
2019-01-30 22:15:19 +03:00
|
|
|
id = x86_match_cpu(i10nm_cpuids);
|
|
|
|
if (!id)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2020-04-24 15:18:14 +03:00
|
|
|
cfg = (struct res_config *)id->driver_data;
|
|
|
|
|
2019-01-30 22:15:19 +03:00
|
|
|
rc = skx_get_hi_lo(0x09a2, off, &tolm, &tohm);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
2020-04-24 15:18:14 +03:00
|
|
|
rc = skx_get_all_bus_mappings(cfg, &i10nm_edac_list);
|
2019-01-30 22:15:19 +03:00
|
|
|
if (rc < 0)
|
|
|
|
goto fail;
|
|
|
|
if (rc == 0) {
|
|
|
|
i10nm_printk(KERN_ERR, "No memory controllers found\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2021-06-11 20:01:19 +03:00
|
|
|
skx_set_mem_cfg(i10nm_check_2lm(cfg));
|
|
|
|
|
2021-06-11 20:01:20 +03:00
|
|
|
rc = i10nm_get_ddr_munits();
|
|
|
|
|
|
|
|
if (i10nm_get_hbm_munits() && rc)
|
2019-01-30 22:15:19 +03:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
list_for_each_entry(d, i10nm_edac_list, list) {
|
2019-06-26 09:16:55 +03:00
|
|
|
rc = skx_get_src_id(d, 0xf8, &src_id);
|
2019-01-30 22:15:19 +03:00
|
|
|
if (rc < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
rc = skx_get_node_id(d, &node_id);
|
|
|
|
if (rc < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
edac_dbg(2, "src_id = %d node_id = %d\n", src_id, node_id);
|
|
|
|
for (i = 0; i < I10NM_NUM_IMC; i++) {
|
|
|
|
if (!d->imc[i].mdev)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
d->imc[i].mc = mc++;
|
|
|
|
d->imc[i].lmc = i;
|
|
|
|
d->imc[i].src_id = src_id;
|
|
|
|
d->imc[i].node_id = node_id;
|
2021-06-11 20:01:20 +03:00
|
|
|
if (d->imc[i].hbm_mc) {
|
|
|
|
d->imc[i].chan_mmio_sz = cfg->hbm_chan_mmio_sz;
|
|
|
|
d->imc[i].num_channels = I10NM_NUM_HBM_CHANNELS;
|
|
|
|
d->imc[i].num_dimms = I10NM_NUM_HBM_DIMMS;
|
|
|
|
} else {
|
|
|
|
d->imc[i].chan_mmio_sz = cfg->ddr_chan_mmio_sz;
|
|
|
|
d->imc[i].num_channels = I10NM_NUM_DDR_CHANNELS;
|
|
|
|
d->imc[i].num_dimms = I10NM_NUM_DDR_DIMMS;
|
|
|
|
}
|
2019-01-30 22:15:19 +03:00
|
|
|
|
|
|
|
rc = skx_register_mci(&d->imc[i], d->imc[i].mdev,
|
|
|
|
"Intel_10nm Socket", EDAC_MOD_STR,
|
2020-11-17 15:49:53 +03:00
|
|
|
i10nm_get_dimm_config, cfg);
|
2019-01-30 22:15:19 +03:00
|
|
|
if (rc < 0)
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = skx_adxl_get();
|
|
|
|
if (rc)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
opstate_init();
|
|
|
|
mce_register_decode_chain(&i10nm_mce_dec);
|
2019-03-22 01:13:39 +03:00
|
|
|
setup_i10nm_debug();
|
2019-01-30 22:15:19 +03:00
|
|
|
|
|
|
|
i10nm_printk(KERN_INFO, "%s\n", I10NM_REVISION);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
|
|
skx_remove();
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit i10nm_exit(void)
|
|
|
|
{
|
|
|
|
edac_dbg(2, "\n");
|
2019-03-22 01:13:39 +03:00
|
|
|
teardown_i10nm_debug();
|
2019-01-30 22:15:19 +03:00
|
|
|
mce_unregister_decode_chain(&i10nm_mce_dec);
|
|
|
|
skx_adxl_put();
|
|
|
|
skx_remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(i10nm_init);
|
|
|
|
module_exit(i10nm_exit);
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
MODULE_DESCRIPTION("MC Driver for Intel 10nm server processors");
|