2022-05-11 10:16:47 -07:00
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// 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) 2022 Intel Corporation. All rights reserved.
# include <linux/firmware.h>
# include <sound/sof/ext_manifest4.h>
# include <sound/sof/ipc4/header.h>
2022-09-19 14:21:08 +02:00
# include <trace/events/sof.h>
2022-05-11 10:16:47 -07:00
# include "ipc4-priv.h"
# include "sof-audio.h"
# include "sof-priv.h"
# include "ops.h"
2022-10-20 15:12:38 +03:00
/* The module ID includes the id of the library it is part of at offset 12 */
# define SOF_IPC4_MOD_LIB_ID_SHIFT 12
2022-10-25 16:27:06 +03:00
static ssize_t sof_ipc4_fw_parse_ext_man ( struct snd_sof_dev * sdev ,
struct sof_ipc4_fw_library * fw_lib )
2022-05-11 10:16:47 -07:00
{
struct sof_ipc4_fw_data * ipc4_data = sdev - > private ;
2022-10-20 15:12:28 +03:00
const struct firmware * fw = fw_lib - > sof_fw . fw ;
2022-05-11 10:16:47 -07:00
struct sof_man4_fw_binary_header * fw_header ;
struct sof_ext_manifest4_hdr * ext_man_hdr ;
struct sof_man4_module_config * fm_config ;
struct sof_ipc4_fw_module * fw_module ;
struct sof_man4_module * fm_entry ;
ssize_t remaining ;
u32 fw_hdr_offset ;
int i ;
if ( ! ipc4_data ) {
dev_err ( sdev - > dev , " %s: ipc4_data is not available \n " , __func__ ) ;
return - EINVAL ;
}
remaining = fw - > size ;
if ( remaining < = sizeof ( * ext_man_hdr ) ) {
dev_err ( sdev - > dev , " Firmware size is too small: %zu \n " , remaining ) ;
return - EINVAL ;
}
ext_man_hdr = ( struct sof_ext_manifest4_hdr * ) fw - > data ;
2022-08-23 15:42:19 +03:00
/*
* At the start of the firmware image we must have an extended manifest .
* Verify that the magic number is correct .
*/
if ( ext_man_hdr - > id ! = SOF_EXT_MAN4_MAGIC_NUMBER ) {
dev_err ( sdev - > dev ,
" Unexpected extended manifest magic number: %#x \n " ,
ext_man_hdr - > id ) ;
return - EINVAL ;
}
2022-05-11 10:16:47 -07:00
fw_hdr_offset = ipc4_data - > manifest_fw_hdr_offset ;
if ( ! fw_hdr_offset )
return - EINVAL ;
if ( remaining < = ext_man_hdr - > len + fw_hdr_offset + sizeof ( * fw_header ) ) {
dev_err ( sdev - > dev , " Invalid firmware size %zu, should be at least %zu \n " ,
remaining , ext_man_hdr - > len + fw_hdr_offset + sizeof ( * fw_header ) ) ;
return - EINVAL ;
}
fw_header = ( struct sof_man4_fw_binary_header * )
( fw - > data + ext_man_hdr - > len + fw_hdr_offset ) ;
remaining - = ( ext_man_hdr - > len + fw_hdr_offset ) ;
if ( remaining < = fw_header - > len ) {
dev_err ( sdev - > dev , " Invalid fw_header->len %u \n " , fw_header - > len ) ;
return - EINVAL ;
}
2022-10-20 15:12:38 +03:00
dev_info ( sdev - > dev , " Loaded firmware library: %s, version: %u.%u.%u.%u \n " ,
fw_header - > name , fw_header - > major_version , fw_header - > minor_version ,
2022-05-11 10:16:47 -07:00
fw_header - > hotfix_version , fw_header - > build_version ) ;
2022-10-20 15:12:38 +03:00
dev_dbg ( sdev - > dev , " Header length: %u, module count: %u \n " ,
fw_header - > len , fw_header - > num_module_entries ) ;
2022-05-11 10:16:47 -07:00
2022-10-20 15:12:28 +03:00
fw_lib - > modules = devm_kmalloc_array ( sdev - > dev , fw_header - > num_module_entries ,
sizeof ( * fw_module ) , GFP_KERNEL ) ;
if ( ! fw_lib - > modules )
2022-05-11 10:16:47 -07:00
return - ENOMEM ;
2022-10-20 15:12:38 +03:00
fw_lib - > name = fw_header - > name ;
2022-10-20 15:12:28 +03:00
fw_lib - > num_modules = fw_header - > num_module_entries ;
fw_module = fw_lib - > modules ;
2022-05-11 10:16:47 -07:00
fm_entry = ( struct sof_man4_module * ) ( ( u8 * ) fw_header + fw_header - > len ) ;
remaining - = fw_header - > len ;
if ( remaining < fw_header - > num_module_entries * sizeof ( * fm_entry ) ) {
dev_err ( sdev - > dev , " Invalid num_module_entries %u \n " ,
fw_header - > num_module_entries ) ;
return - EINVAL ;
}
fm_config = ( struct sof_man4_module_config * )
( fm_entry + fw_header - > num_module_entries ) ;
remaining - = ( fw_header - > num_module_entries * sizeof ( * fm_entry ) ) ;
for ( i = 0 ; i < fw_header - > num_module_entries ; i + + ) {
memcpy ( & fw_module - > man4_module_entry , fm_entry , sizeof ( * fm_entry ) ) ;
if ( fm_entry - > cfg_count ) {
if ( remaining < ( fm_entry - > cfg_offset + fm_entry - > cfg_count ) *
sizeof ( * fm_config ) ) {
dev_err ( sdev - > dev , " Invalid module cfg_offset %u \n " ,
fm_entry - > cfg_offset ) ;
return - EINVAL ;
}
2023-05-22 13:13:10 +03:00
fw_module - > fw_mod_cfg = & fm_config [ fm_entry - > cfg_offset ] ;
2022-05-11 10:16:47 -07:00
dev_dbg ( sdev - > dev ,
" module %s: UUID %pUL cfg_count: %u, bss_size: %#x \n " ,
fm_entry - > name , & fm_entry - > uuid , fm_entry - > cfg_count ,
2023-05-22 13:13:09 +03:00
fm_config [ fm_entry - > cfg_offset ] . is_bytes ) ;
2022-05-11 10:16:47 -07:00
} else {
dev_dbg ( sdev - > dev , " module %s: UUID %pUL \n " , fm_entry - > name ,
& fm_entry - > uuid ) ;
}
fw_module - > man4_module_entry . id = i ;
ida_init ( & fw_module - > m_ida ) ;
fw_module - > private = NULL ;
fw_module + + ;
fm_entry + + ;
}
return ext_man_hdr - > len ;
}
2022-10-20 15:12:28 +03:00
static size_t sof_ipc4_fw_parse_basefw_ext_man ( struct snd_sof_dev * sdev )
{
struct sof_ipc4_fw_data * ipc4_data = sdev - > private ;
struct sof_ipc4_fw_library * fw_lib ;
2022-10-25 16:27:06 +03:00
ssize_t payload_offset ;
2022-10-20 15:12:28 +03:00
int ret ;
fw_lib = devm_kzalloc ( sdev - > dev , sizeof ( * fw_lib ) , GFP_KERNEL ) ;
if ( ! fw_lib )
return - ENOMEM ;
fw_lib - > sof_fw . fw = sdev - > basefw . fw ;
payload_offset = sof_ipc4_fw_parse_ext_man ( sdev , fw_lib ) ;
if ( payload_offset > 0 ) {
fw_lib - > sof_fw . payload_offset = payload_offset ;
/* basefw ID is 0 */
fw_lib - > id = 0 ;
ret = xa_insert ( & ipc4_data - > fw_lib_xa , 0 , fw_lib , GFP_KERNEL ) ;
if ( ret )
return ret ;
}
return payload_offset ;
}
2022-10-20 15:12:38 +03:00
static int sof_ipc4_load_library_by_uuid ( struct snd_sof_dev * sdev ,
unsigned long lib_id , const guid_t * uuid )
{
struct sof_ipc4_fw_data * ipc4_data = sdev - > private ;
struct sof_ipc4_fw_library * fw_lib ;
const char * fw_filename ;
2022-10-25 16:27:06 +03:00
ssize_t payload_offset ;
2022-10-20 15:12:38 +03:00
int ret , i , err ;
if ( ! sdev - > pdata - > fw_lib_prefix ) {
dev_err ( sdev - > dev ,
" Library loading is not supported due to not set library path \n " ) ;
return - EINVAL ;
}
if ( ! ipc4_data - > load_library ) {
dev_err ( sdev - > dev , " Library loading is not supported on this platform \n " ) ;
return - EOPNOTSUPP ;
}
fw_lib = devm_kzalloc ( sdev - > dev , sizeof ( * fw_lib ) , GFP_KERNEL ) ;
if ( ! fw_lib )
return - ENOMEM ;
fw_filename = kasprintf ( GFP_KERNEL , " %s/%pUL.bin " ,
sdev - > pdata - > fw_lib_prefix , uuid ) ;
if ( ! fw_filename ) {
ret = - ENOMEM ;
goto free_fw_lib ;
}
ret = request_firmware ( & fw_lib - > sof_fw . fw , fw_filename , sdev - > dev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " Library file '%s' is missing \n " , fw_filename ) ;
goto free_filename ;
} else {
dev_dbg ( sdev - > dev , " Library file '%s' loaded \n " , fw_filename ) ;
}
payload_offset = sof_ipc4_fw_parse_ext_man ( sdev , fw_lib ) ;
if ( payload_offset < = 0 ) {
if ( ! payload_offset )
ret = - EINVAL ;
else
ret = payload_offset ;
goto release ;
}
fw_lib - > sof_fw . payload_offset = payload_offset ;
fw_lib - > id = lib_id ;
/* Fix up the module ID numbers within the library */
for ( i = 0 ; i < fw_lib - > num_modules ; i + + )
fw_lib - > modules [ i ] . man4_module_entry . id | = ( lib_id < < SOF_IPC4_MOD_LIB_ID_SHIFT ) ;
/*
* Make sure that the DSP is booted and stays up while attempting the
* loading the library for the first time
*/
ret = pm_runtime_resume_and_get ( sdev - > dev ) ;
if ( ret < 0 & & ret ! = - EACCES ) {
dev_err_ratelimited ( sdev - > dev , " %s: pm_runtime resume failed: %d \n " ,
__func__ , ret ) ;
goto release ;
}
ret = ipc4_data - > load_library ( sdev , fw_lib , false ) ;
pm_runtime_mark_last_busy ( sdev - > dev ) ;
err = pm_runtime_put_autosuspend ( sdev - > dev ) ;
if ( err < 0 )
dev_err_ratelimited ( sdev - > dev , " %s: pm_runtime idle failed: %d \n " ,
__func__ , err ) ;
if ( ret )
goto release ;
ret = xa_insert ( & ipc4_data - > fw_lib_xa , lib_id , fw_lib , GFP_KERNEL ) ;
if ( unlikely ( ret ) )
goto release ;
kfree ( fw_filename ) ;
return 0 ;
release :
release_firmware ( fw_lib - > sof_fw . fw ) ;
/* Allocated within sof_ipc4_fw_parse_ext_man() */
devm_kfree ( sdev - > dev , fw_lib - > modules ) ;
free_filename :
kfree ( fw_filename ) ;
free_fw_lib :
devm_kfree ( sdev - > dev , fw_lib ) ;
return ret ;
}
2022-10-20 15:12:29 +03:00
struct sof_ipc4_fw_module * sof_ipc4_find_module_by_uuid ( struct snd_sof_dev * sdev ,
const guid_t * uuid )
{
struct sof_ipc4_fw_data * ipc4_data = sdev - > private ;
struct sof_ipc4_fw_library * fw_lib ;
unsigned long lib_id ;
2022-10-20 15:12:38 +03:00
int i , ret ;
2022-10-20 15:12:29 +03:00
if ( guid_is_null ( uuid ) )
return NULL ;
xa_for_each ( & ipc4_data - > fw_lib_xa , lib_id , fw_lib ) {
for ( i = 0 ; i < fw_lib - > num_modules ; i + + ) {
if ( guid_equal ( uuid , & fw_lib - > modules [ i ] . man4_module_entry . uuid ) )
return & fw_lib - > modules [ i ] ;
}
}
2022-10-20 15:12:38 +03:00
/*
* Do not attempt to load external library in case the maximum number of
* firmware libraries have been already loaded
*/
if ( ( lib_id + 1 ) = = ipc4_data - > max_libs_count ) {
dev_err ( sdev - > dev ,
" %s: Maximum allowed number of libraries reached (%u) \n " ,
__func__ , ipc4_data - > max_libs_count ) ;
return NULL ;
}
/* The module cannot be found, try to load it as a library */
ret = sof_ipc4_load_library_by_uuid ( sdev , lib_id + 1 , uuid ) ;
if ( ret )
return NULL ;
/* Look for the module in the newly loaded library, it should be available now */
xa_for_each_start ( & ipc4_data - > fw_lib_xa , lib_id , fw_lib , lib_id ) {
for ( i = 0 ; i < fw_lib - > num_modules ; i + + ) {
if ( guid_equal ( uuid , & fw_lib - > modules [ i ] . man4_module_entry . uuid ) )
return & fw_lib - > modules [ i ] ;
}
}
2022-10-20 15:12:29 +03:00
return NULL ;
}
2022-05-11 10:16:47 -07:00
static int sof_ipc4_validate_firmware ( struct snd_sof_dev * sdev )
{
struct sof_ipc4_fw_data * ipc4_data = sdev - > private ;
u32 fw_hdr_offset = ipc4_data - > manifest_fw_hdr_offset ;
struct sof_man4_fw_binary_header * fw_header ;
2022-10-20 15:12:21 +03:00
const struct firmware * fw = sdev - > basefw . fw ;
2022-05-11 10:16:47 -07:00
struct sof_ext_manifest4_hdr * ext_man_hdr ;
ext_man_hdr = ( struct sof_ext_manifest4_hdr * ) fw - > data ;
fw_header = ( struct sof_man4_fw_binary_header * )
( fw - > data + ext_man_hdr - > len + fw_hdr_offset ) ;
/* TODO: Add firmware verification code here */
dev_dbg ( sdev - > dev , " Validated firmware version: %u.%u.%u.%u \n " ,
fw_header - > major_version , fw_header - > minor_version ,
fw_header - > hotfix_version , fw_header - > build_version ) ;
return 0 ;
}
2022-10-20 15:12:36 +03:00
int sof_ipc4_query_fw_configuration ( struct snd_sof_dev * sdev )
2022-05-11 10:16:47 -07:00
{
2022-09-09 14:43:30 +03:00
struct sof_ipc4_fw_data * ipc4_data = sdev - > private ;
2022-05-11 10:16:47 -07:00
const struct sof_ipc_ops * iops = sdev - > ipc - > ops ;
struct sof_ipc4_fw_version * fw_ver ;
struct sof_ipc4_tuple * tuple ;
struct sof_ipc4_msg msg ;
size_t offset = 0 ;
int ret ;
/* Get the firmware configuration */
msg . primary = SOF_IPC4_MSG_TARGET ( SOF_IPC4_MODULE_MSG ) ;
msg . primary | = SOF_IPC4_MSG_DIR ( SOF_IPC4_MSG_REQUEST ) ;
msg . primary | = SOF_IPC4_MOD_ID ( SOF_IPC4_MOD_INIT_BASEFW_MOD_ID ) ;
msg . primary | = SOF_IPC4_MOD_INSTANCE ( SOF_IPC4_MOD_INIT_BASEFW_INSTANCE_ID ) ;
msg . extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID ( SOF_IPC4_FW_PARAM_FW_CONFIG ) ;
msg . data_size = sdev - > ipc - > max_payload_size ;
msg . data_ptr = kzalloc ( msg . data_size , GFP_KERNEL ) ;
if ( ! msg . data_ptr )
return - ENOMEM ;
ret = iops - > set_get_data ( sdev , & msg , msg . data_size , false ) ;
if ( ret )
goto out ;
while ( offset < msg . data_size ) {
tuple = ( struct sof_ipc4_tuple * ) ( ( u8 * ) msg . data_ptr + offset ) ;
switch ( tuple - > type ) {
case SOF_IPC4_FW_CFG_FW_VERSION :
fw_ver = ( struct sof_ipc4_fw_version * ) tuple - > value ;
dev_info ( sdev - > dev ,
" Booted firmware version: %u.%u.%u.%u \n " ,
fw_ver - > major , fw_ver - > minor , fw_ver - > hotfix ,
fw_ver - > build ) ;
break ;
case SOF_IPC4_FW_CFG_DL_MAILBOX_BYTES :
2022-09-19 14:21:08 +02:00
trace_sof_ipc4_fw_config ( sdev , " DL mailbox size " , * tuple - > value ) ;
2022-05-11 10:16:47 -07:00
break ;
case SOF_IPC4_FW_CFG_UL_MAILBOX_BYTES :
2022-09-19 14:21:08 +02:00
trace_sof_ipc4_fw_config ( sdev , " UL mailbox size " , * tuple - > value ) ;
2022-05-11 10:16:47 -07:00
break ;
case SOF_IPC4_FW_CFG_TRACE_LOG_BYTES :
2022-09-19 14:21:08 +02:00
trace_sof_ipc4_fw_config ( sdev , " Trace log size " , * tuple - > value ) ;
2022-09-09 14:43:30 +03:00
ipc4_data - > mtrace_log_bytes = * tuple - > value ;
2022-05-11 10:16:47 -07:00
break ;
2022-10-20 15:12:27 +03:00
case SOF_IPC4_FW_CFG_MAX_LIBS_COUNT :
trace_sof_ipc4_fw_config ( sdev , " maximum number of libraries " ,
* tuple - > value ) ;
ipc4_data - > max_libs_count = * tuple - > value ;
if ( ! ipc4_data - > max_libs_count )
ipc4_data - > max_libs_count = 1 ;
break ;
2022-11-08 08:29:09 +08:00
case SOF_IPC4_FW_CFG_MAX_PPL_COUNT :
ipc4_data - > max_num_pipelines = * tuple - > value ;
trace_sof_ipc4_fw_config ( sdev , " Max PPL count %d " ,
ipc4_data - > max_num_pipelines ) ;
if ( ipc4_data - > max_num_pipelines < = 0 ) {
dev_err ( sdev - > dev , " Invalid max_num_pipelines %d " ,
ipc4_data - > max_num_pipelines ) ;
ret = - EINVAL ;
goto out ;
}
break ;
2022-05-11 10:16:47 -07:00
default :
break ;
}
offset + = sizeof ( * tuple ) + tuple - > size ;
}
out :
kfree ( msg . data_ptr ) ;
return ret ;
}
2022-10-20 15:12:38 +03:00
int sof_ipc4_reload_fw_libraries ( struct snd_sof_dev * sdev )
{
struct sof_ipc4_fw_data * ipc4_data = sdev - > private ;
struct sof_ipc4_fw_library * fw_lib ;
unsigned long lib_id ;
int ret = 0 ;
xa_for_each_start ( & ipc4_data - > fw_lib_xa , lib_id , fw_lib , 1 ) {
ret = ipc4_data - > load_library ( sdev , fw_lib , true ) ;
if ( ret ) {
dev_err ( sdev - > dev , " %s: Failed to reload library: %s, %d \n " ,
__func__ , fw_lib - > name , ret ) ;
break ;
}
}
return ret ;
}
2023-05-22 13:13:13 +03:00
/**
* sof_ipc4_update_cpc_from_manifest - Update the cpc in base config from manifest
* @ sdev : SOF device
* @ fw_module : pointer struct sof_ipc4_fw_module to parse
* @ basecfg : Pointer to the base_config to update
*/
void sof_ipc4_update_cpc_from_manifest ( struct snd_sof_dev * sdev ,
struct sof_ipc4_fw_module * fw_module ,
struct sof_ipc4_base_module_cfg * basecfg )
{
const struct sof_man4_module_config * fw_mod_cfg ;
u32 cpc_pick = 0 ;
u32 max_cpc = 0 ;
const char * msg ;
int i ;
if ( ! fw_module - > fw_mod_cfg ) {
msg = " No mod_cfg available for CPC lookup in the firmware file's manifest " ;
goto no_cpc ;
}
/*
* Find the best matching ( highest ) CPC value based on the module ' s
* IBS / OBS configuration inferred from the audio format selection .
*
* The CPC value in each module config entry has been measured and
* recorded as a IBS / OBS / CPC triplet and stored in the firmware file ' s
* manifest
*/
fw_mod_cfg = fw_module - > fw_mod_cfg ;
for ( i = 0 ; i < fw_module - > man4_module_entry . cfg_count ; i + + ) {
if ( basecfg - > obs = = fw_mod_cfg [ i ] . obs & &
basecfg - > ibs = = fw_mod_cfg [ i ] . ibs & &
cpc_pick < fw_mod_cfg [ i ] . cpc )
cpc_pick = fw_mod_cfg [ i ] . cpc ;
if ( max_cpc < fw_mod_cfg [ i ] . cpc )
max_cpc = fw_mod_cfg [ i ] . cpc ;
}
basecfg - > cpc = cpc_pick ;
/* We have a matching configuration for CPC */
if ( basecfg - > cpc )
return ;
/*
* No matching IBS / OBS found , the firmware manifest is missing
* information in the module ' s module configuration table .
*/
if ( ! max_cpc )
msg = " No CPC value available in the firmware file's manifest " ;
else if ( ! cpc_pick )
msg = " No CPC match in the firmware file's manifest " ;
no_cpc :
dev_warn ( sdev - > dev , " %s (UUID: %pUL): %s (ibs/obs: %u/%u) \n " ,
fw_module - > man4_module_entry . name ,
& fw_module - > man4_module_entry . uuid , msg , basecfg - > ibs ,
basecfg - > obs ) ;
dev_warn_once ( sdev - > dev , " Please try to update the firmware. \n " ) ;
dev_warn_once ( sdev - > dev , " If the issue persists, file a bug at \n " ) ;
dev_warn_once ( sdev - > dev , " https://github.com/thesofproject/sof/issues/ \n " ) ;
}
2022-05-11 10:16:47 -07:00
const struct sof_ipc_fw_loader_ops ipc4_loader_ops = {
. validate = sof_ipc4_validate_firmware ,
2022-10-20 15:12:28 +03:00
. parse_ext_manifest = sof_ipc4_fw_parse_basefw_ext_man ,
2022-05-11 10:16:47 -07:00
} ;