2010-05-18 10:35:14 +04:00
/*
* APEI Error INJection support
*
* EINJ provides a hardware error injection mechanism , this is useful
* for debugging and testing of other APEI and RAS features .
*
* For more information about EINJ , please refer to ACPI Specification
* version 4.0 , section 17.5 .
*
2010-05-18 10:35:24 +04:00
* Copyright 2009 - 2010 Intel Corp .
2010-05-18 10:35:14 +04:00
* Author : Huang Ying < ying . huang @ intel . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation .
*
* 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 .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/debugfs.h>
# include <linux/seq_file.h>
# include <linux/nmi.h>
# include <linux/delay.h>
2013-06-07 02:20:51 +04:00
# include <linux/mm.h>
2013-12-18 10:30:49 +04:00
# include <asm/unaligned.h>
2010-05-18 10:35:14 +04:00
# include "apei-internal.h"
2016-05-23 11:28:03 +03:00
# undef pr_fmt
# define pr_fmt(fmt) "EINJ: " fmt
2010-05-18 10:35:14 +04:00
# define SPIN_UNIT 100 /* 100ns */
2011-01-02 17:12:42 +03:00
/* Firmware should respond within 1 milliseconds */
2010-05-18 10:35:14 +04:00
# define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC)
2013-06-07 02:20:51 +04:00
# define ACPI5_VENDOR_BIT BIT(31)
# define MEM_ERROR_MASK (ACPI_EINJ_MEMORY_CORRECTABLE | \
ACPI_EINJ_MEMORY_UNCORRECTABLE | \
ACPI_EINJ_MEMORY_FATAL )
2010-05-18 10:35:14 +04:00
2012-01-18 00:10:16 +04:00
/*
* ACPI version 5 provides a SET_ERROR_TYPE_WITH_ADDRESS action .
*/
static int acpi5 ;
struct set_error_type_with_address {
u32 type ;
u32 vendor_extension ;
u32 flags ;
u32 apicid ;
u64 memory_address ;
u64 memory_address_range ;
u32 pcie_sbdf ;
} ;
enum {
SETWA_FLAGS_APICID = 1 ,
SETWA_FLAGS_MEM = 2 ,
SETWA_FLAGS_PCIE_SBDF = 4 ,
} ;
/*
* Vendor extensions for platform specific operations
*/
struct vendor_error_type_extension {
u32 length ;
u32 pcie_sbdf ;
u16 vendor_id ;
u16 device_id ;
u8 rev_id ;
u8 reserved [ 3 ] ;
} ;
2012-03-15 12:53:36 +04:00
static u32 notrigger ;
2012-01-18 00:10:16 +04:00
static u32 vendor_flags ;
static struct debugfs_blob_wrapper vendor_blob ;
static char vendor_dev [ 64 ] ;
2010-05-18 10:35:24 +04:00
/*
* Some BIOSes allow parameters to the SET_ERROR_TYPE entries in the
* EINJ table through an unpublished extension . Use with caution as
* most will ignore the parameter and make their own choice of address
2011-07-20 12:09:29 +04:00
* for error injection . This extension is used only if
* param_extension module parameter is specified .
2010-05-18 10:35:24 +04:00
*/
struct einj_parameter {
u64 type ;
u64 reserved1 ;
u64 reserved2 ;
u64 param1 ;
u64 param2 ;
} ;
2010-05-18 10:35:14 +04:00
# define EINJ_OP_BUSY 0x1
# define EINJ_STATUS_SUCCESS 0x0
# define EINJ_STATUS_FAIL 0x1
# define EINJ_STATUS_INVAL 0x2
# define EINJ_TAB_ENTRY(tab) \
( ( struct acpi_whea_header * ) ( ( char * ) ( tab ) + \
sizeof ( struct acpi_table_einj ) ) )
2011-07-20 12:09:29 +04:00
static bool param_extension ;
module_param ( param_extension , bool , 0 ) ;
2010-05-18 10:35:14 +04:00
static struct acpi_table_einj * einj_tab ;
static struct apei_resources einj_resources ;
static struct apei_exec_ins_type einj_ins_type [ ] = {
[ ACPI_EINJ_READ_REGISTER ] = {
. flags = APEI_EXEC_INS_ACCESS_REGISTER ,
. run = apei_exec_read_register ,
} ,
[ ACPI_EINJ_READ_REGISTER_VALUE ] = {
. flags = APEI_EXEC_INS_ACCESS_REGISTER ,
. run = apei_exec_read_register_value ,
} ,
[ ACPI_EINJ_WRITE_REGISTER ] = {
. flags = APEI_EXEC_INS_ACCESS_REGISTER ,
. run = apei_exec_write_register ,
} ,
[ ACPI_EINJ_WRITE_REGISTER_VALUE ] = {
. flags = APEI_EXEC_INS_ACCESS_REGISTER ,
. run = apei_exec_write_register_value ,
} ,
[ ACPI_EINJ_NOOP ] = {
. flags = 0 ,
. run = apei_exec_noop ,
} ,
} ;
/*
* Prevent EINJ interpreter to run simultaneously , because the
* corresponding firmware implementation may not work properly when
* invoked simultaneously .
*/
static DEFINE_MUTEX ( einj_mutex ) ;
2012-01-18 00:10:16 +04:00
static void * einj_param ;
2010-05-18 10:35:14 +04:00
static void einj_exec_ctx_init ( struct apei_exec_context * ctx )
{
apei_exec_ctx_init ( ctx , einj_ins_type , ARRAY_SIZE ( einj_ins_type ) ,
EINJ_TAB_ENTRY ( einj_tab ) , einj_tab - > entries ) ;
}
static int __einj_get_available_error_type ( u32 * type )
{
struct apei_exec_context ctx ;
int rc ;
einj_exec_ctx_init ( & ctx ) ;
rc = apei_exec_run ( & ctx , ACPI_EINJ_GET_ERROR_TYPE ) ;
if ( rc )
return rc ;
* type = apei_exec_ctx_get_output ( & ctx ) ;
return 0 ;
}
/* Get error injection capabilities of the platform */
static int einj_get_available_error_type ( u32 * type )
{
int rc ;
mutex_lock ( & einj_mutex ) ;
rc = __einj_get_available_error_type ( type ) ;
mutex_unlock ( & einj_mutex ) ;
return rc ;
}
static int einj_timedout ( u64 * t )
{
if ( ( s64 ) * t < SPIN_UNIT ) {
2016-05-23 11:28:03 +03:00
pr_warning ( FW_WARN " Firmware does not respond in time \n " ) ;
2010-05-18 10:35:14 +04:00
return 1 ;
}
* t - = SPIN_UNIT ;
ndelay ( SPIN_UNIT ) ;
touch_nmi_watchdog ( ) ;
return 0 ;
}
2012-01-18 00:10:16 +04:00
static void check_vendor_extension ( u64 paddr ,
struct set_error_type_with_address * v5param )
{
2012-01-24 03:27:56 +04:00
int offset = v5param - > vendor_extension ;
2012-01-18 00:10:16 +04:00
struct vendor_error_type_extension * v ;
u32 sbdf ;
if ( ! offset )
return ;
2014-05-20 11:39:41 +04:00
v = acpi_os_map_iomem ( paddr + offset , sizeof ( * v ) ) ;
2012-01-18 00:10:16 +04:00
if ( ! v )
return ;
2012-01-24 03:27:56 +04:00
sbdf = v - > pcie_sbdf ;
2012-01-18 00:10:16 +04:00
sprintf ( vendor_dev , " %x:%x:%x.%x vendor_id=%x device_id=%x rev_id=%x \n " ,
sbdf > > 24 , ( sbdf > > 16 ) & 0xff ,
( sbdf > > 11 ) & 0x1f , ( sbdf > > 8 ) & 0x7 ,
2012-01-24 03:27:56 +04:00
v - > vendor_id , v - > device_id , v - > rev_id ) ;
2014-05-20 11:39:41 +04:00
acpi_os_unmap_iomem ( v , sizeof ( * v ) ) ;
2012-01-18 00:10:16 +04:00
}
static void * einj_get_parameter_address ( void )
2010-05-18 10:35:24 +04:00
{
int i ;
2013-12-18 10:30:49 +04:00
u64 pa_v4 = 0 , pa_v5 = 0 ;
2010-05-18 10:35:24 +04:00
struct acpi_whea_header * entry ;
entry = EINJ_TAB_ENTRY ( einj_tab ) ;
for ( i = 0 ; i < einj_tab - > entries ; i + + ) {
if ( entry - > action = = ACPI_EINJ_SET_ERROR_TYPE & &
entry - > instruction = = ACPI_EINJ_WRITE_REGISTER & &
entry - > register_region . space_id = =
ACPI_ADR_SPACE_SYSTEM_MEMORY )
2013-12-18 10:30:49 +04:00
pa_v4 = get_unaligned ( & entry - > register_region . address ) ;
2012-01-18 00:10:16 +04:00
if ( entry - > action = = ACPI_EINJ_SET_ERROR_TYPE_WITH_ADDRESS & &
entry - > instruction = = ACPI_EINJ_WRITE_REGISTER & &
entry - > register_region . space_id = =
ACPI_ADR_SPACE_SYSTEM_MEMORY )
2013-12-18 10:30:49 +04:00
pa_v5 = get_unaligned ( & entry - > register_region . address ) ;
2010-05-18 10:35:24 +04:00
entry + + ;
}
2013-12-18 10:30:49 +04:00
if ( pa_v5 ) {
2012-01-18 00:10:16 +04:00
struct set_error_type_with_address * v5param ;
2014-05-20 11:39:41 +04:00
v5param = acpi_os_map_iomem ( pa_v5 , sizeof ( * v5param ) ) ;
2012-01-18 00:10:16 +04:00
if ( v5param ) {
acpi5 = 1 ;
2013-12-18 10:30:49 +04:00
check_vendor_extension ( pa_v5 , v5param ) ;
2012-01-18 00:10:16 +04:00
return v5param ;
}
}
2013-12-18 10:30:49 +04:00
if ( param_extension & & pa_v4 ) {
2012-01-18 00:10:16 +04:00
struct einj_parameter * v4param ;
2014-05-20 11:39:41 +04:00
v4param = acpi_os_map_iomem ( pa_v4 , sizeof ( * v4param ) ) ;
2012-01-18 00:10:16 +04:00
if ( ! v4param )
2012-01-20 11:57:14 +04:00
return NULL ;
2012-01-24 03:27:56 +04:00
if ( v4param - > reserved1 | | v4param - > reserved2 ) {
2014-05-20 11:39:41 +04:00
acpi_os_unmap_iomem ( v4param , sizeof ( * v4param ) ) ;
2012-01-20 11:57:14 +04:00
return NULL ;
2012-01-18 00:10:16 +04:00
}
return v4param ;
}
2010-05-18 10:35:24 +04:00
2012-01-20 11:57:14 +04:00
return NULL ;
2010-05-18 10:35:24 +04:00
}
2010-05-18 10:35:14 +04:00
/* do sanity check to trigger table */
static int einj_check_trigger_header ( struct acpi_einj_trigger * trigger_tab )
{
if ( trigger_tab - > header_size ! = sizeof ( struct acpi_einj_trigger ) )
return - EINVAL ;
if ( trigger_tab - > table_size > PAGE_SIZE | |
2012-01-09 19:49:44 +04:00
trigger_tab - > table_size < trigger_tab - > header_size )
2010-05-18 10:35:14 +04:00
return - EINVAL ;
if ( trigger_tab - > entry_count ! =
( trigger_tab - > table_size - trigger_tab - > header_size ) /
sizeof ( struct acpi_einj_entry ) )
return - EINVAL ;
return 0 ;
}
2011-12-08 07:25:48 +04:00
static struct acpi_generic_address * einj_get_trigger_parameter_region (
struct acpi_einj_trigger * trigger_tab , u64 param1 , u64 param2 )
{
int i ;
struct acpi_whea_header * entry ;
entry = ( struct acpi_whea_header * )
( ( char * ) trigger_tab + sizeof ( struct acpi_einj_trigger ) ) ;
for ( i = 0 ; i < trigger_tab - > entry_count ; i + + ) {
if ( entry - > action = = ACPI_EINJ_TRIGGER_ERROR & &
2017-08-28 21:13:14 +03:00
entry - > instruction < = ACPI_EINJ_WRITE_REGISTER_VALUE & &
2011-12-08 07:25:48 +04:00
entry - > register_region . space_id = =
ACPI_ADR_SPACE_SYSTEM_MEMORY & &
( entry - > register_region . address & param2 ) = = ( param1 & param2 ) )
return & entry - > register_region ;
entry + + ;
}
return NULL ;
}
2010-05-18 10:35:14 +04:00
/* Execute instructions in trigger error action table */
2011-12-08 07:25:47 +04:00
static int __einj_error_trigger ( u64 trigger_paddr , u32 type ,
u64 param1 , u64 param2 )
2010-05-18 10:35:14 +04:00
{
struct acpi_einj_trigger * trigger_tab = NULL ;
struct apei_exec_context trigger_ctx ;
struct apei_resources trigger_resources ;
struct acpi_whea_header * trigger_entry ;
struct resource * r ;
u32 table_size ;
int rc = - EIO ;
2011-12-08 07:25:48 +04:00
struct acpi_generic_address * trigger_param_region = NULL ;
2010-05-18 10:35:14 +04:00
r = request_mem_region ( trigger_paddr , sizeof ( * trigger_tab ) ,
" APEI EINJ Trigger Table " ) ;
if ( ! r ) {
2016-05-23 11:28:03 +03:00
pr_err ( " Can not request [mem %#010llx-%#010llx] for Trigger table \n " ,
2010-05-18 10:35:14 +04:00
( unsigned long long ) trigger_paddr ,
2011-12-08 07:25:42 +04:00
( unsigned long long ) trigger_paddr +
sizeof ( * trigger_tab ) - 1 ) ;
2010-05-18 10:35:14 +04:00
goto out ;
}
trigger_tab = ioremap_cache ( trigger_paddr , sizeof ( * trigger_tab ) ) ;
if ( ! trigger_tab ) {
2016-05-23 11:28:03 +03:00
pr_err ( " Failed to map trigger table! \n " ) ;
2010-05-18 10:35:14 +04:00
goto out_rel_header ;
}
rc = einj_check_trigger_header ( trigger_tab ) ;
if ( rc ) {
2016-05-23 11:28:03 +03:00
pr_warning ( FW_BUG " Invalid trigger error action table. \n " ) ;
2010-05-18 10:35:14 +04:00
goto out_rel_header ;
}
2012-01-09 19:49:44 +04:00
/* No action structures in the TRIGGER_ERROR table, nothing to do */
if ( ! trigger_tab - > entry_count )
goto out_rel_header ;
2010-05-18 10:35:14 +04:00
rc = - EIO ;
table_size = trigger_tab - > table_size ;
r = request_mem_region ( trigger_paddr + sizeof ( * trigger_tab ) ,
table_size - sizeof ( * trigger_tab ) ,
" APEI EINJ Trigger Table " ) ;
if ( ! r ) {
2016-05-23 11:28:03 +03:00
pr_err ( " Can not request [mem %#010llx-%#010llx] for Trigger Table Entry \n " ,
2011-12-08 07:25:42 +04:00
( unsigned long long ) trigger_paddr + sizeof ( * trigger_tab ) ,
( unsigned long long ) trigger_paddr + table_size - 1 ) ;
2010-05-18 10:35:14 +04:00
goto out_rel_header ;
}
iounmap ( trigger_tab ) ;
trigger_tab = ioremap_cache ( trigger_paddr , table_size ) ;
if ( ! trigger_tab ) {
2016-05-23 11:28:03 +03:00
pr_err ( " Failed to map trigger table! \n " ) ;
2010-05-18 10:35:14 +04:00
goto out_rel_entry ;
}
trigger_entry = ( struct acpi_whea_header * )
( ( char * ) trigger_tab + sizeof ( struct acpi_einj_trigger ) ) ;
apei_resources_init ( & trigger_resources ) ;
apei_exec_ctx_init ( & trigger_ctx , einj_ins_type ,
ARRAY_SIZE ( einj_ins_type ) ,
trigger_entry , trigger_tab - > entry_count ) ;
rc = apei_exec_collect_resources ( & trigger_ctx , & trigger_resources ) ;
if ( rc )
goto out_fini ;
rc = apei_resources_sub ( & trigger_resources , & einj_resources ) ;
if ( rc )
goto out_fini ;
2011-12-08 07:25:47 +04:00
/*
* Some firmware will access target address specified in
* param1 to trigger the error when injecting memory error .
* This will cause resource conflict with regular memory . So
* remove it from trigger table resources .
*/
2013-06-07 02:20:51 +04:00
if ( ( param_extension | | acpi5 ) & & ( type & MEM_ERROR_MASK ) & & param2 ) {
2011-12-08 07:25:47 +04:00
struct apei_resources addr_resources ;
apei_resources_init ( & addr_resources ) ;
2011-12-08 07:25:48 +04:00
trigger_param_region = einj_get_trigger_parameter_region (
trigger_tab , param1 , param2 ) ;
if ( trigger_param_region ) {
rc = apei_resources_add ( & addr_resources ,
trigger_param_region - > address ,
trigger_param_region - > bit_width / 8 , true ) ;
if ( rc )
goto out_fini ;
rc = apei_resources_sub ( & trigger_resources ,
& addr_resources ) ;
}
2011-12-08 07:25:47 +04:00
apei_resources_fini ( & addr_resources ) ;
if ( rc )
goto out_fini ;
}
2010-05-18 10:35:14 +04:00
rc = apei_resources_request ( & trigger_resources , " APEI EINJ Trigger " ) ;
if ( rc )
goto out_fini ;
rc = apei_exec_pre_map_gars ( & trigger_ctx ) ;
if ( rc )
goto out_release ;
rc = apei_exec_run ( & trigger_ctx , ACPI_EINJ_TRIGGER_ERROR ) ;
apei_exec_post_unmap_gars ( & trigger_ctx ) ;
out_release :
apei_resources_release ( & trigger_resources ) ;
out_fini :
apei_resources_fini ( & trigger_resources ) ;
out_rel_entry :
release_mem_region ( trigger_paddr + sizeof ( * trigger_tab ) ,
table_size - sizeof ( * trigger_tab ) ) ;
out_rel_header :
release_mem_region ( trigger_paddr , sizeof ( * trigger_tab ) ) ;
out :
if ( trigger_tab )
iounmap ( trigger_tab ) ;
return rc ;
}
2013-11-07 01:30:36 +04:00
static int __einj_error_inject ( u32 type , u32 flags , u64 param1 , u64 param2 ,
u64 param3 , u64 param4 )
2010-05-18 10:35:14 +04:00
{
struct apei_exec_context ctx ;
u64 val , trigger_paddr , timeout = FIRMWARE_TIMEOUT ;
int rc ;
einj_exec_ctx_init ( & ctx ) ;
2011-07-13 09:14:17 +04:00
rc = apei_exec_run_optional ( & ctx , ACPI_EINJ_BEGIN_OPERATION ) ;
2010-05-18 10:35:14 +04:00
if ( rc )
return rc ;
apei_exec_ctx_set_input ( & ctx , type ) ;
2012-01-18 00:10:16 +04:00
if ( acpi5 ) {
struct set_error_type_with_address * v5param = einj_param ;
2012-01-24 03:27:56 +04:00
v5param - > type = type ;
2013-06-07 02:20:51 +04:00
if ( type & ACPI5_VENDOR_BIT ) {
2012-01-18 00:10:16 +04:00
switch ( vendor_flags ) {
case SETWA_FLAGS_APICID :
2012-01-24 03:27:56 +04:00
v5param - > apicid = param1 ;
2012-01-18 00:10:16 +04:00
break ;
case SETWA_FLAGS_MEM :
2012-01-24 03:27:56 +04:00
v5param - > memory_address = param1 ;
v5param - > memory_address_range = param2 ;
2012-01-18 00:10:16 +04:00
break ;
case SETWA_FLAGS_PCIE_SBDF :
2012-01-24 03:27:56 +04:00
v5param - > pcie_sbdf = param1 ;
2012-01-18 00:10:16 +04:00
break ;
}
2012-01-24 03:27:56 +04:00
v5param - > flags = vendor_flags ;
2013-11-07 01:30:36 +04:00
} else if ( flags ) {
v5param - > flags = flags ;
v5param - > memory_address = param1 ;
v5param - > memory_address_range = param2 ;
v5param - > apicid = param3 ;
v5param - > pcie_sbdf = param4 ;
2012-01-18 00:10:16 +04:00
} else {
switch ( type ) {
case ACPI_EINJ_PROCESSOR_CORRECTABLE :
case ACPI_EINJ_PROCESSOR_UNCORRECTABLE :
case ACPI_EINJ_PROCESSOR_FATAL :
2012-01-24 03:27:56 +04:00
v5param - > apicid = param1 ;
v5param - > flags = SETWA_FLAGS_APICID ;
2012-01-18 00:10:16 +04:00
break ;
case ACPI_EINJ_MEMORY_CORRECTABLE :
case ACPI_EINJ_MEMORY_UNCORRECTABLE :
case ACPI_EINJ_MEMORY_FATAL :
2012-01-24 03:27:56 +04:00
v5param - > memory_address = param1 ;
v5param - > memory_address_range = param2 ;
v5param - > flags = SETWA_FLAGS_MEM ;
2012-01-18 00:10:16 +04:00
break ;
case ACPI_EINJ_PCIX_CORRECTABLE :
case ACPI_EINJ_PCIX_UNCORRECTABLE :
case ACPI_EINJ_PCIX_FATAL :
2012-01-24 03:27:56 +04:00
v5param - > pcie_sbdf = param1 ;
v5param - > flags = SETWA_FLAGS_PCIE_SBDF ;
2012-01-18 00:10:16 +04:00
break ;
}
}
} else {
rc = apei_exec_run ( & ctx , ACPI_EINJ_SET_ERROR_TYPE ) ;
if ( rc )
return rc ;
if ( einj_param ) {
struct einj_parameter * v4param = einj_param ;
2012-01-24 03:27:56 +04:00
v4param - > param1 = param1 ;
v4param - > param2 = param2 ;
2012-01-18 00:10:16 +04:00
}
2010-05-18 10:35:24 +04:00
}
2010-05-18 10:35:14 +04:00
rc = apei_exec_run ( & ctx , ACPI_EINJ_EXECUTE_OPERATION ) ;
if ( rc )
return rc ;
for ( ; ; ) {
rc = apei_exec_run ( & ctx , ACPI_EINJ_CHECK_BUSY_STATUS ) ;
if ( rc )
return rc ;
val = apei_exec_ctx_get_output ( & ctx ) ;
if ( ! ( val & EINJ_OP_BUSY ) )
break ;
if ( einj_timedout ( & timeout ) )
return - EIO ;
}
rc = apei_exec_run ( & ctx , ACPI_EINJ_GET_COMMAND_STATUS ) ;
if ( rc )
return rc ;
val = apei_exec_ctx_get_output ( & ctx ) ;
if ( val ! = EINJ_STATUS_SUCCESS )
return - EBUSY ;
rc = apei_exec_run ( & ctx , ACPI_EINJ_GET_TRIGGER_TABLE ) ;
if ( rc )
return rc ;
trigger_paddr = apei_exec_ctx_get_output ( & ctx ) ;
2012-03-15 12:53:36 +04:00
if ( notrigger = = 0 ) {
rc = __einj_error_trigger ( trigger_paddr , type , param1 , param2 ) ;
if ( rc )
return rc ;
}
2011-07-13 09:14:17 +04:00
rc = apei_exec_run_optional ( & ctx , ACPI_EINJ_END_OPERATION ) ;
2010-05-18 10:35:14 +04:00
return rc ;
}
/* Inject the specified hardware error */
2013-11-07 01:30:36 +04:00
static int einj_error_inject ( u32 type , u32 flags , u64 param1 , u64 param2 ,
u64 param3 , u64 param4 )
2010-05-18 10:35:14 +04:00
{
int rc ;
2016-01-26 23:57:33 +03:00
u64 base_addr , size ;
2010-05-18 10:35:14 +04:00
2013-11-07 01:30:36 +04:00
/* If user manually set "flags", make sure it is legal */
if ( flags & & ( flags &
~ ( SETWA_FLAGS_APICID | SETWA_FLAGS_MEM | SETWA_FLAGS_PCIE_SBDF ) ) )
return - EINVAL ;
2013-06-07 02:20:51 +04:00
/*
* We need extra sanity checks for memory errors .
* Other types leap directly to injection .
*/
/* ensure param1/param2 existed */
if ( ! ( param_extension | | acpi5 ) )
goto inject ;
/* ensure injection is memory related */
if ( type & ACPI5_VENDOR_BIT ) {
if ( vendor_flags ! = SETWA_FLAGS_MEM )
goto inject ;
2013-11-07 01:30:36 +04:00
} else if ( ! ( type & MEM_ERROR_MASK ) & & ! ( flags & SETWA_FLAGS_MEM ) )
2013-06-07 02:20:51 +04:00
goto inject ;
/*
* Disallow crazy address masks that give BIOS leeway to pick
* injection address almost anywhere . Insist on page or
2016-01-26 23:57:33 +03:00
* better granularity and that target address is normal RAM or
* NVDIMM .
2013-06-07 02:20:51 +04:00
*/
2016-01-26 23:57:33 +03:00
base_addr = param1 & param2 ;
size = ~ param2 + 1 ;
if ( ( ( param2 & PAGE_MASK ) ! = PAGE_MASK ) | |
( ( region_intersects ( base_addr , size , IORESOURCE_SYSTEM_RAM , IORES_DESC_NONE )
! = REGION_INTERSECTS ) & &
( region_intersects ( base_addr , size , IORESOURCE_MEM , IORES_DESC_PERSISTENT_MEMORY )
! = REGION_INTERSECTS ) ) )
2013-06-07 02:20:51 +04:00
return - EINVAL ;
inject :
2010-05-18 10:35:14 +04:00
mutex_lock ( & einj_mutex ) ;
2013-11-07 01:30:36 +04:00
rc = __einj_error_inject ( type , flags , param1 , param2 , param3 , param4 ) ;
2010-05-18 10:35:14 +04:00
mutex_unlock ( & einj_mutex ) ;
return rc ;
}
static u32 error_type ;
2013-11-07 01:30:36 +04:00
static u32 error_flags ;
2010-05-18 10:35:24 +04:00
static u64 error_param1 ;
static u64 error_param2 ;
2013-11-07 01:30:36 +04:00
static u64 error_param3 ;
static u64 error_param4 ;
2010-05-18 10:35:14 +04:00
static struct dentry * einj_debug_dir ;
static int available_error_type_show ( struct seq_file * m , void * v )
{
int rc ;
u32 available_error_type = 0 ;
rc = einj_get_available_error_type ( & available_error_type ) ;
if ( rc )
return rc ;
if ( available_error_type & 0x0001 )
seq_printf ( m , " 0x00000001 \t Processor Correctable \n " ) ;
if ( available_error_type & 0x0002 )
seq_printf ( m , " 0x00000002 \t Processor Uncorrectable non-fatal \n " ) ;
if ( available_error_type & 0x0004 )
seq_printf ( m , " 0x00000004 \t Processor Uncorrectable fatal \n " ) ;
if ( available_error_type & 0x0008 )
seq_printf ( m , " 0x00000008 \t Memory Correctable \n " ) ;
if ( available_error_type & 0x0010 )
seq_printf ( m , " 0x00000010 \t Memory Uncorrectable non-fatal \n " ) ;
if ( available_error_type & 0x0020 )
seq_printf ( m , " 0x00000020 \t Memory Uncorrectable fatal \n " ) ;
if ( available_error_type & 0x0040 )
seq_printf ( m , " 0x00000040 \t PCI Express Correctable \n " ) ;
if ( available_error_type & 0x0080 )
seq_printf ( m , " 0x00000080 \t PCI Express Uncorrectable non-fatal \n " ) ;
if ( available_error_type & 0x0100 )
seq_printf ( m , " 0x00000100 \t PCI Express Uncorrectable fatal \n " ) ;
if ( available_error_type & 0x0200 )
seq_printf ( m , " 0x00000200 \t Platform Correctable \n " ) ;
if ( available_error_type & 0x0400 )
seq_printf ( m , " 0x00000400 \t Platform Uncorrectable non-fatal \n " ) ;
if ( available_error_type & 0x0800 )
seq_printf ( m , " 0x00000800 \t Platform Uncorrectable fatal \n " ) ;
return 0 ;
}
static int available_error_type_open ( struct inode * inode , struct file * file )
{
return single_open ( file , available_error_type_show , NULL ) ;
}
static const struct file_operations available_error_type_fops = {
. open = available_error_type_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
} ;
static int error_type_get ( void * data , u64 * val )
{
* val = error_type ;
return 0 ;
}
static int error_type_set ( void * data , u64 val )
{
int rc ;
u32 available_error_type = 0 ;
2012-01-18 00:10:16 +04:00
u32 tval , vendor ;
/*
* Vendor defined types have 0x80000000 bit set , and
* are not enumerated by ACPI_EINJ_GET_ERROR_TYPE
*/
2013-06-07 02:20:51 +04:00
vendor = val & ACPI5_VENDOR_BIT ;
2012-01-18 00:10:16 +04:00
tval = val & 0x7fffffff ;
2010-05-18 10:35:14 +04:00
/* Only one error type can be specified */
2012-01-18 00:10:16 +04:00
if ( tval & ( tval - 1 ) )
2010-05-18 10:35:14 +04:00
return - EINVAL ;
2012-01-18 00:10:16 +04:00
if ( ! vendor ) {
rc = einj_get_available_error_type ( & available_error_type ) ;
if ( rc )
return rc ;
if ( ! ( val & available_error_type ) )
return - EINVAL ;
}
2010-05-18 10:35:14 +04:00
error_type = val ;
return 0 ;
}
DEFINE_SIMPLE_ATTRIBUTE ( error_type_fops , error_type_get ,
error_type_set , " 0x%llx \n " ) ;
static int error_inject_set ( void * data , u64 val )
{
if ( ! error_type )
return - EINVAL ;
2013-11-07 01:30:36 +04:00
return einj_error_inject ( error_type , error_flags , error_param1 , error_param2 ,
error_param3 , error_param4 ) ;
2010-05-18 10:35:14 +04:00
}
DEFINE_SIMPLE_ATTRIBUTE ( error_inject_fops , NULL ,
error_inject_set , " %llu \n " ) ;
static int einj_check_table ( struct acpi_table_einj * einj_tab )
{
2010-09-29 15:53:51 +04:00
if ( ( einj_tab - > header_length ! =
( sizeof ( struct acpi_table_einj ) - sizeof ( einj_tab - > header ) ) )
& & ( einj_tab - > header_length ! = sizeof ( struct acpi_table_einj ) ) )
2010-05-18 10:35:14 +04:00
return - EINVAL ;
if ( einj_tab - > header . length < sizeof ( struct acpi_table_einj ) )
return - EINVAL ;
if ( einj_tab - > entries ! =
( einj_tab - > header . length - sizeof ( struct acpi_table_einj ) ) /
sizeof ( struct acpi_einj_entry ) )
return - EINVAL ;
return 0 ;
}
static int __init einj_init ( void )
{
int rc ;
acpi_status status ;
struct dentry * fentry ;
struct apei_exec_context ctx ;
2016-05-23 11:28:04 +03:00
if ( acpi_disabled ) {
pr_warn ( " ACPI disabled. \n " ) ;
2010-05-18 10:35:14 +04:00
return - ENODEV ;
2016-05-23 11:28:04 +03:00
}
2010-05-18 10:35:14 +04:00
status = acpi_get_table ( ACPI_SIG_EINJ , 0 ,
( struct acpi_table_header * * ) & einj_tab ) ;
2016-05-23 11:28:04 +03:00
if ( status = = AE_NOT_FOUND ) {
pr_warn ( " EINJ table not found. \n " ) ;
2010-05-18 10:35:14 +04:00
return - ENODEV ;
2016-05-23 11:28:04 +03:00
}
2011-12-08 07:25:43 +04:00
else if ( ACPI_FAILURE ( status ) ) {
2016-05-23 11:28:04 +03:00
pr_err ( " Failed to get EINJ table: %s \n " ,
acpi_format_exception ( status ) ) ;
2010-05-18 10:35:14 +04:00
return - EINVAL ;
}
rc = einj_check_table ( einj_tab ) ;
if ( rc ) {
2017-01-17 17:57:20 +03:00
pr_warn ( FW_BUG " Invalid EINJ table. \n " ) ;
2010-05-18 10:35:14 +04:00
return - EINVAL ;
}
rc = - ENOMEM ;
einj_debug_dir = debugfs_create_dir ( " einj " , apei_get_debugfs_dir ( ) ) ;
2016-05-23 11:28:04 +03:00
if ( ! einj_debug_dir ) {
pr_err ( " Error creating debugfs node. \n " ) ;
2010-05-18 10:35:14 +04:00
goto err_cleanup ;
2016-05-23 11:28:04 +03:00
}
2010-05-18 10:35:14 +04:00
fentry = debugfs_create_file ( " available_error_type " , S_IRUSR ,
einj_debug_dir , NULL ,
& available_error_type_fops ) ;
if ( ! fentry )
goto err_cleanup ;
2016-05-23 11:28:04 +03:00
2010-05-18 10:35:14 +04:00
fentry = debugfs_create_file ( " error_type " , S_IRUSR | S_IWUSR ,
einj_debug_dir , NULL , & error_type_fops ) ;
if ( ! fentry )
goto err_cleanup ;
fentry = debugfs_create_file ( " error_inject " , S_IWUSR ,
einj_debug_dir , NULL , & error_inject_fops ) ;
if ( ! fentry )
goto err_cleanup ;
apei_resources_init ( & einj_resources ) ;
einj_exec_ctx_init ( & ctx ) ;
rc = apei_exec_collect_resources ( & ctx , & einj_resources ) ;
2016-05-23 11:28:04 +03:00
if ( rc ) {
pr_err ( " Error collecting EINJ resources. \n " ) ;
2010-05-18 10:35:14 +04:00
goto err_fini ;
2016-05-23 11:28:04 +03:00
}
2010-05-18 10:35:14 +04:00
rc = apei_resources_request ( & einj_resources , " APEI EINJ " ) ;
2016-05-23 11:28:04 +03:00
if ( rc ) {
pr_err ( " Error requesting memory/port resources. \n " ) ;
2010-05-18 10:35:14 +04:00
goto err_fini ;
2016-05-23 11:28:04 +03:00
}
2010-05-18 10:35:14 +04:00
rc = apei_exec_pre_map_gars ( & ctx ) ;
2016-05-23 11:28:04 +03:00
if ( rc ) {
pr_err ( " Error pre-mapping GARs. \n " ) ;
2010-05-18 10:35:14 +04:00
goto err_release ;
2016-05-23 11:28:04 +03:00
}
2012-01-18 00:10:16 +04:00
2013-06-06 03:18:12 +04:00
rc = - ENOMEM ;
2012-01-18 00:10:16 +04:00
einj_param = einj_get_parameter_address ( ) ;
if ( ( param_extension | | acpi5 ) & & einj_param ) {
2013-11-07 01:30:36 +04:00
fentry = debugfs_create_x32 ( " flags " , S_IRUSR | S_IWUSR ,
einj_debug_dir , & error_flags ) ;
if ( ! fentry )
goto err_unmap ;
2012-01-18 00:10:16 +04:00
fentry = debugfs_create_x64 ( " param1 " , S_IRUSR | S_IWUSR ,
einj_debug_dir , & error_param1 ) ;
if ( ! fentry )
goto err_unmap ;
fentry = debugfs_create_x64 ( " param2 " , S_IRUSR | S_IWUSR ,
einj_debug_dir , & error_param2 ) ;
if ( ! fentry )
goto err_unmap ;
2013-11-07 01:30:36 +04:00
fentry = debugfs_create_x64 ( " param3 " , S_IRUSR | S_IWUSR ,
einj_debug_dir , & error_param3 ) ;
if ( ! fentry )
goto err_unmap ;
fentry = debugfs_create_x64 ( " param4 " , S_IRUSR | S_IWUSR ,
einj_debug_dir , & error_param4 ) ;
if ( ! fentry )
goto err_unmap ;
2012-03-15 12:53:36 +04:00
fentry = debugfs_create_x32 ( " notrigger " , S_IRUSR | S_IWUSR ,
einj_debug_dir , & notrigger ) ;
if ( ! fentry )
goto err_unmap ;
2012-01-18 00:10:16 +04:00
}
if ( vendor_dev [ 0 ] ) {
vendor_blob . data = vendor_dev ;
vendor_blob . size = strlen ( vendor_dev ) ;
fentry = debugfs_create_blob ( " vendor " , S_IRUSR ,
einj_debug_dir , & vendor_blob ) ;
if ( ! fentry )
goto err_unmap ;
fentry = debugfs_create_x32 ( " vendor_flags " , S_IRUSR | S_IWUSR ,
einj_debug_dir , & vendor_flags ) ;
if ( ! fentry )
goto err_unmap ;
2010-05-18 10:35:24 +04:00
}
2010-05-18 10:35:14 +04:00
2016-05-23 11:28:03 +03:00
pr_info ( " Error INJection is initialized. \n " ) ;
2010-05-18 10:35:14 +04:00
return 0 ;
2010-05-18 10:35:24 +04:00
err_unmap :
2012-01-24 03:27:56 +04:00
if ( einj_param ) {
acpi_size size = ( acpi5 ) ?
sizeof ( struct set_error_type_with_address ) :
sizeof ( struct einj_parameter ) ;
2014-05-20 11:39:41 +04:00
acpi_os_unmap_iomem ( einj_param , size ) ;
2016-05-23 11:28:04 +03:00
pr_err ( " Error creating param extension debugfs nodes. \n " ) ;
2012-01-24 03:27:56 +04:00
}
2010-05-18 10:35:24 +04:00
apei_exec_post_unmap_gars ( & ctx ) ;
2010-05-18 10:35:14 +04:00
err_release :
apei_resources_release ( & einj_resources ) ;
err_fini :
apei_resources_fini ( & einj_resources ) ;
err_cleanup :
2016-05-23 11:28:04 +03:00
pr_err ( " Error creating primary debugfs nodes. \n " ) ;
2010-05-18 10:35:14 +04:00
debugfs_remove_recursive ( einj_debug_dir ) ;
return rc ;
}
static void __exit einj_exit ( void )
{
struct apei_exec_context ctx ;
2012-01-24 03:27:56 +04:00
if ( einj_param ) {
acpi_size size = ( acpi5 ) ?
sizeof ( struct set_error_type_with_address ) :
sizeof ( struct einj_parameter ) ;
2014-05-20 11:39:41 +04:00
acpi_os_unmap_iomem ( einj_param , size ) ;
2012-01-24 03:27:56 +04:00
}
2010-05-18 10:35:14 +04:00
einj_exec_ctx_init ( & ctx ) ;
apei_exec_post_unmap_gars ( & ctx ) ;
apei_resources_release ( & einj_resources ) ;
apei_resources_fini ( & einj_resources ) ;
debugfs_remove_recursive ( einj_debug_dir ) ;
}
module_init ( einj_init ) ;
module_exit ( einj_exit ) ;
MODULE_AUTHOR ( " Huang Ying " ) ;
MODULE_DESCRIPTION ( " APEI Error INJection support " ) ;
MODULE_LICENSE ( " GPL " ) ;