xref: /linux-6.15/sound/soc/sof/loader.c (revision 143cdcf1)
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 <[email protected]>
9 //
10 // Generic firmware loader.
11 //
12 
13 #include <linux/firmware.h>
14 #include <sound/sof.h>
15 #include <sound/sof/ext_manifest.h>
16 #include "sof-priv.h"
17 #include "ops.h"
18 
19 /* generic module parser for mmaped DSPs */
20 int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev,
21 				struct snd_sof_mod_hdr *module)
22 {
23 	struct snd_sof_blk_hdr *block;
24 	int count, ret;
25 	u32 offset;
26 	size_t remaining;
27 
28 	dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n",
29 		module->size, module->num_blocks, module->type);
30 
31 	block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module));
32 
33 	/* module->size doesn't include header size */
34 	remaining = module->size;
35 	for (count = 0; count < module->num_blocks; count++) {
36 		/* check for wrap */
37 		if (remaining < sizeof(*block)) {
38 			dev_err(sdev->dev, "error: not enough data remaining\n");
39 			return -EINVAL;
40 		}
41 
42 		/* minus header size of block */
43 		remaining -= sizeof(*block);
44 
45 		if (block->size == 0) {
46 			dev_warn(sdev->dev,
47 				 "warning: block %d size zero\n", count);
48 			dev_warn(sdev->dev, " type 0x%x offset 0x%x\n",
49 				 block->type, block->offset);
50 			continue;
51 		}
52 
53 		switch (block->type) {
54 		case SOF_FW_BLK_TYPE_RSRVD0:
55 		case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14:
56 			continue;	/* not handled atm */
57 		case SOF_FW_BLK_TYPE_IRAM:
58 		case SOF_FW_BLK_TYPE_DRAM:
59 		case SOF_FW_BLK_TYPE_SRAM:
60 			offset = block->offset;
61 			break;
62 		default:
63 			dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n",
64 				block->type, count);
65 			return -EINVAL;
66 		}
67 
68 		dev_dbg(sdev->dev,
69 			"block %d type 0x%x size 0x%x ==>  offset 0x%x\n",
70 			count, block->type, block->size, offset);
71 
72 		/* checking block->size to avoid unaligned access */
73 		if (block->size % sizeof(u32)) {
74 			dev_err(sdev->dev, "error: invalid block size 0x%x\n",
75 				block->size);
76 			return -EINVAL;
77 		}
78 		ret = snd_sof_dsp_block_write(sdev, block->type, offset,
79 					      block + 1, block->size);
80 		if (ret < 0) {
81 			dev_err(sdev->dev, "error: write to block type 0x%x failed\n",
82 				block->type);
83 			return ret;
84 		}
85 
86 		if (remaining < block->size) {
87 			dev_err(sdev->dev, "error: not enough data remaining\n");
88 			return -EINVAL;
89 		}
90 
91 		/* minus body size of block */
92 		remaining -= block->size;
93 		/* next block */
94 		block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block)
95 			+ block->size);
96 	}
97 
98 	return 0;
99 }
100 EXPORT_SYMBOL(snd_sof_parse_module_memcpy);
101 
102 int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
103 {
104 	struct snd_sof_pdata *plat_data = sdev->pdata;
105 	const char *fw_filename;
106 	ssize_t ext_man_size;
107 	int ret;
108 
109 	/* Don't request firmware again if firmware is already requested */
110 	if (plat_data->fw)
111 		return 0;
112 
113 	fw_filename = kasprintf(GFP_KERNEL, "%s/%s",
114 				plat_data->fw_filename_prefix,
115 				plat_data->fw_filename);
116 	if (!fw_filename)
117 		return -ENOMEM;
118 
119 	ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev);
120 
121 	if (ret < 0) {
122 		dev_err(sdev->dev,
123 			"error: sof firmware file is missing, you might need to\n");
124 		dev_err(sdev->dev,
125 			"       download it from https://github.com/thesofproject/sof-bin/\n");
126 		goto err;
127 	} else {
128 		dev_dbg(sdev->dev, "request_firmware %s successful\n",
129 			fw_filename);
130 	}
131 
132 	/* check for extended manifest */
133 	ext_man_size = sdev->ipc->ops->fw_loader->parse_ext_manifest(sdev);
134 	if (ext_man_size > 0) {
135 		/* when no error occurred, drop extended manifest */
136 		plat_data->fw_offset = ext_man_size;
137 	} else if (!ext_man_size) {
138 		/* No extended manifest, so nothing to skip during FW load */
139 		dev_dbg(sdev->dev, "firmware doesn't contain extended manifest\n");
140 	} else {
141 		ret = ext_man_size;
142 		dev_err(sdev->dev, "error: firmware %s contains unsupported or invalid extended manifest: %d\n",
143 			fw_filename, ret);
144 	}
145 
146 err:
147 	kfree(fw_filename);
148 
149 	return ret;
150 }
151 EXPORT_SYMBOL(snd_sof_load_firmware_raw);
152 
153 int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev)
154 {
155 	struct snd_sof_pdata *plat_data = sdev->pdata;
156 	int ret;
157 
158 	ret = snd_sof_load_firmware_raw(sdev);
159 	if (ret < 0)
160 		return ret;
161 
162 	/* make sure the FW header and file is valid */
163 	ret = sdev->ipc->ops->fw_loader->validate(sdev);
164 	if (ret < 0) {
165 		dev_err(sdev->dev, "error: invalid FW header\n");
166 		goto error;
167 	}
168 
169 	/* prepare the DSP for FW loading */
170 	ret = snd_sof_dsp_reset(sdev);
171 	if (ret < 0) {
172 		dev_err(sdev->dev, "error: failed to reset DSP\n");
173 		goto error;
174 	}
175 
176 	/* parse and load firmware modules to DSP */
177 	if (sdev->ipc->ops->fw_loader->load_fw_to_dsp) {
178 		ret = sdev->ipc->ops->fw_loader->load_fw_to_dsp(sdev);
179 		if (ret < 0) {
180 			dev_err(sdev->dev, "Firmware loading failed\n");
181 			goto error;
182 		}
183 	}
184 
185 	return 0;
186 
187 error:
188 	release_firmware(plat_data->fw);
189 	plat_data->fw = NULL;
190 	return ret;
191 
192 }
193 EXPORT_SYMBOL(snd_sof_load_firmware_memcpy);
194 
195 int snd_sof_run_firmware(struct snd_sof_dev *sdev)
196 {
197 	int ret;
198 
199 	init_waitqueue_head(&sdev->boot_wait);
200 
201 	/* (re-)enable dsp dump */
202 	sdev->dbg_dump_printed = false;
203 	sdev->ipc_dump_printed = false;
204 
205 	/* create read-only fw_version debugfs to store boot version info */
206 	if (sdev->first_boot) {
207 		ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version,
208 					       sizeof(sdev->fw_version),
209 					       "fw_version", 0444);
210 		/* errors are only due to memory allocation, not debugfs */
211 		if (ret < 0) {
212 			dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n");
213 			return ret;
214 		}
215 	}
216 
217 	/* perform pre fw run operations */
218 	ret = snd_sof_dsp_pre_fw_run(sdev);
219 	if (ret < 0) {
220 		dev_err(sdev->dev, "error: failed pre fw run op\n");
221 		return ret;
222 	}
223 
224 	dev_dbg(sdev->dev, "booting DSP firmware\n");
225 
226 	/* boot the firmware on the DSP */
227 	ret = snd_sof_dsp_run(sdev);
228 	if (ret < 0) {
229 		snd_sof_dsp_dbg_dump(sdev, "Failed to start DSP",
230 				     SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_PCI);
231 		return ret;
232 	}
233 
234 	/*
235 	 * now wait for the DSP to boot. There are 3 possible outcomes:
236 	 * 1. Boot wait times out indicating FW boot failure.
237 	 * 2. FW boots successfully and fw_ready op succeeds.
238 	 * 3. FW boots but fw_ready op fails.
239 	 */
240 	ret = wait_event_timeout(sdev->boot_wait,
241 				 sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS,
242 				 msecs_to_jiffies(sdev->boot_timeout));
243 	if (ret == 0) {
244 		snd_sof_dsp_dbg_dump(sdev, "Firmware boot failure due to timeout",
245 				     SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX |
246 				     SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI);
247 		return -EIO;
248 	}
249 
250 	if (sdev->fw_state == SOF_FW_BOOT_READY_FAILED)
251 		return -EIO; /* FW boots but fw_ready op failed */
252 
253 	/* perform post fw run operations */
254 	ret = snd_sof_dsp_post_fw_run(sdev);
255 	if (ret < 0) {
256 		dev_err(sdev->dev, "error: failed post fw run op\n");
257 		return ret;
258 	}
259 
260 	dev_dbg(sdev->dev, "firmware boot complete\n");
261 	sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE);
262 
263 	return 0;
264 }
265 EXPORT_SYMBOL(snd_sof_run_firmware);
266 
267 void snd_sof_fw_unload(struct snd_sof_dev *sdev)
268 {
269 	/* TODO: support module unloading at runtime */
270 	release_firmware(sdev->pdata->fw);
271 	sdev->pdata->fw = NULL;
272 }
273 EXPORT_SYMBOL(snd_sof_fw_unload);
274