2014-07-04 15:48:32 +04:00
/*
* Intel E3 - 1200
* Copyright ( C ) 2014 Jason Baron < jbaron @ akamai . com >
*
* Support for the E3 - 1200 processor family . Heavily based on previous
* Intel EDAC drivers .
*
* Since the DRAM controller is on the cpu chip , we can use its PCI device
* id to identify these processors .
*
* PCI DRAM controller device ids ( Taken from The PCI ID Repository - http : //pci-ids.ucw.cz/)
*
* 010 8 : Xeon E3 - 1200 Processor Family DRAM Controller
* 010 c : Xeon E3 - 1200 / 2 nd Generation Core Processor Family DRAM Controller
* 0150 : Xeon E3 - 1200 v2 / 3 rd Gen Core processor DRAM Controller
* 015 8 : Xeon E3 - 1200 v2 / Ivy Bridge DRAM Controller
* 015 c : Xeon E3 - 1200 v2 / 3 rd Gen Core processor DRAM Controller
* 0 c04 : Xeon E3 - 1200 v3 / 4 th Gen Core Processor DRAM Controller
* 0 c08 : Xeon E3 - 1200 v3 Processor DRAM Controller
*
* Based on Intel specification :
* http : //www.intel.com/content/dam/www/public/us/en/documents/datasheets/xeon-e3-1200v3-vol-2-datasheet.pdf
* http : //www.intel.com/content/www/us/en/processors/xeon/xeon-e3-1200-family-vol-2-datasheet.html
*
* According to the above datasheet ( p .16 ) :
* "
* 6. Software must not access B0 / D0 / F0 32 - bit memory - mapped registers with
* requests that cross a DW boundary .
* "
*
* Thus , we make use of the explicit : lo_hi_readq ( ) , which breaks the readq into
* 2 readl ( ) calls . This restriction may be lifted in subsequent chip releases ,
* but lo_hi_readq ( ) ensures that we are safe across all e3 - 1200 processors .
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/pci.h>
# include <linux/pci_ids.h>
# include <linux/edac.h>
# include <asm-generic/io-64-nonatomic-lo-hi.h>
# include "edac_core.h"
# define IE31200_REVISION "1.0"
# define EDAC_MOD_STR "ie31200_edac"
# define ie31200_printk(level, fmt, arg...) \
edac_printk ( level , " ie31200 " , fmt , # # arg )
# define PCI_DEVICE_ID_INTEL_IE31200_HB_1 0x0108
# define PCI_DEVICE_ID_INTEL_IE31200_HB_2 0x010c
# define PCI_DEVICE_ID_INTEL_IE31200_HB_3 0x0150
# define PCI_DEVICE_ID_INTEL_IE31200_HB_4 0x0158
# define PCI_DEVICE_ID_INTEL_IE31200_HB_5 0x015c
# define PCI_DEVICE_ID_INTEL_IE31200_HB_6 0x0c04
# define PCI_DEVICE_ID_INTEL_IE31200_HB_7 0x0c08
# define IE31200_DIMMS 4
# define IE31200_RANKS 8
# define IE31200_RANKS_PER_CHANNEL 4
# define IE31200_DIMMS_PER_CHANNEL 2
# define IE31200_CHANNELS 2
/* Intel IE31200 register addresses - device 0 function 0 - DRAM Controller */
# define IE31200_MCHBAR_LOW 0x48
# define IE31200_MCHBAR_HIGH 0x4c
# define IE31200_MCHBAR_MASK GENMASK_ULL(38, 15)
# define IE31200_MMR_WINDOW_SIZE BIT(15)
/*
* Error Status Register ( 16 b )
*
* 15 reserved
* 14 Isochronous TBWRR Run Behind FIFO Full
* ( ITCV )
* 13 Isochronous TBWRR Run Behind FIFO Put
* ( ITSTV )
* 12 reserved
* 11 MCH Thermal Sensor Event
* for SMI / SCI / SERR ( GTSE )
* 10 reserved
* 9 LOCK to non - DRAM Memory Flag ( LCKF )
* 8 reserved
* 7 DRAM Throttle Flag ( DTF )
* 6 : 2 reserved
* 1 Multi - bit DRAM ECC Error Flag ( DMERR )
* 0 Single - bit DRAM ECC Error Flag ( DSERR )
*/
# define IE31200_ERRSTS 0xc8
# define IE31200_ERRSTS_UE BIT(1)
# define IE31200_ERRSTS_CE BIT(0)
# define IE31200_ERRSTS_BITS (IE31200_ERRSTS_UE | IE31200_ERRSTS_CE)
/*
* Channel 0 ECC Error Log ( 64 b )
*
* 63 : 48 Error Column Address ( ERRCOL )
* 47 : 32 Error Row Address ( ERRROW )
* 31 : 29 Error Bank Address ( ERRBANK )
* 28 : 27 Error Rank Address ( ERRRANK )
* 26 : 24 reserved
* 23 : 16 Error Syndrome ( ERRSYND )
* 15 : 2 reserved
* 1 Multiple Bit Error Status ( MERRSTS )
* 0 Correctable Error Status ( CERRSTS )
*/
# define IE31200_C0ECCERRLOG 0x40c8
# define IE31200_C1ECCERRLOG 0x44c8
# define IE31200_ECCERRLOG_CE BIT(0)
# define IE31200_ECCERRLOG_UE BIT(1)
# define IE31200_ECCERRLOG_RANK_BITS GENMASK_ULL(28, 27)
# define IE31200_ECCERRLOG_RANK_SHIFT 27
# define IE31200_ECCERRLOG_SYNDROME_BITS GENMASK_ULL(23, 16)
# define IE31200_ECCERRLOG_SYNDROME_SHIFT 16
# define IE31200_ECCERRLOG_SYNDROME(log) \
( ( log & IE31200_ECCERRLOG_SYNDROME_BITS ) > > \
IE31200_ECCERRLOG_SYNDROME_SHIFT )
# define IE31200_CAPID0 0xe4
# define IE31200_CAPID0_PDCD BIT(4)
# define IE31200_CAPID0_DDPCD BIT(6)
# define IE31200_CAPID0_ECC BIT(1)
# define IE31200_MAD_DIMM_0_OFFSET 0x5004
# define IE31200_MAD_DIMM_SIZE GENMASK_ULL(7, 0)
# define IE31200_MAD_DIMM_A_RANK BIT(17)
# define IE31200_MAD_DIMM_A_WIDTH BIT(19)
# define IE31200_PAGES(n) (n << (28 - PAGE_SHIFT))
static int nr_channels ;
struct ie31200_priv {
void __iomem * window ;
} ;
enum ie31200_chips {
IE31200 = 0 ,
} ;
struct ie31200_dev_info {
const char * ctl_name ;
} ;
struct ie31200_error_info {
u16 errsts ;
u16 errsts2 ;
u64 eccerrlog [ IE31200_CHANNELS ] ;
} ;
static const struct ie31200_dev_info ie31200_devs [ ] = {
[ IE31200 ] = {
. ctl_name = " IE31200 "
} ,
} ;
struct dimm_data {
u8 size ; /* in 256MB multiples */
u8 dual_rank : 1 ,
x16_width : 1 ; /* 0 means x8 width */
} ;
static int how_many_channels ( struct pci_dev * pdev )
{
int n_channels ;
unsigned char capid0_2b ; /* 2nd byte of CAPID0 */
pci_read_config_byte ( pdev , IE31200_CAPID0 + 1 , & capid0_2b ) ;
/* check PDCD: Dual Channel Disable */
if ( capid0_2b & IE31200_CAPID0_PDCD ) {
edac_dbg ( 0 , " In single channel mode \n " ) ;
n_channels = 1 ;
} else {
edac_dbg ( 0 , " In dual channel mode \n " ) ;
n_channels = 2 ;
}
/* check DDPCD - check if both channels are filled */
if ( capid0_2b & IE31200_CAPID0_DDPCD )
edac_dbg ( 0 , " 2 DIMMS per channel disabled \n " ) ;
else
edac_dbg ( 0 , " 2 DIMMS per channel enabled \n " ) ;
return n_channels ;
}
static bool ecc_capable ( struct pci_dev * pdev )
{
unsigned char capid0_4b ; /* 4th byte of CAPID0 */
pci_read_config_byte ( pdev , IE31200_CAPID0 + 3 , & capid0_4b ) ;
if ( capid0_4b & IE31200_CAPID0_ECC )
return false ;
return true ;
}
static int eccerrlog_row ( int channel , u64 log )
{
int rank = ( ( log & IE31200_ECCERRLOG_RANK_BITS ) > >
IE31200_ECCERRLOG_RANK_SHIFT ) ;
return rank | ( channel * IE31200_RANKS_PER_CHANNEL ) ;
}
static void ie31200_clear_error_info ( struct mem_ctl_info * mci )
{
/*
* Clear any error bits .
* ( Yes , we really clear bits by writing 1 to them . )
*/
pci_write_bits16 ( to_pci_dev ( mci - > pdev ) , IE31200_ERRSTS ,
IE31200_ERRSTS_BITS , IE31200_ERRSTS_BITS ) ;
}
static void ie31200_get_and_clear_error_info ( struct mem_ctl_info * mci ,
struct ie31200_error_info * info )
{
struct pci_dev * pdev ;
struct ie31200_priv * priv = mci - > pvt_info ;
void __iomem * window = priv - > window ;
pdev = to_pci_dev ( mci - > pdev ) ;
/*
* This is a mess because there is no atomic way to read all the
* registers at once and the registers can transition from CE being
* overwritten by UE .
*/
pci_read_config_word ( pdev , IE31200_ERRSTS , & info - > errsts ) ;
if ( ! ( info - > errsts & IE31200_ERRSTS_BITS ) )
return ;
info - > eccerrlog [ 0 ] = lo_hi_readq ( window + IE31200_C0ECCERRLOG ) ;
if ( nr_channels = = 2 )
info - > eccerrlog [ 1 ] = lo_hi_readq ( window + IE31200_C1ECCERRLOG ) ;
pci_read_config_word ( pdev , IE31200_ERRSTS , & info - > errsts2 ) ;
/*
* If the error is the same for both reads then the first set
* of reads is valid . If there is a change then there is a CE
* with no info and the second set of reads is valid and
* should be UE info .
*/
if ( ( info - > errsts ^ info - > errsts2 ) & IE31200_ERRSTS_BITS ) {
info - > eccerrlog [ 0 ] = lo_hi_readq ( window + IE31200_C0ECCERRLOG ) ;
if ( nr_channels = = 2 )
info - > eccerrlog [ 1 ] =
lo_hi_readq ( window + IE31200_C1ECCERRLOG ) ;
}
ie31200_clear_error_info ( mci ) ;
}
static void ie31200_process_error_info ( struct mem_ctl_info * mci ,
struct ie31200_error_info * info )
{
int channel ;
u64 log ;
if ( ! ( info - > errsts & IE31200_ERRSTS_BITS ) )
return ;
if ( ( info - > errsts ^ info - > errsts2 ) & IE31200_ERRSTS_BITS ) {
edac_mc_handle_error ( HW_EVENT_ERR_UNCORRECTED , mci , 1 , 0 , 0 , 0 ,
- 1 , - 1 , - 1 , " UE overwrote CE " , " " ) ;
info - > errsts = info - > errsts2 ;
}
for ( channel = 0 ; channel < nr_channels ; channel + + ) {
log = info - > eccerrlog [ channel ] ;
if ( log & IE31200_ECCERRLOG_UE ) {
edac_mc_handle_error ( HW_EVENT_ERR_UNCORRECTED , mci , 1 ,
0 , 0 , 0 ,
eccerrlog_row ( channel , log ) ,
channel , - 1 ,
" ie31200 UE " , " " ) ;
} else if ( log & IE31200_ECCERRLOG_CE ) {
edac_mc_handle_error ( HW_EVENT_ERR_CORRECTED , mci , 1 ,
0 , 0 ,
IE31200_ECCERRLOG_SYNDROME ( log ) ,
eccerrlog_row ( channel , log ) ,
channel , - 1 ,
" ie31200 CE " , " " ) ;
}
}
}
static void ie31200_check ( struct mem_ctl_info * mci )
{
struct ie31200_error_info info ;
edac_dbg ( 1 , " MC%d \n " , mci - > mc_idx ) ;
ie31200_get_and_clear_error_info ( mci , & info ) ;
ie31200_process_error_info ( mci , & info ) ;
}
static void __iomem * ie31200_map_mchbar ( struct pci_dev * pdev )
{
union {
u64 mchbar ;
struct {
u32 mchbar_low ;
u32 mchbar_high ;
} ;
} u ;
void __iomem * window ;
pci_read_config_dword ( pdev , IE31200_MCHBAR_LOW , & u . mchbar_low ) ;
pci_read_config_dword ( pdev , IE31200_MCHBAR_HIGH , & u . mchbar_high ) ;
u . mchbar & = IE31200_MCHBAR_MASK ;
if ( u . mchbar ! = ( resource_size_t ) u . mchbar ) {
ie31200_printk ( KERN_ERR , " mmio space beyond accessible range (0x%llx) \n " ,
( unsigned long long ) u . mchbar ) ;
return NULL ;
}
window = ioremap_nocache ( u . mchbar , IE31200_MMR_WINDOW_SIZE ) ;
if ( ! window )
ie31200_printk ( KERN_ERR , " Cannot map mmio space at 0x%llx \n " ,
( unsigned long long ) u . mchbar ) ;
return window ;
}
static int ie31200_probe1 ( struct pci_dev * pdev , int dev_idx )
{
2014-07-10 01:13:07 +04:00
int i , j , ret ;
2014-07-04 15:48:32 +04:00
struct mem_ctl_info * mci = NULL ;
struct edac_mc_layer layers [ 2 ] ;
struct dimm_data dimm_info [ IE31200_CHANNELS ] [ IE31200_DIMMS_PER_CHANNEL ] ;
void __iomem * window ;
struct ie31200_priv * priv ;
u32 addr_decode ;
edac_dbg ( 0 , " MC: \n " ) ;
if ( ! ecc_capable ( pdev ) ) {
ie31200_printk ( KERN_INFO , " No ECC support \n " ) ;
return - ENODEV ;
}
nr_channels = how_many_channels ( pdev ) ;
layers [ 0 ] . type = EDAC_MC_LAYER_CHIP_SELECT ;
layers [ 0 ] . size = IE31200_DIMMS ;
layers [ 0 ] . is_virt_csrow = true ;
layers [ 1 ] . type = EDAC_MC_LAYER_CHANNEL ;
layers [ 1 ] . size = nr_channels ;
layers [ 1 ] . is_virt_csrow = false ;
mci = edac_mc_alloc ( 0 , ARRAY_SIZE ( layers ) , layers ,
sizeof ( struct ie31200_priv ) ) ;
if ( ! mci )
2014-07-10 01:13:07 +04:00
return - ENOMEM ;
2014-07-04 15:48:32 +04:00
2014-07-10 01:13:07 +04:00
window = ie31200_map_mchbar ( pdev ) ;
if ( ! window ) {
ret = - ENODEV ;
goto fail_free ;
}
2014-07-04 15:48:32 +04:00
2014-07-10 01:13:07 +04:00
edac_dbg ( 3 , " MC: init mci \n " ) ;
2014-07-04 15:48:32 +04:00
mci - > pdev = & pdev - > dev ;
mci - > mtype_cap = MEM_FLAG_DDR3 ;
mci - > edac_ctl_cap = EDAC_FLAG_SECDED ;
mci - > edac_cap = EDAC_FLAG_SECDED ;
mci - > mod_name = EDAC_MOD_STR ;
mci - > mod_ver = IE31200_REVISION ;
mci - > ctl_name = ie31200_devs [ dev_idx ] . ctl_name ;
mci - > dev_name = pci_name ( pdev ) ;
mci - > edac_check = ie31200_check ;
mci - > ctl_page_to_phys = NULL ;
priv = mci - > pvt_info ;
priv - > window = window ;
2014-07-10 01:13:07 +04:00
/* populate DIMM info */
for ( i = 0 ; i < IE31200_CHANNELS ; i + + ) {
addr_decode = readl ( window + IE31200_MAD_DIMM_0_OFFSET +
( i * 4 ) ) ;
edac_dbg ( 0 , " addr_decode: 0x%x \n " , addr_decode ) ;
for ( j = 0 ; j < IE31200_DIMMS_PER_CHANNEL ; j + + ) {
dimm_info [ i ] [ j ] . size = ( addr_decode > > ( j * 8 ) ) &
IE31200_MAD_DIMM_SIZE ;
dimm_info [ i ] [ j ] . dual_rank = ( addr_decode &
( IE31200_MAD_DIMM_A_RANK < < j ) ) ? 1 : 0 ;
dimm_info [ i ] [ j ] . x16_width = ( addr_decode &
( IE31200_MAD_DIMM_A_WIDTH < < j ) ) ? 1 : 0 ;
edac_dbg ( 0 , " size: 0x%x, rank: %d, width: %d \n " ,
dimm_info [ i ] [ j ] . size ,
dimm_info [ i ] [ j ] . dual_rank ,
dimm_info [ i ] [ j ] . x16_width ) ;
}
}
2014-07-04 15:48:32 +04:00
/*
* The dram rank boundary ( DRB ) reg values are boundary addresses
* for each DRAM rank with a granularity of 64 MB . DRB regs are
* cumulative ; the last one will contain the total memory
* contained in all ranks .
*/
for ( i = 0 ; i < IE31200_DIMMS_PER_CHANNEL ; i + + ) {
for ( j = 0 ; j < IE31200_CHANNELS ; j + + ) {
struct dimm_info * dimm ;
unsigned long nr_pages ;
nr_pages = IE31200_PAGES ( dimm_info [ j ] [ i ] . size ) ;
if ( nr_pages = = 0 )
continue ;
if ( dimm_info [ j ] [ i ] . dual_rank ) {
nr_pages = nr_pages / 2 ;
dimm = EDAC_DIMM_PTR ( mci - > layers , mci - > dimms ,
mci - > n_layers , ( i * 2 ) + 1 ,
j , 0 ) ;
dimm - > nr_pages = nr_pages ;
edac_dbg ( 0 , " set nr pages: 0x%lx \n " , nr_pages ) ;
dimm - > grain = 8 ; /* just a guess */
dimm - > mtype = MEM_DDR3 ;
dimm - > dtype = DEV_UNKNOWN ;
dimm - > edac_mode = EDAC_UNKNOWN ;
}
dimm = EDAC_DIMM_PTR ( mci - > layers , mci - > dimms ,
mci - > n_layers , i * 2 , j , 0 ) ;
dimm - > nr_pages = nr_pages ;
edac_dbg ( 0 , " set nr pages: 0x%lx \n " , nr_pages ) ;
dimm - > grain = 8 ; /* same guess */
dimm - > mtype = MEM_DDR3 ;
dimm - > dtype = DEV_UNKNOWN ;
dimm - > edac_mode = EDAC_UNKNOWN ;
}
}
ie31200_clear_error_info ( mci ) ;
if ( edac_mc_add_mc ( mci ) ) {
edac_dbg ( 3 , " MC: failed edac_mc_add_mc() \n " ) ;
2014-07-10 01:13:07 +04:00
ret = - ENODEV ;
goto fail_unmap ;
2014-07-04 15:48:32 +04:00
}
/* get this far and it's successful */
edac_dbg ( 3 , " MC: success \n " ) ;
return 0 ;
fail_unmap :
iounmap ( window ) ;
2014-07-10 01:13:07 +04:00
fail_free :
edac_mc_free ( mci ) ;
return ret ;
2014-07-04 15:48:32 +04:00
}
static int ie31200_init_one ( struct pci_dev * pdev ,
const struct pci_device_id * ent )
{
edac_dbg ( 0 , " MC: \n " ) ;
if ( pci_enable_device ( pdev ) < 0 )
return - EIO ;
return ie31200_probe1 ( pdev , ent - > driver_data ) ;
}
static void ie31200_remove_one ( struct pci_dev * pdev )
{
struct mem_ctl_info * mci ;
struct ie31200_priv * priv ;
edac_dbg ( 0 , " \n " ) ;
mci = edac_mc_del_mc ( & pdev - > dev ) ;
if ( ! mci )
return ;
priv = mci - > pvt_info ;
iounmap ( priv - > window ) ;
edac_mc_free ( mci ) ;
}
static const struct pci_device_id ie31200_pci_tbl [ ] = {
{
PCI_VEND_DEV ( INTEL , IE31200_HB_1 ) , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 ,
IE31200 } ,
{
PCI_VEND_DEV ( INTEL , IE31200_HB_2 ) , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 ,
IE31200 } ,
{
PCI_VEND_DEV ( INTEL , IE31200_HB_3 ) , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 ,
IE31200 } ,
{
PCI_VEND_DEV ( INTEL , IE31200_HB_4 ) , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 ,
IE31200 } ,
{
PCI_VEND_DEV ( INTEL , IE31200_HB_5 ) , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 ,
IE31200 } ,
{
PCI_VEND_DEV ( INTEL , IE31200_HB_6 ) , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 ,
IE31200 } ,
{
PCI_VEND_DEV ( INTEL , IE31200_HB_7 ) , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 ,
IE31200 } ,
{
0 ,
} /* 0 terminated list. */
} ;
MODULE_DEVICE_TABLE ( pci , ie31200_pci_tbl ) ;
static struct pci_driver ie31200_driver = {
. name = EDAC_MOD_STR ,
. probe = ie31200_init_one ,
. remove = ie31200_remove_one ,
. id_table = ie31200_pci_tbl ,
} ;
static int __init ie31200_init ( void )
{
edac_dbg ( 3 , " MC: \n " ) ;
/* Ensure that the OPSTATE is set correctly for POLL or NMI */
opstate_init ( ) ;
return pci_register_driver ( & ie31200_driver ) ;
}
static void __exit ie31200_exit ( void )
{
edac_dbg ( 3 , " MC: \n " ) ;
pci_unregister_driver ( & ie31200_driver ) ;
}
module_init ( ie31200_init ) ;
module_exit ( ie31200_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Jason Baron <jbaron@akamai.com> " ) ;
MODULE_DESCRIPTION ( " MC support for Intel Processor E31200 memory hub controllers " ) ;