884 lines · 23.3 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); 	if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS)		return -EINVAL; 	if (sdev->info_window) {		if (memcmp(sdev->info_window, w, ext_hdr->hdr.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 = devm_kmemdup(sdev->dev, w, ext_hdr->hdr.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;		case SOF_IPC_EXT_UNUSED:		case SOF_IPC_EXT_PROBE_INFO:		case SOF_IPC_EXT_USER_ABI_INFO:			/* They are supported but we don't do anything here */			break;		default:			dev_info(sdev->dev, "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 int ext_man_get_dbg_abi_info(struct snd_sof_dev *sdev,				    const struct sof_ext_man_elem_header *hdr){	const struct ext_man_dbg_abi *dbg_abi =		container_of(hdr, struct ext_man_dbg_abi, hdr); 	if (sdev->first_boot)		dev_dbg(sdev->dev,			"Firmware: DBG_ABI %d:%d:%d\n",			SOF_ABI_VERSION_MAJOR(dbg_abi->dbg_abi.abi_dbg_version),			SOF_ABI_VERSION_MINOR(dbg_abi->dbg_abi.abi_dbg_version),			SOF_ABI_VERSION_PATCH(dbg_abi->dbg_abi.abi_dbg_version)); 	return 0;} static int ext_man_get_config_data(struct snd_sof_dev *sdev,				   const struct sof_ext_man_elem_header *hdr){	const struct sof_ext_man_config_data *config =		container_of(hdr, struct sof_ext_man_config_data, hdr);	const struct sof_config_elem *elem;	int elems_counter;	int elems_size;	int ret = 0;	int i; 	/* calculate elements counter */	elems_size = config->hdr.size - sizeof(struct sof_ext_man_elem_header);	elems_counter = elems_size / sizeof(struct sof_config_elem); 	dev_dbg(sdev->dev, "%s can hold up to %d config elements\n",		__func__, elems_counter); 	for (i = 0; i < elems_counter; ++i) {		elem = &config->elems[i];		dev_dbg(sdev->dev, "%s get index %d token %d val %d\n",			__func__, i, elem->token, elem->value);		switch (elem->token) {		case SOF_EXT_MAN_CONFIG_EMPTY:			/* unused memory space is zero filled - mapped to EMPTY elements */			break;		case SOF_EXT_MAN_CONFIG_IPC_MSG_SIZE:			/* TODO: use ipc msg size from config data */			break;		case SOF_EXT_MAN_CONFIG_MEMORY_USAGE_SCAN:			if (sdev->first_boot && elem->value)				ret = snd_sof_dbg_memory_info_init(sdev);			break;		default:			dev_info(sdev->dev, "Unknown firmware configuration token %d value %d",				 elem->token, elem->value);			break;		}		if (ret < 0) {			dev_err(sdev->dev, "error: processing sof_ext_man_config_data failed for token %d value 0x%x, %d\n",				elem->token, elem->value, ret);			return ret;		}	} 	return 0;} 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;		case SOF_EXT_MAN_ELEM_DBG_ABI:			ret = ext_man_get_dbg_abi_info(sdev, elem_hdr);			break;		case SOF_EXT_MAN_ELEM_CONFIG_DATA:			ret = ext_man_get_config_data(sdev, elem_hdr);			break;		case SOF_EXT_MAN_ELEM_PLATFORM_CONFIG_DATA:			ret = snd_sof_dsp_parse_platform_ext_manifest(sdev, elem_hdr);			break;		default:			dev_info(sdev->dev, "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;	u32 debug_size = 0;	u32 debug_offset = 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:			debug_offset = window_offset + elem->offset;			debug_size = elem->size;			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; 	sdev->debug_box.offset = debug_offset;	sdev->debug_box.size = debug_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);	dev_dbg(sdev->dev, " debug region 0x%x - size 0x%x\n",		debug_offset, debug_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);

Lines 1–500 of 884.