2020-07-17 01:20:31 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Coredump functionality for Remoteproc framework .
*
* Copyright ( c ) 2020 , The Linux Foundation . All rights reserved .
*/
2020-07-17 01:20:34 +03:00
# include <linux/completion.h>
2020-07-17 01:20:31 +03:00
# include <linux/devcoredump.h>
# include <linux/device.h>
# include <linux/kernel.h>
# include <linux/remoteproc.h>
# include "remoteproc_internal.h"
# include "remoteproc_elf_helpers.h"
2020-07-17 01:20:34 +03:00
struct rproc_coredump_state {
struct rproc * rproc ;
void * header ;
struct completion dump_done ;
} ;
2020-07-17 01:20:31 +03:00
/**
* rproc_coredump_cleanup ( ) - clean up dump_segments list
* @ rproc : the remote processor handle
*/
void rproc_coredump_cleanup ( struct rproc * rproc )
{
struct rproc_dump_segment * entry , * tmp ;
list_for_each_entry_safe ( entry , tmp , & rproc - > dump_segments , node ) {
list_del ( & entry - > node ) ;
kfree ( entry ) ;
}
}
/**
* rproc_coredump_add_segment ( ) - add segment of device memory to coredump
* @ rproc : handle of a remote processor
* @ da : device address
* @ size : size of segment
*
* Add device memory to the list of segments to be included in a coredump for
* the remoteproc .
*
* Return : 0 on success , negative errno on error .
*/
int rproc_coredump_add_segment ( struct rproc * rproc , dma_addr_t da , size_t size )
{
struct rproc_dump_segment * segment ;
segment = kzalloc ( sizeof ( * segment ) , GFP_KERNEL ) ;
if ( ! segment )
return - ENOMEM ;
segment - > da = da ;
segment - > size = size ;
list_add_tail ( & segment - > node , & rproc - > dump_segments ) ;
return 0 ;
}
EXPORT_SYMBOL ( rproc_coredump_add_segment ) ;
/**
* rproc_coredump_add_custom_segment ( ) - add custom coredump segment
* @ rproc : handle of a remote processor
* @ da : device address
* @ size : size of segment
* @ dumpfn : custom dump function called for each segment during coredump
* @ priv : private data
*
* Add device memory to the list of segments to be included in the coredump
* and associate the segment with the given custom dump function and private
* data .
*
* Return : 0 on success , negative errno on error .
*/
int rproc_coredump_add_custom_segment ( struct rproc * rproc ,
dma_addr_t da , size_t size ,
void ( * dumpfn ) ( struct rproc * rproc ,
struct rproc_dump_segment * segment ,
2020-07-17 01:20:33 +03:00
void * dest , size_t offset ,
size_t size ) ,
2020-07-17 01:20:31 +03:00
void * priv )
{
struct rproc_dump_segment * segment ;
segment = kzalloc ( sizeof ( * segment ) , GFP_KERNEL ) ;
if ( ! segment )
return - ENOMEM ;
segment - > da = da ;
segment - > size = size ;
segment - > priv = priv ;
segment - > dump = dumpfn ;
list_add_tail ( & segment - > node , & rproc - > dump_segments ) ;
return 0 ;
}
EXPORT_SYMBOL ( rproc_coredump_add_custom_segment ) ;
/**
* rproc_coredump_set_elf_info ( ) - set coredump elf information
* @ rproc : handle of a remote processor
* @ class : elf class for coredump elf file
* @ machine : elf machine for coredump elf file
*
* Set elf information which will be used for coredump elf file .
*
* Return : 0 on success , negative errno on error .
*/
int rproc_coredump_set_elf_info ( struct rproc * rproc , u8 class , u16 machine )
{
if ( class ! = ELFCLASS64 & & class ! = ELFCLASS32 )
return - EINVAL ;
rproc - > elf_class = class ;
rproc - > elf_machine = machine ;
return 0 ;
}
EXPORT_SYMBOL ( rproc_coredump_set_elf_info ) ;
2020-07-17 01:20:34 +03:00
static void rproc_coredump_free ( void * data )
{
struct rproc_coredump_state * dump_state = data ;
vfree ( dump_state - > header ) ;
complete ( & dump_state - > dump_done ) ;
}
static void * rproc_coredump_find_segment ( loff_t user_offset ,
struct list_head * segments ,
size_t * data_left )
{
struct rproc_dump_segment * segment ;
list_for_each_entry ( segment , segments , node ) {
if ( user_offset < segment - > size ) {
* data_left = segment - > size - user_offset ;
return segment ;
}
user_offset - = segment - > size ;
}
* data_left = 0 ;
return NULL ;
}
static void rproc_copy_segment ( struct rproc * rproc , void * dest ,
struct rproc_dump_segment * segment ,
size_t offset , size_t size )
{
void * ptr ;
if ( segment - > dump ) {
segment - > dump ( rproc , segment , dest , offset , size ) ;
} else {
ptr = rproc_da_to_va ( rproc , segment - > da + offset , size ) ;
if ( ! ptr ) {
dev_err ( & rproc - > dev ,
" invalid copy request for segment %pad with offset %zu and size %zu) \n " ,
& segment - > da , offset , size ) ;
memset ( dest , 0xff , size ) ;
} else {
memcpy ( dest , ptr , size ) ;
}
}
}
static ssize_t rproc_coredump_read ( char * buffer , loff_t offset , size_t count ,
void * data , size_t header_sz )
{
size_t seg_data , bytes_left = count ;
ssize_t copy_sz ;
struct rproc_dump_segment * seg ;
struct rproc_coredump_state * dump_state = data ;
struct rproc * rproc = dump_state - > rproc ;
void * elfcore = dump_state - > header ;
/* Copy the vmalloc'ed header first. */
if ( offset < header_sz ) {
copy_sz = memory_read_from_buffer ( buffer , count , & offset ,
elfcore , header_sz ) ;
return copy_sz ;
}
/*
* Find out the segment memory chunk to be copied based on offset .
* Keep copying data until count bytes are read .
*/
while ( bytes_left ) {
seg = rproc_coredump_find_segment ( offset - header_sz ,
& rproc - > dump_segments ,
& seg_data ) ;
/* EOF check */
if ( ! seg ) {
dev_info ( & rproc - > dev , " Ramdump done, %lld bytes read " ,
offset ) ;
break ;
}
copy_sz = min_t ( size_t , bytes_left , seg_data ) ;
rproc_copy_segment ( rproc , buffer , seg , seg - > size - seg_data ,
copy_sz ) ;
offset + = copy_sz ;
buffer + = copy_sz ;
bytes_left - = copy_sz ;
}
return count - bytes_left ;
}
2020-07-17 01:20:31 +03:00
/**
* rproc_coredump ( ) - perform coredump
* @ rproc : rproc handle
*
* This function will generate an ELF header for the registered segments
2020-07-17 01:20:34 +03:00
* and create a devcoredump device associated with rproc . Based on the
* coredump configuration this function will directly copy the segments
* from device memory to userspace or copy segments from device memory to
* a separate buffer , which can then be read by userspace .
* The first approach avoids using extra vmalloc memory . But it will stall
* recovery flow until dump is read by userspace .
2020-07-17 01:20:31 +03:00
*/
void rproc_coredump ( struct rproc * rproc )
{
struct rproc_dump_segment * segment ;
void * phdr ;
void * ehdr ;
size_t data_size ;
size_t offset ;
void * data ;
u8 class = rproc - > elf_class ;
int phnum = 0 ;
2020-07-17 01:20:34 +03:00
struct rproc_coredump_state dump_state ;
enum rproc_dump_mechanism dump_conf = rproc - > dump_conf ;
2020-07-17 01:20:31 +03:00
2020-07-17 01:20:34 +03:00
if ( list_empty ( & rproc - > dump_segments ) | |
dump_conf = = RPROC_COREDUMP_DISABLED )
2020-07-17 01:20:31 +03:00
return ;
if ( class = = ELFCLASSNONE ) {
dev_err ( & rproc - > dev , " Elf class is not set \n " ) ;
return ;
}
data_size = elf_size_of_hdr ( class ) ;
list_for_each_entry ( segment , & rproc - > dump_segments , node ) {
2020-07-17 01:20:34 +03:00
/*
* For default configuration buffer includes headers & segments .
* For inline dump buffer just includes headers as segments are
* directly read from device memory .
*/
data_size + = elf_size_of_phdr ( class ) ;
2020-10-02 21:09:02 +03:00
if ( dump_conf = = RPROC_COREDUMP_ENABLED )
2020-07-17 01:20:34 +03:00
data_size + = segment - > size ;
2020-07-17 01:20:31 +03:00
phnum + + ;
}
data = vmalloc ( data_size ) ;
if ( ! data )
return ;
ehdr = data ;
memset ( ehdr , 0 , elf_size_of_hdr ( class ) ) ;
/* e_ident field is common for both elf32 and elf64 */
elf_hdr_init_ident ( ehdr , class ) ;
elf_hdr_set_e_type ( class , ehdr , ET_CORE ) ;
elf_hdr_set_e_machine ( class , ehdr , rproc - > elf_machine ) ;
elf_hdr_set_e_version ( class , ehdr , EV_CURRENT ) ;
elf_hdr_set_e_entry ( class , ehdr , rproc - > bootaddr ) ;
elf_hdr_set_e_phoff ( class , ehdr , elf_size_of_hdr ( class ) ) ;
elf_hdr_set_e_ehsize ( class , ehdr , elf_size_of_hdr ( class ) ) ;
elf_hdr_set_e_phentsize ( class , ehdr , elf_size_of_phdr ( class ) ) ;
elf_hdr_set_e_phnum ( class , ehdr , phnum ) ;
phdr = data + elf_hdr_get_e_phoff ( class , ehdr ) ;
offset = elf_hdr_get_e_phoff ( class , ehdr ) ;
offset + = elf_size_of_phdr ( class ) * elf_hdr_get_e_phnum ( class , ehdr ) ;
list_for_each_entry ( segment , & rproc - > dump_segments , node ) {
memset ( phdr , 0 , elf_size_of_phdr ( class ) ) ;
elf_phdr_set_p_type ( class , phdr , PT_LOAD ) ;
elf_phdr_set_p_offset ( class , phdr , offset ) ;
elf_phdr_set_p_vaddr ( class , phdr , segment - > da ) ;
elf_phdr_set_p_paddr ( class , phdr , segment - > da ) ;
elf_phdr_set_p_filesz ( class , phdr , segment - > size ) ;
elf_phdr_set_p_memsz ( class , phdr , segment - > size ) ;
elf_phdr_set_p_flags ( class , phdr , PF_R | PF_W | PF_X ) ;
elf_phdr_set_p_align ( class , phdr , 0 ) ;
2020-10-02 21:09:02 +03:00
if ( dump_conf = = RPROC_COREDUMP_ENABLED )
2020-07-17 01:20:34 +03:00
rproc_copy_segment ( rproc , data + offset , segment , 0 ,
segment - > size ) ;
2020-07-17 01:20:31 +03:00
offset + = elf_phdr_get_p_filesz ( class , phdr ) ;
phdr + = elf_size_of_phdr ( class ) ;
}
2020-10-02 21:09:02 +03:00
if ( dump_conf = = RPROC_COREDUMP_ENABLED ) {
2020-07-17 01:20:34 +03:00
dev_coredumpv ( & rproc - > dev , data , data_size , GFP_KERNEL ) ;
return ;
}
/* Initialize the dump state struct to be used by rproc_coredump_read */
dump_state . rproc = rproc ;
dump_state . header = data ;
init_completion ( & dump_state . dump_done ) ;
dev_coredumpm ( & rproc - > dev , NULL , & dump_state , data_size , GFP_KERNEL ,
rproc_coredump_read , rproc_coredump_free ) ;
2020-07-17 01:20:31 +03:00
2020-07-17 01:20:34 +03:00
/*
* Wait until the dump is read and free is called . Data is freed
* by devcoredump framework automatically after 5 minutes .
*/
wait_for_completion ( & dump_state . dump_done ) ;
2020-07-17 01:20:31 +03:00
}