2019-05-27 08:55:01 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2014-03-03 10:25:42 +11:00
/*
* PowerNV OPAL Dump Interface
*
* Copyright 2013 , 2014 IBM Corp .
*/
# include <linux/kobject.h>
# include <linux/mm.h>
# include <linux/slab.h>
# include <linux/vmalloc.h>
# include <linux/pagemap.h>
# include <linux/delay.h>
2015-05-15 14:06:43 +10:00
# include <linux/interrupt.h>
2014-03-03 10:25:42 +11:00
# include <asm/opal.h>
# define DUMP_TYPE_FSP 0x01
struct dump_obj {
struct kobject kobj ;
struct bin_attribute dump_attr ;
uint32_t id ; /* becomes object name */
uint32_t type ;
uint32_t size ;
char * buffer ;
} ;
# define to_dump_obj(x) container_of(x, struct dump_obj, kobj)
struct dump_attribute {
struct attribute attr ;
ssize_t ( * show ) ( struct dump_obj * dump , struct dump_attribute * attr ,
char * buf ) ;
ssize_t ( * store ) ( struct dump_obj * dump , struct dump_attribute * attr ,
const char * buf , size_t count ) ;
} ;
# define to_dump_attr(x) container_of(x, struct dump_attribute, attr)
static ssize_t dump_id_show ( struct dump_obj * dump_obj ,
struct dump_attribute * attr ,
char * buf )
{
return sprintf ( buf , " 0x%x \n " , dump_obj - > id ) ;
}
static const char * dump_type_to_string ( uint32_t type )
{
switch ( type ) {
case 0x01 : return " SP Dump " ;
case 0x02 : return " System/Platform Dump " ;
case 0x03 : return " SMA Dump " ;
default : return " unknown " ;
}
}
static ssize_t dump_type_show ( struct dump_obj * dump_obj ,
struct dump_attribute * attr ,
char * buf )
{
2015-05-15 14:06:43 +10:00
2014-03-03 10:25:42 +11:00
return sprintf ( buf , " 0x%x %s \n " , dump_obj - > type ,
dump_type_to_string ( dump_obj - > type ) ) ;
}
static ssize_t dump_ack_show ( struct dump_obj * dump_obj ,
struct dump_attribute * attr ,
char * buf )
{
return sprintf ( buf , " ack - acknowledge dump \n " ) ;
}
/*
* Send acknowledgement to OPAL
*/
static int64_t dump_send_ack ( uint32_t dump_id )
{
int rc ;
rc = opal_dump_ack ( dump_id ) ;
if ( rc )
pr_warn ( " %s: Failed to send ack to Dump ID 0x%x (%d) \n " ,
__func__ , dump_id , rc ) ;
return rc ;
}
static ssize_t dump_ack_store ( struct dump_obj * dump_obj ,
struct dump_attribute * attr ,
const char * buf ,
size_t count )
{
dump_send_ack ( dump_obj - > id ) ;
2014-04-09 13:47:37 +10:00
sysfs_remove_file_self ( & dump_obj - > kobj , & attr - > attr ) ;
kobject_put ( & dump_obj - > kobj ) ;
2014-03-03 10:25:42 +11:00
return count ;
}
/* Attributes of a dump
* The binary attribute of the dump itself is dynamic
* due to the dynamic size of the dump
*/
static struct dump_attribute id_attribute =
2017-01-12 14:54:13 +11:00
__ATTR ( id , 0444 , dump_id_show , NULL ) ;
2014-03-03 10:25:42 +11:00
static struct dump_attribute type_attribute =
2017-01-12 14:54:13 +11:00
__ATTR ( type , 0444 , dump_type_show , NULL ) ;
2014-03-03 10:25:42 +11:00
static struct dump_attribute ack_attribute =
__ATTR ( acknowledge , 0660 , dump_ack_show , dump_ack_store ) ;
static ssize_t init_dump_show ( struct dump_obj * dump_obj ,
struct dump_attribute * attr ,
char * buf )
{
2014-08-14 12:16:39 +05:30
return sprintf ( buf , " 1 - initiate Service Processor(FSP) dump \ n " ) ;
2014-03-03 10:25:42 +11:00
}
static int64_t dump_fips_init ( uint8_t type )
{
int rc ;
rc = opal_dump_init ( type ) ;
if ( rc )
2014-08-14 12:16:39 +05:30
pr_warn ( " %s: Failed to initiate FSP dump (%d) \n " ,
2014-03-03 10:25:42 +11:00
__func__ , rc ) ;
return rc ;
}
static ssize_t init_dump_store ( struct dump_obj * dump_obj ,
struct dump_attribute * attr ,
const char * buf ,
size_t count )
{
2014-08-14 12:16:39 +05:30
int rc ;
rc = dump_fips_init ( DUMP_TYPE_FSP ) ;
if ( rc = = OPAL_SUCCESS )
pr_info ( " %s: Initiated FSP dump \n " , __func__ ) ;
2014-03-03 10:25:42 +11:00
return count ;
}
static struct dump_attribute initiate_attribute =
__ATTR ( initiate_dump , 0600 , init_dump_show , init_dump_store ) ;
static struct attribute * initiate_attrs [ ] = {
& initiate_attribute . attr ,
NULL ,
} ;
static struct attribute_group initiate_attr_group = {
. attrs = initiate_attrs ,
} ;
static struct kset * dump_kset ;
static ssize_t dump_attr_show ( struct kobject * kobj ,
struct attribute * attr ,
char * buf )
{
struct dump_attribute * attribute ;
struct dump_obj * dump ;
attribute = to_dump_attr ( attr ) ;
dump = to_dump_obj ( kobj ) ;
if ( ! attribute - > show )
return - EIO ;
return attribute - > show ( dump , attribute , buf ) ;
}
static ssize_t dump_attr_store ( struct kobject * kobj ,
struct attribute * attr ,
const char * buf , size_t len )
{
struct dump_attribute * attribute ;
struct dump_obj * dump ;
attribute = to_dump_attr ( attr ) ;
dump = to_dump_obj ( kobj ) ;
if ( ! attribute - > store )
return - EIO ;
return attribute - > store ( dump , attribute , buf , len ) ;
}
static const struct sysfs_ops dump_sysfs_ops = {
. show = dump_attr_show ,
. store = dump_attr_store ,
} ;
static void dump_release ( struct kobject * kobj )
{
struct dump_obj * dump ;
dump = to_dump_obj ( kobj ) ;
vfree ( dump - > buffer ) ;
kfree ( dump ) ;
}
static struct attribute * dump_default_attrs [ ] = {
& id_attribute . attr ,
& type_attribute . attr ,
& ack_attribute . attr ,
NULL ,
} ;
static struct kobj_type dump_ktype = {
. sysfs_ops = & dump_sysfs_ops ,
. release = & dump_release ,
. default_attrs = dump_default_attrs ,
} ;
2014-04-22 15:01:27 +10:00
static int64_t dump_read_info ( uint32_t * dump_id , uint32_t * dump_size , uint32_t * dump_type )
2014-03-03 10:25:42 +11:00
{
2014-04-22 15:01:27 +10:00
__be32 id , size , type ;
2014-03-03 10:25:42 +11:00
int rc ;
2014-04-22 15:01:27 +10:00
type = cpu_to_be32 ( 0xffffffff ) ;
2014-03-03 10:25:42 +11:00
2014-04-22 15:01:27 +10:00
rc = opal_dump_info2 ( & id , & size , & type ) ;
2014-03-03 10:25:42 +11:00
if ( rc = = OPAL_PARAMETER )
2014-04-22 15:01:27 +10:00
rc = opal_dump_info ( & id , & size ) ;
2017-02-20 18:52:10 +05:30
if ( rc ) {
pr_warn ( " %s: Failed to get dump info (%d) \n " ,
__func__ , rc ) ;
return rc ;
}
2014-04-22 15:01:27 +10:00
* dump_id = be32_to_cpu ( id ) ;
* dump_size = be32_to_cpu ( size ) ;
* dump_type = be32_to_cpu ( type ) ;
2014-03-03 10:25:42 +11:00
return rc ;
}
static int64_t dump_read_data ( struct dump_obj * dump )
{
struct opal_sg_list * list ;
uint64_t addr ;
int64_t rc ;
/* Allocate memory */
dump - > buffer = vzalloc ( PAGE_ALIGN ( dump - > size ) ) ;
if ( ! dump - > buffer ) {
pr_err ( " %s : Failed to allocate memory \n " , __func__ ) ;
rc = - ENOMEM ;
goto out ;
}
/* Generate SG list */
2014-04-22 15:01:26 +10:00
list = opal_vmalloc_to_sg_list ( dump - > buffer , dump - > size ) ;
2014-03-03 10:25:42 +11:00
if ( ! list ) {
rc = - ENOMEM ;
goto out ;
}
/* First entry address */
addr = __pa ( list ) ;
/* Fetch data */
rc = OPAL_BUSY_EVENT ;
while ( rc = = OPAL_BUSY | | rc = = OPAL_BUSY_EVENT ) {
rc = opal_dump_read ( dump - > id , addr ) ;
if ( rc = = OPAL_BUSY_EVENT ) {
opal_poll_events ( NULL ) ;
msleep ( 20 ) ;
}
}
if ( rc ! = OPAL_SUCCESS & & rc ! = OPAL_PARTIAL )
pr_warn ( " %s: Extract dump failed for ID 0x%x \n " ,
__func__ , dump - > id ) ;
/* Free SG list */
2014-04-22 15:01:26 +10:00
opal_free_sg_list ( list ) ;
2014-03-03 10:25:42 +11:00
out :
return rc ;
}
static ssize_t dump_attr_read ( struct file * filep , struct kobject * kobj ,
struct bin_attribute * bin_attr ,
char * buffer , loff_t pos , size_t count )
{
ssize_t rc ;
struct dump_obj * dump = to_dump_obj ( kobj ) ;
if ( ! dump - > buffer ) {
rc = dump_read_data ( dump ) ;
if ( rc ! = OPAL_SUCCESS & & rc ! = OPAL_PARTIAL ) {
vfree ( dump - > buffer ) ;
dump - > buffer = NULL ;
return - EIO ;
}
if ( rc = = OPAL_PARTIAL ) {
/* On a partial read, we just return EIO
* and rely on userspace to ask us to try
* again .
*/
2014-08-14 12:16:39 +05:30
pr_info ( " %s: Platform dump partially read. ID = 0x%x \n " ,
2014-03-03 10:25:42 +11:00
__func__ , dump - > id ) ;
return - EIO ;
}
}
memcpy ( buffer , dump - > buffer + pos , count ) ;
/* You may think we could free the dump buffer now and retrieve
* it again later if needed , but due to current firmware limitation ,
* that ' s not the case . So , once read into userspace once ,
* we keep the dump around until it ' s acknowledged by userspace .
*/
return count ;
}
static struct dump_obj * create_dump_obj ( uint32_t id , size_t size ,
uint32_t type )
{
struct dump_obj * dump ;
int rc ;
dump = kzalloc ( sizeof ( * dump ) , GFP_KERNEL ) ;
if ( ! dump )
return NULL ;
dump - > kobj . kset = dump_kset ;
kobject_init ( & dump - > kobj , & dump_ktype ) ;
sysfs_bin_attr_init ( & dump - > dump_attr ) ;
dump - > dump_attr . attr . name = " dump " ;
dump - > dump_attr . attr . mode = 0400 ;
dump - > dump_attr . size = size ;
dump - > dump_attr . read = dump_attr_read ;
dump - > id = id ;
dump - > size = size ;
dump - > type = type ;
rc = kobject_add ( & dump - > kobj , NULL , " 0x%x-0x%x " , type , id ) ;
if ( rc ) {
kobject_put ( & dump - > kobj ) ;
return NULL ;
}
rc = sysfs_create_bin_file ( & dump - > kobj , & dump - > dump_attr ) ;
if ( rc ) {
kobject_put ( & dump - > kobj ) ;
return NULL ;
}
pr_info ( " %s: New platform dump. ID = 0x%x Size %u \n " ,
__func__ , dump - > id , dump - > size ) ;
kobject_uevent ( & dump - > kobj , KOBJ_ADD ) ;
return dump ;
}
2015-05-15 14:06:43 +10:00
static irqreturn_t process_dump ( int irq , void * data )
2014-03-03 10:25:42 +11:00
{
int rc ;
uint32_t dump_id , dump_size , dump_type ;
char name [ 22 ] ;
2016-08-22 12:17:44 +05:30
struct kobject * kobj ;
2014-03-03 10:25:42 +11:00
rc = dump_read_info ( & dump_id , & dump_size , & dump_type ) ;
if ( rc ! = OPAL_SUCCESS )
2017-02-20 18:52:11 +05:30
return IRQ_HANDLED ;
2014-03-03 10:25:42 +11:00
sprintf ( name , " 0x%x-0x%x " , dump_type , dump_id ) ;
/* we may get notified twice, let's handle
* that gracefully and not create two conflicting
* entries .
*/
2016-08-22 12:17:44 +05:30
kobj = kset_find_obj ( dump_kset , name ) ;
if ( kobj ) {
/* Drop reference added by kset_find_obj() */
kobject_put ( kobj ) ;
2017-02-20 18:52:11 +05:30
return IRQ_HANDLED ;
2016-08-22 12:17:44 +05:30
}
2014-03-03 10:25:42 +11:00
2017-02-20 18:52:11 +05:30
create_dump_obj ( dump_id , dump_size , dump_type ) ;
2014-03-03 10:25:42 +11:00
2015-05-15 14:06:43 +10:00
return IRQ_HANDLED ;
2014-03-03 10:25:42 +11:00
}
void __init opal_platform_dump_init ( void )
{
int rc ;
2015-05-15 14:06:43 +10:00
int dump_irq ;
2014-03-03 10:25:42 +11:00
2014-08-19 14:48:02 +10:00
/* ELOG not supported by firmware */
if ( ! opal_check_token ( OPAL_DUMP_READ ) )
return ;
2014-03-03 10:25:42 +11:00
dump_kset = kset_create_and_add ( " dump " , NULL , opal_kobj ) ;
if ( ! dump_kset ) {
pr_warn ( " %s: Failed to create dump kset \n " , __func__ ) ;
return ;
}
rc = sysfs_create_group ( & dump_kset - > kobj , & initiate_attr_group ) ;
if ( rc ) {
pr_warn ( " %s: Failed to create initiate dump attr group \n " ,
__func__ ) ;
kobject_put ( & dump_kset - > kobj ) ;
return ;
}
2015-05-15 14:06:43 +10:00
dump_irq = opal_event_request ( ilog2 ( OPAL_EVENT_DUMP_AVAIL ) ) ;
if ( ! dump_irq ) {
pr_err ( " %s: Can't register OPAL event irq (%d) \n " ,
__func__ , dump_irq ) ;
return ;
}
rc = request_threaded_irq ( dump_irq , NULL , process_dump ,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT ,
" opal-dump " , NULL ) ;
2014-03-03 10:25:42 +11:00
if ( rc ) {
2015-05-15 14:06:43 +10:00
pr_err ( " %s: Can't request OPAL event irq (%d) \n " ,
__func__ , rc ) ;
2014-03-03 10:25:42 +11:00
return ;
}
2015-02-12 16:25:29 +11:00
if ( opal_check_token ( OPAL_DUMP_RESEND ) )
opal_dump_resend_notification ( ) ;
2014-03-03 10:25:42 +11:00
}