2019-01-30 22:15:19 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for Intel ( R ) 10 nm server memory controller .
* Copyright ( c ) 2019 , Intel Corporation .
*
*/
# include <linux/kernel.h>
# include <asm/cpu_device_id.h>
# include <asm/intel-family.h>
# include <asm/mce.h>
# include "edac_module.h"
# include "skx_common.h"
# define I10NM_REVISION "v0.0.3"
# define EDAC_MOD_STR "i10nm_edac"
/* Debug macros */
# define i10nm_printk(level, fmt, arg...) \
edac_printk ( level , " i10nm " , fmt , # # arg )
# define I10NM_GET_SCK_BAR(d, reg) \
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 ) )
# define I10NM_GET_DIMMMTR(m, i, j) \
( * ( u32 * ) ( ( m ) - > mbase + 0x2080c + ( i ) * 0x4000 + ( j ) * 4 ) )
# define I10NM_GET_MCDDRTCFG(m, i, j) \
( * ( u32 * ) ( ( m ) - > mbase + 0x20970 + ( i ) * 0x4000 + ( j ) * 4 ) )
# 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 )
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 ;
}
static int i10nm_get_all_munits ( void )
{
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 ) ;
for ( i = 0 ; i < I10NM_NUM_IMC ; i + + ) {
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 ;
}
static const struct x86_cpu_id i10nm_cpuids [ ] = {
{ X86_VENDOR_INTEL , 6 , INTEL_FAM6_ATOM_TREMONT_X , 0 , 0 } ,
2019-06-13 11:40:27 +03:00
{ X86_VENDOR_INTEL , 6 , INTEL_FAM6_ICELAKE_X , 0 , 0 } ,
{ X86_VENDOR_INTEL , 6 , INTEL_FAM6_ICELAKE_XEON_D , 0 , 0 } ,
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 ;
mcmtr = * ( u32 * ) ( imc - > mbase + 0x20ef8 + chan * 0x4000 ) ;
edac_dbg ( 1 , " ch%d mcmtr reg %x \n " , chan , mcmtr ) ;
return ! ! GET_BITFIELD ( mcmtr , 2 , 2 ) ;
}
static int i10nm_get_dimm_config ( struct mem_ctl_info * mci )
{
struct skx_pvt * pvt = mci - > pvt_info ;
struct skx_imc * imc = pvt - > imc ;
struct dimm_info * dimm ;
u32 mtr , mcddrtcfg ;
int i , j , ndimms ;
for ( i = 0 ; i < I10NM_NUM_CHANNELS ; i + + ) {
if ( ! imc - > mbase )
continue ;
ndimms = 0 ;
for ( j = 0 ; j < I10NM_NUM_DIMMS ; j + + ) {
dimm = EDAC_DIMM_PTR ( mci - > layers , mci - > dimms ,
mci - > n_layers , i , j , 0 ) ;
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 ) )
ndimms + = skx_get_dimm_info ( mtr , 0 , dimm ,
imc , i , j ) ;
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 ;
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 ;
id = x86_match_cpu ( i10nm_cpuids ) ;
if ( ! id )
return - ENODEV ;
rc = skx_get_hi_lo ( 0x09a2 , off , & tolm , & tohm ) ;
if ( rc )
return rc ;
rc = skx_get_all_bus_mappings ( 0x3452 , 0xcc , I10NM , & i10nm_edac_list ) ;
if ( rc < 0 )
goto fail ;
if ( rc = = 0 ) {
i10nm_printk ( KERN_ERR , " No memory controllers found \n " ) ;
return - ENODEV ;
}
rc = i10nm_get_all_munits ( ) ;
if ( rc < 0 )
goto fail ;
list_for_each_entry ( d , i10nm_edac_list , list ) {
rc = skx_get_src_id ( d , & src_id ) ;
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 ;
rc = skx_register_mci ( & d - > imc [ i ] , d - > imc [ i ] . mdev ,
" Intel_10nm Socket " , EDAC_MOD_STR ,
i10nm_get_dimm_config ) ;
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 " ) ;