162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// This file is provided under a dual BSD/GPLv2 license. When using or 462306a36Sopenharmony_ci// redistributing this file, you may do so under either license. 562306a36Sopenharmony_ci// 662306a36Sopenharmony_ci// Copyright(c) 2018 Intel Corporation. All rights reserved. 762306a36Sopenharmony_ci// 862306a36Sopenharmony_ci// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> 962306a36Sopenharmony_ci// 1062306a36Sopenharmony_ci// Generic firmware loader. 1162306a36Sopenharmony_ci// 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/firmware.h> 1462306a36Sopenharmony_ci#include "sof-priv.h" 1562306a36Sopenharmony_ci#include "ops.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ciint snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci struct snd_sof_pdata *plat_data = sdev->pdata; 2062306a36Sopenharmony_ci const char *fw_filename; 2162306a36Sopenharmony_ci ssize_t ext_man_size; 2262306a36Sopenharmony_ci int ret; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci /* Don't request firmware again if firmware is already requested */ 2562306a36Sopenharmony_ci if (sdev->basefw.fw) 2662306a36Sopenharmony_ci return 0; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci fw_filename = kasprintf(GFP_KERNEL, "%s/%s", 2962306a36Sopenharmony_ci plat_data->fw_filename_prefix, 3062306a36Sopenharmony_ci plat_data->fw_filename); 3162306a36Sopenharmony_ci if (!fw_filename) 3262306a36Sopenharmony_ci return -ENOMEM; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci if (ret < 0) { 3762306a36Sopenharmony_ci dev_err(sdev->dev, 3862306a36Sopenharmony_ci "error: sof firmware file is missing, you might need to\n"); 3962306a36Sopenharmony_ci dev_err(sdev->dev, 4062306a36Sopenharmony_ci " download it from https://github.com/thesofproject/sof-bin/\n"); 4162306a36Sopenharmony_ci goto err; 4262306a36Sopenharmony_ci } else { 4362306a36Sopenharmony_ci dev_dbg(sdev->dev, "request_firmware %s successful\n", 4462306a36Sopenharmony_ci fw_filename); 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci /* check for extended manifest */ 4862306a36Sopenharmony_ci ext_man_size = sdev->ipc->ops->fw_loader->parse_ext_manifest(sdev); 4962306a36Sopenharmony_ci if (ext_man_size > 0) { 5062306a36Sopenharmony_ci /* when no error occurred, drop extended manifest */ 5162306a36Sopenharmony_ci sdev->basefw.payload_offset = ext_man_size; 5262306a36Sopenharmony_ci } else if (!ext_man_size) { 5362306a36Sopenharmony_ci /* No extended manifest, so nothing to skip during FW load */ 5462306a36Sopenharmony_ci dev_dbg(sdev->dev, "firmware doesn't contain extended manifest\n"); 5562306a36Sopenharmony_ci } else { 5662306a36Sopenharmony_ci ret = ext_man_size; 5762306a36Sopenharmony_ci dev_err(sdev->dev, "error: firmware %s contains unsupported or invalid extended manifest: %d\n", 5862306a36Sopenharmony_ci fw_filename, ret); 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cierr: 6262306a36Sopenharmony_ci kfree(fw_filename); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return ret; 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_sof_load_firmware_raw); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ciint snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci int ret; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci ret = snd_sof_load_firmware_raw(sdev); 7362306a36Sopenharmony_ci if (ret < 0) 7462306a36Sopenharmony_ci return ret; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* make sure the FW header and file is valid */ 7762306a36Sopenharmony_ci ret = sdev->ipc->ops->fw_loader->validate(sdev); 7862306a36Sopenharmony_ci if (ret < 0) { 7962306a36Sopenharmony_ci dev_err(sdev->dev, "error: invalid FW header\n"); 8062306a36Sopenharmony_ci goto error; 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci /* prepare the DSP for FW loading */ 8462306a36Sopenharmony_ci ret = snd_sof_dsp_reset(sdev); 8562306a36Sopenharmony_ci if (ret < 0) { 8662306a36Sopenharmony_ci dev_err(sdev->dev, "error: failed to reset DSP\n"); 8762306a36Sopenharmony_ci goto error; 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci /* parse and load firmware modules to DSP */ 9162306a36Sopenharmony_ci if (sdev->ipc->ops->fw_loader->load_fw_to_dsp) { 9262306a36Sopenharmony_ci ret = sdev->ipc->ops->fw_loader->load_fw_to_dsp(sdev); 9362306a36Sopenharmony_ci if (ret < 0) { 9462306a36Sopenharmony_ci dev_err(sdev->dev, "Firmware loading failed\n"); 9562306a36Sopenharmony_ci goto error; 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci return 0; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cierror: 10262306a36Sopenharmony_ci release_firmware(sdev->basefw.fw); 10362306a36Sopenharmony_ci sdev->basefw.fw = NULL; 10462306a36Sopenharmony_ci return ret; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ciEXPORT_SYMBOL(snd_sof_load_firmware_memcpy); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ciint snd_sof_run_firmware(struct snd_sof_dev *sdev) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci int ret; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci init_waitqueue_head(&sdev->boot_wait); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci /* (re-)enable dsp dump */ 11662306a36Sopenharmony_ci sdev->dbg_dump_printed = false; 11762306a36Sopenharmony_ci sdev->ipc_dump_printed = false; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci /* create read-only fw_version debugfs to store boot version info */ 12062306a36Sopenharmony_ci if (sdev->first_boot) { 12162306a36Sopenharmony_ci ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version, 12262306a36Sopenharmony_ci sizeof(sdev->fw_version), 12362306a36Sopenharmony_ci "fw_version", 0444); 12462306a36Sopenharmony_ci /* errors are only due to memory allocation, not debugfs */ 12562306a36Sopenharmony_ci if (ret < 0) { 12662306a36Sopenharmony_ci dev_err(sdev->dev, "snd_sof_debugfs_buf_item failed\n"); 12762306a36Sopenharmony_ci return ret; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* perform pre fw run operations */ 13262306a36Sopenharmony_ci ret = snd_sof_dsp_pre_fw_run(sdev); 13362306a36Sopenharmony_ci if (ret < 0) { 13462306a36Sopenharmony_ci dev_err(sdev->dev, "failed pre fw run op\n"); 13562306a36Sopenharmony_ci return ret; 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci dev_dbg(sdev->dev, "booting DSP firmware\n"); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* boot the firmware on the DSP */ 14162306a36Sopenharmony_ci ret = snd_sof_dsp_run(sdev); 14262306a36Sopenharmony_ci if (ret < 0) { 14362306a36Sopenharmony_ci snd_sof_dsp_dbg_dump(sdev, "Failed to start DSP", 14462306a36Sopenharmony_ci SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_PCI); 14562306a36Sopenharmony_ci return ret; 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci /* 14962306a36Sopenharmony_ci * now wait for the DSP to boot. There are 3 possible outcomes: 15062306a36Sopenharmony_ci * 1. Boot wait times out indicating FW boot failure. 15162306a36Sopenharmony_ci * 2. FW boots successfully and fw_ready op succeeds. 15262306a36Sopenharmony_ci * 3. FW boots but fw_ready op fails. 15362306a36Sopenharmony_ci */ 15462306a36Sopenharmony_ci ret = wait_event_timeout(sdev->boot_wait, 15562306a36Sopenharmony_ci sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS, 15662306a36Sopenharmony_ci msecs_to_jiffies(sdev->boot_timeout)); 15762306a36Sopenharmony_ci if (ret == 0) { 15862306a36Sopenharmony_ci snd_sof_dsp_dbg_dump(sdev, "Firmware boot failure due to timeout", 15962306a36Sopenharmony_ci SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX | 16062306a36Sopenharmony_ci SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI); 16162306a36Sopenharmony_ci return -EIO; 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci if (sdev->fw_state == SOF_FW_BOOT_READY_FAILED) 16562306a36Sopenharmony_ci return -EIO; /* FW boots but fw_ready op failed */ 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci dev_dbg(sdev->dev, "firmware boot complete\n"); 16862306a36Sopenharmony_ci sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci /* perform post fw run operations */ 17162306a36Sopenharmony_ci ret = snd_sof_dsp_post_fw_run(sdev); 17262306a36Sopenharmony_ci if (ret < 0) { 17362306a36Sopenharmony_ci dev_err(sdev->dev, "error: failed post fw run op\n"); 17462306a36Sopenharmony_ci return ret; 17562306a36Sopenharmony_ci } 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (sdev->ipc->ops->post_fw_boot) 17862306a36Sopenharmony_ci return sdev->ipc->ops->post_fw_boot(sdev); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci return 0; 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ciEXPORT_SYMBOL(snd_sof_run_firmware); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_civoid snd_sof_fw_unload(struct snd_sof_dev *sdev) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci /* TODO: support module unloading at runtime */ 18762306a36Sopenharmony_ci release_firmware(sdev->basefw.fw); 18862306a36Sopenharmony_ci sdev->basefw.fw = NULL; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ciEXPORT_SYMBOL(snd_sof_fw_unload); 191