801 lines · 20.7 KB · C
RawDownload
// SPDX-License-Identifier: (GPL-2.0-only 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 <sound/sof/ext_manifest.h>#include "ops.h" static int get_ext_windows(struct snd_sof_dev *sdev,			   const struct sof_ipc_ext_data_hdr *ext_hdr){	const struct sof_ipc_window *w =		container_of(ext_hdr, struct sof_ipc_window, ext_hdr);	size_t w_size = struct_size(w, window, w->num_windows); 	if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS)		return -EINVAL; 	if (sdev->info_window) {		if (memcmp(sdev->info_window, w, w_size)) {			dev_err(sdev->dev, "error: mismatch between window descriptor from extended manifest and mailbox");			return -EINVAL;		}		return 0;	} 	/* keep a local copy of the data */	sdev->info_window = kmemdup(w, w_size, GFP_KERNEL);	if (!sdev->info_window)		return -ENOMEM; 	return 0;} static int get_cc_info(struct snd_sof_dev *sdev,		       const struct sof_ipc_ext_data_hdr *ext_hdr){	int ret; 	const struct sof_ipc_cc_version *cc =		container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr); 	if (sdev->cc_version) {		if (memcmp(sdev->cc_version, cc, cc->ext_hdr.hdr.size)) {			dev_err(sdev->dev, "error: receive diverged cc_version descriptions");			return -EINVAL;		}		return 0;	} 	dev_dbg(sdev->dev, "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n",		cc->name, cc->major, cc->minor, cc->micro, cc->desc,		cc->optim); 	/* create read-only cc_version debugfs to store compiler version info */	/* use local copy of the cc_version to prevent data corruption */	if (sdev->first_boot) {		sdev->cc_version = devm_kmalloc(sdev->dev, cc->ext_hdr.hdr.size,						GFP_KERNEL); 		if (!sdev->cc_version)			return -ENOMEM; 		memcpy(sdev->cc_version, cc, cc->ext_hdr.hdr.size);		ret = snd_sof_debugfs_buf_item(sdev, sdev->cc_version,					       cc->ext_hdr.hdr.size,					       "cc_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;		}	} 	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_WINDOW:			ret = get_ext_windows(sdev, ext_hdr);			break;		case SOF_IPC_EXT_CC_INFO:			ret = get_cc_info(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); static int ext_man_get_fw_version(struct snd_sof_dev *sdev,				  const struct sof_ext_man_elem_header *hdr){	const struct sof_ext_man_fw_version *v =		container_of(hdr, struct sof_ext_man_fw_version, hdr); 	memcpy(&sdev->fw_ready.version, &v->version, sizeof(v->version));	sdev->fw_ready.flags = v->flags; 	/* log ABI versions and check FW compatibility */	return snd_sof_ipc_valid(sdev);} static int ext_man_get_windows(struct snd_sof_dev *sdev,			       const struct sof_ext_man_elem_header *hdr){	const struct sof_ext_man_window *w; 	w = container_of(hdr, struct sof_ext_man_window, hdr); 	return get_ext_windows(sdev, &w->ipc_window.ext_hdr);} static int ext_man_get_cc_info(struct snd_sof_dev *sdev,			       const struct sof_ext_man_elem_header *hdr){	const struct sof_ext_man_cc_version *cc; 	cc = container_of(hdr, struct sof_ext_man_cc_version, hdr); 	return get_cc_info(sdev, &cc->cc_version.ext_hdr);} static ssize_t snd_sof_ext_man_size(const struct firmware *fw){	const struct sof_ext_man_header *head; 	head = (struct sof_ext_man_header *)fw->data; 	/*	 * assert fw size is big enough to contain extended manifest header,	 * it prevents from reading unallocated memory from `head` in following	 * step.	 */	if (fw->size < sizeof(*head))		return -EINVAL; 	/*	 * When fw points to extended manifest,	 * then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER.	 */	if (head->magic == SOF_EXT_MAN_MAGIC_NUMBER)		return head->full_size; 	/* otherwise given fw don't have an extended manifest */	return 0;} /* parse extended FW manifest data structures */static int snd_sof_fw_ext_man_parse(struct snd_sof_dev *sdev,				    const struct firmware *fw){	const struct sof_ext_man_elem_header *elem_hdr;	const struct sof_ext_man_header *head;	ssize_t ext_man_size;	ssize_t remaining;	uintptr_t iptr;	int ret = 0; 	head = (struct sof_ext_man_header *)fw->data;	remaining = head->full_size - head->header_size;	ext_man_size = snd_sof_ext_man_size(fw); 	/* Assert firmware starts with extended manifest */	if (ext_man_size <= 0)		return ext_man_size; 	/* incompatible version */	if (SOF_EXT_MAN_VERSION_INCOMPATIBLE(SOF_EXT_MAN_VERSION,					     head->header_version)) {		dev_err(sdev->dev, "error: extended manifest version 0x%X differ from used 0x%X\n",			head->header_version, SOF_EXT_MAN_VERSION);		return -EINVAL;	} 	/* get first extended manifest element header */	iptr = (uintptr_t)fw->data + head->header_size; 	while (remaining > sizeof(*elem_hdr)) {		elem_hdr = (struct sof_ext_man_elem_header *)iptr; 		dev_dbg(sdev->dev, "found sof_ext_man header type %d size 0x%X\n",			elem_hdr->type, elem_hdr->size); 		if (elem_hdr->size < sizeof(*elem_hdr) ||		    elem_hdr->size > remaining) {			dev_err(sdev->dev, "error: invalid sof_ext_man header size, type %d size 0x%X\n",				elem_hdr->type, elem_hdr->size);			return -EINVAL;		} 		/* process structure data */		switch (elem_hdr->type) {		case SOF_EXT_MAN_ELEM_FW_VERSION:			ret = ext_man_get_fw_version(sdev, elem_hdr);			break;		case SOF_EXT_MAN_ELEM_WINDOW:			ret = ext_man_get_windows(sdev, elem_hdr);			break;		case SOF_EXT_MAN_ELEM_CC_VERSION:			ret = ext_man_get_cc_info(sdev, elem_hdr);			break;		default:			dev_warn(sdev->dev, "warning: unknown sof_ext_man header type %d size 0x%X\n",				 elem_hdr->type, elem_hdr->size);			break;		} 		if (ret < 0) {			dev_err(sdev->dev, "error: failed to parse sof_ext_man header type %d size 0x%X\n",				elem_hdr->type, elem_hdr->size);			return ret;		} 		remaining -= elem_hdr->size;		iptr += elem_hdr->size;	} 	if (remaining) {		dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n");		return -EINVAL;	} 	return ext_man_size;} /* * 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:

Lines 1–500 of 801.