2020-05-01 09:58:50 -05:00
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
2019-04-12 11:05:14 -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 firmware loader.
//
# include <linux/firmware.h>
# include <sound/sof.h>
2020-05-20 19:59:08 +03:00
# include <sound/sof/ext_manifest.h>
2019-04-12 11:05:14 -05:00
# include "ops.h"
static int get_ext_windows ( struct snd_sof_dev * sdev ,
2020-04-15 15:27:54 -05:00
const struct sof_ipc_ext_data_hdr * ext_hdr )
2019-04-12 11:05:14 -05:00
{
2020-04-15 15:27:54 -05:00
const struct sof_ipc_window * w =
2019-04-12 11:05:14 -05:00
container_of ( ext_hdr , struct sof_ipc_window , ext_hdr ) ;
if ( w - > num_windows = = 0 | | w - > num_windows > SOF_IPC_MAX_ELEMS )
return - EINVAL ;
2020-05-20 19:59:10 +03:00
if ( sdev - > info_window ) {
2020-08-25 16:58:51 -07:00
if ( memcmp ( sdev - > info_window , w , ext_hdr - > hdr . size ) ) {
2020-05-20 19:59:10 +03:00
dev_err ( sdev - > dev , " error: mismatch between window descriptor from extended manifest and mailbox " ) ;
return - EINVAL ;
}
return 0 ;
}
2019-04-12 11:05:14 -05:00
/* keep a local copy of the data */
2020-08-25 16:58:52 -07:00
sdev - > info_window = devm_kmemdup ( sdev - > dev , w , ext_hdr - > hdr . size ,
GFP_KERNEL ) ;
2019-04-12 11:05:14 -05:00
if ( ! sdev - > info_window )
return - ENOMEM ;
return 0 ;
}
2019-12-17 18:26:11 -06:00
static int get_cc_info ( struct snd_sof_dev * sdev ,
2020-04-15 15:27:54 -05:00
const struct sof_ipc_ext_data_hdr * ext_hdr )
2019-12-17 18:26:11 -06:00
{
int ret ;
2020-04-15 15:27:54 -05:00
const struct sof_ipc_cc_version * cc =
2019-12-17 18:26:11 -06:00
container_of ( ext_hdr , struct sof_ipc_cc_version , ext_hdr ) ;
2020-05-20 19:59:11 +03:00
if ( sdev - > cc_version ) {
if ( memcmp ( sdev - > cc_version , cc , cc - > ext_hdr . hdr . size ) ) {
dev_err ( sdev - > dev , " error: receive diverged cc_version descriptions " ) ;
return - EINVAL ;
}
return 0 ;
}
2019-12-17 18:26:11 -06:00
dev_dbg ( sdev - > dev , " Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s \n " ,
cc - > name , cc - > major , cc - > minor , cc - > micro , cc - > desc ,
cc - > optim ) ;
/* create read-only cc_version debugfs to store compiler version info */
/* use local copy of the cc_version to prevent data corruption */
if ( sdev - > first_boot ) {
sdev - > cc_version = devm_kmalloc ( sdev - > dev , cc - > ext_hdr . hdr . size ,
GFP_KERNEL ) ;
if ( ! sdev - > cc_version )
return - ENOMEM ;
memcpy ( sdev - > cc_version , cc , cc - > ext_hdr . hdr . size ) ;
ret = snd_sof_debugfs_buf_item ( sdev , sdev - > cc_version ,
cc - > ext_hdr . hdr . size ,
" cc_version " , 0444 ) ;
/* errors are only due to memory allocation, not debugfs */
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: snd_sof_debugfs_buf_item failed \n " ) ;
return ret ;
}
}
return 0 ;
}
2019-04-12 11:05:14 -05:00
/* parse the extended FW boot data structures from FW boot message */
int snd_sof_fw_parse_ext_data ( struct snd_sof_dev * sdev , u32 bar , u32 offset )
{
struct sof_ipc_ext_data_hdr * ext_hdr ;
void * ext_data ;
int ret = 0 ;
ext_data = kzalloc ( PAGE_SIZE , GFP_KERNEL ) ;
if ( ! ext_data )
return - ENOMEM ;
/* get first header */
snd_sof_dsp_block_read ( sdev , bar , offset , ext_data ,
sizeof ( * ext_hdr ) ) ;
ext_hdr = ext_data ;
while ( ext_hdr - > hdr . cmd = = SOF_IPC_FW_READY ) {
/* read in ext structure */
2019-12-09 18:48:49 -06:00
snd_sof_dsp_block_read ( sdev , bar , offset + sizeof ( * ext_hdr ) ,
2019-04-12 11:05:14 -05:00
( void * ) ( ( u8 * ) ext_data + sizeof ( * ext_hdr ) ) ,
ext_hdr - > hdr . size - sizeof ( * ext_hdr ) ) ;
dev_dbg ( sdev - > dev , " found ext header type %d size 0x%x \n " ,
ext_hdr - > type , ext_hdr - > hdr . size ) ;
/* process structure data */
switch ( ext_hdr - > type ) {
case SOF_IPC_EXT_WINDOW :
ret = get_ext_windows ( sdev , ext_hdr ) ;
break ;
2019-12-17 18:26:11 -06:00
case SOF_IPC_EXT_CC_INFO :
ret = get_cc_info ( sdev , ext_hdr ) ;
break ;
2020-10-21 21:24:19 +03:00
case SOF_IPC_EXT_UNUSED :
case SOF_IPC_EXT_PROBE_INFO :
case SOF_IPC_EXT_USER_ABI_INFO :
/* They are supported but we don't do anything here */
break ;
2019-04-12 11:05:14 -05:00
default :
2020-11-11 19:33:21 +02:00
dev_info ( sdev - > dev , " unknown ext header type %d size 0x%x \n " ,
2019-12-09 18:48:48 -06:00
ext_hdr - > type , ext_hdr - > hdr . size ) ;
2019-12-09 18:48:49 -06:00
ret = 0 ;
2019-04-12 11:05:14 -05:00
break ;
}
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed to parse ext data type %d \n " ,
ext_hdr - > type ) ;
break ;
}
/* move to next header */
offset + = ext_hdr - > hdr . size ;
snd_sof_dsp_block_read ( sdev , bar , offset , ext_data ,
sizeof ( * ext_hdr ) ) ;
ext_hdr = ext_data ;
}
kfree ( ext_data ) ;
return ret ;
}
EXPORT_SYMBOL ( snd_sof_fw_parse_ext_data ) ;
2020-05-20 19:59:09 +03:00
static int ext_man_get_fw_version ( struct snd_sof_dev * sdev ,
const struct sof_ext_man_elem_header * hdr )
{
const struct sof_ext_man_fw_version * v =
container_of ( hdr , struct sof_ext_man_fw_version , hdr ) ;
memcpy ( & sdev - > fw_ready . version , & v - > version , sizeof ( v - > version ) ) ;
sdev - > fw_ready . flags = v - > flags ;
/* log ABI versions and check FW compatibility */
return snd_sof_ipc_valid ( sdev ) ;
}
2020-05-20 19:59:10 +03:00
static int ext_man_get_windows ( struct snd_sof_dev * sdev ,
const struct sof_ext_man_elem_header * hdr )
{
const struct sof_ext_man_window * w ;
w = container_of ( hdr , struct sof_ext_man_window , hdr ) ;
return get_ext_windows ( sdev , & w - > ipc_window . ext_hdr ) ;
}
2020-05-20 19:59:11 +03:00
static int ext_man_get_cc_info ( struct snd_sof_dev * sdev ,
const struct sof_ext_man_elem_header * hdr )
{
const struct sof_ext_man_cc_version * cc ;
cc = container_of ( hdr , struct sof_ext_man_cc_version , hdr ) ;
return get_cc_info ( sdev , & cc - > cc_version . ext_hdr ) ;
}
2020-08-25 16:58:53 -07:00
static int ext_man_get_dbg_abi_info ( struct snd_sof_dev * sdev ,
const struct sof_ext_man_elem_header * hdr )
{
const struct ext_man_dbg_abi * dbg_abi =
container_of ( hdr , struct ext_man_dbg_abi , hdr ) ;
if ( sdev - > first_boot )
dev_dbg ( sdev - > dev ,
" Firmware: DBG_ABI %d:%d:%d \n " ,
SOF_ABI_VERSION_MAJOR ( dbg_abi - > dbg_abi . abi_dbg_version ) ,
SOF_ABI_VERSION_MINOR ( dbg_abi - > dbg_abi . abi_dbg_version ) ,
SOF_ABI_VERSION_PATCH ( dbg_abi - > dbg_abi . abi_dbg_version ) ) ;
return 0 ;
}
2020-11-24 20:00:14 +02:00
static int ext_man_get_config_data ( struct snd_sof_dev * sdev ,
const struct sof_ext_man_elem_header * hdr )
{
const struct sof_ext_man_config_data * config =
container_of ( hdr , struct sof_ext_man_config_data , hdr ) ;
const struct sof_config_elem * elem ;
int elems_counter ;
int elems_size ;
2020-11-24 20:00:17 +02:00
int ret = 0 ;
2020-11-24 20:00:14 +02:00
int i ;
/* calculate elements counter */
elems_size = config - > hdr . size - sizeof ( struct sof_ext_man_elem_header ) ;
elems_counter = elems_size / sizeof ( struct sof_config_elem ) ;
dev_dbg ( sdev - > dev , " %s can hold up to %d config elements \n " ,
__func__ , elems_counter ) ;
for ( i = 0 ; i < elems_counter ; + + i ) {
elem = & config - > elems [ i ] ;
dev_dbg ( sdev - > dev , " %s get index %d token %d val %d \n " ,
__func__ , i , elem - > token , elem - > value ) ;
switch ( elem - > token ) {
case SOF_EXT_MAN_CONFIG_EMPTY :
/* unused memory space is zero filled - mapped to EMPTY elements */
break ;
case SOF_EXT_MAN_CONFIG_IPC_MSG_SIZE :
/* TODO: use ipc msg size from config data */
break ;
2020-11-24 20:00:17 +02:00
case SOF_EXT_MAN_CONFIG_MEMORY_USAGE_SCAN :
if ( sdev - > first_boot & & elem - > value )
ret = snd_sof_dbg_memory_info_init ( sdev ) ;
break ;
2020-11-24 20:00:14 +02:00
default :
dev_info ( sdev - > dev , " Unknown firmware configuration token %d value %d " ,
elem - > token , elem - > value ) ;
break ;
}
2020-11-24 20:00:17 +02:00
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: processing sof_ext_man_config_data failed for token %d value 0x%x, %d \n " ,
elem - > token , elem - > value , ret ) ;
return ret ;
}
2020-11-24 20:00:14 +02:00
}
return 0 ;
}
2020-05-20 19:59:08 +03:00
static ssize_t snd_sof_ext_man_size ( const struct firmware * fw )
{
const struct sof_ext_man_header * head ;
head = ( struct sof_ext_man_header * ) fw - > data ;
/*
* assert fw size is big enough to contain extended manifest header ,
* it prevents from reading unallocated memory from ` head ` in following
* step .
*/
if ( fw - > size < sizeof ( * head ) )
return - EINVAL ;
/*
* When fw points to extended manifest ,
* then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER .
*/
if ( head - > magic = = SOF_EXT_MAN_MAGIC_NUMBER )
return head - > full_size ;
/* otherwise given fw don't have an extended manifest */
return 0 ;
}
/* parse extended FW manifest data structures */
static int snd_sof_fw_ext_man_parse ( struct snd_sof_dev * sdev ,
const struct firmware * fw )
{
const struct sof_ext_man_elem_header * elem_hdr ;
const struct sof_ext_man_header * head ;
ssize_t ext_man_size ;
ssize_t remaining ;
uintptr_t iptr ;
int ret = 0 ;
head = ( struct sof_ext_man_header * ) fw - > data ;
remaining = head - > full_size - head - > header_size ;
ext_man_size = snd_sof_ext_man_size ( fw ) ;
/* Assert firmware starts with extended manifest */
if ( ext_man_size < = 0 )
return ext_man_size ;
/* incompatible version */
if ( SOF_EXT_MAN_VERSION_INCOMPATIBLE ( SOF_EXT_MAN_VERSION ,
head - > header_version ) ) {
dev_err ( sdev - > dev , " error: extended manifest version 0x%X differ from used 0x%X \n " ,
head - > header_version , SOF_EXT_MAN_VERSION ) ;
return - EINVAL ;
}
/* get first extended manifest element header */
iptr = ( uintptr_t ) fw - > data + head - > header_size ;
while ( remaining > sizeof ( * elem_hdr ) ) {
elem_hdr = ( struct sof_ext_man_elem_header * ) iptr ;
dev_dbg ( sdev - > dev , " found sof_ext_man header type %d size 0x%X \n " ,
elem_hdr - > type , elem_hdr - > size ) ;
if ( elem_hdr - > size < sizeof ( * elem_hdr ) | |
elem_hdr - > size > remaining ) {
dev_err ( sdev - > dev , " error: invalid sof_ext_man header size, type %d size 0x%X \n " ,
elem_hdr - > type , elem_hdr - > size ) ;
return - EINVAL ;
}
/* process structure data */
switch ( elem_hdr - > type ) {
2020-05-20 19:59:09 +03:00
case SOF_EXT_MAN_ELEM_FW_VERSION :
ret = ext_man_get_fw_version ( sdev , elem_hdr ) ;
break ;
2020-05-20 19:59:10 +03:00
case SOF_EXT_MAN_ELEM_WINDOW :
ret = ext_man_get_windows ( sdev , elem_hdr ) ;
break ;
2020-05-20 19:59:11 +03:00
case SOF_EXT_MAN_ELEM_CC_VERSION :
ret = ext_man_get_cc_info ( sdev , elem_hdr ) ;
break ;
2020-08-25 16:58:53 -07:00
case SOF_EXT_MAN_ELEM_DBG_ABI :
ret = ext_man_get_dbg_abi_info ( sdev , elem_hdr ) ;
break ;
2020-11-24 20:00:14 +02:00
case SOF_EXT_MAN_ELEM_CONFIG_DATA :
ret = ext_man_get_config_data ( sdev , elem_hdr ) ;
break ;
2020-11-27 18:40:18 +02:00
case SOF_EXT_MAN_ELEM_PLATFORM_CONFIG_DATA :
ret = snd_sof_dsp_parse_platform_ext_manifest ( sdev , elem_hdr ) ;
break ;
2020-05-20 19:59:08 +03:00
default :
2020-11-11 19:33:21 +02:00
dev_info ( sdev - > dev , " unknown sof_ext_man header type %d size 0x%X \n " ,
2020-05-20 19:59:08 +03:00
elem_hdr - > type , elem_hdr - > size ) ;
break ;
}
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed to parse sof_ext_man header type %d size 0x%X \n " ,
elem_hdr - > type , elem_hdr - > size ) ;
return ret ;
}
remaining - = elem_hdr - > size ;
iptr + = elem_hdr - > size ;
}
if ( remaining ) {
dev_err ( sdev - > dev , " error: sof_ext_man header is inconsistent \n " ) ;
return - EINVAL ;
}
return ext_man_size ;
}
2019-08-07 10:02:01 -05:00
/*
* IPC Firmware ready .
*/
static void sof_get_windows ( struct snd_sof_dev * sdev )
{
struct sof_ipc_window_elem * elem ;
u32 outbox_offset = 0 ;
u32 stream_offset = 0 ;
u32 inbox_offset = 0 ;
u32 outbox_size = 0 ;
u32 stream_size = 0 ;
u32 inbox_size = 0 ;
2020-08-25 16:58:54 -07:00
u32 debug_size = 0 ;
u32 debug_offset = 0 ;
2019-08-07 10:02:01 -05:00
int window_offset ;
int bar ;
int i ;
if ( ! sdev - > info_window ) {
dev_err ( sdev - > dev , " error: have no window info \n " ) ;
return ;
}
bar = snd_sof_dsp_get_bar_index ( sdev , SOF_FW_BLK_TYPE_SRAM ) ;
if ( bar < 0 ) {
dev_err ( sdev - > dev , " error: have no bar mapping \n " ) ;
return ;
}
for ( i = 0 ; i < sdev - > info_window - > num_windows ; i + + ) {
elem = & sdev - > info_window - > window [ i ] ;
window_offset = snd_sof_dsp_get_window_offset ( sdev , elem - > id ) ;
if ( window_offset < 0 ) {
dev_warn ( sdev - > dev , " warn: no offset for window %d \n " ,
elem - > id ) ;
continue ;
}
switch ( elem - > type ) {
case SOF_IPC_REGION_UPBOX :
inbox_offset = window_offset + elem - > offset ;
inbox_size = elem - > size ;
snd_sof_debugfs_io_item ( sdev ,
sdev - > bar [ bar ] +
inbox_offset ,
elem - > size , " inbox " ,
SOF_DEBUGFS_ACCESS_D0_ONLY ) ;
break ;
case SOF_IPC_REGION_DOWNBOX :
outbox_offset = window_offset + elem - > offset ;
outbox_size = elem - > size ;
snd_sof_debugfs_io_item ( sdev ,
sdev - > bar [ bar ] +
outbox_offset ,
elem - > size , " outbox " ,
SOF_DEBUGFS_ACCESS_D0_ONLY ) ;
break ;
case SOF_IPC_REGION_TRACE :
snd_sof_debugfs_io_item ( sdev ,
sdev - > bar [ bar ] +
window_offset +
elem - > offset ,
elem - > size , " etrace " ,
SOF_DEBUGFS_ACCESS_D0_ONLY ) ;
break ;
case SOF_IPC_REGION_DEBUG :
2020-08-25 16:58:54 -07:00
debug_offset = window_offset + elem - > offset ;
debug_size = elem - > size ;
2019-08-07 10:02:01 -05:00
snd_sof_debugfs_io_item ( sdev ,
sdev - > bar [ bar ] +
window_offset +
elem - > offset ,
elem - > size , " debug " ,
SOF_DEBUGFS_ACCESS_D0_ONLY ) ;
break ;
case SOF_IPC_REGION_STREAM :
stream_offset = window_offset + elem - > offset ;
stream_size = elem - > size ;
snd_sof_debugfs_io_item ( sdev ,
sdev - > bar [ bar ] +
stream_offset ,
elem - > size , " stream " ,
SOF_DEBUGFS_ACCESS_D0_ONLY ) ;
break ;
case SOF_IPC_REGION_REGS :
snd_sof_debugfs_io_item ( sdev ,
sdev - > bar [ bar ] +
window_offset +
elem - > offset ,
elem - > size , " regs " ,
SOF_DEBUGFS_ACCESS_D0_ONLY ) ;
break ;
case SOF_IPC_REGION_EXCEPTION :
sdev - > dsp_oops_offset = window_offset + elem - > offset ;
snd_sof_debugfs_io_item ( sdev ,
sdev - > bar [ bar ] +
window_offset +
elem - > offset ,
elem - > size , " exception " ,
SOF_DEBUGFS_ACCESS_D0_ONLY ) ;
break ;
default :
dev_err ( sdev - > dev , " error: get illegal window info \n " ) ;
return ;
}
}
if ( outbox_size = = 0 | | inbox_size = = 0 ) {
dev_err ( sdev - > dev , " error: get illegal mailbox window \n " ) ;
return ;
}
snd_sof_dsp_mailbox_init ( sdev , inbox_offset , inbox_size ,
outbox_offset , outbox_size ) ;
sdev - > stream_box . offset = stream_offset ;
sdev - > stream_box . size = stream_size ;
2020-08-25 16:58:54 -07:00
sdev - > debug_box . offset = debug_offset ;
sdev - > debug_box . size = debug_size ;
2019-08-07 10:02:01 -05:00
dev_dbg ( sdev - > dev , " mailbox upstream 0x%x - size 0x%x \n " ,
inbox_offset , inbox_size ) ;
dev_dbg ( sdev - > dev , " mailbox downstream 0x%x - size 0x%x \n " ,
outbox_offset , outbox_size ) ;
dev_dbg ( sdev - > dev , " stream region 0x%x - size 0x%x \n " ,
stream_offset , stream_size ) ;
2020-08-25 16:58:54 -07:00
dev_dbg ( sdev - > dev , " debug region 0x%x - size 0x%x \n " ,
debug_offset , debug_size ) ;
2019-08-07 10:02:01 -05:00
}
/* check for ABI compatibility and create memory windows on first boot */
int sof_fw_ready ( struct snd_sof_dev * sdev , u32 msg_id )
{
struct sof_ipc_fw_ready * fw_ready = & sdev - > fw_ready ;
int offset ;
int bar ;
int ret ;
/* mailbox must be on 4k boundary */
offset = snd_sof_dsp_get_mailbox_offset ( sdev ) ;
if ( offset < 0 ) {
dev_err ( sdev - > dev , " error: have no mailbox offset \n " ) ;
return offset ;
}
bar = snd_sof_dsp_get_bar_index ( sdev , SOF_FW_BLK_TYPE_SRAM ) ;
if ( bar < 0 ) {
dev_err ( sdev - > dev , " error: have no bar mapping \n " ) ;
return - EINVAL ;
}
dev_dbg ( sdev - > dev , " ipc: DSP is ready 0x%8.8x offset 0x%x \n " ,
msg_id , offset ) ;
/* no need to re-check version/ABI for subsequent boots */
if ( ! sdev - > first_boot )
return 0 ;
/* copy data from the DSP FW ready offset */
2021-05-21 12:28:01 +03:00
snd_sof_dsp_block_read ( sdev , bar , offset , fw_ready , sizeof ( * fw_ready ) ) ;
2019-08-07 10:02:01 -05:00
/* make sure ABI version is compatible */
ret = snd_sof_ipc_valid ( sdev ) ;
if ( ret < 0 )
return ret ;
/* now check for extended data */
snd_sof_fw_parse_ext_data ( sdev , bar , offset +
sizeof ( struct sof_ipc_fw_ready ) ) ;
sof_get_windows ( sdev ) ;
return 0 ;
}
EXPORT_SYMBOL ( sof_fw_ready ) ;
2019-04-12 11:05:14 -05:00
/* generic module parser for mmaped DSPs */
int snd_sof_parse_module_memcpy ( struct snd_sof_dev * sdev ,
struct snd_sof_mod_hdr * module )
{
struct snd_sof_blk_hdr * block ;
2019-07-22 09:13:48 -05:00
int count , bar ;
2019-04-12 11:05:14 -05:00
u32 offset ;
size_t remaining ;
dev_dbg ( sdev - > dev , " new module size 0x%x blocks 0x%x type 0x%x \n " ,
module - > size , module - > num_blocks , module - > type ) ;
block = ( struct snd_sof_blk_hdr * ) ( ( u8 * ) module + sizeof ( * module ) ) ;
/* module->size doesn't include header size */
remaining = module - > size ;
for ( count = 0 ; count < module - > num_blocks ; count + + ) {
/* check for wrap */
if ( remaining < sizeof ( * block ) ) {
dev_err ( sdev - > dev , " error: not enough data remaining \n " ) ;
return - EINVAL ;
}
/* minus header size of block */
remaining - = sizeof ( * block ) ;
if ( block - > size = = 0 ) {
dev_warn ( sdev - > dev ,
" warning: block %d size zero \n " , count ) ;
dev_warn ( sdev - > dev , " type 0x%x offset 0x%x \n " ,
block - > type , block - > offset ) ;
continue ;
}
switch ( block - > type ) {
case SOF_FW_BLK_TYPE_RSRVD0 :
2019-07-22 09:13:49 -05:00
case SOF_FW_BLK_TYPE_ROM . . . SOF_FW_BLK_TYPE_RSRVD14 :
2019-04-12 11:05:14 -05:00
continue ; /* not handled atm */
case SOF_FW_BLK_TYPE_IRAM :
case SOF_FW_BLK_TYPE_DRAM :
2019-07-22 09:13:49 -05:00
case SOF_FW_BLK_TYPE_SRAM :
2019-04-12 11:05:14 -05:00
offset = block - > offset ;
2019-07-22 09:13:48 -05:00
bar = snd_sof_dsp_get_bar_index ( sdev , block - > type ) ;
if ( bar < 0 ) {
dev_err ( sdev - > dev ,
" error: no BAR mapping for block type 0x%x \n " ,
block - > type ) ;
return bar ;
}
2019-04-12 11:05:14 -05:00
break ;
default :
dev_err ( sdev - > dev , " error: bad type 0x%x for block 0x%x \n " ,
block - > type , count ) ;
return - EINVAL ;
}
dev_dbg ( sdev - > dev ,
" block %d type 0x%x size 0x%x ==> offset 0x%x \n " ,
count , block - > type , block - > size , offset ) ;
/* checking block->size to avoid unaligned access */
if ( block - > size % sizeof ( u32 ) ) {
dev_err ( sdev - > dev , " error: invalid block size 0x%x \n " ,
block - > size ) ;
return - EINVAL ;
}
2019-07-22 09:13:48 -05:00
snd_sof_dsp_block_write ( sdev , bar , offset ,
2019-04-12 11:05:14 -05:00
block + 1 , block - > size ) ;
if ( remaining < block - > size ) {
dev_err ( sdev - > dev , " error: not enough data remaining \n " ) ;
return - EINVAL ;
}
/* minus body size of block */
remaining - = block - > size ;
/* next block */
block = ( struct snd_sof_blk_hdr * ) ( ( u8 * ) block + sizeof ( * block )
+ block - > size ) ;
}
return 0 ;
}
EXPORT_SYMBOL ( snd_sof_parse_module_memcpy ) ;
2020-04-15 15:27:55 -05:00
static int check_header ( struct snd_sof_dev * sdev , const struct firmware * fw ,
size_t fw_offset )
2019-04-12 11:05:14 -05:00
{
struct snd_sof_fw_header * header ;
2020-04-15 15:27:55 -05:00
size_t fw_size = fw - > size - fw_offset ;
2020-05-20 19:59:07 +03:00
if ( fw - > size < = fw_offset ) {
2020-04-15 15:27:55 -05:00
dev_err ( sdev - > dev , " error: firmware size must be greater than firmware offset \n " ) ;
return - EINVAL ;
}
2019-04-12 11:05:14 -05:00
/* Read the header information from the data pointer */
2020-04-15 15:27:55 -05:00
header = ( struct snd_sof_fw_header * ) ( fw - > data + fw_offset ) ;
2019-04-12 11:05:14 -05:00
/* verify FW sig */
if ( strncmp ( header - > sig , SND_SOF_FW_SIG , SND_SOF_FW_SIG_SIZE ) ! = 0 ) {
dev_err ( sdev - > dev , " error: invalid firmware signature \n " ) ;
return - EINVAL ;
}
/* check size is valid */
2020-04-15 15:27:55 -05:00
if ( fw_size ! = header - > file_size + sizeof ( * header ) ) {
2019-04-12 11:05:14 -05:00
dev_err ( sdev - > dev , " error: invalid filesize mismatch got 0x%zx expected 0x%zx \n " ,
2020-04-15 15:27:55 -05:00
fw_size , header - > file_size + sizeof ( * header ) ) ;
2019-04-12 11:05:14 -05:00
return - EINVAL ;
}
dev_dbg ( sdev - > dev , " header size=0x%x modules=0x%x abi=0x%x size=%zu \n " ,
header - > file_size , header - > num_modules ,
header - > abi , sizeof ( * header ) ) ;
return 0 ;
}
2020-04-15 15:27:55 -05:00
static int load_modules ( struct snd_sof_dev * sdev , const struct firmware * fw ,
size_t fw_offset )
2019-04-12 11:05:14 -05:00
{
struct snd_sof_fw_header * header ;
struct snd_sof_mod_hdr * module ;
int ( * load_module ) ( struct snd_sof_dev * sof_dev ,
struct snd_sof_mod_hdr * hdr ) ;
int ret , count ;
size_t remaining ;
2020-04-15 15:27:55 -05:00
header = ( struct snd_sof_fw_header * ) ( fw - > data + fw_offset ) ;
2019-04-12 11:05:14 -05:00
load_module = sof_ops ( sdev ) - > load_module ;
if ( ! load_module )
return - EINVAL ;
/* parse each module */
2020-04-15 15:27:55 -05:00
module = ( struct snd_sof_mod_hdr * ) ( fw - > data + fw_offset +
sizeof ( * header ) ) ;
remaining = fw - > size - sizeof ( * header ) - fw_offset ;
2019-04-12 11:05:14 -05:00
/* check for wrap */
if ( remaining > fw - > size ) {
dev_err ( sdev - > dev , " error: fw size smaller than header size \n " ) ;
return - EINVAL ;
}
for ( count = 0 ; count < header - > num_modules ; count + + ) {
/* check for wrap */
if ( remaining < sizeof ( * module ) ) {
dev_err ( sdev - > dev , " error: not enough data remaining \n " ) ;
return - EINVAL ;
}
/* minus header size of module */
remaining - = sizeof ( * module ) ;
/* module */
ret = load_module ( sdev , module ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: invalid module %d \n " , count ) ;
return ret ;
}
if ( remaining < module - > size ) {
dev_err ( sdev - > dev , " error: not enough data remaining \n " ) ;
return - EINVAL ;
}
/* minus body size of module */
remaining - = module - > size ;
module = ( struct snd_sof_mod_hdr * ) ( ( u8 * ) module
+ sizeof ( * module ) + module - > size ) ;
}
return 0 ;
}
int snd_sof_load_firmware_raw ( struct snd_sof_dev * sdev )
{
struct snd_sof_pdata * plat_data = sdev - > pdata ;
const char * fw_filename ;
2020-05-20 19:59:08 +03:00
ssize_t ext_man_size ;
2019-04-12 11:05:14 -05:00
int ret ;
/* Don't request firmware again if firmware is already requested */
if ( plat_data - > fw )
return 0 ;
fw_filename = kasprintf ( GFP_KERNEL , " %s/%s " ,
plat_data - > fw_filename_prefix ,
plat_data - > fw_filename ) ;
if ( ! fw_filename )
return - ENOMEM ;
ret = request_firmware ( & plat_data - > fw , fw_filename , sdev - > dev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: request firmware %s failed err: %d \n " ,
fw_filename , ret ) ;
2021-01-27 14:23:58 +02:00
dev_err ( sdev - > dev ,
" you may need to download the firmware from https://github.com/thesofproject/sof-bin/ \n " ) ;
2020-05-20 19:59:08 +03:00
goto err ;
2020-01-07 10:08:40 -06:00
} else {
dev_dbg ( sdev - > dev , " request_firmware %s successful \n " ,
fw_filename ) ;
2019-04-12 11:05:14 -05:00
}
2020-05-20 19:59:08 +03:00
/* check for extended manifest */
ext_man_size = snd_sof_fw_ext_man_parse ( sdev , plat_data - > fw ) ;
if ( ext_man_size > 0 ) {
/* when no error occurred, drop extended manifest */
plat_data - > fw_offset = ext_man_size ;
} else if ( ! ext_man_size ) {
/* No extended manifest, so nothing to skip during FW load */
dev_dbg ( sdev - > dev , " firmware doesn't contain extended manifest \n " ) ;
} else {
ret = ext_man_size ;
dev_err ( sdev - > dev , " error: firmware %s contains unsupported or invalid extended manifest: %d \n " ,
fw_filename , ret ) ;
}
err :
2019-04-12 11:05:14 -05:00
kfree ( fw_filename ) ;
return ret ;
}
EXPORT_SYMBOL ( snd_sof_load_firmware_raw ) ;
int snd_sof_load_firmware_memcpy ( struct snd_sof_dev * sdev )
{
struct snd_sof_pdata * plat_data = sdev - > pdata ;
int ret ;
ret = snd_sof_load_firmware_raw ( sdev ) ;
if ( ret < 0 )
return ret ;
/* make sure the FW header and file is valid */
2020-04-15 15:27:55 -05:00
ret = check_header ( sdev , plat_data - > fw , plat_data - > fw_offset ) ;
2019-04-12 11:05:14 -05:00
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: invalid FW header \n " ) ;
goto error ;
}
/* prepare the DSP for FW loading */
ret = snd_sof_dsp_reset ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed to reset DSP \n " ) ;
goto error ;
}
/* parse and load firmware modules to DSP */
2020-04-15 15:27:55 -05:00
ret = load_modules ( sdev , plat_data - > fw , plat_data - > fw_offset ) ;
2019-04-12 11:05:14 -05:00
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: invalid FW modules \n " ) ;
goto error ;
}
return 0 ;
error :
release_firmware ( plat_data - > fw ) ;
plat_data - > fw = NULL ;
return ret ;
}
EXPORT_SYMBOL ( snd_sof_load_firmware_memcpy ) ;
int snd_sof_load_firmware ( struct snd_sof_dev * sdev )
{
dev_dbg ( sdev - > dev , " loading firmware \n " ) ;
if ( sof_ops ( sdev ) - > load_firmware )
return sof_ops ( sdev ) - > load_firmware ( sdev ) ;
return 0 ;
}
EXPORT_SYMBOL ( snd_sof_load_firmware ) ;
int snd_sof_run_firmware ( struct snd_sof_dev * sdev )
{
int ret ;
init_waitqueue_head ( & sdev - > boot_wait ) ;
2019-06-03 11:18:18 -05:00
/* create read-only fw_version debugfs to store boot version info */
2019-04-12 11:05:14 -05:00
if ( sdev - > first_boot ) {
ret = snd_sof_debugfs_buf_item ( sdev , & sdev - > fw_version ,
sizeof ( sdev - > fw_version ) ,
2019-06-03 11:18:18 -05:00
" fw_version " , 0444 ) ;
2019-04-12 11:05:14 -05:00
/* errors are only due to memory allocation, not debugfs */
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: snd_sof_debugfs_buf_item failed \n " ) ;
return ret ;
}
}
/* perform pre fw run operations */
ret = snd_sof_dsp_pre_fw_run ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed pre fw run op \n " ) ;
return ret ;
}
dev_dbg ( sdev - > dev , " booting DSP firmware \n " ) ;
/* boot the firmware on the DSP */
ret = snd_sof_dsp_run ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed to reset DSP \n " ) ;
return ret ;
}
2019-12-17 18:26:09 -06:00
/*
* now wait for the DSP to boot . There are 3 possible outcomes :
* 1. Boot wait times out indicating FW boot failure .
* 2. FW boots successfully and fw_ready op succeeds .
* 3. FW boots but fw_ready op fails .
*/
ret = wait_event_timeout ( sdev - > boot_wait ,
sdev - > fw_state > SOF_FW_BOOT_IN_PROGRESS ,
2019-04-12 11:05:14 -05:00
msecs_to_jiffies ( sdev - > boot_timeout ) ) ;
if ( ret = = 0 ) {
dev_err ( sdev - > dev , " error: firmware boot failure \n " ) ;
2020-12-11 12:07:42 +02:00
snd_sof_dsp_dbg_dump ( sdev , SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX |
2020-12-11 12:07:43 +02:00
SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_FORCE_ERR_LEVEL ) ;
2019-12-17 18:26:09 -06:00
sdev - > fw_state = SOF_FW_BOOT_FAILED ;
2019-04-12 11:05:14 -05:00
return - EIO ;
}
2019-12-17 18:26:09 -06:00
if ( sdev - > fw_state = = SOF_FW_BOOT_COMPLETE )
2020-04-02 20:49:48 +02:00
dev_dbg ( sdev - > dev , " firmware boot complete \n " ) ;
2019-12-17 18:26:09 -06:00
else
return - EIO ; /* FW boots but fw_ready op failed */
2019-04-12 11:05:14 -05:00
/* perform post fw run operations */
ret = snd_sof_dsp_post_fw_run ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed post fw run op \n " ) ;
return ret ;
}
return 0 ;
}
EXPORT_SYMBOL ( snd_sof_run_firmware ) ;
void snd_sof_fw_unload ( struct snd_sof_dev * sdev )
{
/* TODO: support module unloading at runtime */
}
EXPORT_SYMBOL ( snd_sof_fw_unload ) ;