2020-05-01 09:58:50 -05:00
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
2019-04-12 11:05:08 -05:00
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018 Intel Corporation. All rights reserved.
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
// Generic debug routines used to export DSP MMIO and memories to userspace
// for firmware debugging.
//
# include <linux/debugfs.h>
# include <linux/io.h>
# include <linux/pm_runtime.h>
2020-11-24 20:00:17 +02:00
# include <sound/sof/ext_manifest.h>
# include <sound/sof/debug.h>
2019-04-12 11:05:08 -05:00
# include "sof-priv.h"
# include "ops.h"
2019-06-03 11:18:20 -05:00
static ssize_t sof_dfsentry_write ( struct file * file , const char __user * buffer ,
size_t count , loff_t * ppos )
{
size_t size ;
char * string ;
int ret ;
2021-02-08 18:38:57 +08:00
string = kzalloc ( count + 1 , GFP_KERNEL ) ;
2019-06-03 11:18:20 -05:00
if ( ! string )
return - ENOMEM ;
size = simple_write_to_buffer ( string , count , ppos , buffer , count ) ;
ret = size ;
kfree ( string ) ;
return ret ;
}
2019-04-12 11:05:08 -05:00
static ssize_t sof_dfsentry_read ( struct file * file , char __user * buffer ,
size_t count , loff_t * ppos )
{
struct snd_sof_dfsentry * dfse = file - > private_data ;
struct snd_sof_dev * sdev = dfse - > sdev ;
loff_t pos = * ppos ;
size_t size_ret ;
int skip = 0 ;
int size ;
u8 * buf ;
size = dfse - > size ;
/* validate position & count */
if ( pos < 0 )
return - EINVAL ;
if ( pos > = size | | ! count )
return 0 ;
/* find the minimum. min() is not used since it adds sparse warnings */
if ( count > size - pos )
count = size - pos ;
/* align io read start to u32 multiple */
pos = ALIGN_DOWN ( pos , 4 ) ;
/* intermediate buffer size must be u32 multiple */
size = ALIGN ( count , 4 ) ;
/* if start position is unaligned, read extra u32 */
if ( unlikely ( pos ! = * ppos ) ) {
skip = * ppos - pos ;
if ( pos + size + 4 < dfse - > size )
size + = 4 ;
}
buf = kzalloc ( size , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
if ( dfse - > type = = SOF_DFSENTRY_TYPE_IOMEM ) {
# if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
/*
* If the DSP is active : copy from IO .
* If the DSP is suspended :
* - Copy from IO if the memory is always accessible .
* - Otherwise , copy from cached buffer .
*/
if ( pm_runtime_active ( sdev - > dev ) | |
dfse - > access_type = = SOF_DEBUGFS_ACCESS_ALWAYS ) {
memcpy_fromio ( buf , dfse - > io_mem + pos , size ) ;
} else {
dev_info ( sdev - > dev ,
" Copying cached debugfs data \n " ) ;
memcpy ( buf , dfse - > cache_buf + pos , size ) ;
}
# else
/* if the DSP is in D3 */
if ( ! pm_runtime_active ( sdev - > dev ) & &
dfse - > access_type = = SOF_DEBUGFS_ACCESS_D0_ONLY ) {
dev_err ( sdev - > dev ,
2019-07-31 15:17:16 +02:00
" error: debugfs entry cannot be read in DSP D3 \n " ) ;
2019-04-12 11:05:08 -05:00
kfree ( buf ) ;
return - EINVAL ;
}
memcpy_fromio ( buf , dfse - > io_mem + pos , size ) ;
# endif
} else {
memcpy ( buf , ( ( u8 * ) ( dfse - > buf ) + pos ) , size ) ;
}
/* copy to userspace */
size_ret = copy_to_user ( buffer , buf + skip , count ) ;
kfree ( buf ) ;
/* update count & position if copy succeeded */
if ( size_ret )
return - EFAULT ;
* ppos = pos + count ;
return count ;
}
static const struct file_operations sof_dfs_fops = {
. open = simple_open ,
. read = sof_dfsentry_read ,
. llseek = default_llseek ,
2019-06-03 11:18:20 -05:00
. write = sof_dfsentry_write ,
2019-04-12 11:05:08 -05:00
} ;
/* create FS entry for debug files that can expose DSP memories, registers */
2021-09-15 15:21:16 +03:00
static int snd_sof_debugfs_io_item ( struct snd_sof_dev * sdev ,
void __iomem * base , size_t size ,
const char * name ,
enum sof_debugfs_access_type access_type )
2019-04-12 11:05:08 -05:00
{
struct snd_sof_dfsentry * dfse ;
if ( ! sdev )
return - EINVAL ;
dfse = devm_kzalloc ( sdev - > dev , sizeof ( * dfse ) , GFP_KERNEL ) ;
if ( ! dfse )
return - ENOMEM ;
dfse - > type = SOF_DFSENTRY_TYPE_IOMEM ;
dfse - > io_mem = base ;
dfse - > size = size ;
dfse - > sdev = sdev ;
dfse - > access_type = access_type ;
# if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
/*
* allocate cache buffer that will be used to save the mem window
* contents prior to suspend
*/
if ( access_type = = SOF_DEBUGFS_ACCESS_D0_ONLY ) {
dfse - > cache_buf = devm_kzalloc ( sdev - > dev , size , GFP_KERNEL ) ;
if ( ! dfse - > cache_buf )
return - ENOMEM ;
}
# endif
2019-07-31 15:17:16 +02:00
debugfs_create_file ( name , 0444 , sdev - > debugfs_root , dfse ,
& sof_dfs_fops ) ;
2019-04-12 11:05:08 -05:00
2019-07-31 15:17:16 +02:00
/* add to dfsentry list */
list_add ( & dfse - > list , & sdev - > dfsentry_list ) ;
2019-04-12 11:05:08 -05:00
return 0 ;
}
2021-09-15 15:21:12 +03:00
int snd_sof_debugfs_add_region_item_iomem ( struct snd_sof_dev * sdev ,
enum snd_sof_fw_blk_type blk_type , u32 offset ,
size_t size , const char * name ,
enum sof_debugfs_access_type access_type )
{
int bar = snd_sof_dsp_get_bar_index ( sdev , blk_type ) ;
if ( bar < 0 )
return bar ;
return snd_sof_debugfs_io_item ( sdev , sdev - > bar [ bar ] + offset , size , name ,
access_type ) ;
}
EXPORT_SYMBOL_GPL ( snd_sof_debugfs_add_region_item_iomem ) ;
2019-04-12 11:05:08 -05:00
/* create FS entry for debug files to expose kernel memory */
int snd_sof_debugfs_buf_item ( struct snd_sof_dev * sdev ,
void * base , size_t size ,
2019-06-03 11:18:18 -05:00
const char * name , mode_t mode )
2019-04-12 11:05:08 -05:00
{
struct snd_sof_dfsentry * dfse ;
if ( ! sdev )
return - EINVAL ;
dfse = devm_kzalloc ( sdev - > dev , sizeof ( * dfse ) , GFP_KERNEL ) ;
if ( ! dfse )
return - ENOMEM ;
dfse - > type = SOF_DFSENTRY_TYPE_BUF ;
dfse - > buf = base ;
dfse - > size = size ;
dfse - > sdev = sdev ;
2019-07-31 15:17:16 +02:00
debugfs_create_file ( name , mode , sdev - > debugfs_root , dfse ,
& sof_dfs_fops ) ;
/* add to dfsentry list */
list_add ( & dfse - > list , & sdev - > dfsentry_list ) ;
2019-04-12 11:05:08 -05:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_sof_debugfs_buf_item ) ;
2020-11-24 20:00:17 +02:00
static int memory_info_update ( struct snd_sof_dev * sdev , char * buf , size_t buff_size )
{
struct sof_ipc_cmd_hdr msg = {
. size = sizeof ( struct sof_ipc_cmd_hdr ) ,
. cmd = SOF_IPC_GLB_DEBUG | SOF_IPC_DEBUG_MEM_USAGE ,
} ;
struct sof_ipc_dbg_mem_usage * reply ;
int len ;
int ret ;
int i ;
reply = kmalloc ( SOF_IPC_MSG_MAX_SIZE , GFP_KERNEL ) ;
if ( ! reply )
return - ENOMEM ;
2022-04-26 13:41:04 -05:00
ret = pm_runtime_resume_and_get ( sdev - > dev ) ;
2020-11-24 20:00:17 +02:00
if ( ret < 0 & & ret ! = - EACCES ) {
dev_err ( sdev - > dev , " error: enabling device failed: %d \n " , ret ) ;
goto error ;
}
2022-03-30 13:19:24 -07:00
ret = sof_ipc_tx_message ( sdev - > ipc , & msg , msg . size , reply , SOF_IPC_MSG_MAX_SIZE ) ;
2020-11-24 20:00:17 +02:00
pm_runtime_mark_last_busy ( sdev - > dev ) ;
pm_runtime_put_autosuspend ( sdev - > dev ) ;
if ( ret < 0 | | reply - > rhdr . error < 0 ) {
ret = min ( ret , reply - > rhdr . error ) ;
dev_err ( sdev - > dev , " error: reading memory info failed, %d \n " , ret ) ;
goto error ;
}
if ( struct_size ( reply , elems , reply - > num_elems ) ! = reply - > rhdr . hdr . size ) {
dev_err ( sdev - > dev , " error: invalid memory info ipc struct size, %d \n " ,
reply - > rhdr . hdr . size ) ;
ret = - EINVAL ;
goto error ;
}
for ( i = 0 , len = 0 ; i < reply - > num_elems ; i + + ) {
2022-08-01 18:54:19 +02:00
ret = scnprintf ( buf + len , buff_size - len , " zone %d.%d used %#8x free %#8x \n " ,
reply - > elems [ i ] . zone , reply - > elems [ i ] . id ,
reply - > elems [ i ] . used , reply - > elems [ i ] . free ) ;
2020-11-24 20:00:17 +02:00
if ( ret < 0 )
goto error ;
len + = ret ;
}
ret = len ;
error :
kfree ( reply ) ;
return ret ;
}
static ssize_t memory_info_read ( struct file * file , char __user * to , size_t count , loff_t * ppos )
{
struct snd_sof_dfsentry * dfse = file - > private_data ;
struct snd_sof_dev * sdev = dfse - > sdev ;
int data_length ;
/* read memory info from FW only once for each file read */
if ( ! * ppos ) {
dfse - > buf_data_size = 0 ;
data_length = memory_info_update ( sdev , dfse - > buf , dfse - > size ) ;
if ( data_length < 0 )
return data_length ;
dfse - > buf_data_size = data_length ;
}
return simple_read_from_buffer ( to , count , ppos , dfse - > buf , dfse - > buf_data_size ) ;
}
static int memory_info_open ( struct inode * inode , struct file * file )
{
struct snd_sof_dfsentry * dfse = inode - > i_private ;
struct snd_sof_dev * sdev = dfse - > sdev ;
file - > private_data = dfse ;
/* allocate buffer memory only in first open run, to save memory when unused */
if ( ! dfse - > buf ) {
dfse - > buf = devm_kmalloc ( sdev - > dev , PAGE_SIZE , GFP_KERNEL ) ;
if ( ! dfse - > buf )
return - ENOMEM ;
dfse - > size = PAGE_SIZE ;
}
return 0 ;
}
static const struct file_operations memory_info_fops = {
. open = memory_info_open ,
. read = memory_info_read ,
. llseek = default_llseek ,
} ;
int snd_sof_dbg_memory_info_init ( struct snd_sof_dev * sdev )
{
struct snd_sof_dfsentry * dfse ;
dfse = devm_kzalloc ( sdev - > dev , sizeof ( * dfse ) , GFP_KERNEL ) ;
if ( ! dfse )
return - ENOMEM ;
/* don't allocate buffer before first usage, to save memory when unused */
dfse - > type = SOF_DFSENTRY_TYPE_BUF ;
dfse - > sdev = sdev ;
debugfs_create_file ( " memory_info " , 0444 , sdev - > debugfs_root , dfse , & memory_info_fops ) ;
/* add to dfsentry list */
list_add ( & dfse - > list , & sdev - > dfsentry_list ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_sof_dbg_memory_info_init ) ;
2019-04-12 11:05:08 -05:00
int snd_sof_dbg_init ( struct snd_sof_dev * sdev )
{
2022-04-14 13:48:10 -05:00
struct snd_sof_dsp_ops * ops = sof_ops ( sdev ) ;
2019-04-12 11:05:08 -05:00
const struct snd_sof_debugfs_map * map ;
int i ;
int err ;
/* use "sof" as top level debugFS dir */
sdev - > debugfs_root = debugfs_create_dir ( " sof " , NULL ) ;
/* init dfsentry list */
INIT_LIST_HEAD ( & sdev - > dfsentry_list ) ;
/* create debugFS files for platform specific MMIO/DSP memories */
for ( i = 0 ; i < ops - > debug_map_count ; i + + ) {
map = & ops - > debug_map [ i ] ;
err = snd_sof_debugfs_io_item ( sdev , sdev - > bar [ map - > bar ] +
map - > offset , map - > size ,
map - > name , map - > access_type ) ;
/* errors are only due to memory allocation, not debugfs */
if ( err < 0 )
return err ;
}
2022-12-20 14:56:29 +02:00
return snd_sof_debugfs_buf_item ( sdev , & sdev - > fw_state ,
sizeof ( sdev - > fw_state ) ,
" fw_state " , 0444 ) ;
2019-04-12 11:05:08 -05:00
}
EXPORT_SYMBOL_GPL ( snd_sof_dbg_init ) ;
void snd_sof_free_debug ( struct snd_sof_dev * sdev )
{
debugfs_remove_recursive ( sdev - > debugfs_root ) ;
}
EXPORT_SYMBOL_GPL ( snd_sof_free_debug ) ;
2019-09-27 15:05:29 -05:00
2021-10-06 14:06:37 +03:00
static const struct soc_fw_state_info {
2021-12-23 13:36:18 +02:00
enum sof_fw_state state ;
2021-10-06 14:06:37 +03:00
const char * name ;
} fw_state_dbg [ ] = {
{ SOF_FW_BOOT_NOT_STARTED , " SOF_FW_BOOT_NOT_STARTED " } ,
2023-04-04 12:21:06 +03:00
{ SOF_DSPLESS_MODE , " SOF_DSPLESS_MODE " } ,
2021-10-06 14:06:37 +03:00
{ SOF_FW_BOOT_PREPARE , " SOF_FW_BOOT_PREPARE " } ,
{ SOF_FW_BOOT_IN_PROGRESS , " SOF_FW_BOOT_IN_PROGRESS " } ,
{ SOF_FW_BOOT_FAILED , " SOF_FW_BOOT_FAILED " } ,
{ SOF_FW_BOOT_READY_FAILED , " SOF_FW_BOOT_READY_FAILED " } ,
2021-12-23 13:36:16 +02:00
{ SOF_FW_BOOT_READY_OK , " SOF_FW_BOOT_READY_OK " } ,
2021-10-06 14:06:37 +03:00
{ SOF_FW_BOOT_COMPLETE , " SOF_FW_BOOT_COMPLETE " } ,
2021-12-23 13:36:15 +02:00
{ SOF_FW_CRASHED , " SOF_FW_CRASHED " } ,
2021-10-06 14:06:37 +03:00
} ;
2021-12-23 13:36:27 +02:00
static void snd_sof_dbg_print_fw_state ( struct snd_sof_dev * sdev , const char * level )
2021-10-06 14:06:37 +03:00
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( fw_state_dbg ) ; i + + ) {
if ( sdev - > fw_state = = fw_state_dbg [ i ] . state ) {
2021-12-23 13:36:27 +02:00
dev_printk ( level , sdev - > dev , " fw_state: %s (%d) \n " ,
fw_state_dbg [ i ] . name , i ) ;
2021-10-06 14:06:37 +03:00
return ;
}
}
2021-12-23 13:36:27 +02:00
dev_printk ( level , sdev - > dev , " fw_state: UNKNOWN (%d) \n " , sdev - > fw_state ) ;
2021-10-06 14:06:37 +03:00
}
2021-12-23 13:36:14 +02:00
void snd_sof_dsp_dbg_dump ( struct snd_sof_dev * sdev , const char * msg , u32 flags )
2021-10-06 14:06:33 +03:00
{
2022-03-04 14:57:32 -06:00
char * level = ( flags & SOF_DBG_DUMP_OPTIONAL ) ? KERN_DEBUG : KERN_ERR ;
2021-12-23 13:36:12 +02:00
bool print_all = sof_debug_check_flag ( SOF_DBG_PRINT_ALL_DUMPS ) ;
2021-10-06 14:06:34 +03:00
if ( flags & SOF_DBG_DUMP_OPTIONAL & & ! print_all )
return ;
2021-10-06 14:06:33 +03:00
if ( sof_ops ( sdev ) - > dbg_dump & & ! sdev - > dbg_dump_printed ) {
2021-12-23 13:36:27 +02:00
dev_printk ( level , sdev - > dev ,
" ------------[ DSP dump start ]------------ \n " ) ;
2021-12-23 13:36:14 +02:00
if ( msg )
2021-12-23 13:36:27 +02:00
dev_printk ( level , sdev - > dev , " %s \n " , msg ) ;
snd_sof_dbg_print_fw_state ( sdev , level ) ;
2021-10-06 14:06:33 +03:00
sof_ops ( sdev ) - > dbg_dump ( sdev , flags ) ;
2021-12-23 13:36:27 +02:00
dev_printk ( level , sdev - > dev ,
" ------------[ DSP dump end ]------------ \n " ) ;
2021-10-06 14:06:34 +03:00
if ( ! print_all )
2021-10-06 14:06:33 +03:00
sdev - > dbg_dump_printed = true ;
2021-12-23 13:36:14 +02:00
} else if ( msg ) {
2021-12-23 13:36:27 +02:00
dev_printk ( level , sdev - > dev , " %s \n " , msg ) ;
2021-10-06 14:06:33 +03:00
}
}
EXPORT_SYMBOL ( snd_sof_dsp_dbg_dump ) ;
static void snd_sof_ipc_dump ( struct snd_sof_dev * sdev )
{
if ( sof_ops ( sdev ) - > ipc_dump & & ! sdev - > ipc_dump_printed ) {
dev_err ( sdev - > dev , " ------------[ IPC dump start ]------------ \n " ) ;
sof_ops ( sdev ) - > ipc_dump ( sdev ) ;
dev_err ( sdev - > dev , " ------------[ IPC dump end ]------------ \n " ) ;
2021-12-23 13:36:12 +02:00
if ( ! sof_debug_check_flag ( SOF_DBG_PRINT_ALL_DUMPS ) )
2021-10-06 14:06:33 +03:00
sdev - > ipc_dump_printed = true ;
}
}
2022-06-10 11:04:21 +03:00
void snd_sof_handle_fw_exception ( struct snd_sof_dev * sdev , const char * msg )
2019-09-27 15:05:29 -05:00
{
if ( IS_ENABLED ( CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT ) | |
2021-12-23 13:36:12 +02:00
sof_debug_check_flag ( SOF_DBG_RETAIN_CTX ) ) {
2019-09-27 15:05:29 -05:00
/* should we prevent DSP entering D3 ? */
2021-10-06 14:06:30 +03:00
if ( ! sdev - > ipc_dump_printed )
dev_info ( sdev - > dev ,
" preventing DSP entering D3 state to preserve context \n " ) ;
2019-09-27 15:05:29 -05:00
pm_runtime_get_noresume ( sdev - > dev ) ;
}
/* dump vital information to the logs */
snd_sof_ipc_dump ( sdev ) ;
2022-06-10 11:04:21 +03:00
snd_sof_dsp_dbg_dump ( sdev , msg , SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX ) ;
2022-05-16 13:47:07 +03:00
sof_fw_trace_fw_crashed ( sdev ) ;
2019-09-27 15:05:29 -05:00
}
EXPORT_SYMBOL ( snd_sof_handle_fw_exception ) ;