// 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 #include #include #include #include "ipc4-priv.h" #include "sof-audio.h" #include "sof-priv.h" #include "ops.h" /* The module ID includes the id of the library it is part of at offset 12 */ #define SOF_IPC4_MOD_LIB_ID_SHIFT 12 static ssize_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; const struct firmware *fw = fw_lib->sof_fw.fw; 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; /* * 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; } 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; } 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, fw_header->hotfix_version, fw_header->build_version); dev_dbg(sdev->dev, "Header length: %u, module count: %u\n", fw_header->len, fw_header->num_module_entries); fw_lib->modules = devm_kmalloc_array(sdev->dev, fw_header->num_module_entries, sizeof(*fw_module), GFP_KERNEL); if (!fw_lib->modules) return -ENOMEM; fw_lib->name = fw_header->name; fw_lib->num_modules = fw_header->num_module_entries; fw_module = fw_lib->modules; 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; } fw_module->fw_mod_cfg = &fm_config[fm_entry->cfg_offset]; 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, fm_config[fm_entry->cfg_offset].is_bytes); } 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; } 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; ssize_t payload_offset; 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; } 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; ssize_t payload_offset; 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; } 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; int i, ret; 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]; } } /* * 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]; } } return NULL; } 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; const struct firmware *fw = sdev->basefw.fw; 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; } int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; 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: trace_sof_ipc4_fw_config(sdev, "DL mailbox size", *tuple->value); break; case SOF_IPC4_FW_CFG_UL_MAILBOX_BYTES: trace_sof_ipc4_fw_config(sdev, "UL mailbox size", *tuple->value); break; case SOF_IPC4_FW_CFG_TRACE_LOG_BYTES: trace_sof_ipc4_fw_config(sdev, "Trace log size", *tuple->value); ipc4_data->mtrace_log_bytes = *tuple->value; break; 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; 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; case SOF_IPC4_FW_CONTEXT_SAVE: ipc4_data->fw_context_save = *tuple->value; break; default: break; } offset += sizeof(*tuple) + tuple->size; } out: kfree(msg.data_ptr); return ret; } 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; } /** * 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_dbg(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); } const struct sof_ipc_fw_loader_ops ipc4_loader_ops = { .validate = sof_ipc4_validate_firmware, .parse_ext_manifest = sof_ipc4_fw_parse_basefw_ext_man, };