2010-05-26 14:43:54 -07:00
/*
* RAM Oops / Panic logger
*
* Copyright ( C ) 2010 Marco Stornelli < marco . stornelli @ gmail . com >
2012-05-03 15:45:02 +10:00
* Copyright ( C ) 2011 Kees Cook < keescook @ chromium . org >
2010-05-26 14:43:54 -07:00
*
* 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 .
*
* 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 . , 51 Franklin St , Fifth Floor , Boston , MA
* 02110 - 1301 USA
*
*/
2011-07-26 16:08:57 -07:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-05-26 14:43:54 -07:00
# include <linux/kernel.h>
2011-07-29 17:11:32 +04:00
# include <linux/err.h>
2010-05-26 14:43:54 -07:00
# include <linux/module.h>
2012-05-03 15:45:02 +10:00
# include <linux/pstore.h>
2010-05-26 14:43:54 -07:00
# include <linux/time.h>
# include <linux/io.h>
# include <linux/ioport.h>
2010-10-27 15:34:52 -07:00
# include <linux/platform_device.h>
2011-07-26 16:08:57 -07:00
# include <linux/slab.h>
2012-05-16 05:43:08 -07:00
# include <linux/pstore_ram.h>
2010-05-26 14:43:54 -07:00
# define RAMOOPS_KERNMSG_HDR "===="
2011-07-26 16:08:59 -07:00
# define MIN_MEM_SIZE 4096UL
2010-05-26 14:43:54 -07:00
2011-07-26 16:08:59 -07:00
static ulong record_size = MIN_MEM_SIZE ;
module_param ( record_size , ulong , 0400 ) ;
MODULE_PARM_DESC ( record_size ,
" size of each dump done on oops/panic " ) ;
2010-05-26 14:43:54 -07:00
2012-05-26 06:20:23 -07:00
static ulong ramoops_console_size = MIN_MEM_SIZE ;
module_param_named ( console_size , ramoops_console_size , ulong , 0400 ) ;
MODULE_PARM_DESC ( console_size , " size of kernel console log " ) ;
2010-05-26 14:43:54 -07:00
static ulong mem_address ;
module_param ( mem_address , ulong , 0400 ) ;
MODULE_PARM_DESC ( mem_address ,
" start of reserved RAM used to store oops/panic logs " ) ;
static ulong mem_size ;
module_param ( mem_size , ulong , 0400 ) ;
MODULE_PARM_DESC ( mem_size ,
" size of reserved RAM used to store oops/panic logs " ) ;
static int dump_oops = 1 ;
module_param ( dump_oops , int , 0600 ) ;
MODULE_PARM_DESC ( dump_oops ,
" set to 1 to dump oopses, 0 to only dump panics (default 1) " ) ;
2012-05-17 00:15:34 -07:00
static int ramoops_ecc ;
module_param_named ( ecc , ramoops_ecc , int , 0600 ) ;
MODULE_PARM_DESC ( ramoops_ecc ,
" set to 1 to enable ECC support " ) ;
2012-05-03 15:45:02 +10:00
struct ramoops_context {
2012-05-17 00:15:18 -07:00
struct persistent_ram_zone * * przs ;
2012-05-26 06:20:23 -07:00
struct persistent_ram_zone * cprz ;
2010-05-26 14:43:54 -07:00
phys_addr_t phys_addr ;
unsigned long size ;
2012-05-03 15:45:02 +10:00
size_t record_size ;
2012-05-26 06:20:23 -07:00
size_t console_size ;
2011-07-26 16:08:58 -07:00
int dump_oops ;
2012-05-17 00:15:34 -07:00
bool ecc ;
2012-05-26 06:20:20 -07:00
unsigned int max_dump_cnt ;
unsigned int dump_write_cnt ;
unsigned int dump_read_cnt ;
2012-05-26 06:20:23 -07:00
unsigned int console_read_cnt ;
2012-05-03 15:45:02 +10:00
struct pstore_info pstore ;
} ;
2010-05-26 14:43:54 -07:00
2011-07-26 16:08:57 -07:00
static struct platform_device * dummy ;
static struct ramoops_platform_data * dummy_data ;
2012-05-03 15:45:02 +10:00
static int ramoops_pstore_open ( struct pstore_info * psi )
{
struct ramoops_context * cxt = psi - > data ;
2012-05-26 06:20:20 -07:00
cxt - > dump_read_cnt = 0 ;
2012-05-26 06:20:23 -07:00
cxt - > console_read_cnt = 0 ;
2012-05-03 15:45:02 +10:00
return 0 ;
}
2012-05-26 06:20:22 -07:00
static struct persistent_ram_zone *
ramoops_get_next_prz ( struct persistent_ram_zone * przs [ ] , uint * c , uint max ,
u64 * id ,
enum pstore_type_id * typep , enum pstore_type_id type ,
bool update )
{
struct persistent_ram_zone * prz ;
int i = ( * c ) + + ;
if ( i > = max )
return NULL ;
prz = przs [ i ] ;
if ( update ) {
/* Update old/shadowed buffer. */
persistent_ram_save_old ( prz ) ;
if ( ! persistent_ram_old_size ( prz ) )
return NULL ;
}
* typep = type ;
* id = i ;
return prz ;
}
2012-05-03 15:45:02 +10:00
static ssize_t ramoops_pstore_read ( u64 * id , enum pstore_type_id * type ,
struct timespec * time ,
char * * buf ,
struct pstore_info * psi )
2010-05-26 14:43:54 -07:00
{
2012-05-03 15:45:02 +10:00
ssize_t size ;
struct ramoops_context * cxt = psi - > data ;
2012-05-17 00:15:18 -07:00
struct persistent_ram_zone * prz ;
2012-05-03 15:45:02 +10:00
2012-05-26 06:20:22 -07:00
prz = ramoops_get_next_prz ( cxt - > przs , & cxt - > dump_read_cnt ,
cxt - > max_dump_cnt , id , type ,
PSTORE_TYPE_DMESG , 1 ) ;
2012-05-26 06:20:23 -07:00
if ( ! prz )
prz = ramoops_get_next_prz ( & cxt - > cprz , & cxt - > console_read_cnt ,
1 , id , type , PSTORE_TYPE_CONSOLE , 0 ) ;
2012-05-26 06:20:22 -07:00
if ( ! prz )
return 0 ;
2012-05-17 00:15:18 -07:00
2012-05-03 15:45:02 +10:00
/* TODO(kees): Bogus time for the moment. */
time - > tv_sec = 0 ;
time - > tv_nsec = 0 ;
2012-05-17 00:15:18 -07:00
size = persistent_ram_old_size ( prz ) ;
2012-05-03 15:45:02 +10:00
* buf = kmalloc ( size , GFP_KERNEL ) ;
if ( * buf = = NULL )
return - ENOMEM ;
2012-05-17 00:15:18 -07:00
memcpy ( * buf , persistent_ram_old ( prz ) , size ) ;
2012-05-03 15:45:02 +10:00
return size ;
}
2012-05-17 00:15:18 -07:00
static size_t ramoops_write_kmsg_hdr ( struct persistent_ram_zone * prz )
{
char * hdr ;
struct timeval timestamp ;
size_t len ;
do_gettimeofday ( & timestamp ) ;
hdr = kasprintf ( GFP_ATOMIC , RAMOOPS_KERNMSG_HDR " %lu.%lu \n " ,
( long ) timestamp . tv_sec , ( long ) timestamp . tv_usec ) ;
WARN_ON_ONCE ( ! hdr ) ;
len = hdr ? strlen ( hdr ) : 0 ;
persistent_ram_write ( prz , hdr , len ) ;
kfree ( hdr ) ;
return len ;
}
2012-05-03 15:45:02 +10:00
static int ramoops_pstore_write ( enum pstore_type_id type ,
enum kmsg_dump_reason reason ,
u64 * id ,
unsigned int part ,
size_t size , struct pstore_info * psi )
{
struct ramoops_context * cxt = psi - > data ;
2012-05-26 06:20:20 -07:00
struct persistent_ram_zone * prz = cxt - > przs [ cxt - > dump_write_cnt ] ;
2012-05-17 00:15:18 -07:00
size_t hlen ;
2012-05-03 15:45:02 +10:00
2012-05-26 06:20:23 -07:00
if ( type = = PSTORE_TYPE_CONSOLE ) {
if ( ! cxt - > cprz )
return - ENOMEM ;
persistent_ram_write ( cxt - > cprz , cxt - > pstore . buf , size ) ;
return 0 ;
}
2012-05-03 15:45:02 +10:00
if ( type ! = PSTORE_TYPE_DMESG )
return - EINVAL ;
2010-05-26 14:43:54 -07:00
2012-05-03 15:45:02 +10:00
/* Out of the various dmesg dump types, ramoops is currently designed
* to only store crash logs , rather than storing general kernel logs .
*/
2011-01-12 16:59:29 -08:00
if ( reason ! = KMSG_DUMP_OOPS & &
2012-01-12 17:20:11 -08:00
reason ! = KMSG_DUMP_PANIC )
2012-05-03 15:45:02 +10:00
return - EINVAL ;
2011-01-12 16:59:29 -08:00
2012-05-03 15:45:02 +10:00
/* Skip Oopes when configured to do so. */
2011-07-26 16:08:58 -07:00
if ( reason = = KMSG_DUMP_OOPS & & ! cxt - > dump_oops )
2012-05-03 15:45:02 +10:00
return - EINVAL ;
/* Explicitly only take the first part of any new crash.
* If our buffer is larger than kmsg_bytes , this can never happen ,
* and if our buffer is smaller than kmsg_bytes , we don ' t want the
* report split across multiple records .
*/
if ( part ! = 1 )
return - ENOSPC ;
2010-05-26 14:43:54 -07:00
2012-05-17 00:15:18 -07:00
hlen = ramoops_write_kmsg_hdr ( prz ) ;
if ( size + hlen > prz - > buffer_size )
size = prz - > buffer_size - hlen ;
persistent_ram_write ( prz , cxt - > pstore . buf , size ) ;
2010-05-26 14:43:54 -07:00
2012-05-26 06:20:20 -07:00
cxt - > dump_write_cnt = ( cxt - > dump_write_cnt + 1 ) % cxt - > max_dump_cnt ;
2012-05-03 15:45:02 +10:00
return 0 ;
}
static int ramoops_pstore_erase ( enum pstore_type_id type , u64 id ,
struct pstore_info * psi )
{
struct ramoops_context * cxt = psi - > data ;
2012-05-26 06:20:23 -07:00
struct persistent_ram_zone * prz ;
2012-05-03 15:45:02 +10:00
2012-05-26 06:20:23 -07:00
switch ( type ) {
case PSTORE_TYPE_DMESG :
if ( id > = cxt - > max_dump_cnt )
return - EINVAL ;
prz = cxt - > przs [ id ] ;
break ;
case PSTORE_TYPE_CONSOLE :
prz = cxt - > cprz ;
break ;
default :
2012-05-03 15:45:02 +10:00
return - EINVAL ;
2012-05-26 06:20:23 -07:00
}
2012-05-03 15:45:02 +10:00
2012-05-26 06:20:23 -07:00
persistent_ram_free_old ( prz ) ;
persistent_ram_zap ( prz ) ;
2012-05-03 15:45:02 +10:00
return 0 ;
2010-05-26 14:43:54 -07:00
}
2012-05-03 15:45:02 +10:00
static struct ramoops_context oops_cxt = {
. pstore = {
. owner = THIS_MODULE ,
. name = " ramoops " ,
. open = ramoops_pstore_open ,
. read = ramoops_pstore_read ,
. write = ramoops_pstore_write ,
. erase = ramoops_pstore_erase ,
} ,
} ;
2012-05-26 06:20:21 -07:00
static void ramoops_free_przs ( struct ramoops_context * cxt )
{
int i ;
if ( ! cxt - > przs )
return ;
2012-06-18 19:15:51 -07:00
for ( i = 0 ; ! IS_ERR_OR_NULL ( cxt - > przs [ i ] ) ; i + + )
2012-05-26 06:20:21 -07:00
persistent_ram_free ( cxt - > przs [ i ] ) ;
kfree ( cxt - > przs ) ;
}
static int ramoops_init_przs ( struct device * dev , struct ramoops_context * cxt ,
phys_addr_t * paddr , size_t dump_mem_sz )
{
int err = - ENOMEM ;
int i ;
if ( ! cxt - > record_size )
return 0 ;
cxt - > max_dump_cnt = dump_mem_sz / cxt - > record_size ;
if ( ! cxt - > max_dump_cnt )
return - ENOMEM ;
cxt - > przs = kzalloc ( sizeof ( * cxt - > przs ) * cxt - > max_dump_cnt ,
GFP_KERNEL ) ;
if ( ! cxt - > przs ) {
dev_err ( dev , " failed to initialize a prz array for dumps \n " ) ;
return - ENOMEM ;
}
for ( i = 0 ; i < cxt - > max_dump_cnt ; i + + ) {
size_t sz = cxt - > record_size ;
cxt - > przs [ i ] = persistent_ram_new ( * paddr , sz , cxt - > ecc ) ;
if ( IS_ERR ( cxt - > przs [ i ] ) ) {
err = PTR_ERR ( cxt - > przs [ i ] ) ;
dev_err ( dev , " failed to request mem region (0x%zx@0x%llx): %d \n " ,
sz , ( unsigned long long ) * paddr , err ) ;
goto fail_prz ;
}
* paddr + = sz ;
}
return 0 ;
fail_prz :
ramoops_free_przs ( cxt ) ;
return err ;
}
2012-05-26 06:20:23 -07:00
static int ramoops_init_prz ( struct device * dev , struct ramoops_context * cxt ,
struct persistent_ram_zone * * prz ,
phys_addr_t * paddr , size_t sz )
{
if ( ! sz )
return 0 ;
if ( * paddr + sz > * paddr + cxt - > size )
return - ENOMEM ;
* prz = persistent_ram_new ( * paddr , sz , cxt - > ecc ) ;
if ( IS_ERR ( * prz ) ) {
int err = PTR_ERR ( * prz ) ;
dev_err ( dev , " failed to request mem region (0x%zx@0x%llx): %d \n " ,
sz , ( unsigned long long ) * paddr , err ) ;
return err ;
}
persistent_ram_zap ( * prz ) ;
* paddr + = sz ;
return 0 ;
}
2012-06-18 19:15:50 -07:00
static int __devinit ramoops_probe ( struct platform_device * pdev )
2010-05-26 14:43:54 -07:00
{
2012-05-17 00:15:18 -07:00
struct device * dev = & pdev - > dev ;
2010-10-27 15:34:52 -07:00
struct ramoops_platform_data * pdata = pdev - > dev . platform_data ;
2010-05-26 14:43:54 -07:00
struct ramoops_context * cxt = & oops_cxt ;
2012-05-26 06:20:21 -07:00
size_t dump_mem_sz ;
phys_addr_t paddr ;
2010-05-26 14:43:54 -07:00
int err = - EINVAL ;
2012-05-03 15:45:02 +10:00
/* Only a single ramoops area allowed at a time, so fail extra
* probes .
*/
2012-05-26 06:20:20 -07:00
if ( cxt - > max_dump_cnt )
2012-05-03 15:45:02 +10:00
goto fail_out ;
2012-05-26 06:20:23 -07:00
if ( ! pdata - > mem_size | | ( ! pdata - > record_size & & ! pdata - > console_size ) ) {
pr_err ( " The memory size and the record/console size must be "
2011-07-26 16:08:59 -07:00
" non-zero \n " ) ;
2012-05-03 15:45:02 +10:00
goto fail_out ;
2010-05-26 14:43:54 -07:00
}
2012-01-12 17:20:58 -08:00
pdata - > mem_size = rounddown_pow_of_two ( pdata - > mem_size ) ;
pdata - > record_size = rounddown_pow_of_two ( pdata - > record_size ) ;
2012-05-26 06:20:23 -07:00
pdata - > console_size = rounddown_pow_of_two ( pdata - > console_size ) ;
2010-05-26 14:43:54 -07:00
2012-05-26 06:20:20 -07:00
cxt - > dump_read_cnt = 0 ;
2011-07-26 16:08:57 -07:00
cxt - > size = pdata - > mem_size ;
cxt - > phys_addr = pdata - > mem_address ;
2011-07-26 16:08:59 -07:00
cxt - > record_size = pdata - > record_size ;
2012-05-26 06:20:23 -07:00
cxt - > console_size = pdata - > console_size ;
2011-07-26 16:08:58 -07:00
cxt - > dump_oops = pdata - > dump_oops ;
2012-05-17 00:15:34 -07:00
cxt - > ecc = pdata - > ecc ;
2010-05-26 14:43:54 -07:00
2012-05-26 06:20:21 -07:00
paddr = cxt - > phys_addr ;
2012-05-17 00:15:18 -07:00
2012-05-26 06:20:23 -07:00
dump_mem_sz = cxt - > size - cxt - > console_size ;
2012-05-26 06:20:21 -07:00
err = ramoops_init_przs ( dev , cxt , & paddr , dump_mem_sz ) ;
2012-05-26 06:20:23 -07:00
if ( err )
goto fail_out ;
err = ramoops_init_prz ( dev , cxt , & cxt - > cprz , & paddr , cxt - > console_size ) ;
if ( err )
goto fail_init_cprz ;
if ( ! cxt - > przs & & ! cxt - > cprz ) {
2012-05-26 06:20:21 -07:00
pr_err ( " memory size too small, minimum is %lu \n " ,
2012-05-26 06:20:23 -07:00
cxt - > console_size + cxt - > record_size ) ;
goto fail_cnt ;
2012-05-17 00:15:18 -07:00
}
2012-05-03 15:45:02 +10:00
cxt - > pstore . data = cxt ;
2012-05-26 06:20:23 -07:00
/*
* Console can handle any buffer size , so prefer dumps buffer
* size since usually it is smaller .
*/
if ( cxt - > przs )
cxt - > pstore . bufsize = cxt - > przs [ 0 ] - > buffer_size ;
else
cxt - > pstore . bufsize = cxt - > cprz - > buffer_size ;
2012-05-03 15:45:02 +10:00
cxt - > pstore . buf = kmalloc ( cxt - > pstore . bufsize , GFP_KERNEL ) ;
spin_lock_init ( & cxt - > pstore . buf_lock ) ;
if ( ! cxt - > pstore . buf ) {
pr_err ( " cannot allocate pstore buffer \n " ) ;
goto fail_clear ;
}
err = pstore_register ( & cxt - > pstore ) ;
2010-05-26 14:43:54 -07:00
if ( err ) {
2012-05-03 15:45:02 +10:00
pr_err ( " registering with pstore failed \n " ) ;
2012-05-17 00:15:18 -07:00
goto fail_buf ;
2010-05-26 14:43:54 -07:00
}
2012-01-12 17:20:59 -08:00
/*
* Update the module parameter variables as well so they are visible
* through / sys / module / ramoops / parameters /
*/
mem_size = pdata - > mem_size ;
mem_address = pdata - > mem_address ;
record_size = pdata - > record_size ;
dump_oops = pdata - > dump_oops ;
2012-05-26 06:20:23 -07:00
pr_info ( " attached 0x%lx@0x%llx, ecc: %s \n " ,
2012-05-03 15:45:03 +10:00
cxt - > size , ( unsigned long long ) cxt - > phys_addr ,
2012-05-17 00:15:34 -07:00
ramoops_ecc ? " on " : " off " ) ;
2012-05-03 15:45:02 +10:00
2010-05-26 14:43:54 -07:00
return 0 ;
2012-05-03 15:45:02 +10:00
fail_buf :
kfree ( cxt - > pstore . buf ) ;
fail_clear :
cxt - > pstore . bufsize = 0 ;
2012-05-26 06:20:20 -07:00
cxt - > max_dump_cnt = 0 ;
2012-05-26 06:20:23 -07:00
fail_cnt :
kfree ( cxt - > cprz ) ;
fail_init_cprz :
2012-05-26 06:20:21 -07:00
ramoops_free_przs ( cxt ) ;
2012-05-03 15:45:02 +10:00
fail_out :
2010-05-26 14:43:54 -07:00
return err ;
}
2010-10-27 15:34:52 -07:00
static int __exit ramoops_remove ( struct platform_device * pdev )
2010-05-26 14:43:54 -07:00
{
2012-05-03 15:45:02 +10:00
#if 0
/* TODO(kees): We cannot unload ramoops since pstore doesn't support
* unregistering yet .
*/
2010-05-26 14:43:54 -07:00
struct ramoops_context * cxt = & oops_cxt ;
iounmap ( cxt - > virt_addr ) ;
release_mem_region ( cxt - > phys_addr , cxt - > size ) ;
2012-05-26 06:20:20 -07:00
cxt - > max_dump_cnt = 0 ;
2012-05-03 15:45:02 +10:00
/* TODO(kees): When pstore supports unregistering, call it here. */
kfree ( cxt - > pstore . buf ) ;
cxt - > pstore . bufsize = 0 ;
2010-10-27 15:34:52 -07:00
return 0 ;
2012-05-03 15:45:02 +10:00
# endif
return - EBUSY ;
2010-05-26 14:43:54 -07:00
}
2010-10-27 15:34:52 -07:00
static struct platform_driver ramoops_driver = {
2012-06-18 19:15:50 -07:00
. probe = ramoops_probe ,
2010-10-27 15:34:52 -07:00
. remove = __exit_p ( ramoops_remove ) ,
. driver = {
. name = " ramoops " ,
. owner = THIS_MODULE ,
} ,
} ;
2012-06-18 19:15:50 -07:00
static void ramoops_register_dummy ( void )
2010-10-27 15:34:52 -07:00
{
2012-06-18 19:15:50 -07:00
if ( ! mem_size )
return ;
pr_info ( " using module parameters \n " ) ;
dummy_data = kzalloc ( sizeof ( * dummy_data ) , GFP_KERNEL ) ;
if ( ! dummy_data ) {
pr_info ( " could not allocate pdata \n " ) ;
return ;
2011-07-26 16:08:57 -07:00
}
2012-06-18 19:15:50 -07:00
dummy_data - > mem_size = mem_size ;
dummy_data - > mem_address = mem_address ;
dummy_data - > record_size = record_size ;
dummy_data - > console_size = ramoops_console_size ;
dummy_data - > dump_oops = dump_oops ;
dummy_data - > ecc = ramoops_ecc ;
dummy = platform_device_register_data ( NULL , " ramoops " , - 1 ,
dummy_data , sizeof ( struct ramoops_platform_data ) ) ;
if ( IS_ERR ( dummy ) ) {
pr_info ( " could not create platform device: %ld \n " ,
PTR_ERR ( dummy ) ) ;
}
}
static int __init ramoops_init ( void )
{
ramoops_register_dummy ( ) ;
return platform_driver_register ( & ramoops_driver ) ;
2010-10-27 15:34:52 -07:00
}
2012-06-18 19:15:50 -07:00
postcore_initcall ( ramoops_init ) ;
2010-10-27 15:34:52 -07:00
static void __exit ramoops_exit ( void )
{
platform_driver_unregister ( & ramoops_driver ) ;
2011-07-26 16:08:57 -07:00
kfree ( dummy_data ) ;
2010-10-27 15:34:52 -07:00
}
2010-05-26 14:43:54 -07:00
module_exit ( ramoops_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Marco Stornelli <marco.stornelli@gmail.com> " ) ;
MODULE_DESCRIPTION ( " RAM Oops/Panic logger/driver " ) ;