2017-05-16 17:46:48 +02:00
/*
* cros_ec_debugfs - debug logs for Chrome OS EC
*
* Copyright 2015 Google , Inc .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/circ_buf.h>
# include <linux/debugfs.h>
# include <linux/delay.h>
# include <linux/fs.h>
# include <linux/mfd/cros_ec.h>
# include <linux/mfd/cros_ec_commands.h>
# include <linux/mutex.h>
# include <linux/poll.h>
# include <linux/sched.h>
# include <linux/slab.h>
# include <linux/wait.h>
# define LOG_SHIFT 14
# define LOG_SIZE (1 << LOG_SHIFT)
# define LOG_POLL_SEC 10
# define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1))
/* struct cros_ec_debugfs - ChromeOS EC debugging information
*
* @ ec : EC device this debugfs information belongs to
* @ dir : dentry for debugfs files
* @ log_buffer : circular buffer for console log information
* @ read_msg : preallocated EC command and buffer to read console log
* @ log_mutex : mutex to protect circular buffer
* @ log_wq : waitqueue for log readers
* @ log_poll_work : recurring task to poll EC for new console log data
2017-05-16 17:46:48 +02:00
* @ panicinfo_blob : panicinfo debugfs blob
2017-05-16 17:46:48 +02:00
*/
struct cros_ec_debugfs {
struct cros_ec_dev * ec ;
struct dentry * dir ;
2017-05-16 17:46:48 +02:00
/* EC log */
2017-05-16 17:46:48 +02:00
struct circ_buf log_buffer ;
struct cros_ec_command * read_msg ;
struct mutex log_mutex ;
wait_queue_head_t log_wq ;
struct delayed_work log_poll_work ;
2017-05-16 17:46:48 +02:00
/* EC panicinfo */
struct debugfs_blob_wrapper panicinfo_blob ;
2017-05-16 17:46:48 +02:00
} ;
/*
* We need to make sure that the EC log buffer on the UART is large enough ,
* so that it is unlikely enough to overlow within LOG_POLL_SEC .
*/
static void cros_ec_console_log_work ( struct work_struct * __work )
{
struct cros_ec_debugfs * debug_info =
container_of ( to_delayed_work ( __work ) ,
struct cros_ec_debugfs ,
log_poll_work ) ;
struct cros_ec_dev * ec = debug_info - > ec ;
struct circ_buf * cb = & debug_info - > log_buffer ;
struct cros_ec_command snapshot_msg = {
. command = EC_CMD_CONSOLE_SNAPSHOT + ec - > cmd_offset ,
} ;
struct ec_params_console_read_v1 * read_params =
( struct ec_params_console_read_v1 * ) debug_info - > read_msg - > data ;
uint8_t * ec_buffer = ( uint8_t * ) debug_info - > read_msg - > data ;
int idx ;
int buf_space ;
int ret ;
ret = cros_ec_cmd_xfer ( ec - > ec_dev , & snapshot_msg ) ;
if ( ret < 0 ) {
dev_err ( ec - > dev , " EC communication failed \n " ) ;
goto resched ;
}
if ( snapshot_msg . result ! = EC_RES_SUCCESS ) {
dev_err ( ec - > dev , " EC failed to snapshot the console log \n " ) ;
goto resched ;
}
/* Loop until we have read everything, or there's an error. */
mutex_lock ( & debug_info - > log_mutex ) ;
buf_space = CIRC_SPACE ( cb - > head , cb - > tail , LOG_SIZE ) ;
while ( 1 ) {
if ( ! buf_space ) {
dev_info_once ( ec - > dev ,
" Some logs may have been dropped... \n " ) ;
break ;
}
memset ( read_params , ' \0 ' , sizeof ( * read_params ) ) ;
read_params - > subcmd = CONSOLE_READ_RECENT ;
ret = cros_ec_cmd_xfer ( ec - > ec_dev , debug_info - > read_msg ) ;
if ( ret < 0 ) {
dev_err ( ec - > dev , " EC communication failed \n " ) ;
break ;
}
if ( debug_info - > read_msg - > result ! = EC_RES_SUCCESS ) {
dev_err ( ec - > dev ,
" EC failed to read the console log \n " ) ;
break ;
}
/* If the buffer is empty, we're done here. */
if ( ret = = 0 | | ec_buffer [ 0 ] = = ' \0 ' )
break ;
idx = 0 ;
while ( idx < ret & & ec_buffer [ idx ] ! = ' \0 ' & & buf_space > 0 ) {
cb - > buf [ cb - > head ] = ec_buffer [ idx ] ;
cb - > head = CIRC_ADD ( cb - > head , LOG_SIZE , 1 ) ;
idx + + ;
buf_space - - ;
}
wake_up ( & debug_info - > log_wq ) ;
}
mutex_unlock ( & debug_info - > log_mutex ) ;
resched :
schedule_delayed_work ( & debug_info - > log_poll_work ,
msecs_to_jiffies ( LOG_POLL_SEC * 1000 ) ) ;
}
static int cros_ec_console_log_open ( struct inode * inode , struct file * file )
{
file - > private_data = inode - > i_private ;
return nonseekable_open ( inode , file ) ;
}
static ssize_t cros_ec_console_log_read ( struct file * file , char __user * buf ,
size_t count , loff_t * ppos )
{
struct cros_ec_debugfs * debug_info = file - > private_data ;
struct circ_buf * cb = & debug_info - > log_buffer ;
ssize_t ret ;
mutex_lock ( & debug_info - > log_mutex ) ;
while ( ! CIRC_CNT ( cb - > head , cb - > tail , LOG_SIZE ) ) {
if ( file - > f_flags & O_NONBLOCK ) {
ret = - EAGAIN ;
goto error ;
}
mutex_unlock ( & debug_info - > log_mutex ) ;
ret = wait_event_interruptible ( debug_info - > log_wq ,
CIRC_CNT ( cb - > head , cb - > tail , LOG_SIZE ) ) ;
if ( ret < 0 )
return ret ;
mutex_lock ( & debug_info - > log_mutex ) ;
}
/* Only copy until the end of the circular buffer, and let userspace
* retry to get the rest of the data .
*/
ret = min_t ( size_t , CIRC_CNT_TO_END ( cb - > head , cb - > tail , LOG_SIZE ) ,
count ) ;
if ( copy_to_user ( buf , cb - > buf + cb - > tail , ret ) ) {
ret = - EFAULT ;
goto error ;
}
cb - > tail = CIRC_ADD ( cb - > tail , LOG_SIZE , ret ) ;
error :
mutex_unlock ( & debug_info - > log_mutex ) ;
return ret ;
}
2017-07-03 06:39:46 -04:00
static __poll_t cros_ec_console_log_poll ( struct file * file ,
2017-05-16 17:46:48 +02:00
poll_table * wait )
{
struct cros_ec_debugfs * debug_info = file - > private_data ;
2017-07-03 06:39:46 -04:00
__poll_t mask = 0 ;
2017-05-16 17:46:48 +02:00
poll_wait ( file , & debug_info - > log_wq , wait ) ;
mutex_lock ( & debug_info - > log_mutex ) ;
if ( CIRC_CNT ( debug_info - > log_buffer . head ,
debug_info - > log_buffer . tail ,
LOG_SIZE ) )
2018-02-11 14:34:03 -08:00
mask | = EPOLLIN | EPOLLRDNORM ;
2017-05-16 17:46:48 +02:00
mutex_unlock ( & debug_info - > log_mutex ) ;
return mask ;
}
static int cros_ec_console_log_release ( struct inode * inode , struct file * file )
{
return 0 ;
}
const struct file_operations cros_ec_console_log_fops = {
. owner = THIS_MODULE ,
. open = cros_ec_console_log_open ,
. read = cros_ec_console_log_read ,
. llseek = no_llseek ,
. poll = cros_ec_console_log_poll ,
. release = cros_ec_console_log_release ,
} ;
static int ec_read_version_supported ( struct cros_ec_dev * ec )
{
struct ec_params_get_cmd_versions_v1 * params ;
struct ec_response_get_cmd_versions * response ;
int ret ;
struct cros_ec_command * msg ;
2017-02-16 09:49:29 -08:00
msg = kzalloc ( sizeof ( * msg ) + max ( sizeof ( * params ) , sizeof ( * response ) ) ,
2017-05-16 17:46:48 +02:00
GFP_KERNEL ) ;
if ( ! msg )
return 0 ;
msg - > command = EC_CMD_GET_CMD_VERSIONS + ec - > cmd_offset ;
2017-02-16 09:49:29 -08:00
msg - > outsize = sizeof ( * params ) ;
msg - > insize = sizeof ( * response ) ;
2017-05-16 17:46:48 +02:00
params = ( struct ec_params_get_cmd_versions_v1 * ) msg - > data ;
params - > cmd = EC_CMD_CONSOLE_READ ;
response = ( struct ec_response_get_cmd_versions * ) msg - > data ;
ret = cros_ec_cmd_xfer ( ec - > ec_dev , msg ) > = 0 & &
msg - > result = = EC_RES_SUCCESS & &
( response - > version_mask & EC_VER_MASK ( 1 ) ) ;
kfree ( msg ) ;
return ret ;
}
static int cros_ec_create_console_log ( struct cros_ec_debugfs * debug_info )
{
struct cros_ec_dev * ec = debug_info - > ec ;
char * buf ;
int read_params_size ;
int read_response_size ;
if ( ! ec_read_version_supported ( ec ) ) {
dev_warn ( ec - > dev ,
" device does not support reading the console log \n " ) ;
return 0 ;
}
buf = devm_kzalloc ( ec - > dev , LOG_SIZE , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
read_params_size = sizeof ( struct ec_params_console_read_v1 ) ;
read_response_size = ec - > ec_dev - > max_response ;
debug_info - > read_msg = devm_kzalloc ( ec - > dev ,
sizeof ( * debug_info - > read_msg ) +
max ( read_params_size , read_response_size ) , GFP_KERNEL ) ;
if ( ! debug_info - > read_msg )
return - ENOMEM ;
debug_info - > read_msg - > version = 1 ;
debug_info - > read_msg - > command = EC_CMD_CONSOLE_READ + ec - > cmd_offset ;
debug_info - > read_msg - > outsize = read_params_size ;
debug_info - > read_msg - > insize = read_response_size ;
debug_info - > log_buffer . buf = buf ;
debug_info - > log_buffer . head = 0 ;
debug_info - > log_buffer . tail = 0 ;
mutex_init ( & debug_info - > log_mutex ) ;
init_waitqueue_head ( & debug_info - > log_wq ) ;
if ( ! debugfs_create_file ( " console_log " ,
S_IFREG | S_IRUGO ,
debug_info - > dir ,
debug_info ,
& cros_ec_console_log_fops ) )
return - ENOMEM ;
INIT_DELAYED_WORK ( & debug_info - > log_poll_work ,
cros_ec_console_log_work ) ;
schedule_delayed_work ( & debug_info - > log_poll_work , 0 ) ;
return 0 ;
}
static void cros_ec_cleanup_console_log ( struct cros_ec_debugfs * debug_info )
{
if ( debug_info - > log_buffer . buf ) {
cancel_delayed_work_sync ( & debug_info - > log_poll_work ) ;
mutex_destroy ( & debug_info - > log_mutex ) ;
}
}
2017-05-16 17:46:48 +02:00
static int cros_ec_create_panicinfo ( struct cros_ec_debugfs * debug_info )
{
struct cros_ec_device * ec_dev = debug_info - > ec - > ec_dev ;
int ret ;
struct cros_ec_command * msg ;
int insize ;
insize = ec_dev - > max_response ;
msg = devm_kzalloc ( debug_info - > ec - > dev ,
sizeof ( * msg ) + insize , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
msg - > command = EC_CMD_GET_PANIC_INFO ;
msg - > insize = insize ;
ret = cros_ec_cmd_xfer ( ec_dev , msg ) ;
if ( ret < 0 ) {
dev_warn ( debug_info - > ec - > dev , " Cannot read panicinfo. \n " ) ;
ret = 0 ;
goto free ;
}
/* No panic data */
if ( ret = = 0 )
goto free ;
debug_info - > panicinfo_blob . data = msg - > data ;
debug_info - > panicinfo_blob . size = ret ;
if ( ! debugfs_create_blob ( " panicinfo " ,
S_IFREG | S_IRUGO ,
debug_info - > dir ,
& debug_info - > panicinfo_blob ) ) {
ret = - ENOMEM ;
goto free ;
}
return 0 ;
free :
devm_kfree ( debug_info - > ec - > dev , msg ) ;
return ret ;
}
2017-05-16 17:46:48 +02:00
int cros_ec_debugfs_init ( struct cros_ec_dev * ec )
{
struct cros_ec_platform * ec_platform = dev_get_platdata ( ec - > dev ) ;
const char * name = ec_platform - > ec_name ;
struct cros_ec_debugfs * debug_info ;
int ret ;
debug_info = devm_kzalloc ( ec - > dev , sizeof ( * debug_info ) , GFP_KERNEL ) ;
if ( ! debug_info )
return - ENOMEM ;
debug_info - > ec = ec ;
debug_info - > dir = debugfs_create_dir ( name , NULL ) ;
if ( ! debug_info - > dir )
return - ENOMEM ;
2017-05-16 17:46:48 +02:00
ret = cros_ec_create_panicinfo ( debug_info ) ;
if ( ret )
goto remove_debugfs ;
2017-05-16 17:46:48 +02:00
ret = cros_ec_create_console_log ( debug_info ) ;
if ( ret )
goto remove_debugfs ;
ec - > debug_info = debug_info ;
return 0 ;
remove_debugfs :
debugfs_remove_recursive ( debug_info - > dir ) ;
return ret ;
}
2017-11-20 17:15:25 +01:00
EXPORT_SYMBOL ( cros_ec_debugfs_init ) ;
2017-05-16 17:46:48 +02:00
void cros_ec_debugfs_remove ( struct cros_ec_dev * ec )
{
if ( ! ec - > debug_info )
return ;
debugfs_remove_recursive ( ec - > debug_info - > dir ) ;
cros_ec_cleanup_console_log ( ec - > debug_info ) ;
}
2017-11-20 17:15:25 +01:00
EXPORT_SYMBOL ( cros_ec_debugfs_remove ) ;