2014-07-29 17:10:07 +04:00
/*
* OPAL hypervisor Maintenance interrupt handling support in PowreNV .
*
* 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 , see < http : //www.gnu.org/licenses/>.
*
* Copyright 2014 IBM Corporation
* Author : Mahesh Salgaonkar < mahesh @ linux . vnet . ibm . com >
*/
# undef DEBUG
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/of.h>
# include <linux/mm.h>
# include <linux/slab.h>
# include <asm/opal.h>
# include <asm/cputable.h>
2014-08-12 13:17:04 +04:00
# include <asm/machdep.h>
2014-07-29 17:10:07 +04:00
static int opal_hmi_handler_nb_init ;
struct OpalHmiEvtNode {
struct list_head list ;
struct OpalHMIEvent hmi_evt ;
} ;
static LIST_HEAD ( opal_hmi_evt_list ) ;
static DEFINE_SPINLOCK ( opal_hmi_evt_lock ) ;
static void print_hmi_event_info ( struct OpalHMIEvent * hmi_evt )
{
const char * level , * sevstr , * error_info ;
static const char * hmi_error_types [ ] = {
" Malfunction Alert " ,
" Processor Recovery done " ,
" Processor recovery occurred again " ,
" Processor recovery occurred for masked error " ,
" Timer facility experienced an error " ,
" TFMR SPR is corrupted " ,
" UPS (Uniterrupted Power System) Overflow indication " ,
" An XSCOM operation failure " ,
" An XSCOM operation completed " ,
" SCOM has set a reserved FIR bit to cause recovery " ,
" Debug trigger has set a reserved FIR bit to cause recovery " ,
" A hypervisor resource error occurred "
} ;
/* Print things out */
2014-11-20 07:14:36 +03:00
if ( hmi_evt - > version < OpalHMIEvt_V1 ) {
2014-07-29 17:10:07 +04:00
pr_err ( " HMI Interrupt, Unknown event version %d ! \n " ,
hmi_evt - > version ) ;
return ;
}
switch ( hmi_evt - > severity ) {
case OpalHMI_SEV_NO_ERROR :
level = KERN_INFO ;
sevstr = " Harmless " ;
break ;
case OpalHMI_SEV_WARNING :
level = KERN_WARNING ;
sevstr = " " ;
break ;
case OpalHMI_SEV_ERROR_SYNC :
level = KERN_ERR ;
sevstr = " Severe " ;
break ;
case OpalHMI_SEV_FATAL :
default :
level = KERN_ERR ;
sevstr = " Fatal " ;
break ;
}
printk ( " %s%s Hypervisor Maintenance interrupt [%s] \n " ,
level , sevstr ,
hmi_evt - > disposition = = OpalHMI_DISPOSITION_RECOVERED ?
" Recovered " : " Not recovered " ) ;
error_info = hmi_evt - > type < ARRAY_SIZE ( hmi_error_types ) ?
hmi_error_types [ hmi_evt - > type ]
: " Unknown " ;
printk ( " %s Error detail: %s \n " , level , error_info ) ;
printk ( " %s HMER: %016llx \n " , level , be64_to_cpu ( hmi_evt - > hmer ) ) ;
if ( ( hmi_evt - > type = = OpalHMI_ERROR_TFAC ) | |
( hmi_evt - > type = = OpalHMI_ERROR_TFMR_PARITY ) )
printk ( " %s TFMR: %016llx \n " , level ,
be64_to_cpu ( hmi_evt - > tfmr ) ) ;
}
static void hmi_event_handler ( struct work_struct * work )
{
unsigned long flags ;
struct OpalHMIEvent * hmi_evt ;
struct OpalHmiEvtNode * msg_node ;
uint8_t disposition ;
spin_lock_irqsave ( & opal_hmi_evt_lock , flags ) ;
while ( ! list_empty ( & opal_hmi_evt_list ) ) {
msg_node = list_entry ( opal_hmi_evt_list . next ,
struct OpalHmiEvtNode , list ) ;
list_del ( & msg_node - > list ) ;
spin_unlock_irqrestore ( & opal_hmi_evt_lock , flags ) ;
hmi_evt = ( struct OpalHMIEvent * ) & msg_node - > hmi_evt ;
print_hmi_event_info ( hmi_evt ) ;
disposition = hmi_evt - > disposition ;
kfree ( msg_node ) ;
/*
* Check if HMI event has been recovered or not . If not
* then we can ' t continue , invoke panic .
*/
if ( disposition ! = OpalHMI_DISPOSITION_RECOVERED )
panic ( " Unrecoverable HMI exception " ) ;
spin_lock_irqsave ( & opal_hmi_evt_lock , flags ) ;
}
spin_unlock_irqrestore ( & opal_hmi_evt_lock , flags ) ;
}
static DECLARE_WORK ( hmi_event_work , hmi_event_handler ) ;
/*
* opal_handle_hmi_event - notifier handler that queues up HMI events
* to be preocessed later .
*/
static int opal_handle_hmi_event ( struct notifier_block * nb ,
unsigned long msg_type , void * msg )
{
unsigned long flags ;
struct OpalHMIEvent * hmi_evt ;
struct opal_msg * hmi_msg = msg ;
struct OpalHmiEvtNode * msg_node ;
/* Sanity Checks */
if ( msg_type ! = OPAL_MSG_HMI_EVT )
return 0 ;
/* HMI event info starts from param[0] */
hmi_evt = ( struct OpalHMIEvent * ) & hmi_msg - > params [ 0 ] ;
/* Delay the logging of HMI events to workqueue. */
msg_node = kzalloc ( sizeof ( * msg_node ) , GFP_ATOMIC ) ;
if ( ! msg_node ) {
pr_err ( " HMI: out of memory, Opal message event not handled \n " ) ;
return - ENOMEM ;
}
memcpy ( & msg_node - > hmi_evt , hmi_evt , sizeof ( struct OpalHMIEvent ) ) ;
spin_lock_irqsave ( & opal_hmi_evt_lock , flags ) ;
list_add ( & msg_node - > list , & opal_hmi_evt_list ) ;
spin_unlock_irqrestore ( & opal_hmi_evt_lock , flags ) ;
schedule_work ( & hmi_event_work ) ;
return 0 ;
}
static struct notifier_block opal_hmi_handler_nb = {
. notifier_call = opal_handle_hmi_event ,
. next = NULL ,
. priority = 0 ,
} ;
2015-05-15 07:06:36 +03:00
int __init opal_hmi_handler_init ( void )
2014-07-29 17:10:07 +04:00
{
int ret ;
if ( ! opal_hmi_handler_nb_init ) {
ret = opal_message_notifier_register (
OPAL_MSG_HMI_EVT , & opal_hmi_handler_nb ) ;
if ( ret ) {
pr_err ( " %s: Can't register OPAL event notifier (%d) \n " ,
__func__ , ret ) ;
return ret ;
}
opal_hmi_handler_nb_init = 1 ;
}
return 0 ;
}