x86/sb_edac: Add row column translation for Broadwell
The sb_edac driver lacks translation for DIMM internal address. Add memory address translation for row/column/bank/bank_group on Broadwell. Signed-off-by: Youquan Song <youquan.song@intel.com> Signed-off-by: Tony Luck <tony.luck@intel.com> Link: https://lore.kernel.org/all/20220722233338.341567-1-tony.luck@intel.com
This commit is contained in:
parent
d5f5e49953
commit
d389059685
@ -335,6 +335,12 @@ struct sbridge_info {
|
|||||||
struct sbridge_channel {
|
struct sbridge_channel {
|
||||||
u32 ranks;
|
u32 ranks;
|
||||||
u32 dimms;
|
u32 dimms;
|
||||||
|
struct dimm {
|
||||||
|
u32 rowbits;
|
||||||
|
u32 colbits;
|
||||||
|
u32 bank_xor_enable;
|
||||||
|
u32 amap_fine;
|
||||||
|
} dimm[MAX_DIMMS];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct pci_id_descr {
|
struct pci_id_descr {
|
||||||
@ -1603,7 +1609,7 @@ static int __populate_dimms(struct mem_ctl_info *mci,
|
|||||||
banks = 8;
|
banks = 8;
|
||||||
|
|
||||||
for (i = 0; i < channels; i++) {
|
for (i = 0; i < channels; i++) {
|
||||||
u32 mtr;
|
u32 mtr, amap = 0;
|
||||||
|
|
||||||
int max_dimms_per_channel;
|
int max_dimms_per_channel;
|
||||||
|
|
||||||
@ -1615,6 +1621,7 @@ static int __populate_dimms(struct mem_ctl_info *mci,
|
|||||||
max_dimms_per_channel = ARRAY_SIZE(mtr_regs);
|
max_dimms_per_channel = ARRAY_SIZE(mtr_regs);
|
||||||
if (!pvt->pci_tad[i])
|
if (!pvt->pci_tad[i])
|
||||||
continue;
|
continue;
|
||||||
|
pci_read_config_dword(pvt->pci_tad[i], 0x8c, &amap);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j = 0; j < max_dimms_per_channel; j++) {
|
for (j = 0; j < max_dimms_per_channel; j++) {
|
||||||
@ -1627,6 +1634,7 @@ static int __populate_dimms(struct mem_ctl_info *mci,
|
|||||||
mtr_regs[j], &mtr);
|
mtr_regs[j], &mtr);
|
||||||
}
|
}
|
||||||
edac_dbg(4, "Channel #%d MTR%d = %x\n", i, j, mtr);
|
edac_dbg(4, "Channel #%d MTR%d = %x\n", i, j, mtr);
|
||||||
|
|
||||||
if (IS_DIMM_PRESENT(mtr)) {
|
if (IS_DIMM_PRESENT(mtr)) {
|
||||||
if (!IS_ECC_ENABLED(pvt->info.mcmtr)) {
|
if (!IS_ECC_ENABLED(pvt->info.mcmtr)) {
|
||||||
sbridge_printk(KERN_ERR, "CPU SrcID #%d, Ha #%d, Channel #%d has DIMMs, but ECC is disabled\n",
|
sbridge_printk(KERN_ERR, "CPU SrcID #%d, Ha #%d, Channel #%d has DIMMs, but ECC is disabled\n",
|
||||||
@ -1661,6 +1669,11 @@ static int __populate_dimms(struct mem_ctl_info *mci,
|
|||||||
dimm->dtype = pvt->info.get_width(pvt, mtr);
|
dimm->dtype = pvt->info.get_width(pvt, mtr);
|
||||||
dimm->mtype = mtype;
|
dimm->mtype = mtype;
|
||||||
dimm->edac_mode = mode;
|
dimm->edac_mode = mode;
|
||||||
|
pvt->channel[i].dimm[j].rowbits = order_base_2(rows);
|
||||||
|
pvt->channel[i].dimm[j].colbits = order_base_2(cols);
|
||||||
|
pvt->channel[i].dimm[j].bank_xor_enable =
|
||||||
|
GET_BITFIELD(pvt->info.mcmtr, 9, 9);
|
||||||
|
pvt->channel[i].dimm[j].amap_fine = GET_BITFIELD(amap, 0, 0);
|
||||||
snprintf(dimm->label, sizeof(dimm->label),
|
snprintf(dimm->label, sizeof(dimm->label),
|
||||||
"CPU_SrcID#%u_Ha#%u_Chan#%u_DIMM#%u",
|
"CPU_SrcID#%u_Ha#%u_Chan#%u_DIMM#%u",
|
||||||
pvt->sbridge_dev->source_id, pvt->sbridge_dev->dom, i, j);
|
pvt->sbridge_dev->source_id, pvt->sbridge_dev->dom, i, j);
|
||||||
@ -1922,6 +1935,99 @@ static struct mem_ctl_info *get_mci_for_node_id(u8 node_id, u8 ha)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static u8 sb_close_row[] = {
|
||||||
|
15, 16, 17, 18, 20, 21, 22, 28, 10, 11, 12, 13, 29, 30, 31, 32, 33
|
||||||
|
};
|
||||||
|
|
||||||
|
static u8 sb_close_column[] = {
|
||||||
|
3, 4, 5, 14, 19, 23, 24, 25, 26, 27
|
||||||
|
};
|
||||||
|
|
||||||
|
static u8 sb_open_row[] = {
|
||||||
|
14, 15, 16, 20, 28, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33
|
||||||
|
};
|
||||||
|
|
||||||
|
static u8 sb_open_column[] = {
|
||||||
|
3, 4, 5, 6, 7, 8, 9, 10, 11, 12
|
||||||
|
};
|
||||||
|
|
||||||
|
static u8 sb_open_fine_column[] = {
|
||||||
|
3, 4, 5, 7, 8, 9, 10, 11, 12, 13
|
||||||
|
};
|
||||||
|
|
||||||
|
static int sb_bits(u64 addr, int nbits, u8 *bits)
|
||||||
|
{
|
||||||
|
int i, res = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < nbits; i++)
|
||||||
|
res |= ((addr >> bits[i]) & 1) << i;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sb_bank_bits(u64 addr, int b0, int b1, int do_xor, int x0, int x1)
|
||||||
|
{
|
||||||
|
int ret = GET_BITFIELD(addr, b0, b0) | (GET_BITFIELD(addr, b1, b1) << 1);
|
||||||
|
|
||||||
|
if (do_xor)
|
||||||
|
ret ^= GET_BITFIELD(addr, x0, x0) | (GET_BITFIELD(addr, x1, x1) << 1);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sb_decode_ddr4(struct mem_ctl_info *mci, int ch, u8 rank,
|
||||||
|
u64 rank_addr, char *msg)
|
||||||
|
{
|
||||||
|
int dimmno = 0;
|
||||||
|
int row, col, bank_address, bank_group;
|
||||||
|
struct sbridge_pvt *pvt;
|
||||||
|
u32 bg0 = 0, rowbits = 0, colbits = 0;
|
||||||
|
u32 amap_fine = 0, bank_xor_enable = 0;
|
||||||
|
|
||||||
|
dimmno = (rank < 12) ? rank / 4 : 2;
|
||||||
|
pvt = mci->pvt_info;
|
||||||
|
amap_fine = pvt->channel[ch].dimm[dimmno].amap_fine;
|
||||||
|
bg0 = amap_fine ? 6 : 13;
|
||||||
|
rowbits = pvt->channel[ch].dimm[dimmno].rowbits;
|
||||||
|
colbits = pvt->channel[ch].dimm[dimmno].colbits;
|
||||||
|
bank_xor_enable = pvt->channel[ch].dimm[dimmno].bank_xor_enable;
|
||||||
|
|
||||||
|
if (pvt->is_lockstep) {
|
||||||
|
pr_warn_once("LockStep row/column decode is not supported yet!\n");
|
||||||
|
msg[0] = '\0';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pvt->is_close_pg) {
|
||||||
|
row = sb_bits(rank_addr, rowbits, sb_close_row);
|
||||||
|
col = sb_bits(rank_addr, colbits, sb_close_column);
|
||||||
|
col |= 0x400; /* C10 is autoprecharge, always set */
|
||||||
|
bank_address = sb_bank_bits(rank_addr, 8, 9, bank_xor_enable, 22, 28);
|
||||||
|
bank_group = sb_bank_bits(rank_addr, 6, 7, bank_xor_enable, 20, 21);
|
||||||
|
} else {
|
||||||
|
row = sb_bits(rank_addr, rowbits, sb_open_row);
|
||||||
|
if (amap_fine)
|
||||||
|
col = sb_bits(rank_addr, colbits, sb_open_fine_column);
|
||||||
|
else
|
||||||
|
col = sb_bits(rank_addr, colbits, sb_open_column);
|
||||||
|
bank_address = sb_bank_bits(rank_addr, 18, 19, bank_xor_enable, 22, 23);
|
||||||
|
bank_group = sb_bank_bits(rank_addr, bg0, 17, bank_xor_enable, 20, 21);
|
||||||
|
}
|
||||||
|
|
||||||
|
row &= (1u << rowbits) - 1;
|
||||||
|
|
||||||
|
sprintf(msg, "row:0x%x col:0x%x bank_addr:%d bank_group:%d",
|
||||||
|
row, col, bank_address, bank_group);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sb_decode_ddr3(struct mem_ctl_info *mci, int ch, u8 rank,
|
||||||
|
u64 rank_addr, char *msg)
|
||||||
|
{
|
||||||
|
pr_warn_once("DDR3 row/column decode not support yet!\n");
|
||||||
|
msg[0] = '\0';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int get_memory_error_data(struct mem_ctl_info *mci,
|
static int get_memory_error_data(struct mem_ctl_info *mci,
|
||||||
u64 addr,
|
u64 addr,
|
||||||
u8 *socket, u8 *ha,
|
u8 *socket, u8 *ha,
|
||||||
@ -1937,12 +2043,13 @@ static int get_memory_error_data(struct mem_ctl_info *mci,
|
|||||||
int interleave_mode, shiftup = 0;
|
int interleave_mode, shiftup = 0;
|
||||||
unsigned int sad_interleave[MAX_INTERLEAVE];
|
unsigned int sad_interleave[MAX_INTERLEAVE];
|
||||||
u32 reg, dram_rule;
|
u32 reg, dram_rule;
|
||||||
u8 ch_way, sck_way, pkg, sad_ha = 0;
|
u8 ch_way, sck_way, pkg, sad_ha = 0, rankid = 0;
|
||||||
u32 tad_offset;
|
u32 tad_offset;
|
||||||
u32 rir_way;
|
u32 rir_way;
|
||||||
u32 mb, gb;
|
u32 mb, gb;
|
||||||
u64 ch_addr, offset, limit = 0, prv = 0;
|
u64 ch_addr, offset, limit = 0, prv = 0;
|
||||||
|
u64 rank_addr;
|
||||||
|
enum mem_type mtype;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Step 0) Check if the address is at special memory ranges
|
* Step 0) Check if the address is at special memory ranges
|
||||||
@ -2226,6 +2333,28 @@ static int get_memory_error_data(struct mem_ctl_info *mci,
|
|||||||
pci_read_config_dword(pvt->pci_tad[base_ch], rir_offset[n_rir][idx], ®);
|
pci_read_config_dword(pvt->pci_tad[base_ch], rir_offset[n_rir][idx], ®);
|
||||||
*rank = RIR_RNK_TGT(pvt->info.type, reg);
|
*rank = RIR_RNK_TGT(pvt->info.type, reg);
|
||||||
|
|
||||||
|
if (pvt->info.type == BROADWELL) {
|
||||||
|
if (pvt->is_close_pg)
|
||||||
|
shiftup = 6;
|
||||||
|
else
|
||||||
|
shiftup = 13;
|
||||||
|
|
||||||
|
rank_addr = ch_addr >> shiftup;
|
||||||
|
rank_addr /= (1 << rir_way);
|
||||||
|
rank_addr <<= shiftup;
|
||||||
|
rank_addr |= ch_addr & GENMASK_ULL(shiftup - 1, 0);
|
||||||
|
rank_addr -= RIR_OFFSET(pvt->info.type, reg);
|
||||||
|
|
||||||
|
mtype = pvt->info.get_memory_type(pvt);
|
||||||
|
rankid = *rank;
|
||||||
|
if (mtype == MEM_DDR4 || mtype == MEM_RDDR4)
|
||||||
|
sb_decode_ddr4(mci, base_ch, rankid, rank_addr, msg);
|
||||||
|
else
|
||||||
|
sb_decode_ddr3(mci, base_ch, rankid, rank_addr, msg);
|
||||||
|
} else {
|
||||||
|
msg[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
edac_dbg(0, "RIR#%d: channel address 0x%08Lx < 0x%08Lx, RIR interleave %d, index %d\n",
|
edac_dbg(0, "RIR#%d: channel address 0x%08Lx < 0x%08Lx, RIR interleave %d, index %d\n",
|
||||||
n_rir,
|
n_rir,
|
||||||
ch_addr,
|
ch_addr,
|
||||||
@ -2950,7 +3079,7 @@ static void sbridge_mce_output_error(struct mem_ctl_info *mci,
|
|||||||
struct mem_ctl_info *new_mci;
|
struct mem_ctl_info *new_mci;
|
||||||
struct sbridge_pvt *pvt = mci->pvt_info;
|
struct sbridge_pvt *pvt = mci->pvt_info;
|
||||||
enum hw_event_mc_err_type tp_event;
|
enum hw_event_mc_err_type tp_event;
|
||||||
char *optype, msg[256];
|
char *optype, msg[256], msg_full[512];
|
||||||
bool ripv = GET_BITFIELD(m->mcgstatus, 0, 0);
|
bool ripv = GET_BITFIELD(m->mcgstatus, 0, 0);
|
||||||
bool overflow = GET_BITFIELD(m->status, 62, 62);
|
bool overflow = GET_BITFIELD(m->status, 62, 62);
|
||||||
bool uncorrected_error = GET_BITFIELD(m->status, 61, 61);
|
bool uncorrected_error = GET_BITFIELD(m->status, 61, 61);
|
||||||
@ -3089,18 +3218,17 @@ static void sbridge_mce_output_error(struct mem_ctl_info *mci,
|
|||||||
*/
|
*/
|
||||||
if (!pvt->is_lockstep && !pvt->is_cur_addr_mirrored && !pvt->is_close_pg)
|
if (!pvt->is_lockstep && !pvt->is_cur_addr_mirrored && !pvt->is_close_pg)
|
||||||
channel = first_channel;
|
channel = first_channel;
|
||||||
|
snprintf(msg_full, sizeof(msg_full),
|
||||||
snprintf(msg, sizeof(msg),
|
"%s%s area:%s err_code:%04x:%04x socket:%d ha:%d channel_mask:%ld rank:%d %s",
|
||||||
"%s%s area:%s err_code:%04x:%04x socket:%d ha:%d channel_mask:%ld rank:%d",
|
|
||||||
overflow ? " OVERFLOW" : "",
|
overflow ? " OVERFLOW" : "",
|
||||||
(uncorrected_error && recoverable) ? " recoverable" : "",
|
(uncorrected_error && recoverable) ? " recoverable" : "",
|
||||||
area_type,
|
area_type,
|
||||||
mscod, errcode,
|
mscod, errcode,
|
||||||
socket, ha,
|
socket, ha,
|
||||||
channel_mask,
|
channel_mask,
|
||||||
rank);
|
rank, msg);
|
||||||
|
|
||||||
edac_dbg(0, "%s\n", msg);
|
edac_dbg(0, "%s\n", msg_full);
|
||||||
|
|
||||||
/* FIXME: need support for channel mask */
|
/* FIXME: need support for channel mask */
|
||||||
|
|
||||||
@ -3111,7 +3239,7 @@ static void sbridge_mce_output_error(struct mem_ctl_info *mci,
|
|||||||
edac_mc_handle_error(tp_event, mci, core_err_cnt,
|
edac_mc_handle_error(tp_event, mci, core_err_cnt,
|
||||||
m->addr >> PAGE_SHIFT, m->addr & ~PAGE_MASK, 0,
|
m->addr >> PAGE_SHIFT, m->addr & ~PAGE_MASK, 0,
|
||||||
channel, dimm, -1,
|
channel, dimm, -1,
|
||||||
optype, msg);
|
optype, msg_full);
|
||||||
return;
|
return;
|
||||||
err_parsing:
|
err_parsing:
|
||||||
edac_mc_handle_error(tp_event, mci, core_err_cnt, 0, 0, 0,
|
edac_mc_handle_error(tp_event, mci, core_err_cnt, 0, 0, 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user