877 lines · 22.0 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 IPC layer that can work over MMIO and SPI/I2C. PHY layer provided// by platform driver code.// #include <linux/mutex.h>#include <linux/types.h> #include "sof-priv.h"#include "sof-audio.h"#include "ops.h" static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id);static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd); /* * IPC message Tx/Rx message handling. */ /* SOF generic IPC data */struct snd_sof_ipc {	struct snd_sof_dev *sdev; 	/* protects messages and the disable flag */	struct mutex tx_mutex;	/* disables further sending of ipc's */	bool disable_ipc_tx; 	struct snd_sof_ipc_msg msg;}; struct sof_ipc_ctrl_data_params {	size_t msg_bytes;	size_t hdr_bytes;	size_t pl_size;	size_t elems;	u32 num_msg;	u8 *src;	u8 *dst;}; #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC)static void ipc_log_header(struct device *dev, u8 *text, u32 cmd){	u8 *str;	u8 *str2 = NULL;	u32 glb;	u32 type;	bool vdbg = false; 	glb = cmd & SOF_GLB_TYPE_MASK;	type = cmd & SOF_CMD_TYPE_MASK; 	switch (glb) {	case SOF_IPC_GLB_REPLY:		str = "GLB_REPLY"; break;	case SOF_IPC_GLB_COMPOUND:		str = "GLB_COMPOUND"; break;	case SOF_IPC_GLB_TPLG_MSG:		str = "GLB_TPLG_MSG";		switch (type) {		case SOF_IPC_TPLG_COMP_NEW:			str2 = "COMP_NEW"; break;		case SOF_IPC_TPLG_COMP_FREE:			str2 = "COMP_FREE"; break;		case SOF_IPC_TPLG_COMP_CONNECT:			str2 = "COMP_CONNECT"; break;		case SOF_IPC_TPLG_PIPE_NEW:			str2 = "PIPE_NEW"; break;		case SOF_IPC_TPLG_PIPE_FREE:			str2 = "PIPE_FREE"; break;		case SOF_IPC_TPLG_PIPE_CONNECT:			str2 = "PIPE_CONNECT"; break;		case SOF_IPC_TPLG_PIPE_COMPLETE:			str2 = "PIPE_COMPLETE"; break;		case SOF_IPC_TPLG_BUFFER_NEW:			str2 = "BUFFER_NEW"; break;		case SOF_IPC_TPLG_BUFFER_FREE:			str2 = "BUFFER_FREE"; break;		default:			str2 = "unknown type"; break;		}		break;	case SOF_IPC_GLB_PM_MSG:		str = "GLB_PM_MSG";		switch (type) {		case SOF_IPC_PM_CTX_SAVE:			str2 = "CTX_SAVE"; break;		case SOF_IPC_PM_CTX_RESTORE:			str2 = "CTX_RESTORE"; break;		case SOF_IPC_PM_CTX_SIZE:			str2 = "CTX_SIZE"; break;		case SOF_IPC_PM_CLK_SET:			str2 = "CLK_SET"; break;		case SOF_IPC_PM_CLK_GET:			str2 = "CLK_GET"; break;		case SOF_IPC_PM_CLK_REQ:			str2 = "CLK_REQ"; break;		case SOF_IPC_PM_CORE_ENABLE:			str2 = "CORE_ENABLE"; break;		case SOF_IPC_PM_GATE:			str2 = "GATE"; break;		default:			str2 = "unknown type"; break;		}		break;	case SOF_IPC_GLB_COMP_MSG:		str = "GLB_COMP_MSG";		switch (type) {		case SOF_IPC_COMP_SET_VALUE:			str2 = "SET_VALUE"; break;		case SOF_IPC_COMP_GET_VALUE:			str2 = "GET_VALUE"; break;		case SOF_IPC_COMP_SET_DATA:			str2 = "SET_DATA"; break;		case SOF_IPC_COMP_GET_DATA:			str2 = "GET_DATA"; break;		default:			str2 = "unknown type"; break;		}		break;	case SOF_IPC_GLB_STREAM_MSG:		str = "GLB_STREAM_MSG";		switch (type) {		case SOF_IPC_STREAM_PCM_PARAMS:			str2 = "PCM_PARAMS"; break;		case SOF_IPC_STREAM_PCM_PARAMS_REPLY:			str2 = "PCM_REPLY"; break;		case SOF_IPC_STREAM_PCM_FREE:			str2 = "PCM_FREE"; break;		case SOF_IPC_STREAM_TRIG_START:			str2 = "TRIG_START"; break;		case SOF_IPC_STREAM_TRIG_STOP:			str2 = "TRIG_STOP"; break;		case SOF_IPC_STREAM_TRIG_PAUSE:			str2 = "TRIG_PAUSE"; break;		case SOF_IPC_STREAM_TRIG_RELEASE:			str2 = "TRIG_RELEASE"; break;		case SOF_IPC_STREAM_TRIG_DRAIN:			str2 = "TRIG_DRAIN"; break;		case SOF_IPC_STREAM_TRIG_XRUN:			str2 = "TRIG_XRUN"; break;		case SOF_IPC_STREAM_POSITION:			vdbg = true;			str2 = "POSITION"; break;		case SOF_IPC_STREAM_VORBIS_PARAMS:			str2 = "VORBIS_PARAMS"; break;		case SOF_IPC_STREAM_VORBIS_FREE:			str2 = "VORBIS_FREE"; break;		default:			str2 = "unknown type"; break;		}		break;	case SOF_IPC_FW_READY:		str = "FW_READY"; break;	case SOF_IPC_GLB_DAI_MSG:		str = "GLB_DAI_MSG";		switch (type) {		case SOF_IPC_DAI_CONFIG:			str2 = "CONFIG"; break;		case SOF_IPC_DAI_LOOPBACK:			str2 = "LOOPBACK"; break;		default:			str2 = "unknown type"; break;		}		break;	case SOF_IPC_GLB_TRACE_MSG:		str = "GLB_TRACE_MSG"; break;	case SOF_IPC_GLB_TEST_MSG:		str = "GLB_TEST_MSG";		switch (type) {		case SOF_IPC_TEST_IPC_FLOOD:			str2 = "IPC_FLOOD"; break;		default:			str2 = "unknown type"; break;		}		break;	case SOF_IPC_GLB_DEBUG:		str = "GLB_DEBUG";		switch (type) {		case SOF_IPC_DEBUG_MEM_USAGE:			str2 = "MEM_USAGE"; break;		default:			str2 = "unknown type"; break;		}		break;	default:		str = "unknown GLB command"; break;	} 	if (str2) {		if (vdbg)			dev_vdbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2);		else			dev_dbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2);	} else {		dev_dbg(dev, "%s: 0x%x: %s\n", text, cmd, str);	}}#elsestatic inline void ipc_log_header(struct device *dev, u8 *text, u32 cmd){	if ((cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_TRACE_MSG)		dev_dbg(dev, "%s: 0x%x\n", text, cmd);}#endif /* wait for IPC message reply */static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg,			void *reply_data){	struct snd_sof_dev *sdev = ipc->sdev;	struct sof_ipc_cmd_hdr *hdr = msg->msg_data;	int ret; 	/* wait for DSP IPC completion */	ret = wait_event_timeout(msg->waitq, msg->ipc_complete,				 msecs_to_jiffies(sdev->ipc_timeout)); 	if (ret == 0) {		dev_err(sdev->dev, "error: ipc timed out for 0x%x size %d\n",			hdr->cmd, hdr->size);		snd_sof_handle_fw_exception(ipc->sdev);		ret = -ETIMEDOUT;	} else {		ret = msg->reply_error;		if (ret < 0) {			dev_err(sdev->dev, "error: ipc error for 0x%x size %zu\n",				hdr->cmd, msg->reply_size);		} else {			ipc_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd);			if (msg->reply_size)				/* copy the data returned from DSP */				memcpy(reply_data, msg->reply_data,				       msg->reply_size);		}	} 	return ret;} /* send IPC message from host to DSP */static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header,				       void *msg_data, size_t msg_bytes,				       void *reply_data, size_t reply_bytes){	struct snd_sof_dev *sdev = ipc->sdev;	struct snd_sof_ipc_msg *msg;	int ret; 	if (ipc->disable_ipc_tx)		return -ENODEV; 	/*	 * The spin-lock is also still needed to protect message objects against	 * other atomic contexts.	 */	spin_lock_irq(&sdev->ipc_lock); 	/* initialise the message */	msg = &ipc->msg; 	msg->header = header;	msg->msg_size = msg_bytes;	msg->reply_size = reply_bytes;	msg->reply_error = 0; 	/* attach any data */	if (msg_bytes)		memcpy(msg->msg_data, msg_data, msg_bytes); 	sdev->msg = msg; 	ret = snd_sof_dsp_send_msg(sdev, msg);	/* Next reply that we receive will be related to this message */	if (!ret)		msg->ipc_complete = false; 	spin_unlock_irq(&sdev->ipc_lock); 	if (ret < 0) {		dev_err_ratelimited(sdev->dev,				    "error: ipc tx failed with error %d\n",				    ret);		return ret;	} 	ipc_log_header(sdev->dev, "ipc tx", msg->header); 	/* now wait for completion */	if (!ret)		ret = tx_wait_done(ipc, msg, reply_data); 	return ret;} /* send IPC message from host to DSP */int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header,		       void *msg_data, size_t msg_bytes, void *reply_data,		       size_t reply_bytes){	const struct sof_dsp_power_state target_state = {		.state = SOF_DSP_PM_D0,	};	int ret; 	/* ensure the DSP is in D0 before sending a new IPC */	ret = snd_sof_dsp_set_power_state(ipc->sdev, &target_state);	if (ret < 0) {		dev_err(ipc->sdev->dev, "error: resuming DSP %d\n", ret);		return ret;	} 	return sof_ipc_tx_message_no_pm(ipc, header, msg_data, msg_bytes,					reply_data, reply_bytes);}EXPORT_SYMBOL(sof_ipc_tx_message); /* * send IPC message from host to DSP without modifying the DSP state. * This will be used for IPC's that can be handled by the DSP * even in a low-power D0 substate. */int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header,			     void *msg_data, size_t msg_bytes,			     void *reply_data, size_t reply_bytes){	int ret; 	if (msg_bytes > SOF_IPC_MSG_MAX_SIZE ||	    reply_bytes > SOF_IPC_MSG_MAX_SIZE)		return -ENOBUFS; 	/* Serialise IPC TX */	mutex_lock(&ipc->tx_mutex); 	ret = sof_ipc_tx_message_unlocked(ipc, header, msg_data, msg_bytes,					  reply_data, reply_bytes); 	mutex_unlock(&ipc->tx_mutex); 	return ret;}EXPORT_SYMBOL(sof_ipc_tx_message_no_pm); /* handle reply message from DSP */void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id){	struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; 	if (msg->ipc_complete) {		dev_dbg(sdev->dev,			"no reply expected, received 0x%x, will be ignored",			msg_id);		return;	} 	/* wake up and return the error if we have waiters on this message ? */	msg->ipc_complete = true;	wake_up(&msg->waitq);}EXPORT_SYMBOL(snd_sof_ipc_reply); /* DSP firmware has sent host a message  */void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev){	struct sof_ipc_cmd_hdr hdr;	u32 cmd, type;	int err = 0; 	/* read back header */	snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr));	ipc_log_header(sdev->dev, "ipc rx", hdr.cmd); 	cmd = hdr.cmd & SOF_GLB_TYPE_MASK;	type = hdr.cmd & SOF_CMD_TYPE_MASK; 	/* check message type */	switch (cmd) {	case SOF_IPC_GLB_REPLY:		dev_err(sdev->dev, "error: ipc reply unknown\n");		break;	case SOF_IPC_FW_READY:		/* check for FW boot completion */		if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) {			err = sof_ops(sdev)->fw_ready(sdev, cmd);			if (err < 0)				sdev->fw_state = SOF_FW_BOOT_READY_FAILED;			else				sdev->fw_state = SOF_FW_BOOT_COMPLETE; 			/* wake up firmware loader */			wake_up(&sdev->boot_wait);		}		break;	case SOF_IPC_GLB_COMPOUND:	case SOF_IPC_GLB_TPLG_MSG:	case SOF_IPC_GLB_PM_MSG:	case SOF_IPC_GLB_COMP_MSG:		break;	case SOF_IPC_GLB_STREAM_MSG:		/* need to pass msg id into the function */		ipc_stream_message(sdev, hdr.cmd);		break;	case SOF_IPC_GLB_TRACE_MSG:		ipc_trace_message(sdev, type);		break;	default:		dev_err(sdev->dev, "error: unknown DSP message 0x%x\n", cmd);		break;	} 	ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd);}EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); /* * IPC trace mechanism. */ static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id){	struct sof_ipc_dma_trace_posn posn; 	switch (msg_id) {	case SOF_IPC_TRACE_DMA_POSITION:		/* read back full message */		snd_sof_ipc_msg_data(sdev, NULL, &posn, sizeof(posn));		snd_sof_trace_update_pos(sdev, &posn);		break;	default:		dev_err(sdev->dev, "error: unhandled trace message %x\n",			msg_id);		break;	}} /* * IPC stream position. */ static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id){	struct snd_soc_component *scomp = sdev->component;	struct snd_sof_pcm_stream *stream;	struct sof_ipc_stream_posn posn;	struct snd_sof_pcm *spcm;	int direction; 	spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction);	if (!spcm) {		dev_err(sdev->dev,			"error: period elapsed for unknown stream, msg_id %d\n",			msg_id);		return;	} 	stream = &spcm->stream[direction];	snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); 	dev_vdbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n",		 posn.host_posn, posn.dai_posn, posn.wallclock); 	memcpy(&stream->posn, &posn, sizeof(posn)); 	/* only inform ALSA for period_wakeup mode */	if (!stream->substream->runtime->no_period_wakeup)		snd_sof_pcm_period_elapsed(stream->substream);} /* DSP notifies host of an XRUN within FW */static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id){	struct snd_soc_component *scomp = sdev->component;	struct snd_sof_pcm_stream *stream;	struct sof_ipc_stream_posn posn;	struct snd_sof_pcm *spcm;	int direction; 	spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction);	if (!spcm) {		dev_err(sdev->dev, "error: XRUN for unknown stream, msg_id %d\n",			msg_id);		return;	} 	stream = &spcm->stream[direction];	snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); 	dev_dbg(sdev->dev,  "posn XRUN: host %llx comp %d size %d\n",		posn.host_posn, posn.xrun_comp_id, posn.xrun_size); 

Lines 1–500 of 877.