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