170cd5254SLiam Girdwood // SPDX-License-Identifier: (GPL-2.0 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> 1470cd5254SLiam Girdwood #include <sound/sof.h> 1570cd5254SLiam Girdwood #include "ops.h" 1670cd5254SLiam Girdwood 1770cd5254SLiam Girdwood static int get_ext_windows(struct snd_sof_dev *sdev, 1870cd5254SLiam Girdwood struct sof_ipc_ext_data_hdr *ext_hdr) 1970cd5254SLiam Girdwood { 2070cd5254SLiam Girdwood struct sof_ipc_window *w = 2170cd5254SLiam Girdwood container_of(ext_hdr, struct sof_ipc_window, ext_hdr); 2270cd5254SLiam Girdwood 2370cd5254SLiam Girdwood if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS) 2470cd5254SLiam Girdwood return -EINVAL; 2570cd5254SLiam Girdwood 2670cd5254SLiam Girdwood /* keep a local copy of the data */ 27b11c5b5eSGustavo A. R. Silva sdev->info_window = kmemdup(w, struct_size(w, window, w->num_windows), 28b11c5b5eSGustavo A. R. Silva GFP_KERNEL); 2970cd5254SLiam Girdwood if (!sdev->info_window) 3070cd5254SLiam Girdwood return -ENOMEM; 3170cd5254SLiam Girdwood 3270cd5254SLiam Girdwood return 0; 3370cd5254SLiam Girdwood } 3470cd5254SLiam Girdwood 3570cd5254SLiam Girdwood /* parse the extended FW boot data structures from FW boot message */ 3670cd5254SLiam Girdwood int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) 3770cd5254SLiam Girdwood { 3870cd5254SLiam Girdwood struct sof_ipc_ext_data_hdr *ext_hdr; 3970cd5254SLiam Girdwood void *ext_data; 4070cd5254SLiam Girdwood int ret = 0; 4170cd5254SLiam Girdwood 4270cd5254SLiam Girdwood ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL); 4370cd5254SLiam Girdwood if (!ext_data) 4470cd5254SLiam Girdwood return -ENOMEM; 4570cd5254SLiam Girdwood 4670cd5254SLiam Girdwood /* get first header */ 4770cd5254SLiam Girdwood snd_sof_dsp_block_read(sdev, bar, offset, ext_data, 4870cd5254SLiam Girdwood sizeof(*ext_hdr)); 4970cd5254SLiam Girdwood ext_hdr = ext_data; 5070cd5254SLiam Girdwood 5170cd5254SLiam Girdwood while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { 5270cd5254SLiam Girdwood /* read in ext structure */ 5370cd5254SLiam Girdwood offset += sizeof(*ext_hdr); 5470cd5254SLiam Girdwood snd_sof_dsp_block_read(sdev, bar, offset, 5570cd5254SLiam Girdwood (void *)((u8 *)ext_data + sizeof(*ext_hdr)), 5670cd5254SLiam Girdwood ext_hdr->hdr.size - sizeof(*ext_hdr)); 5770cd5254SLiam Girdwood 5870cd5254SLiam Girdwood dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n", 5970cd5254SLiam Girdwood ext_hdr->type, ext_hdr->hdr.size); 6070cd5254SLiam Girdwood 6170cd5254SLiam Girdwood /* process structure data */ 6270cd5254SLiam Girdwood switch (ext_hdr->type) { 6370cd5254SLiam Girdwood case SOF_IPC_EXT_DMA_BUFFER: 6470cd5254SLiam Girdwood break; 6570cd5254SLiam Girdwood case SOF_IPC_EXT_WINDOW: 6670cd5254SLiam Girdwood ret = get_ext_windows(sdev, ext_hdr); 6770cd5254SLiam Girdwood break; 6870cd5254SLiam Girdwood default: 6970cd5254SLiam Girdwood break; 7070cd5254SLiam Girdwood } 7170cd5254SLiam Girdwood 7270cd5254SLiam Girdwood if (ret < 0) { 7370cd5254SLiam Girdwood dev_err(sdev->dev, "error: failed to parse ext data type %d\n", 7470cd5254SLiam Girdwood ext_hdr->type); 7570cd5254SLiam Girdwood break; 7670cd5254SLiam Girdwood } 7770cd5254SLiam Girdwood 7870cd5254SLiam Girdwood /* move to next header */ 7970cd5254SLiam Girdwood offset += ext_hdr->hdr.size; 8070cd5254SLiam Girdwood snd_sof_dsp_block_read(sdev, bar, offset, ext_data, 8170cd5254SLiam Girdwood sizeof(*ext_hdr)); 8270cd5254SLiam Girdwood ext_hdr = ext_data; 8370cd5254SLiam Girdwood } 8470cd5254SLiam Girdwood 8570cd5254SLiam Girdwood kfree(ext_data); 8670cd5254SLiam Girdwood return ret; 8770cd5254SLiam Girdwood } 8870cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_fw_parse_ext_data); 8970cd5254SLiam Girdwood 90*83ee7ab1SDaniel Baluta /* 91*83ee7ab1SDaniel Baluta * IPC Firmware ready. 92*83ee7ab1SDaniel Baluta */ 93*83ee7ab1SDaniel Baluta static void sof_get_windows(struct snd_sof_dev *sdev) 94*83ee7ab1SDaniel Baluta { 95*83ee7ab1SDaniel Baluta struct sof_ipc_window_elem *elem; 96*83ee7ab1SDaniel Baluta u32 outbox_offset = 0; 97*83ee7ab1SDaniel Baluta u32 stream_offset = 0; 98*83ee7ab1SDaniel Baluta u32 inbox_offset = 0; 99*83ee7ab1SDaniel Baluta u32 outbox_size = 0; 100*83ee7ab1SDaniel Baluta u32 stream_size = 0; 101*83ee7ab1SDaniel Baluta u32 inbox_size = 0; 102*83ee7ab1SDaniel Baluta int window_offset; 103*83ee7ab1SDaniel Baluta int bar; 104*83ee7ab1SDaniel Baluta int i; 105*83ee7ab1SDaniel Baluta 106*83ee7ab1SDaniel Baluta if (!sdev->info_window) { 107*83ee7ab1SDaniel Baluta dev_err(sdev->dev, "error: have no window info\n"); 108*83ee7ab1SDaniel Baluta return; 109*83ee7ab1SDaniel Baluta } 110*83ee7ab1SDaniel Baluta 111*83ee7ab1SDaniel Baluta bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM); 112*83ee7ab1SDaniel Baluta if (bar < 0) { 113*83ee7ab1SDaniel Baluta dev_err(sdev->dev, "error: have no bar mapping\n"); 114*83ee7ab1SDaniel Baluta return; 115*83ee7ab1SDaniel Baluta } 116*83ee7ab1SDaniel Baluta 117*83ee7ab1SDaniel Baluta for (i = 0; i < sdev->info_window->num_windows; i++) { 118*83ee7ab1SDaniel Baluta elem = &sdev->info_window->window[i]; 119*83ee7ab1SDaniel Baluta 120*83ee7ab1SDaniel Baluta window_offset = snd_sof_dsp_get_window_offset(sdev, elem->id); 121*83ee7ab1SDaniel Baluta if (window_offset < 0) { 122*83ee7ab1SDaniel Baluta dev_warn(sdev->dev, "warn: no offset for window %d\n", 123*83ee7ab1SDaniel Baluta elem->id); 124*83ee7ab1SDaniel Baluta continue; 125*83ee7ab1SDaniel Baluta } 126*83ee7ab1SDaniel Baluta 127*83ee7ab1SDaniel Baluta switch (elem->type) { 128*83ee7ab1SDaniel Baluta case SOF_IPC_REGION_UPBOX: 129*83ee7ab1SDaniel Baluta inbox_offset = window_offset + elem->offset; 130*83ee7ab1SDaniel Baluta inbox_size = elem->size; 131*83ee7ab1SDaniel Baluta snd_sof_debugfs_io_item(sdev, 132*83ee7ab1SDaniel Baluta sdev->bar[bar] + 133*83ee7ab1SDaniel Baluta inbox_offset, 134*83ee7ab1SDaniel Baluta elem->size, "inbox", 135*83ee7ab1SDaniel Baluta SOF_DEBUGFS_ACCESS_D0_ONLY); 136*83ee7ab1SDaniel Baluta break; 137*83ee7ab1SDaniel Baluta case SOF_IPC_REGION_DOWNBOX: 138*83ee7ab1SDaniel Baluta outbox_offset = window_offset + elem->offset; 139*83ee7ab1SDaniel Baluta outbox_size = elem->size; 140*83ee7ab1SDaniel Baluta snd_sof_debugfs_io_item(sdev, 141*83ee7ab1SDaniel Baluta sdev->bar[bar] + 142*83ee7ab1SDaniel Baluta outbox_offset, 143*83ee7ab1SDaniel Baluta elem->size, "outbox", 144*83ee7ab1SDaniel Baluta SOF_DEBUGFS_ACCESS_D0_ONLY); 145*83ee7ab1SDaniel Baluta break; 146*83ee7ab1SDaniel Baluta case SOF_IPC_REGION_TRACE: 147*83ee7ab1SDaniel Baluta snd_sof_debugfs_io_item(sdev, 148*83ee7ab1SDaniel Baluta sdev->bar[bar] + 149*83ee7ab1SDaniel Baluta window_offset + 150*83ee7ab1SDaniel Baluta elem->offset, 151*83ee7ab1SDaniel Baluta elem->size, "etrace", 152*83ee7ab1SDaniel Baluta SOF_DEBUGFS_ACCESS_D0_ONLY); 153*83ee7ab1SDaniel Baluta break; 154*83ee7ab1SDaniel Baluta case SOF_IPC_REGION_DEBUG: 155*83ee7ab1SDaniel Baluta snd_sof_debugfs_io_item(sdev, 156*83ee7ab1SDaniel Baluta sdev->bar[bar] + 157*83ee7ab1SDaniel Baluta window_offset + 158*83ee7ab1SDaniel Baluta elem->offset, 159*83ee7ab1SDaniel Baluta elem->size, "debug", 160*83ee7ab1SDaniel Baluta SOF_DEBUGFS_ACCESS_D0_ONLY); 161*83ee7ab1SDaniel Baluta break; 162*83ee7ab1SDaniel Baluta case SOF_IPC_REGION_STREAM: 163*83ee7ab1SDaniel Baluta stream_offset = window_offset + elem->offset; 164*83ee7ab1SDaniel Baluta stream_size = elem->size; 165*83ee7ab1SDaniel Baluta snd_sof_debugfs_io_item(sdev, 166*83ee7ab1SDaniel Baluta sdev->bar[bar] + 167*83ee7ab1SDaniel Baluta stream_offset, 168*83ee7ab1SDaniel Baluta elem->size, "stream", 169*83ee7ab1SDaniel Baluta SOF_DEBUGFS_ACCESS_D0_ONLY); 170*83ee7ab1SDaniel Baluta break; 171*83ee7ab1SDaniel Baluta case SOF_IPC_REGION_REGS: 172*83ee7ab1SDaniel Baluta snd_sof_debugfs_io_item(sdev, 173*83ee7ab1SDaniel Baluta sdev->bar[bar] + 174*83ee7ab1SDaniel Baluta window_offset + 175*83ee7ab1SDaniel Baluta elem->offset, 176*83ee7ab1SDaniel Baluta elem->size, "regs", 177*83ee7ab1SDaniel Baluta SOF_DEBUGFS_ACCESS_D0_ONLY); 178*83ee7ab1SDaniel Baluta break; 179*83ee7ab1SDaniel Baluta case SOF_IPC_REGION_EXCEPTION: 180*83ee7ab1SDaniel Baluta sdev->dsp_oops_offset = window_offset + elem->offset; 181*83ee7ab1SDaniel Baluta snd_sof_debugfs_io_item(sdev, 182*83ee7ab1SDaniel Baluta sdev->bar[bar] + 183*83ee7ab1SDaniel Baluta window_offset + 184*83ee7ab1SDaniel Baluta elem->offset, 185*83ee7ab1SDaniel Baluta elem->size, "exception", 186*83ee7ab1SDaniel Baluta SOF_DEBUGFS_ACCESS_D0_ONLY); 187*83ee7ab1SDaniel Baluta break; 188*83ee7ab1SDaniel Baluta default: 189*83ee7ab1SDaniel Baluta dev_err(sdev->dev, "error: get illegal window info\n"); 190*83ee7ab1SDaniel Baluta return; 191*83ee7ab1SDaniel Baluta } 192*83ee7ab1SDaniel Baluta } 193*83ee7ab1SDaniel Baluta 194*83ee7ab1SDaniel Baluta if (outbox_size == 0 || inbox_size == 0) { 195*83ee7ab1SDaniel Baluta dev_err(sdev->dev, "error: get illegal mailbox window\n"); 196*83ee7ab1SDaniel Baluta return; 197*83ee7ab1SDaniel Baluta } 198*83ee7ab1SDaniel Baluta 199*83ee7ab1SDaniel Baluta snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size, 200*83ee7ab1SDaniel Baluta outbox_offset, outbox_size); 201*83ee7ab1SDaniel Baluta sdev->stream_box.offset = stream_offset; 202*83ee7ab1SDaniel Baluta sdev->stream_box.size = stream_size; 203*83ee7ab1SDaniel Baluta 204*83ee7ab1SDaniel Baluta dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", 205*83ee7ab1SDaniel Baluta inbox_offset, inbox_size); 206*83ee7ab1SDaniel Baluta dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", 207*83ee7ab1SDaniel Baluta outbox_offset, outbox_size); 208*83ee7ab1SDaniel Baluta dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", 209*83ee7ab1SDaniel Baluta stream_offset, stream_size); 210*83ee7ab1SDaniel Baluta } 211*83ee7ab1SDaniel Baluta 212*83ee7ab1SDaniel Baluta /* check for ABI compatibility and create memory windows on first boot */ 213*83ee7ab1SDaniel Baluta int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) 214*83ee7ab1SDaniel Baluta { 215*83ee7ab1SDaniel Baluta struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; 216*83ee7ab1SDaniel Baluta int offset; 217*83ee7ab1SDaniel Baluta int bar; 218*83ee7ab1SDaniel Baluta int ret; 219*83ee7ab1SDaniel Baluta 220*83ee7ab1SDaniel Baluta /* mailbox must be on 4k boundary */ 221*83ee7ab1SDaniel Baluta offset = snd_sof_dsp_get_mailbox_offset(sdev); 222*83ee7ab1SDaniel Baluta if (offset < 0) { 223*83ee7ab1SDaniel Baluta dev_err(sdev->dev, "error: have no mailbox offset\n"); 224*83ee7ab1SDaniel Baluta return offset; 225*83ee7ab1SDaniel Baluta } 226*83ee7ab1SDaniel Baluta 227*83ee7ab1SDaniel Baluta bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM); 228*83ee7ab1SDaniel Baluta if (bar < 0) { 229*83ee7ab1SDaniel Baluta dev_err(sdev->dev, "error: have no bar mapping\n"); 230*83ee7ab1SDaniel Baluta return -EINVAL; 231*83ee7ab1SDaniel Baluta } 232*83ee7ab1SDaniel Baluta 233*83ee7ab1SDaniel Baluta dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n", 234*83ee7ab1SDaniel Baluta msg_id, offset); 235*83ee7ab1SDaniel Baluta 236*83ee7ab1SDaniel Baluta /* no need to re-check version/ABI for subsequent boots */ 237*83ee7ab1SDaniel Baluta if (!sdev->first_boot) 238*83ee7ab1SDaniel Baluta return 0; 239*83ee7ab1SDaniel Baluta 240*83ee7ab1SDaniel Baluta /* copy data from the DSP FW ready offset */ 241*83ee7ab1SDaniel Baluta sof_block_read(sdev, bar, offset, fw_ready, sizeof(*fw_ready)); 242*83ee7ab1SDaniel Baluta 243*83ee7ab1SDaniel Baluta /* make sure ABI version is compatible */ 244*83ee7ab1SDaniel Baluta ret = snd_sof_ipc_valid(sdev); 245*83ee7ab1SDaniel Baluta if (ret < 0) 246*83ee7ab1SDaniel Baluta return ret; 247*83ee7ab1SDaniel Baluta 248*83ee7ab1SDaniel Baluta /* now check for extended data */ 249*83ee7ab1SDaniel Baluta snd_sof_fw_parse_ext_data(sdev, bar, offset + 250*83ee7ab1SDaniel Baluta sizeof(struct sof_ipc_fw_ready)); 251*83ee7ab1SDaniel Baluta 252*83ee7ab1SDaniel Baluta sof_get_windows(sdev); 253*83ee7ab1SDaniel Baluta 254*83ee7ab1SDaniel Baluta return 0; 255*83ee7ab1SDaniel Baluta } 256*83ee7ab1SDaniel Baluta EXPORT_SYMBOL(sof_fw_ready); 257*83ee7ab1SDaniel Baluta 25870cd5254SLiam Girdwood /* generic module parser for mmaped DSPs */ 25970cd5254SLiam Girdwood int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, 26070cd5254SLiam Girdwood struct snd_sof_mod_hdr *module) 26170cd5254SLiam Girdwood { 26270cd5254SLiam Girdwood struct snd_sof_blk_hdr *block; 2637198879eSDaniel Baluta int count, bar; 26470cd5254SLiam Girdwood u32 offset; 26570cd5254SLiam Girdwood size_t remaining; 26670cd5254SLiam Girdwood 26770cd5254SLiam Girdwood dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n", 26870cd5254SLiam Girdwood module->size, module->num_blocks, module->type); 26970cd5254SLiam Girdwood 27070cd5254SLiam Girdwood block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module)); 27170cd5254SLiam Girdwood 27270cd5254SLiam Girdwood /* module->size doesn't include header size */ 27370cd5254SLiam Girdwood remaining = module->size; 27470cd5254SLiam Girdwood for (count = 0; count < module->num_blocks; count++) { 27570cd5254SLiam Girdwood /* check for wrap */ 27670cd5254SLiam Girdwood if (remaining < sizeof(*block)) { 27770cd5254SLiam Girdwood dev_err(sdev->dev, "error: not enough data remaining\n"); 27870cd5254SLiam Girdwood return -EINVAL; 27970cd5254SLiam Girdwood } 28070cd5254SLiam Girdwood 28170cd5254SLiam Girdwood /* minus header size of block */ 28270cd5254SLiam Girdwood remaining -= sizeof(*block); 28370cd5254SLiam Girdwood 28470cd5254SLiam Girdwood if (block->size == 0) { 28570cd5254SLiam Girdwood dev_warn(sdev->dev, 28670cd5254SLiam Girdwood "warning: block %d size zero\n", count); 28770cd5254SLiam Girdwood dev_warn(sdev->dev, " type 0x%x offset 0x%x\n", 28870cd5254SLiam Girdwood block->type, block->offset); 28970cd5254SLiam Girdwood continue; 29070cd5254SLiam Girdwood } 29170cd5254SLiam Girdwood 29270cd5254SLiam Girdwood switch (block->type) { 29370cd5254SLiam Girdwood case SOF_FW_BLK_TYPE_RSRVD0: 294441c58cfSDaniel Baluta case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14: 29570cd5254SLiam Girdwood continue; /* not handled atm */ 29670cd5254SLiam Girdwood case SOF_FW_BLK_TYPE_IRAM: 29770cd5254SLiam Girdwood case SOF_FW_BLK_TYPE_DRAM: 298441c58cfSDaniel Baluta case SOF_FW_BLK_TYPE_SRAM: 29970cd5254SLiam Girdwood offset = block->offset; 3007198879eSDaniel Baluta bar = snd_sof_dsp_get_bar_index(sdev, block->type); 3017198879eSDaniel Baluta if (bar < 0) { 3027198879eSDaniel Baluta dev_err(sdev->dev, 3037198879eSDaniel Baluta "error: no BAR mapping for block type 0x%x\n", 3047198879eSDaniel Baluta block->type); 3057198879eSDaniel Baluta return bar; 3067198879eSDaniel Baluta } 30770cd5254SLiam Girdwood break; 30870cd5254SLiam Girdwood default: 30970cd5254SLiam Girdwood dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n", 31070cd5254SLiam Girdwood block->type, count); 31170cd5254SLiam Girdwood return -EINVAL; 31270cd5254SLiam Girdwood } 31370cd5254SLiam Girdwood 31470cd5254SLiam Girdwood dev_dbg(sdev->dev, 31570cd5254SLiam Girdwood "block %d type 0x%x size 0x%x ==> offset 0x%x\n", 31670cd5254SLiam Girdwood count, block->type, block->size, offset); 31770cd5254SLiam Girdwood 31870cd5254SLiam Girdwood /* checking block->size to avoid unaligned access */ 31970cd5254SLiam Girdwood if (block->size % sizeof(u32)) { 32070cd5254SLiam Girdwood dev_err(sdev->dev, "error: invalid block size 0x%x\n", 32170cd5254SLiam Girdwood block->size); 32270cd5254SLiam Girdwood return -EINVAL; 32370cd5254SLiam Girdwood } 3247198879eSDaniel Baluta snd_sof_dsp_block_write(sdev, bar, offset, 32570cd5254SLiam Girdwood block + 1, block->size); 32670cd5254SLiam Girdwood 32770cd5254SLiam Girdwood if (remaining < block->size) { 32870cd5254SLiam Girdwood dev_err(sdev->dev, "error: not enough data remaining\n"); 32970cd5254SLiam Girdwood return -EINVAL; 33070cd5254SLiam Girdwood } 33170cd5254SLiam Girdwood 33270cd5254SLiam Girdwood /* minus body size of block */ 33370cd5254SLiam Girdwood remaining -= block->size; 33470cd5254SLiam Girdwood /* next block */ 33570cd5254SLiam Girdwood block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block) 33670cd5254SLiam Girdwood + block->size); 33770cd5254SLiam Girdwood } 33870cd5254SLiam Girdwood 33970cd5254SLiam Girdwood return 0; 34070cd5254SLiam Girdwood } 34170cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_parse_module_memcpy); 34270cd5254SLiam Girdwood 34370cd5254SLiam Girdwood static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw) 34470cd5254SLiam Girdwood { 34570cd5254SLiam Girdwood struct snd_sof_fw_header *header; 34670cd5254SLiam Girdwood 34770cd5254SLiam Girdwood /* Read the header information from the data pointer */ 34870cd5254SLiam Girdwood header = (struct snd_sof_fw_header *)fw->data; 34970cd5254SLiam Girdwood 35070cd5254SLiam Girdwood /* verify FW sig */ 35170cd5254SLiam Girdwood if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) { 35270cd5254SLiam Girdwood dev_err(sdev->dev, "error: invalid firmware signature\n"); 35370cd5254SLiam Girdwood return -EINVAL; 35470cd5254SLiam Girdwood } 35570cd5254SLiam Girdwood 35670cd5254SLiam Girdwood /* check size is valid */ 35770cd5254SLiam Girdwood if (fw->size != header->file_size + sizeof(*header)) { 35870cd5254SLiam Girdwood dev_err(sdev->dev, "error: invalid filesize mismatch got 0x%zx expected 0x%zx\n", 35970cd5254SLiam Girdwood fw->size, header->file_size + sizeof(*header)); 36070cd5254SLiam Girdwood return -EINVAL; 36170cd5254SLiam Girdwood } 36270cd5254SLiam Girdwood 36370cd5254SLiam Girdwood dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n", 36470cd5254SLiam Girdwood header->file_size, header->num_modules, 36570cd5254SLiam Girdwood header->abi, sizeof(*header)); 36670cd5254SLiam Girdwood 36770cd5254SLiam Girdwood return 0; 36870cd5254SLiam Girdwood } 36970cd5254SLiam Girdwood 37070cd5254SLiam Girdwood static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw) 37170cd5254SLiam Girdwood { 37270cd5254SLiam Girdwood struct snd_sof_fw_header *header; 37370cd5254SLiam Girdwood struct snd_sof_mod_hdr *module; 37470cd5254SLiam Girdwood int (*load_module)(struct snd_sof_dev *sof_dev, 37570cd5254SLiam Girdwood struct snd_sof_mod_hdr *hdr); 37670cd5254SLiam Girdwood int ret, count; 37770cd5254SLiam Girdwood size_t remaining; 37870cd5254SLiam Girdwood 37970cd5254SLiam Girdwood header = (struct snd_sof_fw_header *)fw->data; 38070cd5254SLiam Girdwood load_module = sof_ops(sdev)->load_module; 38170cd5254SLiam Girdwood if (!load_module) 38270cd5254SLiam Girdwood return -EINVAL; 38370cd5254SLiam Girdwood 38470cd5254SLiam Girdwood /* parse each module */ 38570cd5254SLiam Girdwood module = (struct snd_sof_mod_hdr *)((u8 *)(fw->data) + sizeof(*header)); 38670cd5254SLiam Girdwood remaining = fw->size - sizeof(*header); 38770cd5254SLiam Girdwood /* check for wrap */ 38870cd5254SLiam Girdwood if (remaining > fw->size) { 38970cd5254SLiam Girdwood dev_err(sdev->dev, "error: fw size smaller than header size\n"); 39070cd5254SLiam Girdwood return -EINVAL; 39170cd5254SLiam Girdwood } 39270cd5254SLiam Girdwood 39370cd5254SLiam Girdwood for (count = 0; count < header->num_modules; count++) { 39470cd5254SLiam Girdwood /* check for wrap */ 39570cd5254SLiam Girdwood if (remaining < sizeof(*module)) { 39670cd5254SLiam Girdwood dev_err(sdev->dev, "error: not enough data remaining\n"); 39770cd5254SLiam Girdwood return -EINVAL; 39870cd5254SLiam Girdwood } 39970cd5254SLiam Girdwood 40070cd5254SLiam Girdwood /* minus header size of module */ 40170cd5254SLiam Girdwood remaining -= sizeof(*module); 40270cd5254SLiam Girdwood 40370cd5254SLiam Girdwood /* module */ 40470cd5254SLiam Girdwood ret = load_module(sdev, module); 40570cd5254SLiam Girdwood if (ret < 0) { 40670cd5254SLiam Girdwood dev_err(sdev->dev, "error: invalid module %d\n", count); 40770cd5254SLiam Girdwood return ret; 40870cd5254SLiam Girdwood } 40970cd5254SLiam Girdwood 41070cd5254SLiam Girdwood if (remaining < module->size) { 41170cd5254SLiam Girdwood dev_err(sdev->dev, "error: not enough data remaining\n"); 41270cd5254SLiam Girdwood return -EINVAL; 41370cd5254SLiam Girdwood } 41470cd5254SLiam Girdwood 41570cd5254SLiam Girdwood /* minus body size of module */ 41670cd5254SLiam Girdwood remaining -= module->size; 41770cd5254SLiam Girdwood module = (struct snd_sof_mod_hdr *)((u8 *)module 41870cd5254SLiam Girdwood + sizeof(*module) + module->size); 41970cd5254SLiam Girdwood } 42070cd5254SLiam Girdwood 42170cd5254SLiam Girdwood return 0; 42270cd5254SLiam Girdwood } 42370cd5254SLiam Girdwood 42470cd5254SLiam Girdwood int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) 42570cd5254SLiam Girdwood { 42670cd5254SLiam Girdwood struct snd_sof_pdata *plat_data = sdev->pdata; 42770cd5254SLiam Girdwood const char *fw_filename; 42870cd5254SLiam Girdwood int ret; 42970cd5254SLiam Girdwood 43070cd5254SLiam Girdwood /* set code loading condition to true */ 43170cd5254SLiam Girdwood sdev->code_loading = 1; 43270cd5254SLiam Girdwood 43370cd5254SLiam Girdwood /* Don't request firmware again if firmware is already requested */ 43470cd5254SLiam Girdwood if (plat_data->fw) 43570cd5254SLiam Girdwood return 0; 43670cd5254SLiam Girdwood 43770cd5254SLiam Girdwood fw_filename = kasprintf(GFP_KERNEL, "%s/%s", 43870cd5254SLiam Girdwood plat_data->fw_filename_prefix, 43970cd5254SLiam Girdwood plat_data->fw_filename); 44070cd5254SLiam Girdwood if (!fw_filename) 44170cd5254SLiam Girdwood return -ENOMEM; 44270cd5254SLiam Girdwood 44370cd5254SLiam Girdwood ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev); 44470cd5254SLiam Girdwood 44570cd5254SLiam Girdwood if (ret < 0) { 44670cd5254SLiam Girdwood dev_err(sdev->dev, "error: request firmware %s failed err: %d\n", 44770cd5254SLiam Girdwood fw_filename, ret); 44870cd5254SLiam Girdwood } 44970cd5254SLiam Girdwood 45070cd5254SLiam Girdwood kfree(fw_filename); 45170cd5254SLiam Girdwood 45270cd5254SLiam Girdwood return ret; 45370cd5254SLiam Girdwood } 45470cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_load_firmware_raw); 45570cd5254SLiam Girdwood 45670cd5254SLiam Girdwood int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) 45770cd5254SLiam Girdwood { 45870cd5254SLiam Girdwood struct snd_sof_pdata *plat_data = sdev->pdata; 45970cd5254SLiam Girdwood int ret; 46070cd5254SLiam Girdwood 46170cd5254SLiam Girdwood ret = snd_sof_load_firmware_raw(sdev); 46270cd5254SLiam Girdwood if (ret < 0) 46370cd5254SLiam Girdwood return ret; 46470cd5254SLiam Girdwood 46570cd5254SLiam Girdwood /* make sure the FW header and file is valid */ 46670cd5254SLiam Girdwood ret = check_header(sdev, plat_data->fw); 46770cd5254SLiam Girdwood if (ret < 0) { 46870cd5254SLiam Girdwood dev_err(sdev->dev, "error: invalid FW header\n"); 46970cd5254SLiam Girdwood goto error; 47070cd5254SLiam Girdwood } 47170cd5254SLiam Girdwood 47270cd5254SLiam Girdwood /* prepare the DSP for FW loading */ 47370cd5254SLiam Girdwood ret = snd_sof_dsp_reset(sdev); 47470cd5254SLiam Girdwood if (ret < 0) { 47570cd5254SLiam Girdwood dev_err(sdev->dev, "error: failed to reset DSP\n"); 47670cd5254SLiam Girdwood goto error; 47770cd5254SLiam Girdwood } 47870cd5254SLiam Girdwood 47970cd5254SLiam Girdwood /* parse and load firmware modules to DSP */ 48070cd5254SLiam Girdwood ret = load_modules(sdev, plat_data->fw); 48170cd5254SLiam Girdwood if (ret < 0) { 48270cd5254SLiam Girdwood dev_err(sdev->dev, "error: invalid FW modules\n"); 48370cd5254SLiam Girdwood goto error; 48470cd5254SLiam Girdwood } 48570cd5254SLiam Girdwood 48670cd5254SLiam Girdwood return 0; 48770cd5254SLiam Girdwood 48870cd5254SLiam Girdwood error: 48970cd5254SLiam Girdwood release_firmware(plat_data->fw); 49070cd5254SLiam Girdwood plat_data->fw = NULL; 49170cd5254SLiam Girdwood return ret; 49270cd5254SLiam Girdwood 49370cd5254SLiam Girdwood } 49470cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_load_firmware_memcpy); 49570cd5254SLiam Girdwood 49670cd5254SLiam Girdwood int snd_sof_load_firmware(struct snd_sof_dev *sdev) 49770cd5254SLiam Girdwood { 49870cd5254SLiam Girdwood dev_dbg(sdev->dev, "loading firmware\n"); 49970cd5254SLiam Girdwood 50070cd5254SLiam Girdwood if (sof_ops(sdev)->load_firmware) 50170cd5254SLiam Girdwood return sof_ops(sdev)->load_firmware(sdev); 50270cd5254SLiam Girdwood return 0; 50370cd5254SLiam Girdwood } 50470cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_load_firmware); 50570cd5254SLiam Girdwood 50670cd5254SLiam Girdwood int snd_sof_run_firmware(struct snd_sof_dev *sdev) 50770cd5254SLiam Girdwood { 50870cd5254SLiam Girdwood int ret; 50970cd5254SLiam Girdwood int init_core_mask; 51070cd5254SLiam Girdwood 51170cd5254SLiam Girdwood init_waitqueue_head(&sdev->boot_wait); 51270cd5254SLiam Girdwood sdev->boot_complete = false; 51370cd5254SLiam Girdwood 5145c9714f6SRanjani Sridharan /* create read-only fw_version debugfs to store boot version info */ 51570cd5254SLiam Girdwood if (sdev->first_boot) { 51670cd5254SLiam Girdwood ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version, 51770cd5254SLiam Girdwood sizeof(sdev->fw_version), 5185c9714f6SRanjani Sridharan "fw_version", 0444); 51970cd5254SLiam Girdwood /* errors are only due to memory allocation, not debugfs */ 52070cd5254SLiam Girdwood if (ret < 0) { 52170cd5254SLiam Girdwood dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n"); 52270cd5254SLiam Girdwood return ret; 52370cd5254SLiam Girdwood } 52470cd5254SLiam Girdwood } 52570cd5254SLiam Girdwood 52670cd5254SLiam Girdwood /* perform pre fw run operations */ 52770cd5254SLiam Girdwood ret = snd_sof_dsp_pre_fw_run(sdev); 52870cd5254SLiam Girdwood if (ret < 0) { 52970cd5254SLiam Girdwood dev_err(sdev->dev, "error: failed pre fw run op\n"); 53070cd5254SLiam Girdwood return ret; 53170cd5254SLiam Girdwood } 53270cd5254SLiam Girdwood 53370cd5254SLiam Girdwood dev_dbg(sdev->dev, "booting DSP firmware\n"); 53470cd5254SLiam Girdwood 53570cd5254SLiam Girdwood /* boot the firmware on the DSP */ 53670cd5254SLiam Girdwood ret = snd_sof_dsp_run(sdev); 53770cd5254SLiam Girdwood if (ret < 0) { 53870cd5254SLiam Girdwood dev_err(sdev->dev, "error: failed to reset DSP\n"); 53970cd5254SLiam Girdwood return ret; 54070cd5254SLiam Girdwood } 54170cd5254SLiam Girdwood 54270cd5254SLiam Girdwood init_core_mask = ret; 54370cd5254SLiam Girdwood 54470cd5254SLiam Girdwood /* now wait for the DSP to boot */ 54570cd5254SLiam Girdwood ret = wait_event_timeout(sdev->boot_wait, sdev->boot_complete, 54670cd5254SLiam Girdwood msecs_to_jiffies(sdev->boot_timeout)); 54770cd5254SLiam Girdwood if (ret == 0) { 54870cd5254SLiam Girdwood dev_err(sdev->dev, "error: firmware boot failure\n"); 549a69270d8SKai Vehmanen /* after this point FW_READY msg should be ignored */ 550a69270d8SKai Vehmanen sdev->boot_complete = true; 55170cd5254SLiam Girdwood snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX | 55270cd5254SLiam Girdwood SOF_DBG_TEXT | SOF_DBG_PCI); 55370cd5254SLiam Girdwood return -EIO; 55470cd5254SLiam Girdwood } 55570cd5254SLiam Girdwood 55670cd5254SLiam Girdwood dev_info(sdev->dev, "firmware boot complete\n"); 55770cd5254SLiam Girdwood 55870cd5254SLiam Girdwood /* perform post fw run operations */ 55970cd5254SLiam Girdwood ret = snd_sof_dsp_post_fw_run(sdev); 56070cd5254SLiam Girdwood if (ret < 0) { 56170cd5254SLiam Girdwood dev_err(sdev->dev, "error: failed post fw run op\n"); 56270cd5254SLiam Girdwood return ret; 56370cd5254SLiam Girdwood } 56470cd5254SLiam Girdwood 56570cd5254SLiam Girdwood /* fw boot is complete. Update the active cores mask */ 56670cd5254SLiam Girdwood sdev->enabled_cores_mask = init_core_mask; 56770cd5254SLiam Girdwood 56870cd5254SLiam Girdwood return 0; 56970cd5254SLiam Girdwood } 57070cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_run_firmware); 57170cd5254SLiam Girdwood 57270cd5254SLiam Girdwood void snd_sof_fw_unload(struct snd_sof_dev *sdev) 57370cd5254SLiam Girdwood { 57470cd5254SLiam Girdwood /* TODO: support module unloading at runtime */ 57570cd5254SLiam Girdwood } 57670cd5254SLiam Girdwood EXPORT_SYMBOL(snd_sof_fw_unload); 577