1// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
2//
3// This file is provided under a dual BSD/GPLv2 license. When using or
4// redistributing this file, you may do so under either license.
5//
6// Copyright(c) 2018 Intel Corporation. All rights reserved.
7//
8// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
9//
10// Generic firmware loader.
11//
12
13#include <linux/firmware.h>
14#include "sof-priv.h"
15#include "ops.h"
16
17int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
18{
19 struct snd_sof_pdata *plat_data = sdev->pdata;
20 const char *fw_filename;
21 ssize_t ext_man_size;
22 int ret;
23
24 /* Don't request firmware again if firmware is already requested */
25 if (sdev->basefw.fw)
26 return 0;
27
28 fw_filename = kasprintf(GFP_KERNEL, fmt: "%s/%s",
29 plat_data->fw_filename_prefix,
30 plat_data->fw_filename);
31 if (!fw_filename)
32 return -ENOMEM;
33
34 ret = request_firmware(fw: &sdev->basefw.fw, name: fw_filename, device: sdev->dev);
35
36 if (ret < 0) {
37 dev_err(sdev->dev,
38 "error: sof firmware file is missing, you might need to\n");
39 dev_err(sdev->dev,
40 " download it from https://github.com/thesofproject/sof-bin/\n");
41 goto err;
42 } else {
43 dev_dbg(sdev->dev, "request_firmware %s successful\n",
44 fw_filename);
45 }
46
47 /* check for extended manifest */
48 ext_man_size = sdev->ipc->ops->fw_loader->parse_ext_manifest(sdev);
49 if (ext_man_size > 0) {
50 /* when no error occurred, drop extended manifest */
51 sdev->basefw.payload_offset = ext_man_size;
52 } else if (!ext_man_size) {
53 /* No extended manifest, so nothing to skip during FW load */
54 dev_dbg(sdev->dev, "firmware doesn't contain extended manifest\n");
55 } else {
56 ret = ext_man_size;
57 dev_err(sdev->dev, "error: firmware %s contains unsupported or invalid extended manifest: %d\n",
58 fw_filename, ret);
59 }
60
61err:
62 kfree(objp: fw_filename);
63
64 return ret;
65}
66EXPORT_SYMBOL(snd_sof_load_firmware_raw);
67
68int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev)
69{
70 int ret;
71
72 ret = snd_sof_load_firmware_raw(sdev);
73 if (ret < 0)
74 return ret;
75
76 /* make sure the FW header and file is valid */
77 ret = sdev->ipc->ops->fw_loader->validate(sdev);
78 if (ret < 0) {
79 dev_err(sdev->dev, "error: invalid FW header\n");
80 goto error;
81 }
82
83 /* prepare the DSP for FW loading */
84 ret = snd_sof_dsp_reset(sdev);
85 if (ret < 0) {
86 dev_err(sdev->dev, "error: failed to reset DSP\n");
87 goto error;
88 }
89
90 /* parse and load firmware modules to DSP */
91 if (sdev->ipc->ops->fw_loader->load_fw_to_dsp) {
92 ret = sdev->ipc->ops->fw_loader->load_fw_to_dsp(sdev);
93 if (ret < 0) {
94 dev_err(sdev->dev, "Firmware loading failed\n");
95 goto error;
96 }
97 }
98
99 return 0;
100
101error:
102 release_firmware(fw: sdev->basefw.fw);
103 sdev->basefw.fw = NULL;
104 return ret;
105
106}
107EXPORT_SYMBOL(snd_sof_load_firmware_memcpy);
108
109int snd_sof_run_firmware(struct snd_sof_dev *sdev)
110{
111 int ret;
112
113 init_waitqueue_head(&sdev->boot_wait);
114
115 /* (re-)enable dsp dump */
116 sdev->dbg_dump_printed = false;
117 sdev->ipc_dump_printed = false;
118
119 /* create read-only fw_version debugfs to store boot version info */
120 if (sdev->first_boot) {
121 ret = snd_sof_debugfs_buf_item(sdev, base: &sdev->fw_version,
122 size: sizeof(sdev->fw_version),
123 name: "fw_version", mode: 0444);
124 /* errors are only due to memory allocation, not debugfs */
125 if (ret < 0) {
126 dev_err(sdev->dev, "snd_sof_debugfs_buf_item failed\n");
127 return ret;
128 }
129 }
130
131 /* perform pre fw run operations */
132 ret = snd_sof_dsp_pre_fw_run(sdev);
133 if (ret < 0) {
134 dev_err(sdev->dev, "failed pre fw run op\n");
135 return ret;
136 }
137
138 dev_dbg(sdev->dev, "booting DSP firmware\n");
139
140 /* boot the firmware on the DSP */
141 ret = snd_sof_dsp_run(sdev);
142 if (ret < 0) {
143 snd_sof_dsp_dbg_dump(sdev, msg: "Failed to start DSP",
144 SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_PCI);
145 return ret;
146 }
147
148 /*
149 * now wait for the DSP to boot. There are 3 possible outcomes:
150 * 1. Boot wait times out indicating FW boot failure.
151 * 2. FW boots successfully and fw_ready op succeeds.
152 * 3. FW boots but fw_ready op fails.
153 */
154 ret = wait_event_timeout(sdev->boot_wait,
155 sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS,
156 msecs_to_jiffies(sdev->boot_timeout));
157 if (ret == 0) {
158 snd_sof_dsp_dbg_dump(sdev, msg: "Firmware boot failure due to timeout",
159 SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX |
160 SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI);
161 return -EIO;
162 }
163
164 if (sdev->fw_state == SOF_FW_BOOT_READY_FAILED)
165 return -EIO; /* FW boots but fw_ready op failed */
166
167 dev_dbg(sdev->dev, "firmware boot complete\n");
168 sof_set_fw_state(sdev, new_state: SOF_FW_BOOT_COMPLETE);
169
170 /* perform post fw run operations */
171 ret = snd_sof_dsp_post_fw_run(sdev);
172 if (ret < 0) {
173 dev_err(sdev->dev, "error: failed post fw run op\n");
174 return ret;
175 }
176
177 if (sdev->ipc->ops->post_fw_boot)
178 return sdev->ipc->ops->post_fw_boot(sdev);
179
180 return 0;
181}
182EXPORT_SYMBOL(snd_sof_run_firmware);
183
184void snd_sof_fw_unload(struct snd_sof_dev *sdev)
185{
186 /* TODO: support module unloading at runtime */
187 release_firmware(fw: sdev->basefw.fw);
188 sdev->basefw.fw = NULL;
189}
190EXPORT_SYMBOL(snd_sof_fw_unload);
191

source code of linux/sound/soc/sof/loader.c