xref: /linux/sound/soc/sof/loader.c (revision 4f373ccf226e37a20fdc15a3df8034517a6045fd)
1e149ca29SPierre-Louis Bossart // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
270cd5254SLiam Girdwood //
370cd5254SLiam Girdwood // This file is provided under a dual BSD/GPLv2 license.  When using or
470cd5254SLiam Girdwood // redistributing this file, you may do so under either license.
570cd5254SLiam Girdwood //
670cd5254SLiam Girdwood // Copyright(c) 2018 Intel Corporation. All rights reserved.
770cd5254SLiam Girdwood //
870cd5254SLiam Girdwood // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
970cd5254SLiam Girdwood //
1070cd5254SLiam Girdwood // Generic firmware loader.
1170cd5254SLiam Girdwood //
1270cd5254SLiam Girdwood 
1370cd5254SLiam Girdwood #include <linux/firmware.h>
14febf5da8SRander Wang #include "sof-priv.h"
1570cd5254SLiam Girdwood #include "ops.h"
1670cd5254SLiam Girdwood 
1770cd5254SLiam Girdwood int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
1870cd5254SLiam Girdwood {
1970cd5254SLiam Girdwood 	struct snd_sof_pdata *plat_data = sdev->pdata;
2070cd5254SLiam Girdwood 	const char *fw_filename;
21a80cf198SKarol Trzcinski 	ssize_t ext_man_size;
2270cd5254SLiam Girdwood 	int ret;
2370cd5254SLiam Girdwood 
2470cd5254SLiam Girdwood 	/* Don't request firmware again if firmware is already requested */
25*4f373ccfSPeter Ujfalusi 	if (sdev->basefw.fw)
2670cd5254SLiam Girdwood 		return 0;
2770cd5254SLiam Girdwood 
2870cd5254SLiam Girdwood 	fw_filename = kasprintf(GFP_KERNEL, "%s/%s",
2970cd5254SLiam Girdwood 				plat_data->fw_filename_prefix,
3070cd5254SLiam Girdwood 				plat_data->fw_filename);
3170cd5254SLiam Girdwood 	if (!fw_filename)
3270cd5254SLiam Girdwood 		return -ENOMEM;
3370cd5254SLiam Girdwood 
34*4f373ccfSPeter Ujfalusi 	ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev);
3570cd5254SLiam Girdwood 
3670cd5254SLiam Girdwood 	if (ret < 0) {
3789e641aeSKai Vehmanen 		dev_err(sdev->dev,
3825766ee4SPeter Ujfalusi 			"error: sof firmware file is missing, you might need to\n");
3925766ee4SPeter Ujfalusi 		dev_err(sdev->dev,
4025766ee4SPeter Ujfalusi 			"       download it from https://github.com/thesofproject/sof-bin/\n");
41a80cf198SKarol Trzcinski 		goto err;
42490a625bSPierre-Louis Bossart 	} else {
43490a625bSPierre-Louis Bossart 		dev_dbg(sdev->dev, "request_firmware %s successful\n",
44490a625bSPierre-Louis Bossart 			fw_filename);
4570cd5254SLiam Girdwood 	}
4670cd5254SLiam Girdwood 
47a80cf198SKarol Trzcinski 	/* check for extended manifest */
48143cdcf1SPeter Ujfalusi 	ext_man_size = sdev->ipc->ops->fw_loader->parse_ext_manifest(sdev);
49a80cf198SKarol Trzcinski 	if (ext_man_size > 0) {
50a80cf198SKarol Trzcinski 		/* when no error occurred, drop extended manifest */
51*4f373ccfSPeter Ujfalusi 		sdev->basefw.payload_offset = ext_man_size;
52a80cf198SKarol Trzcinski 	} else if (!ext_man_size) {
53a80cf198SKarol Trzcinski 		/* No extended manifest, so nothing to skip during FW load */
54a80cf198SKarol Trzcinski 		dev_dbg(sdev->dev, "firmware doesn't contain extended manifest\n");
55a80cf198SKarol Trzcinski 	} else {
56a80cf198SKarol Trzcinski 		ret = ext_man_size;
57a80cf198SKarol Trzcinski 		dev_err(sdev->dev, "error: firmware %s contains unsupported or invalid extended manifest: %d\n",
58a80cf198SKarol Trzcinski 			fw_filename, ret);
59a80cf198SKarol Trzcinski 	}
60a80cf198SKarol Trzcinski 
61*4f373ccfSPeter Ujfalusi 	/*
62*4f373ccfSPeter Ujfalusi 	 * Until the platform code is switched to use the new container the fw
63*4f373ccfSPeter Ujfalusi 	 * and payload offset must be set in plat_data
64*4f373ccfSPeter Ujfalusi 	 */
65*4f373ccfSPeter Ujfalusi 	plat_data->fw = sdev->basefw.fw;
66*4f373ccfSPeter Ujfalusi 	plat_data->fw_offset = sdev->basefw.payload_offset;
67a80cf198SKarol Trzcinski err:
6870cd5254SLiam Girdwood 	kfree(fw_filename);
6970cd5254SLiam Girdwood 
7070cd5254SLiam Girdwood 	return ret;
7170cd5254SLiam Girdwood }
7270cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_load_firmware_raw);
7370cd5254SLiam Girdwood 
7470cd5254SLiam Girdwood int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev)
7570cd5254SLiam Girdwood {
7670cd5254SLiam Girdwood 	struct snd_sof_pdata *plat_data = sdev->pdata;
7770cd5254SLiam Girdwood 	int ret;
7870cd5254SLiam Girdwood 
7970cd5254SLiam Girdwood 	ret = snd_sof_load_firmware_raw(sdev);
8070cd5254SLiam Girdwood 	if (ret < 0)
8170cd5254SLiam Girdwood 		return ret;
8270cd5254SLiam Girdwood 
8370cd5254SLiam Girdwood 	/* make sure the FW header and file is valid */
84143cdcf1SPeter Ujfalusi 	ret = sdev->ipc->ops->fw_loader->validate(sdev);
8570cd5254SLiam Girdwood 	if (ret < 0) {
8670cd5254SLiam Girdwood 		dev_err(sdev->dev, "error: invalid FW header\n");
8770cd5254SLiam Girdwood 		goto error;
8870cd5254SLiam Girdwood 	}
8970cd5254SLiam Girdwood 
9070cd5254SLiam Girdwood 	/* prepare the DSP for FW loading */
9170cd5254SLiam Girdwood 	ret = snd_sof_dsp_reset(sdev);
9270cd5254SLiam Girdwood 	if (ret < 0) {
9370cd5254SLiam Girdwood 		dev_err(sdev->dev, "error: failed to reset DSP\n");
9470cd5254SLiam Girdwood 		goto error;
9570cd5254SLiam Girdwood 	}
9670cd5254SLiam Girdwood 
9770cd5254SLiam Girdwood 	/* parse and load firmware modules to DSP */
98143cdcf1SPeter Ujfalusi 	if (sdev->ipc->ops->fw_loader->load_fw_to_dsp) {
99143cdcf1SPeter Ujfalusi 		ret = sdev->ipc->ops->fw_loader->load_fw_to_dsp(sdev);
10070cd5254SLiam Girdwood 		if (ret < 0) {
101143cdcf1SPeter Ujfalusi 			dev_err(sdev->dev, "Firmware loading failed\n");
10270cd5254SLiam Girdwood 			goto error;
10370cd5254SLiam Girdwood 		}
104143cdcf1SPeter Ujfalusi 	}
10570cd5254SLiam Girdwood 
10670cd5254SLiam Girdwood 	return 0;
10770cd5254SLiam Girdwood 
10870cd5254SLiam Girdwood error:
109*4f373ccfSPeter Ujfalusi 	release_firmware(sdev->basefw.fw);
110*4f373ccfSPeter Ujfalusi 	sdev->basefw.fw = NULL;
11170cd5254SLiam Girdwood 	plat_data->fw = NULL;
11270cd5254SLiam Girdwood 	return ret;
11370cd5254SLiam Girdwood 
11470cd5254SLiam Girdwood }
11570cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_load_firmware_memcpy);
11670cd5254SLiam Girdwood 
11770cd5254SLiam Girdwood int snd_sof_run_firmware(struct snd_sof_dev *sdev)
11870cd5254SLiam Girdwood {
11970cd5254SLiam Girdwood 	int ret;
12070cd5254SLiam Girdwood 
12170cd5254SLiam Girdwood 	init_waitqueue_head(&sdev->boot_wait);
12270cd5254SLiam Girdwood 
1239ff90859SPeter Ujfalusi 	/* (re-)enable dsp dump */
1249ff90859SPeter Ujfalusi 	sdev->dbg_dump_printed = false;
1259ff90859SPeter Ujfalusi 	sdev->ipc_dump_printed = false;
1269ff90859SPeter Ujfalusi 
1275c9714f6SRanjani Sridharan 	/* create read-only fw_version debugfs to store boot version info */
12870cd5254SLiam Girdwood 	if (sdev->first_boot) {
12970cd5254SLiam Girdwood 		ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version,
13070cd5254SLiam Girdwood 					       sizeof(sdev->fw_version),
1315c9714f6SRanjani Sridharan 					       "fw_version", 0444);
13270cd5254SLiam Girdwood 		/* errors are only due to memory allocation, not debugfs */
13370cd5254SLiam Girdwood 		if (ret < 0) {
13470cd5254SLiam Girdwood 			dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n");
13570cd5254SLiam Girdwood 			return ret;
13670cd5254SLiam Girdwood 		}
13770cd5254SLiam Girdwood 	}
13870cd5254SLiam Girdwood 
13970cd5254SLiam Girdwood 	/* perform pre fw run operations */
14070cd5254SLiam Girdwood 	ret = snd_sof_dsp_pre_fw_run(sdev);
14170cd5254SLiam Girdwood 	if (ret < 0) {
14270cd5254SLiam Girdwood 		dev_err(sdev->dev, "error: failed pre fw run op\n");
14370cd5254SLiam Girdwood 		return ret;
14470cd5254SLiam Girdwood 	}
14570cd5254SLiam Girdwood 
14670cd5254SLiam Girdwood 	dev_dbg(sdev->dev, "booting DSP firmware\n");
14770cd5254SLiam Girdwood 
14870cd5254SLiam Girdwood 	/* boot the firmware on the DSP */
14970cd5254SLiam Girdwood 	ret = snd_sof_dsp_run(sdev);
15070cd5254SLiam Girdwood 	if (ret < 0) {
1512f148430SPeter Ujfalusi 		snd_sof_dsp_dbg_dump(sdev, "Failed to start DSP",
1522f148430SPeter Ujfalusi 				     SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_PCI);
15370cd5254SLiam Girdwood 		return ret;
15470cd5254SLiam Girdwood 	}
15570cd5254SLiam Girdwood 
1566ca5cecbSRanjani Sridharan 	/*
1576ca5cecbSRanjani Sridharan 	 * now wait for the DSP to boot. There are 3 possible outcomes:
1586ca5cecbSRanjani Sridharan 	 * 1. Boot wait times out indicating FW boot failure.
1596ca5cecbSRanjani Sridharan 	 * 2. FW boots successfully and fw_ready op succeeds.
1606ca5cecbSRanjani Sridharan 	 * 3. FW boots but fw_ready op fails.
1616ca5cecbSRanjani Sridharan 	 */
1626ca5cecbSRanjani Sridharan 	ret = wait_event_timeout(sdev->boot_wait,
1636ca5cecbSRanjani Sridharan 				 sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS,
16470cd5254SLiam Girdwood 				 msecs_to_jiffies(sdev->boot_timeout));
16570cd5254SLiam Girdwood 	if (ret == 0) {
1662f148430SPeter Ujfalusi 		snd_sof_dsp_dbg_dump(sdev, "Firmware boot failure due to timeout",
1672f148430SPeter Ujfalusi 				     SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX |
16823013335SPeter Ujfalusi 				     SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI);
16970cd5254SLiam Girdwood 		return -EIO;
17070cd5254SLiam Girdwood 	}
17170cd5254SLiam Girdwood 
172b2e9eb3aSPeter Ujfalusi 	if (sdev->fw_state == SOF_FW_BOOT_READY_FAILED)
1736ca5cecbSRanjani Sridharan 		return -EIO; /* FW boots but fw_ready op failed */
17470cd5254SLiam Girdwood 
1759b9db0d6SRanjani Sridharan 	dev_dbg(sdev->dev, "firmware boot complete\n");
1769b9db0d6SRanjani Sridharan 	sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE);
1779b9db0d6SRanjani Sridharan 
17870cd5254SLiam Girdwood 	/* perform post fw run operations */
17970cd5254SLiam Girdwood 	ret = snd_sof_dsp_post_fw_run(sdev);
18070cd5254SLiam Girdwood 	if (ret < 0) {
18170cd5254SLiam Girdwood 		dev_err(sdev->dev, "error: failed post fw run op\n");
18270cd5254SLiam Girdwood 		return ret;
18370cd5254SLiam Girdwood 	}
18470cd5254SLiam Girdwood 
1851dd4b999SPeter Ujfalusi 	if (sdev->first_boot && sdev->ipc->ops->fw_loader->query_fw_configuration)
1861dd4b999SPeter Ujfalusi 		return sdev->ipc->ops->fw_loader->query_fw_configuration(sdev);
1871dd4b999SPeter Ujfalusi 
18870cd5254SLiam Girdwood 	return 0;
18970cd5254SLiam Girdwood }
19070cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_run_firmware);
19170cd5254SLiam Girdwood 
19270cd5254SLiam Girdwood void snd_sof_fw_unload(struct snd_sof_dev *sdev)
19370cd5254SLiam Girdwood {
19470cd5254SLiam Girdwood 	/* TODO: support module unloading at runtime */
195*4f373ccfSPeter Ujfalusi 	release_firmware(sdev->basefw.fw);
196*4f373ccfSPeter Ujfalusi 	sdev->basefw.fw = NULL;
1978a8e1813SMarc Herbert 	sdev->pdata->fw = NULL;
19870cd5254SLiam Girdwood }
19970cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_fw_unload);
200