xref: /linux/sound/soc/sof/loader.c (revision 5ab1679d6aab2e7855cd9241d4d832d1cda0ca46)
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 <sound/sof.h>
15 #include <sound/sof/ext_manifest.h>
16 #include "sof-priv.h"
17 #include "ops.h"
18 
19 static int get_ext_windows(struct snd_sof_dev *sdev,
20 			   const struct sof_ipc_ext_data_hdr *ext_hdr)
21 {
22 	const struct sof_ipc_window *w =
23 		container_of(ext_hdr, struct sof_ipc_window, ext_hdr);
24 
25 	if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS)
26 		return -EINVAL;
27 
28 	if (sdev->info_window) {
29 		if (memcmp(sdev->info_window, w, ext_hdr->hdr.size)) {
30 			dev_err(sdev->dev, "error: mismatch between window descriptor from extended manifest and mailbox");
31 			return -EINVAL;
32 		}
33 		return 0;
34 	}
35 
36 	/* keep a local copy of the data */
37 	sdev->info_window = devm_kmemdup(sdev->dev, w, ext_hdr->hdr.size,
38 					 GFP_KERNEL);
39 	if (!sdev->info_window)
40 		return -ENOMEM;
41 
42 	return 0;
43 }
44 
45 static int get_cc_info(struct snd_sof_dev *sdev,
46 		       const struct sof_ipc_ext_data_hdr *ext_hdr)
47 {
48 	int ret;
49 
50 	const struct sof_ipc_cc_version *cc =
51 		container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr);
52 
53 	if (sdev->cc_version) {
54 		if (memcmp(sdev->cc_version, cc, cc->ext_hdr.hdr.size)) {
55 			dev_err(sdev->dev, "error: receive diverged cc_version descriptions");
56 			return -EINVAL;
57 		}
58 		return 0;
59 	}
60 
61 	dev_dbg(sdev->dev, "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n",
62 		cc->name, cc->major, cc->minor, cc->micro, cc->desc,
63 		cc->optim);
64 
65 	/* create read-only cc_version debugfs to store compiler version info */
66 	/* use local copy of the cc_version to prevent data corruption */
67 	if (sdev->first_boot) {
68 		sdev->cc_version = devm_kmalloc(sdev->dev, cc->ext_hdr.hdr.size,
69 						GFP_KERNEL);
70 
71 		if (!sdev->cc_version)
72 			return -ENOMEM;
73 
74 		memcpy(sdev->cc_version, cc, cc->ext_hdr.hdr.size);
75 		ret = snd_sof_debugfs_buf_item(sdev, sdev->cc_version,
76 					       cc->ext_hdr.hdr.size,
77 					       "cc_version", 0444);
78 
79 		/* errors are only due to memory allocation, not debugfs */
80 		if (ret < 0) {
81 			dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n");
82 			return ret;
83 		}
84 	}
85 
86 	return 0;
87 }
88 
89 static int ext_man_get_fw_version(struct snd_sof_dev *sdev,
90 				  const struct sof_ext_man_elem_header *hdr)
91 {
92 	const struct sof_ext_man_fw_version *v =
93 		container_of(hdr, struct sof_ext_man_fw_version, hdr);
94 
95 	memcpy(&sdev->fw_ready.version, &v->version, sizeof(v->version));
96 	sdev->fw_ready.flags = v->flags;
97 
98 	/* log ABI versions and check FW compatibility */
99 	return snd_sof_ipc_valid(sdev);
100 }
101 
102 static int ext_man_get_windows(struct snd_sof_dev *sdev,
103 			       const struct sof_ext_man_elem_header *hdr)
104 {
105 	const struct sof_ext_man_window *w;
106 
107 	w = container_of(hdr, struct sof_ext_man_window, hdr);
108 
109 	return get_ext_windows(sdev, &w->ipc_window.ext_hdr);
110 }
111 
112 static int ext_man_get_cc_info(struct snd_sof_dev *sdev,
113 			       const struct sof_ext_man_elem_header *hdr)
114 {
115 	const struct sof_ext_man_cc_version *cc;
116 
117 	cc = container_of(hdr, struct sof_ext_man_cc_version, hdr);
118 
119 	return get_cc_info(sdev, &cc->cc_version.ext_hdr);
120 }
121 
122 static int ext_man_get_dbg_abi_info(struct snd_sof_dev *sdev,
123 				    const struct sof_ext_man_elem_header *hdr)
124 {
125 	const struct ext_man_dbg_abi *dbg_abi =
126 		container_of(hdr, struct ext_man_dbg_abi, hdr);
127 
128 	if (sdev->first_boot)
129 		dev_dbg(sdev->dev,
130 			"Firmware: DBG_ABI %d:%d:%d\n",
131 			SOF_ABI_VERSION_MAJOR(dbg_abi->dbg_abi.abi_dbg_version),
132 			SOF_ABI_VERSION_MINOR(dbg_abi->dbg_abi.abi_dbg_version),
133 			SOF_ABI_VERSION_PATCH(dbg_abi->dbg_abi.abi_dbg_version));
134 
135 	return 0;
136 }
137 
138 static int ext_man_get_config_data(struct snd_sof_dev *sdev,
139 				   const struct sof_ext_man_elem_header *hdr)
140 {
141 	const struct sof_ext_man_config_data *config =
142 		container_of(hdr, struct sof_ext_man_config_data, hdr);
143 	const struct sof_config_elem *elem;
144 	int elems_counter;
145 	int elems_size;
146 	int ret = 0;
147 	int i;
148 
149 	/* calculate elements counter */
150 	elems_size = config->hdr.size - sizeof(struct sof_ext_man_elem_header);
151 	elems_counter = elems_size / sizeof(struct sof_config_elem);
152 
153 	dev_dbg(sdev->dev, "%s can hold up to %d config elements\n",
154 		__func__, elems_counter);
155 
156 	for (i = 0; i < elems_counter; ++i) {
157 		elem = &config->elems[i];
158 		dev_dbg(sdev->dev, "%s get index %d token %d val %d\n",
159 			__func__, i, elem->token, elem->value);
160 		switch (elem->token) {
161 		case SOF_EXT_MAN_CONFIG_EMPTY:
162 			/* unused memory space is zero filled - mapped to EMPTY elements */
163 			break;
164 		case SOF_EXT_MAN_CONFIG_IPC_MSG_SIZE:
165 			/* TODO: use ipc msg size from config data */
166 			break;
167 		case SOF_EXT_MAN_CONFIG_MEMORY_USAGE_SCAN:
168 			if (sdev->first_boot && elem->value)
169 				ret = snd_sof_dbg_memory_info_init(sdev);
170 			break;
171 		default:
172 			dev_info(sdev->dev, "Unknown firmware configuration token %d value %d",
173 				 elem->token, elem->value);
174 			break;
175 		}
176 		if (ret < 0) {
177 			dev_err(sdev->dev, "error: processing sof_ext_man_config_data failed for token %d value 0x%x, %d\n",
178 				elem->token, elem->value, ret);
179 			return ret;
180 		}
181 	}
182 
183 	return 0;
184 }
185 
186 static ssize_t snd_sof_ext_man_size(const struct firmware *fw)
187 {
188 	const struct sof_ext_man_header *head;
189 
190 	head = (struct sof_ext_man_header *)fw->data;
191 
192 	/*
193 	 * assert fw size is big enough to contain extended manifest header,
194 	 * it prevents from reading unallocated memory from `head` in following
195 	 * step.
196 	 */
197 	if (fw->size < sizeof(*head))
198 		return -EINVAL;
199 
200 	/*
201 	 * When fw points to extended manifest,
202 	 * then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER.
203 	 */
204 	if (head->magic == SOF_EXT_MAN_MAGIC_NUMBER)
205 		return head->full_size;
206 
207 	/* otherwise given fw don't have an extended manifest */
208 	return 0;
209 }
210 
211 /* parse extended FW manifest data structures */
212 static int snd_sof_fw_ext_man_parse(struct snd_sof_dev *sdev,
213 				    const struct firmware *fw)
214 {
215 	const struct sof_ext_man_elem_header *elem_hdr;
216 	const struct sof_ext_man_header *head;
217 	ssize_t ext_man_size;
218 	ssize_t remaining;
219 	uintptr_t iptr;
220 	int ret = 0;
221 
222 	head = (struct sof_ext_man_header *)fw->data;
223 	remaining = head->full_size - head->header_size;
224 	ext_man_size = snd_sof_ext_man_size(fw);
225 
226 	/* Assert firmware starts with extended manifest */
227 	if (ext_man_size <= 0)
228 		return ext_man_size;
229 
230 	/* incompatible version */
231 	if (SOF_EXT_MAN_VERSION_INCOMPATIBLE(SOF_EXT_MAN_VERSION,
232 					     head->header_version)) {
233 		dev_err(sdev->dev, "error: extended manifest version 0x%X differ from used 0x%X\n",
234 			head->header_version, SOF_EXT_MAN_VERSION);
235 		return -EINVAL;
236 	}
237 
238 	/* get first extended manifest element header */
239 	iptr = (uintptr_t)fw->data + head->header_size;
240 
241 	while (remaining > sizeof(*elem_hdr)) {
242 		elem_hdr = (struct sof_ext_man_elem_header *)iptr;
243 
244 		dev_dbg(sdev->dev, "found sof_ext_man header type %d size 0x%X\n",
245 			elem_hdr->type, elem_hdr->size);
246 
247 		if (elem_hdr->size < sizeof(*elem_hdr) ||
248 		    elem_hdr->size > remaining) {
249 			dev_err(sdev->dev, "error: invalid sof_ext_man header size, type %d size 0x%X\n",
250 				elem_hdr->type, elem_hdr->size);
251 			return -EINVAL;
252 		}
253 
254 		/* process structure data */
255 		switch (elem_hdr->type) {
256 		case SOF_EXT_MAN_ELEM_FW_VERSION:
257 			ret = ext_man_get_fw_version(sdev, elem_hdr);
258 			break;
259 		case SOF_EXT_MAN_ELEM_WINDOW:
260 			ret = ext_man_get_windows(sdev, elem_hdr);
261 			break;
262 		case SOF_EXT_MAN_ELEM_CC_VERSION:
263 			ret = ext_man_get_cc_info(sdev, elem_hdr);
264 			break;
265 		case SOF_EXT_MAN_ELEM_DBG_ABI:
266 			ret = ext_man_get_dbg_abi_info(sdev, elem_hdr);
267 			break;
268 		case SOF_EXT_MAN_ELEM_CONFIG_DATA:
269 			ret = ext_man_get_config_data(sdev, elem_hdr);
270 			break;
271 		case SOF_EXT_MAN_ELEM_PLATFORM_CONFIG_DATA:
272 			ret = snd_sof_dsp_parse_platform_ext_manifest(sdev, elem_hdr);
273 			break;
274 		default:
275 			dev_info(sdev->dev, "unknown sof_ext_man header type %d size 0x%X\n",
276 				 elem_hdr->type, elem_hdr->size);
277 			break;
278 		}
279 
280 		if (ret < 0) {
281 			dev_err(sdev->dev, "error: failed to parse sof_ext_man header type %d size 0x%X\n",
282 				elem_hdr->type, elem_hdr->size);
283 			return ret;
284 		}
285 
286 		remaining -= elem_hdr->size;
287 		iptr += elem_hdr->size;
288 	}
289 
290 	if (remaining) {
291 		dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n");
292 		return -EINVAL;
293 	}
294 
295 	return ext_man_size;
296 }
297 
298 /* generic module parser for mmaped DSPs */
299 int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev,
300 				struct snd_sof_mod_hdr *module)
301 {
302 	struct snd_sof_blk_hdr *block;
303 	int count, ret;
304 	u32 offset;
305 	size_t remaining;
306 
307 	dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n",
308 		module->size, module->num_blocks, module->type);
309 
310 	block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module));
311 
312 	/* module->size doesn't include header size */
313 	remaining = module->size;
314 	for (count = 0; count < module->num_blocks; count++) {
315 		/* check for wrap */
316 		if (remaining < sizeof(*block)) {
317 			dev_err(sdev->dev, "error: not enough data remaining\n");
318 			return -EINVAL;
319 		}
320 
321 		/* minus header size of block */
322 		remaining -= sizeof(*block);
323 
324 		if (block->size == 0) {
325 			dev_warn(sdev->dev,
326 				 "warning: block %d size zero\n", count);
327 			dev_warn(sdev->dev, " type 0x%x offset 0x%x\n",
328 				 block->type, block->offset);
329 			continue;
330 		}
331 
332 		switch (block->type) {
333 		case SOF_FW_BLK_TYPE_RSRVD0:
334 		case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14:
335 			continue;	/* not handled atm */
336 		case SOF_FW_BLK_TYPE_IRAM:
337 		case SOF_FW_BLK_TYPE_DRAM:
338 		case SOF_FW_BLK_TYPE_SRAM:
339 			offset = block->offset;
340 			break;
341 		default:
342 			dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n",
343 				block->type, count);
344 			return -EINVAL;
345 		}
346 
347 		dev_dbg(sdev->dev,
348 			"block %d type 0x%x size 0x%x ==>  offset 0x%x\n",
349 			count, block->type, block->size, offset);
350 
351 		/* checking block->size to avoid unaligned access */
352 		if (block->size % sizeof(u32)) {
353 			dev_err(sdev->dev, "error: invalid block size 0x%x\n",
354 				block->size);
355 			return -EINVAL;
356 		}
357 		ret = snd_sof_dsp_block_write(sdev, block->type, offset,
358 					      block + 1, block->size);
359 		if (ret < 0) {
360 			dev_err(sdev->dev, "error: write to block type 0x%x failed\n",
361 				block->type);
362 			return ret;
363 		}
364 
365 		if (remaining < block->size) {
366 			dev_err(sdev->dev, "error: not enough data remaining\n");
367 			return -EINVAL;
368 		}
369 
370 		/* minus body size of block */
371 		remaining -= block->size;
372 		/* next block */
373 		block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block)
374 			+ block->size);
375 	}
376 
377 	return 0;
378 }
379 EXPORT_SYMBOL(snd_sof_parse_module_memcpy);
380 
381 static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw,
382 			size_t fw_offset)
383 {
384 	struct snd_sof_fw_header *header;
385 	size_t fw_size = fw->size - fw_offset;
386 
387 	if (fw->size <= fw_offset) {
388 		dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n");
389 		return -EINVAL;
390 	}
391 
392 	/* Read the header information from the data pointer */
393 	header = (struct snd_sof_fw_header *)(fw->data + fw_offset);
394 
395 	/* verify FW sig */
396 	if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) {
397 		dev_err(sdev->dev, "error: invalid firmware signature\n");
398 		return -EINVAL;
399 	}
400 
401 	/* check size is valid */
402 	if (fw_size != header->file_size + sizeof(*header)) {
403 		dev_err(sdev->dev, "error: invalid filesize mismatch got 0x%zx expected 0x%zx\n",
404 			fw_size, header->file_size + sizeof(*header));
405 		return -EINVAL;
406 	}
407 
408 	dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n",
409 		header->file_size, header->num_modules,
410 		header->abi, sizeof(*header));
411 
412 	return 0;
413 }
414 
415 static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw,
416 			size_t fw_offset)
417 {
418 	struct snd_sof_fw_header *header;
419 	struct snd_sof_mod_hdr *module;
420 	int (*load_module)(struct snd_sof_dev *sof_dev,
421 			   struct snd_sof_mod_hdr *hdr);
422 	int ret, count;
423 	size_t remaining;
424 
425 	header = (struct snd_sof_fw_header *)(fw->data + fw_offset);
426 	load_module = sof_ops(sdev)->load_module;
427 	if (!load_module)
428 		return -EINVAL;
429 
430 	/* parse each module */
431 	module = (struct snd_sof_mod_hdr *)(fw->data + fw_offset +
432 					    sizeof(*header));
433 	remaining = fw->size - sizeof(*header) - fw_offset;
434 	/* check for wrap */
435 	if (remaining > fw->size) {
436 		dev_err(sdev->dev, "error: fw size smaller than header size\n");
437 		return -EINVAL;
438 	}
439 
440 	for (count = 0; count < header->num_modules; count++) {
441 		/* check for wrap */
442 		if (remaining < sizeof(*module)) {
443 			dev_err(sdev->dev, "error: not enough data remaining\n");
444 			return -EINVAL;
445 		}
446 
447 		/* minus header size of module */
448 		remaining -= sizeof(*module);
449 
450 		/* module */
451 		ret = load_module(sdev, module);
452 		if (ret < 0) {
453 			dev_err(sdev->dev, "error: invalid module %d\n", count);
454 			return ret;
455 		}
456 
457 		if (remaining < module->size) {
458 			dev_err(sdev->dev, "error: not enough data remaining\n");
459 			return -EINVAL;
460 		}
461 
462 		/* minus body size of module */
463 		remaining -=  module->size;
464 		module = (struct snd_sof_mod_hdr *)((u8 *)module
465 			+ sizeof(*module) + module->size);
466 	}
467 
468 	return 0;
469 }
470 
471 int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
472 {
473 	struct snd_sof_pdata *plat_data = sdev->pdata;
474 	const char *fw_filename;
475 	ssize_t ext_man_size;
476 	int ret;
477 
478 	/* Don't request firmware again if firmware is already requested */
479 	if (plat_data->fw)
480 		return 0;
481 
482 	fw_filename = kasprintf(GFP_KERNEL, "%s/%s",
483 				plat_data->fw_filename_prefix,
484 				plat_data->fw_filename);
485 	if (!fw_filename)
486 		return -ENOMEM;
487 
488 	ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev);
489 
490 	if (ret < 0) {
491 		dev_err(sdev->dev,
492 			"error: sof firmware file is missing, you might need to\n");
493 		dev_err(sdev->dev,
494 			"       download it from https://github.com/thesofproject/sof-bin/\n");
495 		goto err;
496 	} else {
497 		dev_dbg(sdev->dev, "request_firmware %s successful\n",
498 			fw_filename);
499 	}
500 
501 	/* check for extended manifest */
502 	ext_man_size = snd_sof_fw_ext_man_parse(sdev, plat_data->fw);
503 	if (ext_man_size > 0) {
504 		/* when no error occurred, drop extended manifest */
505 		plat_data->fw_offset = ext_man_size;
506 	} else if (!ext_man_size) {
507 		/* No extended manifest, so nothing to skip during FW load */
508 		dev_dbg(sdev->dev, "firmware doesn't contain extended manifest\n");
509 	} else {
510 		ret = ext_man_size;
511 		dev_err(sdev->dev, "error: firmware %s contains unsupported or invalid extended manifest: %d\n",
512 			fw_filename, ret);
513 	}
514 
515 err:
516 	kfree(fw_filename);
517 
518 	return ret;
519 }
520 EXPORT_SYMBOL(snd_sof_load_firmware_raw);
521 
522 int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev)
523 {
524 	struct snd_sof_pdata *plat_data = sdev->pdata;
525 	int ret;
526 
527 	ret = snd_sof_load_firmware_raw(sdev);
528 	if (ret < 0)
529 		return ret;
530 
531 	/* make sure the FW header and file is valid */
532 	ret = check_header(sdev, plat_data->fw, plat_data->fw_offset);
533 	if (ret < 0) {
534 		dev_err(sdev->dev, "error: invalid FW header\n");
535 		goto error;
536 	}
537 
538 	/* prepare the DSP for FW loading */
539 	ret = snd_sof_dsp_reset(sdev);
540 	if (ret < 0) {
541 		dev_err(sdev->dev, "error: failed to reset DSP\n");
542 		goto error;
543 	}
544 
545 	/* parse and load firmware modules to DSP */
546 	ret = load_modules(sdev, plat_data->fw, plat_data->fw_offset);
547 	if (ret < 0) {
548 		dev_err(sdev->dev, "error: invalid FW modules\n");
549 		goto error;
550 	}
551 
552 	return 0;
553 
554 error:
555 	release_firmware(plat_data->fw);
556 	plat_data->fw = NULL;
557 	return ret;
558 
559 }
560 EXPORT_SYMBOL(snd_sof_load_firmware_memcpy);
561 
562 int snd_sof_run_firmware(struct snd_sof_dev *sdev)
563 {
564 	int ret;
565 
566 	init_waitqueue_head(&sdev->boot_wait);
567 
568 	/* (re-)enable dsp dump */
569 	sdev->dbg_dump_printed = false;
570 	sdev->ipc_dump_printed = false;
571 
572 	/* create read-only fw_version debugfs to store boot version info */
573 	if (sdev->first_boot) {
574 		ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version,
575 					       sizeof(sdev->fw_version),
576 					       "fw_version", 0444);
577 		/* errors are only due to memory allocation, not debugfs */
578 		if (ret < 0) {
579 			dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n");
580 			return ret;
581 		}
582 	}
583 
584 	/* perform pre fw run operations */
585 	ret = snd_sof_dsp_pre_fw_run(sdev);
586 	if (ret < 0) {
587 		dev_err(sdev->dev, "error: failed pre fw run op\n");
588 		return ret;
589 	}
590 
591 	dev_dbg(sdev->dev, "booting DSP firmware\n");
592 
593 	/* boot the firmware on the DSP */
594 	ret = snd_sof_dsp_run(sdev);
595 	if (ret < 0) {
596 		snd_sof_dsp_dbg_dump(sdev, "Failed to start DSP",
597 				     SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_PCI);
598 		return ret;
599 	}
600 
601 	/*
602 	 * now wait for the DSP to boot. There are 3 possible outcomes:
603 	 * 1. Boot wait times out indicating FW boot failure.
604 	 * 2. FW boots successfully and fw_ready op succeeds.
605 	 * 3. FW boots but fw_ready op fails.
606 	 */
607 	ret = wait_event_timeout(sdev->boot_wait,
608 				 sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS,
609 				 msecs_to_jiffies(sdev->boot_timeout));
610 	if (ret == 0) {
611 		snd_sof_dsp_dbg_dump(sdev, "Firmware boot failure due to timeout",
612 				     SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX |
613 				     SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI);
614 		return -EIO;
615 	}
616 
617 	if (sdev->fw_state == SOF_FW_BOOT_READY_FAILED)
618 		return -EIO; /* FW boots but fw_ready op failed */
619 
620 	/* perform post fw run operations */
621 	ret = snd_sof_dsp_post_fw_run(sdev);
622 	if (ret < 0) {
623 		dev_err(sdev->dev, "error: failed post fw run op\n");
624 		return ret;
625 	}
626 
627 	dev_dbg(sdev->dev, "firmware boot complete\n");
628 	sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE);
629 
630 	return 0;
631 }
632 EXPORT_SYMBOL(snd_sof_run_firmware);
633 
634 void snd_sof_fw_unload(struct snd_sof_dev *sdev)
635 {
636 	/* TODO: support module unloading at runtime */
637 	release_firmware(sdev->pdata->fw);
638 	sdev->pdata->fw = NULL;
639 }
640 EXPORT_SYMBOL(snd_sof_fw_unload);
641