mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 11:18:45 +07:00
6bb03c21e4
An error occurs during parsing more than one ext_data from the mailbox, because of invalid data offset handling. Fix by removing the incorrect duplicate increment of the offset. The return value is also reset in the switch case. This does not change the behavior but improves readability - there is no longer a need to check what the return value of get_ext_windows is. Signed-off-by: Karol Trzcinski <karolx.trzcinski@linux.intel.com> Signed-off-by: Bartosz Kokoszko <bartoszx.kokoszko@linux.intel.com> Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Link: https://lore.kernel.org/r/20191210004854.16845-4-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
580 lines
14 KiB
C
580 lines
14 KiB
C
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
|
|
//
|
|
// This file is provided under a dual BSD/GPLv2 license. When using or
|
|
// redistributing this file, you may do so under either license.
|
|
//
|
|
// Copyright(c) 2018 Intel Corporation. All rights reserved.
|
|
//
|
|
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
|
|
//
|
|
// Generic firmware loader.
|
|
//
|
|
|
|
#include <linux/firmware.h>
|
|
#include <sound/sof.h>
|
|
#include "ops.h"
|
|
|
|
static int get_ext_windows(struct snd_sof_dev *sdev,
|
|
struct sof_ipc_ext_data_hdr *ext_hdr)
|
|
{
|
|
struct sof_ipc_window *w =
|
|
container_of(ext_hdr, struct sof_ipc_window, ext_hdr);
|
|
|
|
if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS)
|
|
return -EINVAL;
|
|
|
|
/* keep a local copy of the data */
|
|
sdev->info_window = kmemdup(w, struct_size(w, window, w->num_windows),
|
|
GFP_KERNEL);
|
|
if (!sdev->info_window)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* parse the extended FW boot data structures from FW boot message */
|
|
int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset)
|
|
{
|
|
struct sof_ipc_ext_data_hdr *ext_hdr;
|
|
void *ext_data;
|
|
int ret = 0;
|
|
|
|
ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (!ext_data)
|
|
return -ENOMEM;
|
|
|
|
/* get first header */
|
|
snd_sof_dsp_block_read(sdev, bar, offset, ext_data,
|
|
sizeof(*ext_hdr));
|
|
ext_hdr = ext_data;
|
|
|
|
while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) {
|
|
/* read in ext structure */
|
|
snd_sof_dsp_block_read(sdev, bar, offset + sizeof(*ext_hdr),
|
|
(void *)((u8 *)ext_data + sizeof(*ext_hdr)),
|
|
ext_hdr->hdr.size - sizeof(*ext_hdr));
|
|
|
|
dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n",
|
|
ext_hdr->type, ext_hdr->hdr.size);
|
|
|
|
/* process structure data */
|
|
switch (ext_hdr->type) {
|
|
case SOF_IPC_EXT_DMA_BUFFER:
|
|
ret = 0;
|
|
break;
|
|
case SOF_IPC_EXT_WINDOW:
|
|
ret = get_ext_windows(sdev, ext_hdr);
|
|
break;
|
|
default:
|
|
dev_warn(sdev->dev, "warning: unknown ext header type %d size 0x%x\n",
|
|
ext_hdr->type, ext_hdr->hdr.size);
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: failed to parse ext data type %d\n",
|
|
ext_hdr->type);
|
|
break;
|
|
}
|
|
|
|
/* move to next header */
|
|
offset += ext_hdr->hdr.size;
|
|
snd_sof_dsp_block_read(sdev, bar, offset, ext_data,
|
|
sizeof(*ext_hdr));
|
|
ext_hdr = ext_data;
|
|
}
|
|
|
|
kfree(ext_data);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(snd_sof_fw_parse_ext_data);
|
|
|
|
/*
|
|
* IPC Firmware ready.
|
|
*/
|
|
static void sof_get_windows(struct snd_sof_dev *sdev)
|
|
{
|
|
struct sof_ipc_window_elem *elem;
|
|
u32 outbox_offset = 0;
|
|
u32 stream_offset = 0;
|
|
u32 inbox_offset = 0;
|
|
u32 outbox_size = 0;
|
|
u32 stream_size = 0;
|
|
u32 inbox_size = 0;
|
|
int window_offset;
|
|
int bar;
|
|
int i;
|
|
|
|
if (!sdev->info_window) {
|
|
dev_err(sdev->dev, "error: have no window info\n");
|
|
return;
|
|
}
|
|
|
|
bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM);
|
|
if (bar < 0) {
|
|
dev_err(sdev->dev, "error: have no bar mapping\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < sdev->info_window->num_windows; i++) {
|
|
elem = &sdev->info_window->window[i];
|
|
|
|
window_offset = snd_sof_dsp_get_window_offset(sdev, elem->id);
|
|
if (window_offset < 0) {
|
|
dev_warn(sdev->dev, "warn: no offset for window %d\n",
|
|
elem->id);
|
|
continue;
|
|
}
|
|
|
|
switch (elem->type) {
|
|
case SOF_IPC_REGION_UPBOX:
|
|
inbox_offset = window_offset + elem->offset;
|
|
inbox_size = elem->size;
|
|
snd_sof_debugfs_io_item(sdev,
|
|
sdev->bar[bar] +
|
|
inbox_offset,
|
|
elem->size, "inbox",
|
|
SOF_DEBUGFS_ACCESS_D0_ONLY);
|
|
break;
|
|
case SOF_IPC_REGION_DOWNBOX:
|
|
outbox_offset = window_offset + elem->offset;
|
|
outbox_size = elem->size;
|
|
snd_sof_debugfs_io_item(sdev,
|
|
sdev->bar[bar] +
|
|
outbox_offset,
|
|
elem->size, "outbox",
|
|
SOF_DEBUGFS_ACCESS_D0_ONLY);
|
|
break;
|
|
case SOF_IPC_REGION_TRACE:
|
|
snd_sof_debugfs_io_item(sdev,
|
|
sdev->bar[bar] +
|
|
window_offset +
|
|
elem->offset,
|
|
elem->size, "etrace",
|
|
SOF_DEBUGFS_ACCESS_D0_ONLY);
|
|
break;
|
|
case SOF_IPC_REGION_DEBUG:
|
|
snd_sof_debugfs_io_item(sdev,
|
|
sdev->bar[bar] +
|
|
window_offset +
|
|
elem->offset,
|
|
elem->size, "debug",
|
|
SOF_DEBUGFS_ACCESS_D0_ONLY);
|
|
break;
|
|
case SOF_IPC_REGION_STREAM:
|
|
stream_offset = window_offset + elem->offset;
|
|
stream_size = elem->size;
|
|
snd_sof_debugfs_io_item(sdev,
|
|
sdev->bar[bar] +
|
|
stream_offset,
|
|
elem->size, "stream",
|
|
SOF_DEBUGFS_ACCESS_D0_ONLY);
|
|
break;
|
|
case SOF_IPC_REGION_REGS:
|
|
snd_sof_debugfs_io_item(sdev,
|
|
sdev->bar[bar] +
|
|
window_offset +
|
|
elem->offset,
|
|
elem->size, "regs",
|
|
SOF_DEBUGFS_ACCESS_D0_ONLY);
|
|
break;
|
|
case SOF_IPC_REGION_EXCEPTION:
|
|
sdev->dsp_oops_offset = window_offset + elem->offset;
|
|
snd_sof_debugfs_io_item(sdev,
|
|
sdev->bar[bar] +
|
|
window_offset +
|
|
elem->offset,
|
|
elem->size, "exception",
|
|
SOF_DEBUGFS_ACCESS_D0_ONLY);
|
|
break;
|
|
default:
|
|
dev_err(sdev->dev, "error: get illegal window info\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (outbox_size == 0 || inbox_size == 0) {
|
|
dev_err(sdev->dev, "error: get illegal mailbox window\n");
|
|
return;
|
|
}
|
|
|
|
snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size,
|
|
outbox_offset, outbox_size);
|
|
sdev->stream_box.offset = stream_offset;
|
|
sdev->stream_box.size = stream_size;
|
|
|
|
dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n",
|
|
inbox_offset, inbox_size);
|
|
dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n",
|
|
outbox_offset, outbox_size);
|
|
dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n",
|
|
stream_offset, stream_size);
|
|
}
|
|
|
|
/* check for ABI compatibility and create memory windows on first boot */
|
|
int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id)
|
|
{
|
|
struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready;
|
|
int offset;
|
|
int bar;
|
|
int ret;
|
|
|
|
/* mailbox must be on 4k boundary */
|
|
offset = snd_sof_dsp_get_mailbox_offset(sdev);
|
|
if (offset < 0) {
|
|
dev_err(sdev->dev, "error: have no mailbox offset\n");
|
|
return offset;
|
|
}
|
|
|
|
bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM);
|
|
if (bar < 0) {
|
|
dev_err(sdev->dev, "error: have no bar mapping\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n",
|
|
msg_id, offset);
|
|
|
|
/* no need to re-check version/ABI for subsequent boots */
|
|
if (!sdev->first_boot)
|
|
return 0;
|
|
|
|
/* copy data from the DSP FW ready offset */
|
|
sof_block_read(sdev, bar, offset, fw_ready, sizeof(*fw_ready));
|
|
|
|
/* make sure ABI version is compatible */
|
|
ret = snd_sof_ipc_valid(sdev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* now check for extended data */
|
|
snd_sof_fw_parse_ext_data(sdev, bar, offset +
|
|
sizeof(struct sof_ipc_fw_ready));
|
|
|
|
sof_get_windows(sdev);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(sof_fw_ready);
|
|
|
|
/* generic module parser for mmaped DSPs */
|
|
int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev,
|
|
struct snd_sof_mod_hdr *module)
|
|
{
|
|
struct snd_sof_blk_hdr *block;
|
|
int count, bar;
|
|
u32 offset;
|
|
size_t remaining;
|
|
|
|
dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n",
|
|
module->size, module->num_blocks, module->type);
|
|
|
|
block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module));
|
|
|
|
/* module->size doesn't include header size */
|
|
remaining = module->size;
|
|
for (count = 0; count < module->num_blocks; count++) {
|
|
/* check for wrap */
|
|
if (remaining < sizeof(*block)) {
|
|
dev_err(sdev->dev, "error: not enough data remaining\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* minus header size of block */
|
|
remaining -= sizeof(*block);
|
|
|
|
if (block->size == 0) {
|
|
dev_warn(sdev->dev,
|
|
"warning: block %d size zero\n", count);
|
|
dev_warn(sdev->dev, " type 0x%x offset 0x%x\n",
|
|
block->type, block->offset);
|
|
continue;
|
|
}
|
|
|
|
switch (block->type) {
|
|
case SOF_FW_BLK_TYPE_RSRVD0:
|
|
case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14:
|
|
continue; /* not handled atm */
|
|
case SOF_FW_BLK_TYPE_IRAM:
|
|
case SOF_FW_BLK_TYPE_DRAM:
|
|
case SOF_FW_BLK_TYPE_SRAM:
|
|
offset = block->offset;
|
|
bar = snd_sof_dsp_get_bar_index(sdev, block->type);
|
|
if (bar < 0) {
|
|
dev_err(sdev->dev,
|
|
"error: no BAR mapping for block type 0x%x\n",
|
|
block->type);
|
|
return bar;
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n",
|
|
block->type, count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(sdev->dev,
|
|
"block %d type 0x%x size 0x%x ==> offset 0x%x\n",
|
|
count, block->type, block->size, offset);
|
|
|
|
/* checking block->size to avoid unaligned access */
|
|
if (block->size % sizeof(u32)) {
|
|
dev_err(sdev->dev, "error: invalid block size 0x%x\n",
|
|
block->size);
|
|
return -EINVAL;
|
|
}
|
|
snd_sof_dsp_block_write(sdev, bar, offset,
|
|
block + 1, block->size);
|
|
|
|
if (remaining < block->size) {
|
|
dev_err(sdev->dev, "error: not enough data remaining\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* minus body size of block */
|
|
remaining -= block->size;
|
|
/* next block */
|
|
block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block)
|
|
+ block->size);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(snd_sof_parse_module_memcpy);
|
|
|
|
static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw)
|
|
{
|
|
struct snd_sof_fw_header *header;
|
|
|
|
/* Read the header information from the data pointer */
|
|
header = (struct snd_sof_fw_header *)fw->data;
|
|
|
|
/* verify FW sig */
|
|
if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) {
|
|
dev_err(sdev->dev, "error: invalid firmware signature\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check size is valid */
|
|
if (fw->size != header->file_size + sizeof(*header)) {
|
|
dev_err(sdev->dev, "error: invalid filesize mismatch got 0x%zx expected 0x%zx\n",
|
|
fw->size, header->file_size + sizeof(*header));
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n",
|
|
header->file_size, header->num_modules,
|
|
header->abi, sizeof(*header));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw)
|
|
{
|
|
struct snd_sof_fw_header *header;
|
|
struct snd_sof_mod_hdr *module;
|
|
int (*load_module)(struct snd_sof_dev *sof_dev,
|
|
struct snd_sof_mod_hdr *hdr);
|
|
int ret, count;
|
|
size_t remaining;
|
|
|
|
header = (struct snd_sof_fw_header *)fw->data;
|
|
load_module = sof_ops(sdev)->load_module;
|
|
if (!load_module)
|
|
return -EINVAL;
|
|
|
|
/* parse each module */
|
|
module = (struct snd_sof_mod_hdr *)((u8 *)(fw->data) + sizeof(*header));
|
|
remaining = fw->size - sizeof(*header);
|
|
/* check for wrap */
|
|
if (remaining > fw->size) {
|
|
dev_err(sdev->dev, "error: fw size smaller than header size\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (count = 0; count < header->num_modules; count++) {
|
|
/* check for wrap */
|
|
if (remaining < sizeof(*module)) {
|
|
dev_err(sdev->dev, "error: not enough data remaining\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* minus header size of module */
|
|
remaining -= sizeof(*module);
|
|
|
|
/* module */
|
|
ret = load_module(sdev, module);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: invalid module %d\n", count);
|
|
return ret;
|
|
}
|
|
|
|
if (remaining < module->size) {
|
|
dev_err(sdev->dev, "error: not enough data remaining\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* minus body size of module */
|
|
remaining -= module->size;
|
|
module = (struct snd_sof_mod_hdr *)((u8 *)module
|
|
+ sizeof(*module) + module->size);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
|
|
{
|
|
struct snd_sof_pdata *plat_data = sdev->pdata;
|
|
const char *fw_filename;
|
|
int ret;
|
|
|
|
/* set code loading condition to true */
|
|
sdev->code_loading = 1;
|
|
|
|
/* Don't request firmware again if firmware is already requested */
|
|
if (plat_data->fw)
|
|
return 0;
|
|
|
|
fw_filename = kasprintf(GFP_KERNEL, "%s/%s",
|
|
plat_data->fw_filename_prefix,
|
|
plat_data->fw_filename);
|
|
if (!fw_filename)
|
|
return -ENOMEM;
|
|
|
|
ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev);
|
|
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: request firmware %s failed err: %d\n",
|
|
fw_filename, ret);
|
|
}
|
|
|
|
kfree(fw_filename);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(snd_sof_load_firmware_raw);
|
|
|
|
int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev)
|
|
{
|
|
struct snd_sof_pdata *plat_data = sdev->pdata;
|
|
int ret;
|
|
|
|
ret = snd_sof_load_firmware_raw(sdev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* make sure the FW header and file is valid */
|
|
ret = check_header(sdev, plat_data->fw);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: invalid FW header\n");
|
|
goto error;
|
|
}
|
|
|
|
/* prepare the DSP for FW loading */
|
|
ret = snd_sof_dsp_reset(sdev);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: failed to reset DSP\n");
|
|
goto error;
|
|
}
|
|
|
|
/* parse and load firmware modules to DSP */
|
|
ret = load_modules(sdev, plat_data->fw);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: invalid FW modules\n");
|
|
goto error;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
release_firmware(plat_data->fw);
|
|
plat_data->fw = NULL;
|
|
return ret;
|
|
|
|
}
|
|
EXPORT_SYMBOL(snd_sof_load_firmware_memcpy);
|
|
|
|
int snd_sof_load_firmware(struct snd_sof_dev *sdev)
|
|
{
|
|
dev_dbg(sdev->dev, "loading firmware\n");
|
|
|
|
if (sof_ops(sdev)->load_firmware)
|
|
return sof_ops(sdev)->load_firmware(sdev);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(snd_sof_load_firmware);
|
|
|
|
int snd_sof_run_firmware(struct snd_sof_dev *sdev)
|
|
{
|
|
int ret;
|
|
int init_core_mask;
|
|
|
|
init_waitqueue_head(&sdev->boot_wait);
|
|
sdev->boot_complete = false;
|
|
|
|
/* create read-only fw_version debugfs to store boot version info */
|
|
if (sdev->first_boot) {
|
|
ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version,
|
|
sizeof(sdev->fw_version),
|
|
"fw_version", 0444);
|
|
/* errors are only due to memory allocation, not debugfs */
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* perform pre fw run operations */
|
|
ret = snd_sof_dsp_pre_fw_run(sdev);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: failed pre fw run op\n");
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(sdev->dev, "booting DSP firmware\n");
|
|
|
|
/* boot the firmware on the DSP */
|
|
ret = snd_sof_dsp_run(sdev);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: failed to reset DSP\n");
|
|
return ret;
|
|
}
|
|
|
|
init_core_mask = ret;
|
|
|
|
/* now wait for the DSP to boot */
|
|
ret = wait_event_timeout(sdev->boot_wait, sdev->boot_complete,
|
|
msecs_to_jiffies(sdev->boot_timeout));
|
|
if (ret == 0) {
|
|
dev_err(sdev->dev, "error: firmware boot failure\n");
|
|
snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX |
|
|
SOF_DBG_TEXT | SOF_DBG_PCI);
|
|
/* after this point FW_READY msg should be ignored */
|
|
sdev->boot_complete = true;
|
|
return -EIO;
|
|
}
|
|
|
|
dev_info(sdev->dev, "firmware boot complete\n");
|
|
|
|
/* perform post fw run operations */
|
|
ret = snd_sof_dsp_post_fw_run(sdev);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: failed post fw run op\n");
|
|
return ret;
|
|
}
|
|
|
|
/* fw boot is complete. Update the active cores mask */
|
|
sdev->enabled_cores_mask = init_core_mask;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(snd_sof_run_firmware);
|
|
|
|
void snd_sof_fw_unload(struct snd_sof_dev *sdev)
|
|
{
|
|
/* TODO: support module unloading at runtime */
|
|
}
|
|
EXPORT_SYMBOL(snd_sof_fw_unload);
|