2010-05-27 01:43:54 +04:00
/*
* RAM Oops / Panic logger
*
* Copyright ( C ) 2010 Marco Stornelli < marco . stornelli @ gmail . com >
2012-05-03 09:45:02 +04:00
* Copyright ( C ) 2011 Kees Cook < keescook @ chromium . org >
2010-05-27 01:43:54 +04: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-27 03:08:57 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-05-27 01:43:54 +04:00
# include <linux/kernel.h>
2011-07-29 17:11:32 +04:00
# include <linux/err.h>
2010-05-27 01:43:54 +04:00
# include <linux/module.h>
2012-05-03 09:45:02 +04:00
# include <linux/pstore.h>
2010-05-27 01:43:54 +04:00
# 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>
2012-05-16 16:43:08 +04:00
# include <linux/pstore_ram.h>
2010-05-27 01:43:54 +04:00
# define RAMOOPS_KERNMSG_HDR "===="
2011-07-27 03:08:59 +04:00
# define MIN_MEM_SIZE 4096UL
2010-05-27 01:43:54 +04:00
2011-07-27 03:08:59 +04: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-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) " ) ;
2012-05-17 11:15:34 +04: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 09:45:02 +04:00
struct ramoops_context {
2012-05-17 11:15:18 +04:00
struct persistent_ram_zone * * przs ;
2010-05-27 01:43:54 +04:00
phys_addr_t phys_addr ;
unsigned long size ;
2012-05-03 09:45:02 +04:00
size_t record_size ;
2011-07-27 03:08:58 +04:00
int dump_oops ;
2012-05-17 11:15:34 +04:00
bool ecc ;
2012-05-03 09:45:02 +04:00
unsigned int count ;
unsigned int max_count ;
unsigned int read_count ;
struct pstore_info pstore ;
} ;
2010-05-27 01:43:54 +04:00
2011-07-27 03:08:57 +04:00
static struct platform_device * dummy ;
static struct ramoops_platform_data * dummy_data ;
2012-05-03 09:45:02 +04:00
static int ramoops_pstore_open ( struct pstore_info * psi )
{
struct ramoops_context * cxt = psi - > data ;
cxt - > read_count = 0 ;
return 0 ;
}
static ssize_t ramoops_pstore_read ( u64 * id , enum pstore_type_id * type ,
struct timespec * time ,
char * * buf ,
struct pstore_info * psi )
2010-05-27 01:43:54 +04:00
{
2012-05-03 09:45:02 +04:00
ssize_t size ;
struct ramoops_context * cxt = psi - > data ;
2012-05-17 11:15:18 +04:00
struct persistent_ram_zone * prz ;
2012-05-03 09:45:02 +04:00
if ( cxt - > read_count > = cxt - > max_count )
return - EINVAL ;
2012-05-17 11:15:18 +04:00
2012-05-03 09:45:02 +04:00
* id = cxt - > read_count + + ;
2012-05-17 11:15:18 +04:00
prz = cxt - > przs [ * id ] ;
2012-05-03 09:45:02 +04:00
/* Only supports dmesg output so far. */
* type = PSTORE_TYPE_DMESG ;
/* TODO(kees): Bogus time for the moment. */
time - > tv_sec = 0 ;
time - > tv_nsec = 0 ;
2012-05-26 17:07:49 +04:00
/* Update old/shadowed buffer. */
persistent_ram_save_old ( prz ) ;
2012-05-17 11:15:18 +04:00
size = persistent_ram_old_size ( prz ) ;
2012-05-03 09:45:02 +04:00
* buf = kmalloc ( size , GFP_KERNEL ) ;
if ( * buf = = NULL )
return - ENOMEM ;
2012-05-17 11:15:18 +04:00
memcpy ( * buf , persistent_ram_old ( prz ) , size ) ;
2012-05-03 09:45:02 +04:00
return size ;
}
2012-05-17 11:15:18 +04: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 09:45:02 +04: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-17 11:15:18 +04:00
struct persistent_ram_zone * prz = cxt - > przs [ cxt - > count ] ;
size_t hlen ;
2012-05-03 09:45:02 +04:00
/* Currently ramoops is designed to only store dmesg dumps. */
if ( type ! = PSTORE_TYPE_DMESG )
return - EINVAL ;
2010-05-27 01:43:54 +04:00
2012-05-03 09:45:02 +04: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-13 03:59:29 +03:00
if ( reason ! = KMSG_DUMP_OOPS & &
2012-01-13 05:20:11 +04:00
reason ! = KMSG_DUMP_PANIC )
2012-05-03 09:45:02 +04:00
return - EINVAL ;
2011-01-13 03:59:29 +03:00
2012-05-03 09:45:02 +04:00
/* Skip Oopes when configured to do so. */
2011-07-27 03:08:58 +04:00
if ( reason = = KMSG_DUMP_OOPS & & ! cxt - > dump_oops )
2012-05-03 09:45:02 +04: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-27 01:43:54 +04:00
2012-05-17 11:15:18 +04: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-27 01:43:54 +04:00
cxt - > count = ( cxt - > count + 1 ) % cxt - > max_count ;
2012-05-03 09:45:02 +04: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 ;
if ( id > = cxt - > max_count )
return - EINVAL ;
2012-05-17 11:15:18 +04:00
persistent_ram_free_old ( cxt - > przs [ id ] ) ;
2012-05-26 17:07:52 +04:00
persistent_ram_zap ( cxt - > przs [ id ] ) ;
2012-05-03 09:45:02 +04:00
return 0 ;
2010-05-27 01:43:54 +04:00
}
2012-05-03 09:45:02 +04: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 ,
} ,
} ;
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
{
2012-05-17 11:15:18 +04:00
struct device * dev = & pdev - > dev ;
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 ;
2012-05-17 11:15:18 +04:00
int i ;
2010-05-27 01:43:54 +04:00
2012-05-03 09:45:02 +04:00
/* Only a single ramoops area allowed at a time, so fail extra
* probes .
*/
if ( cxt - > max_count )
goto fail_out ;
2011-07-27 03:08:59 +04:00
if ( ! pdata - > mem_size | | ! pdata - > record_size ) {
pr_err ( " The memory size and the record size must be "
" non-zero \n " ) ;
2012-05-03 09:45:02 +04:00
goto fail_out ;
2010-05-27 01:43:54 +04:00
}
2012-01-13 05:20:58 +04:00
pdata - > mem_size = rounddown_pow_of_two ( pdata - > mem_size ) ;
pdata - > record_size = rounddown_pow_of_two ( pdata - > record_size ) ;
2010-05-27 01:43:54 +04:00
2011-07-27 03:08:59 +04:00
/* Check for the minimum memory size */
if ( pdata - > mem_size < MIN_MEM_SIZE & &
pdata - > record_size < MIN_MEM_SIZE ) {
2012-05-03 09:45:02 +04:00
pr_err ( " memory size too small, minimum is %lu \n " ,
MIN_MEM_SIZE ) ;
goto fail_out ;
2010-05-27 01:43:54 +04:00
}
2011-07-27 03:08:59 +04:00
if ( pdata - > mem_size < pdata - > record_size ) {
pr_err ( " The memory size must be larger than the "
" records size \n " ) ;
2012-05-03 09:45:02 +04:00
goto fail_out ;
2011-07-27 03:08:59 +04:00
}
cxt - > max_count = pdata - > mem_size / pdata - > 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 ;
2011-07-27 03:08:59 +04:00
cxt - > record_size = pdata - > record_size ;
2011-07-27 03:08:58 +04:00
cxt - > dump_oops = pdata - > dump_oops ;
2012-05-17 11:15:34 +04:00
cxt - > ecc = pdata - > ecc ;
2010-05-27 01:43:54 +04:00
2012-05-17 11:15:18 +04:00
cxt - > przs = kzalloc ( sizeof ( * cxt - > przs ) * cxt - > max_count , GFP_KERNEL ) ;
if ( ! cxt - > przs ) {
err = - ENOMEM ;
dev_err ( dev , " failed to initialize a prz array \n " ) ;
goto fail_out ;
}
for ( i = 0 ; i < cxt - > max_count ; i + + ) {
size_t sz = cxt - > record_size ;
phys_addr_t start = cxt - > phys_addr + sz * i ;
2012-05-17 11:15:34 +04:00
cxt - > przs [ i ] = persistent_ram_new ( start , sz , cxt - > ecc ) ;
2012-05-17 11:15:18 +04:00
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 ) start , err ) ;
goto fail_przs ;
}
}
2012-05-03 09:45:02 +04:00
cxt - > pstore . data = cxt ;
2012-05-17 11:15:18 +04:00
cxt - > pstore . bufsize = cxt - > przs [ 0 ] - > buffer_size ;
2012-05-03 09:45:02 +04: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-27 01:43:54 +04:00
if ( err ) {
2012-05-03 09:45:02 +04:00
pr_err ( " registering with pstore failed \n " ) ;
2012-05-17 11:15:18 +04:00
goto fail_buf ;
2010-05-27 01:43:54 +04:00
}
2012-01-13 05:20:59 +04: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-17 11:15:34 +04:00
pr_info ( " attached 0x%lx@0x%llx (%ux0x%zx), ecc: %s \n " ,
2012-05-03 09:45:03 +04:00
cxt - > size , ( unsigned long long ) cxt - > phys_addr ,
2012-05-17 11:15:34 +04:00
cxt - > max_count , cxt - > record_size ,
ramoops_ecc ? " on " : " off " ) ;
2012-05-03 09:45:02 +04:00
2010-05-27 01:43:54 +04:00
return 0 ;
2012-05-03 09:45:02 +04:00
fail_buf :
kfree ( cxt - > pstore . buf ) ;
fail_clear :
cxt - > pstore . bufsize = 0 ;
cxt - > max_count = 0 ;
2012-05-17 11:15:18 +04:00
fail_przs :
for ( i = 0 ; cxt - > przs [ i ] ; i + + )
persistent_ram_free ( cxt - > przs [ i ] ) ;
kfree ( cxt - > przs ) ;
2012-05-03 09:45:02 +04:00
fail_out :
2010-05-27 01:43:54 +04:00
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
{
2012-05-03 09:45:02 +04:00
#if 0
/* TODO(kees): We cannot unload ramoops since pstore doesn't support
* unregistering yet .
*/
2010-05-27 01:43:54 +04:00
struct ramoops_context * cxt = & oops_cxt ;
iounmap ( cxt - > virt_addr ) ;
release_mem_region ( cxt - > phys_addr , cxt - > size ) ;
2012-05-03 09:45:02 +04:00
cxt - > max_count = 0 ;
/* TODO(kees): When pstore supports unregistering, call it here. */
kfree ( cxt - > pstore . buf ) ;
cxt - > pstore . bufsize = 0 ;
2010-10-28 02:34:52 +04:00
return 0 ;
2012-05-03 09:45:02 +04:00
# endif
return - EBUSY ;
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 .
*/
2011-07-27 03:08:57 +04:00
pr_info ( " platform device not found, using module parameters \n " ) ;
2011-07-27 03:08:57 +04:00
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 ;
2011-07-27 03:08:59 +04:00
dummy_data - > record_size = record_size ;
2011-07-27 03:08:58 +04:00
dummy_data - > dump_oops = dump_oops ;
2012-05-17 11:15:34 +04:00
dummy_data - > ecc = ramoops_ecc ;
2011-07-27 03:08:57 +04:00
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 " ) ;