2010-05-27 01:43:54 +04:00
/*
* RAM Oops / Panic logger
*
* Copyright ( C ) 2010 Marco Stornelli < marco . stornelli @ gmail . 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 .
*
* 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
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/kmsg_dump.h>
# include <linux/time.h>
# include <linux/io.h>
# include <linux/ioport.h>
2010-10-28 02:34:52 +04:00
# include <linux/platform_device.h>
2011-07-27 03:08:57 +04:00
# include <linux/slab.h>
2010-10-28 02:34:52 +04:00
# include <linux/ramoops.h>
2010-05-27 01:43:54 +04:00
# define RAMOOPS_KERNMSG_HDR "===="
2011-01-13 04:01:11 +03:00
# define RECORD_SIZE 4096UL
2010-05-27 01:43:54 +04: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) " ) ;
static struct ramoops_context {
struct kmsg_dumper dump ;
void * virt_addr ;
phys_addr_t phys_addr ;
unsigned long size ;
int count ;
int max_count ;
} oops_cxt ;
2011-07-27 03:08:57 +04:00
static struct platform_device * dummy ;
static struct ramoops_platform_data * dummy_data ;
2010-05-27 01:43:54 +04:00
static void ramoops_do_dump ( struct kmsg_dumper * dumper ,
enum kmsg_dump_reason reason , const char * s1 , unsigned long l1 ,
const char * s2 , unsigned long l2 )
{
struct ramoops_context * cxt = container_of ( dumper ,
struct ramoops_context , dump ) ;
unsigned long s1_start , s2_start ;
unsigned long l1_cpy , l2_cpy ;
2010-12-25 12:57:09 +03:00
int res , hdr_size ;
char * buf , * buf_orig ;
2010-05-27 01:43:54 +04:00
struct timeval timestamp ;
2011-01-13 03:59:29 +03:00
if ( reason ! = KMSG_DUMP_OOPS & &
reason ! = KMSG_DUMP_PANIC & &
reason ! = KMSG_DUMP_KEXEC )
return ;
2010-05-27 01:43:54 +04:00
/* Only dump oopses if dump_oops is set */
if ( reason = = KMSG_DUMP_OOPS & & ! dump_oops )
return ;
2011-01-13 04:01:11 +03:00
buf = cxt - > virt_addr + ( cxt - > count * RECORD_SIZE ) ;
2010-12-25 12:57:09 +03:00
buf_orig = buf ;
2010-05-27 01:43:54 +04:00
memset ( buf , ' \0 ' , RECORD_SIZE ) ;
res = sprintf ( buf , " %s " , RAMOOPS_KERNMSG_HDR ) ;
buf + = res ;
do_gettimeofday ( & timestamp ) ;
res = sprintf ( buf , " %lu.%lu \n " , ( long ) timestamp . tv_sec , ( long ) timestamp . tv_usec ) ;
buf + = res ;
2010-12-25 12:57:09 +03:00
hdr_size = buf - buf_orig ;
2011-01-13 04:01:11 +03:00
l2_cpy = min ( l2 , RECORD_SIZE - hdr_size ) ;
l1_cpy = min ( l1 , RECORD_SIZE - hdr_size - l2_cpy ) ;
2010-05-27 01:43:54 +04:00
s2_start = l2 - l2_cpy ;
s1_start = l1 - l1_cpy ;
memcpy ( buf , s1 + s1_start , l1_cpy ) ;
memcpy ( buf + l1_cpy , s2 + s2_start , l2_cpy ) ;
cxt - > count = ( cxt - > count + 1 ) % cxt - > max_count ;
}
2010-10-28 02:34:52 +04:00
static int __init ramoops_probe ( struct platform_device * pdev )
2010-05-27 01:43:54 +04:00
{
2010-10-28 02:34:52 +04:00
struct ramoops_platform_data * pdata = pdev - > dev . platform_data ;
2010-05-27 01:43:54 +04:00
struct ramoops_context * cxt = & oops_cxt ;
int err = - EINVAL ;
2011-07-27 03:08:57 +04:00
if ( ! pdata - > mem_size ) {
2010-05-27 01:43:54 +04:00
printk ( KERN_ERR " ramoops: invalid size specification " ) ;
goto fail3 ;
}
2011-07-27 03:08:57 +04:00
rounddown_pow_of_two ( pdata - > mem_size ) ;
2010-05-27 01:43:54 +04:00
2011-07-27 03:08:57 +04:00
if ( pdata - > mem_size < RECORD_SIZE ) {
2010-05-27 01:43:54 +04:00
printk ( KERN_ERR " ramoops: size too small " ) ;
goto fail3 ;
}
2011-07-27 03:08:57 +04:00
cxt - > max_count = pdata - > mem_size / RECORD_SIZE ;
2010-05-27 01:43:54 +04:00
cxt - > count = 0 ;
2011-07-27 03:08:57 +04:00
cxt - > size = pdata - > mem_size ;
cxt - > phys_addr = pdata - > mem_address ;
2010-05-27 01:43:54 +04:00
if ( ! request_mem_region ( cxt - > phys_addr , cxt - > size , " ramoops " ) ) {
printk ( KERN_ERR " ramoops: request mem region failed " ) ;
err = - EINVAL ;
goto fail3 ;
}
cxt - > virt_addr = ioremap ( cxt - > phys_addr , cxt - > size ) ;
if ( ! cxt - > virt_addr ) {
printk ( KERN_ERR " ramoops: ioremap failed " ) ;
goto fail2 ;
}
cxt - > dump . dump = ramoops_do_dump ;
err = kmsg_dump_register ( & cxt - > dump ) ;
if ( err ) {
printk ( KERN_ERR " ramoops: registering kmsg dumper failed " ) ;
goto fail1 ;
}
return 0 ;
fail1 :
iounmap ( cxt - > virt_addr ) ;
fail2 :
release_mem_region ( cxt - > phys_addr , cxt - > size ) ;
fail3 :
return err ;
}
2010-10-28 02:34:52 +04:00
static int __exit ramoops_remove ( struct platform_device * pdev )
2010-05-27 01:43:54 +04:00
{
struct ramoops_context * cxt = & oops_cxt ;
if ( kmsg_dump_unregister ( & cxt - > dump ) < 0 )
printk ( KERN_WARNING " ramoops: could not unregister kmsg_dumper " ) ;
iounmap ( cxt - > virt_addr ) ;
release_mem_region ( cxt - > phys_addr , cxt - > size ) ;
2010-10-28 02:34:52 +04:00
return 0 ;
2010-05-27 01:43:54 +04:00
}
2010-10-28 02:34:52 +04:00
static struct platform_driver ramoops_driver = {
. remove = __exit_p ( ramoops_remove ) ,
. driver = {
. name = " ramoops " ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init ramoops_init ( void )
{
2011-07-27 03:08:57 +04:00
int ret ;
ret = platform_driver_probe ( & ramoops_driver , ramoops_probe ) ;
if ( ret = = - ENODEV ) {
/*
* If we didn ' t find a platform device , we use module parameters
* building platform data on the fly .
*/
dummy_data = kzalloc ( sizeof ( struct ramoops_platform_data ) ,
GFP_KERNEL ) ;
if ( ! dummy_data )
return - ENOMEM ;
dummy_data - > mem_size = mem_size ;
dummy_data - > mem_address = mem_address ;
dummy = platform_create_bundle ( & ramoops_driver , ramoops_probe ,
NULL , 0 , dummy_data ,
sizeof ( struct ramoops_platform_data ) ) ;
if ( IS_ERR ( dummy ) )
ret = PTR_ERR ( dummy ) ;
else
ret = 0 ;
}
return ret ;
2010-10-28 02:34:52 +04:00
}
static void __exit ramoops_exit ( void )
{
platform_driver_unregister ( & ramoops_driver ) ;
2011-07-27 03:08:57 +04:00
kfree ( dummy_data ) ;
2010-10-28 02:34:52 +04:00
}
2010-05-27 01:43:54 +04:00
module_init ( ramoops_init ) ;
module_exit ( ramoops_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Marco Stornelli <marco.stornelli@gmail.com> " ) ;
MODULE_DESCRIPTION ( " RAM Oops/Panic logger/driver " ) ;