2013-10-30 18:35:11 +04:00
/*
* Machine check exception handling CPU - side for power7 and power8
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
*
* Copyright 2013 IBM Corporation
* Author : Mahesh Salgaonkar < mahesh @ linux . vnet . ibm . com >
*/
# undef DEBUG
# define pr_fmt(fmt) "mce_power: " fmt
# include <linux/types.h>
# include <linux/ptrace.h>
# include <asm/mmu.h>
# include <asm/mce.h>
/* flush SLBs and reload */
static void flush_and_reload_slb ( void )
{
struct slb_shadow * slb ;
unsigned long i , n ;
/* Invalidate all SLBs */
asm volatile ( " slbmte %0,%0; slbia " : : " r " ( 0 ) ) ;
# ifdef CONFIG_KVM_BOOK3S_HANDLER
/*
* If machine check is hit when in guest or in transition , we will
* only flush the SLBs and continue .
*/
if ( get_paca ( ) - > kvm_hstate . in_guest )
return ;
# endif
/* For host kernel, reload the SLBs from shadow SLB buffer. */
slb = get_slb_shadow ( ) ;
if ( ! slb )
return ;
2013-12-16 03:47:54 +04:00
n = min_t ( u32 , be32_to_cpu ( slb - > persistent ) , SLB_MIN_SIZE ) ;
2013-10-30 18:35:11 +04:00
/* Load up the SLB entries from shadow SLB */
for ( i = 0 ; i < n ; i + + ) {
2013-12-16 03:47:54 +04:00
unsigned long rb = be64_to_cpu ( slb - > save_area [ i ] . esid ) ;
unsigned long rs = be64_to_cpu ( slb - > save_area [ i ] . vsid ) ;
2013-10-30 18:35:11 +04:00
rb = ( rb & ~ 0xFFFul ) | i ;
asm volatile ( " slbmte %0,%1 " : : " r " ( rs ) , " r " ( rb ) ) ;
}
}
static long mce_handle_derror ( uint64_t dsisr , uint64_t slb_error_bits )
{
long handled = 1 ;
/*
* flush and reload SLBs for SLB errors and flush TLBs for TLB errors .
* reset the error bits whenever we handle them so that at the end
* we can check whether we handled all of them or not .
* */
if ( dsisr & slb_error_bits ) {
flush_and_reload_slb ( ) ;
/* reset error bits */
dsisr & = ~ ( slb_error_bits ) ;
}
if ( dsisr & P7_DSISR_MC_TLB_MULTIHIT_MFTLB ) {
if ( cur_cpu_spec & & cur_cpu_spec - > flush_tlb )
cur_cpu_spec - > flush_tlb ( TLBIEL_INVAL_PAGE ) ;
/* reset error bits */
dsisr & = ~ P7_DSISR_MC_TLB_MULTIHIT_MFTLB ;
}
/* Any other errors we don't understand? */
if ( dsisr & 0xffffffffUL )
handled = 0 ;
return handled ;
}
static long mce_handle_derror_p7 ( uint64_t dsisr )
{
return mce_handle_derror ( dsisr , P7_DSISR_MC_SLB_ERRORS ) ;
}
static long mce_handle_common_ierror ( uint64_t srr1 )
{
long handled = 0 ;
switch ( P7_SRR1_MC_IFETCH ( srr1 ) ) {
case 0 :
break ;
case P7_SRR1_MC_IFETCH_SLB_PARITY :
case P7_SRR1_MC_IFETCH_SLB_MULTIHIT :
/* flush and reload SLBs for SLB errors. */
flush_and_reload_slb ( ) ;
handled = 1 ;
break ;
case P7_SRR1_MC_IFETCH_TLB_MULTIHIT :
if ( cur_cpu_spec & & cur_cpu_spec - > flush_tlb ) {
cur_cpu_spec - > flush_tlb ( TLBIEL_INVAL_PAGE ) ;
handled = 1 ;
}
break ;
default :
break ;
}
return handled ;
}
static long mce_handle_ierror_p7 ( uint64_t srr1 )
{
long handled = 0 ;
handled = mce_handle_common_ierror ( srr1 ) ;
if ( P7_SRR1_MC_IFETCH ( srr1 ) = = P7_SRR1_MC_IFETCH_SLB_BOTH ) {
flush_and_reload_slb ( ) ;
handled = 1 ;
}
return handled ;
}
2013-10-30 18:35:40 +04:00
static void mce_get_common_ierror ( struct mce_error_info * mce_err , uint64_t srr1 )
{
switch ( P7_SRR1_MC_IFETCH ( srr1 ) ) {
case P7_SRR1_MC_IFETCH_SLB_PARITY :
mce_err - > error_type = MCE_ERROR_TYPE_SLB ;
mce_err - > u . slb_error_type = MCE_SLB_ERROR_PARITY ;
break ;
case P7_SRR1_MC_IFETCH_SLB_MULTIHIT :
mce_err - > error_type = MCE_ERROR_TYPE_SLB ;
mce_err - > u . slb_error_type = MCE_SLB_ERROR_MULTIHIT ;
break ;
case P7_SRR1_MC_IFETCH_TLB_MULTIHIT :
mce_err - > error_type = MCE_ERROR_TYPE_TLB ;
mce_err - > u . tlb_error_type = MCE_TLB_ERROR_MULTIHIT ;
break ;
case P7_SRR1_MC_IFETCH_UE :
case P7_SRR1_MC_IFETCH_UE_IFU_INTERNAL :
mce_err - > error_type = MCE_ERROR_TYPE_UE ;
mce_err - > u . ue_error_type = MCE_UE_ERROR_IFETCH ;
break ;
case P7_SRR1_MC_IFETCH_UE_TLB_RELOAD :
mce_err - > error_type = MCE_ERROR_TYPE_UE ;
mce_err - > u . ue_error_type =
MCE_UE_ERROR_PAGE_TABLE_WALK_IFETCH ;
break ;
}
}
static void mce_get_ierror_p7 ( struct mce_error_info * mce_err , uint64_t srr1 )
{
mce_get_common_ierror ( mce_err , srr1 ) ;
if ( P7_SRR1_MC_IFETCH ( srr1 ) = = P7_SRR1_MC_IFETCH_SLB_BOTH ) {
mce_err - > error_type = MCE_ERROR_TYPE_SLB ;
mce_err - > u . slb_error_type = MCE_SLB_ERROR_INDETERMINATE ;
}
}
static void mce_get_derror_p7 ( struct mce_error_info * mce_err , uint64_t dsisr )
{
if ( dsisr & P7_DSISR_MC_UE ) {
mce_err - > error_type = MCE_ERROR_TYPE_UE ;
mce_err - > u . ue_error_type = MCE_UE_ERROR_LOAD_STORE ;
} else if ( dsisr & P7_DSISR_MC_UE_TABLEWALK ) {
mce_err - > error_type = MCE_ERROR_TYPE_UE ;
mce_err - > u . ue_error_type =
MCE_UE_ERROR_PAGE_TABLE_WALK_LOAD_STORE ;
} else if ( dsisr & P7_DSISR_MC_ERAT_MULTIHIT ) {
mce_err - > error_type = MCE_ERROR_TYPE_ERAT ;
mce_err - > u . erat_error_type = MCE_ERAT_ERROR_MULTIHIT ;
} else if ( dsisr & P7_DSISR_MC_SLB_MULTIHIT ) {
mce_err - > error_type = MCE_ERROR_TYPE_SLB ;
mce_err - > u . slb_error_type = MCE_SLB_ERROR_MULTIHIT ;
} else if ( dsisr & P7_DSISR_MC_SLB_PARITY_MFSLB ) {
mce_err - > error_type = MCE_ERROR_TYPE_SLB ;
mce_err - > u . slb_error_type = MCE_SLB_ERROR_PARITY ;
} else if ( dsisr & P7_DSISR_MC_TLB_MULTIHIT_MFTLB ) {
mce_err - > error_type = MCE_ERROR_TYPE_TLB ;
mce_err - > u . tlb_error_type = MCE_TLB_ERROR_MULTIHIT ;
} else if ( dsisr & P7_DSISR_MC_SLB_MULTIHIT_PARITY ) {
mce_err - > error_type = MCE_ERROR_TYPE_SLB ;
mce_err - > u . slb_error_type = MCE_SLB_ERROR_INDETERMINATE ;
}
}
2013-10-30 18:35:11 +04:00
long __machine_check_early_realmode_p7 ( struct pt_regs * regs )
{
2013-10-30 18:35:40 +04:00
uint64_t srr1 , addr ;
2013-10-30 18:35:11 +04:00
long handled = 1 ;
2013-10-30 18:35:40 +04:00
struct mce_error_info mce_error_info = { 0 } ;
2013-10-30 18:35:11 +04:00
srr1 = regs - > msr ;
2013-10-30 18:35:40 +04:00
/*
* Handle memory errors depending whether this was a load / store or
* ifetch exception . Also , populate the mce error_type and
* type - specific error_type from either SRR1 or DSISR , depending
* whether this was a load / store or ifetch exception
*/
if ( P7_SRR1_MC_LOADSTORE ( srr1 ) ) {
2013-10-30 18:35:11 +04:00
handled = mce_handle_derror_p7 ( regs - > dsisr ) ;
2013-10-30 18:35:40 +04:00
mce_get_derror_p7 ( & mce_error_info , regs - > dsisr ) ;
addr = regs - > dar ;
} else {
2013-10-30 18:35:11 +04:00
handled = mce_handle_ierror_p7 ( srr1 ) ;
2013-10-30 18:35:40 +04:00
mce_get_ierror_p7 ( & mce_error_info , srr1 ) ;
addr = regs - > nip ;
}
2013-10-30 18:35:11 +04:00
2013-10-30 18:35:40 +04:00
save_mce_event ( regs , handled , & mce_error_info , addr ) ;
2013-10-30 18:35:11 +04:00
return handled ;
}
2013-10-30 18:35:26 +04:00
2013-10-30 18:35:40 +04:00
static void mce_get_ierror_p8 ( struct mce_error_info * mce_err , uint64_t srr1 )
{
mce_get_common_ierror ( mce_err , srr1 ) ;
if ( P7_SRR1_MC_IFETCH ( srr1 ) = = P8_SRR1_MC_IFETCH_ERAT_MULTIHIT ) {
mce_err - > error_type = MCE_ERROR_TYPE_ERAT ;
mce_err - > u . erat_error_type = MCE_ERAT_ERROR_MULTIHIT ;
}
}
static void mce_get_derror_p8 ( struct mce_error_info * mce_err , uint64_t dsisr )
{
mce_get_derror_p7 ( mce_err , dsisr ) ;
if ( dsisr & P8_DSISR_MC_ERAT_MULTIHIT_SEC ) {
mce_err - > error_type = MCE_ERROR_TYPE_ERAT ;
mce_err - > u . erat_error_type = MCE_ERAT_ERROR_MULTIHIT ;
}
}
2013-10-30 18:35:26 +04:00
static long mce_handle_ierror_p8 ( uint64_t srr1 )
{
long handled = 0 ;
handled = mce_handle_common_ierror ( srr1 ) ;
if ( P7_SRR1_MC_IFETCH ( srr1 ) = = P8_SRR1_MC_IFETCH_ERAT_MULTIHIT ) {
flush_and_reload_slb ( ) ;
handled = 1 ;
}
return handled ;
}
static long mce_handle_derror_p8 ( uint64_t dsisr )
{
return mce_handle_derror ( dsisr , P8_DSISR_MC_SLB_ERRORS ) ;
}
long __machine_check_early_realmode_p8 ( struct pt_regs * regs )
{
2013-10-30 18:35:40 +04:00
uint64_t srr1 , addr ;
2013-10-30 18:35:26 +04:00
long handled = 1 ;
2013-10-30 18:35:40 +04:00
struct mce_error_info mce_error_info = { 0 } ;
2013-10-30 18:35:26 +04:00
srr1 = regs - > msr ;
2013-10-30 18:35:40 +04:00
if ( P7_SRR1_MC_LOADSTORE ( srr1 ) ) {
2013-10-30 18:35:26 +04:00
handled = mce_handle_derror_p8 ( regs - > dsisr ) ;
2013-10-30 18:35:40 +04:00
mce_get_derror_p8 ( & mce_error_info , regs - > dsisr ) ;
addr = regs - > dar ;
} else {
2013-10-30 18:35:26 +04:00
handled = mce_handle_ierror_p8 ( srr1 ) ;
2013-10-30 18:35:40 +04:00
mce_get_ierror_p8 ( & mce_error_info , srr1 ) ;
addr = regs - > nip ;
}
2013-10-30 18:35:26 +04:00
2013-10-30 18:35:40 +04:00
save_mce_event ( regs , handled , & mce_error_info , addr ) ;
2013-10-30 18:35:26 +04:00
return handled ;
}