forked from Minki/linux
e80634a75a
Skylake logs some additional useful information in per-channel registers in addition the the architectural status/addr/misc logged in the machine check bank. Pick up this information and add it to the EDAC log: retry_rd_err_[five 32-bit register values] Sorry, no definitions for these registers. OEMs and DIMM vendors will be able to use them to isolate which cells in the DIMM are causing problems. correrrcnt[per rank corrected error counts] Note that if additional errors are logged while these registers are being read, you may see a jumble of values some from earlier errors, others from later errors (since the registers report the most recent logged error). The correrrcnt registers provide error counts per possible rank. If these counts only change by one since the previous error logged for this channel, then it is safe to assume that the registers logged provide a coherent view of one error. With this change EDAC logs look like this: EDAC MC4: 1 CE memory read error on CPU_SrcID#2_MC#0_Chan#1_DIMM#0 (channel:1 slot:0 page:0x8f26018 offset:0x0 grain:32 syndrome:0x0 - err_code:0x0101:0x0091 socket:2 imc:0 rank:0 bg:0 ba:0 row:0x1f880 col:0x200 retry_rd_err_log[0001a209 00000000 00000001 04800001 0001f880] correrrcnt[0001 0000 0000 0000 0000 0000 0000 0000]) Acked-by: Aristeu Rozanski <aris@redhat.com> Signed-off-by: Tony Luck <tony.luck@intel.com>
147 lines
3.5 KiB
C
147 lines
3.5 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Common codes for both the skx_edac driver and Intel 10nm server EDAC driver.
|
|
* Originally split out from the skx_edac driver.
|
|
*
|
|
* Copyright (c) 2018, Intel Corporation.
|
|
*/
|
|
|
|
#ifndef _SKX_COMM_EDAC_H
|
|
#define _SKX_COMM_EDAC_H
|
|
|
|
#define MSG_SIZE 1024
|
|
|
|
/*
|
|
* Debug macros
|
|
*/
|
|
#define skx_printk(level, fmt, arg...) \
|
|
edac_printk(level, "skx", fmt, ##arg)
|
|
|
|
#define skx_mc_printk(mci, level, fmt, arg...) \
|
|
edac_mc_chipset_printk(mci, level, "skx", fmt, ##arg)
|
|
|
|
/*
|
|
* Get a bit field at register value <v>, from bit <lo> to bit <hi>
|
|
*/
|
|
#define GET_BITFIELD(v, lo, hi) \
|
|
(((v) & GENMASK_ULL((hi), (lo))) >> (lo))
|
|
|
|
#define SKX_NUM_IMC 2 /* Memory controllers per socket */
|
|
#define SKX_NUM_CHANNELS 3 /* Channels per memory controller */
|
|
#define SKX_NUM_DIMMS 2 /* Max DIMMS per channel */
|
|
|
|
#define I10NM_NUM_IMC 4
|
|
#define I10NM_NUM_CHANNELS 2
|
|
#define I10NM_NUM_DIMMS 2
|
|
|
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
|
#define NUM_IMC MAX(SKX_NUM_IMC, I10NM_NUM_IMC)
|
|
#define NUM_CHANNELS MAX(SKX_NUM_CHANNELS, I10NM_NUM_CHANNELS)
|
|
#define NUM_DIMMS MAX(SKX_NUM_DIMMS, I10NM_NUM_DIMMS)
|
|
|
|
#define IS_DIMM_PRESENT(r) GET_BITFIELD(r, 15, 15)
|
|
#define IS_NVDIMM_PRESENT(r, i) GET_BITFIELD(r, i, i)
|
|
|
|
/*
|
|
* Each cpu socket contains some pci devices that provide global
|
|
* information, and also some that are local to each of the two
|
|
* memory controllers on the die.
|
|
*/
|
|
struct skx_dev {
|
|
struct list_head list;
|
|
u8 bus[4];
|
|
int seg;
|
|
struct pci_dev *sad_all;
|
|
struct pci_dev *util_all;
|
|
struct pci_dev *uracu; /* for i10nm CPU */
|
|
u32 mcroute;
|
|
struct skx_imc {
|
|
struct mem_ctl_info *mci;
|
|
struct pci_dev *mdev; /* for i10nm CPU */
|
|
void __iomem *mbase; /* for i10nm CPU */
|
|
u8 mc; /* system wide mc# */
|
|
u8 lmc; /* socket relative mc# */
|
|
u8 src_id, node_id;
|
|
struct skx_channel {
|
|
struct pci_dev *cdev;
|
|
struct pci_dev *edev;
|
|
struct skx_dimm {
|
|
u8 close_pg;
|
|
u8 bank_xor_enable;
|
|
u8 fine_grain_bank;
|
|
u8 rowbits;
|
|
u8 colbits;
|
|
} dimms[NUM_DIMMS];
|
|
} chan[NUM_CHANNELS];
|
|
} imc[NUM_IMC];
|
|
};
|
|
|
|
struct skx_pvt {
|
|
struct skx_imc *imc;
|
|
};
|
|
|
|
enum type {
|
|
SKX,
|
|
I10NM
|
|
};
|
|
|
|
enum {
|
|
INDEX_SOCKET,
|
|
INDEX_MEMCTRL,
|
|
INDEX_CHANNEL,
|
|
INDEX_DIMM,
|
|
INDEX_MAX
|
|
};
|
|
|
|
struct decoded_addr {
|
|
struct skx_dev *dev;
|
|
u64 addr;
|
|
int socket;
|
|
int imc;
|
|
int channel;
|
|
u64 chan_addr;
|
|
int sktways;
|
|
int chanways;
|
|
int dimm;
|
|
int rank;
|
|
int channel_rank;
|
|
u64 rank_address;
|
|
int row;
|
|
int column;
|
|
int bank_address;
|
|
int bank_group;
|
|
};
|
|
|
|
typedef int (*get_dimm_config_f)(struct mem_ctl_info *mci);
|
|
typedef bool (*skx_decode_f)(struct decoded_addr *res);
|
|
typedef void (*skx_show_retry_log_f)(struct decoded_addr *res, char *msg, int len);
|
|
|
|
int __init skx_adxl_get(void);
|
|
void __exit skx_adxl_put(void);
|
|
void skx_set_decode(skx_decode_f decode, skx_show_retry_log_f show_retry_log);
|
|
|
|
int skx_get_src_id(struct skx_dev *d, int off, u8 *id);
|
|
int skx_get_node_id(struct skx_dev *d, u8 *id);
|
|
|
|
int skx_get_all_bus_mappings(unsigned int did, int off, enum type,
|
|
struct list_head **list);
|
|
|
|
int skx_get_hi_lo(unsigned int did, int off[], u64 *tolm, u64 *tohm);
|
|
|
|
int skx_get_dimm_info(u32 mtr, u32 amap, struct dimm_info *dimm,
|
|
struct skx_imc *imc, int chan, int dimmno);
|
|
|
|
int skx_get_nvdimm_info(struct dimm_info *dimm, struct skx_imc *imc,
|
|
int chan, int dimmno, const char *mod_str);
|
|
|
|
int skx_register_mci(struct skx_imc *imc, struct pci_dev *pdev,
|
|
const char *ctl_name, const char *mod_str,
|
|
get_dimm_config_f get_dimm_config);
|
|
|
|
int skx_mce_check_error(struct notifier_block *nb, unsigned long val,
|
|
void *data);
|
|
|
|
void skx_remove(void);
|
|
|
|
#endif /* _SKX_COMM_EDAC_H */
|