Unverified Commit f23e8f3a authored by Mark Brown's avatar Mark Brown
Browse files

ASoC: SOF: ipc4-pcm: Do not reset ChainDMA if it is

Merge series from Peter Ujfalusi <peter.ujfalusi@linux.intel.com>:

The current code will reset the ChainDMA on release unconditionally which
can result the following error when the CHainDMA is not allocated:

ipc tx      : 0xe040000|0x0: GLB_CHAIN_DMA
ipc tx reply: 0x2e000007|0x0: GLB_CHAIN_DMA
FW reported error: 7 - Unsupported operation requested
ipc error for msg 0xe040000|0x0
sof_pcm_stream_free: pcm_ops hw_free failed -22

Background:
Pulseaudio and Pipewire on startup opens all available streams and
closes them without triggering a start (after probing it's capabilities).
parents a93830a9 7211814f
Loading
Loading
Loading
Loading
+79 −36
Original line number Diff line number Diff line
@@ -37,6 +37,25 @@ struct sof_ipc4_timestamp_info {
	snd_pcm_sframes_t delay;
};

/**
 * struct sof_ipc4_pcm_stream_priv - IPC4 specific private data
 * @time_info: pointer to time info struct if it is supported, otherwise NULL
 * @chain_dma_allocated: indicates the ChainDMA allocation state
 */
struct sof_ipc4_pcm_stream_priv {
	struct sof_ipc4_timestamp_info *time_info;

	bool chain_dma_allocated;
};

static inline struct sof_ipc4_timestamp_info *
sof_ipc4_sps_to_time_info(struct snd_sof_pcm_stream *sps)
{
	struct sof_ipc4_pcm_stream_priv *stream_priv = sps->private;

	return stream_priv->time_info;
}

static int sof_ipc4_set_multi_pipeline_state(struct snd_sof_dev *sdev, u32 state,
					     struct ipc4_pipeline_set_state_data *trigger_list)
{
@@ -253,14 +272,17 @@ sof_ipc4_update_pipeline_state(struct snd_sof_dev *sdev, int state, int cmd,
 */

static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev,
				      int direction,
				      struct snd_sof_pcm *spcm, int direction,
				      struct snd_sof_pcm_stream_pipeline_list *pipeline_list,
				      int state, int cmd)
{
	struct sof_ipc4_fw_data *ipc4_data = sdev->private;
	struct sof_ipc4_pcm_stream_priv *stream_priv;
	bool allocate, enable, set_fifo_size;
	struct sof_ipc4_msg msg = {{ 0 }};
	int i;
	int ret, i;

	stream_priv = spcm->stream[direction].private;

	switch (state) {
	case SOF_IPC4_PIPE_RUNNING: /* Allocate and start chained dma */
@@ -281,6 +303,11 @@ static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev,
		set_fifo_size = false;
		break;
	case SOF_IPC4_PIPE_RESET: /* Disable and free chained DMA. */

		/* ChainDMA can only be reset if it has been allocated */
		if (!stream_priv->chain_dma_allocated)
			return 0;

		allocate = false;
		enable = false;
		set_fifo_size = false;
@@ -338,7 +365,12 @@ static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev,
	if (enable)
		msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK;

	return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
	ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
	/* Update the ChainDMA allocation state */
	if (!ret)
		stream_priv->chain_dma_allocated = allocate;

	return ret;
}

static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component,
@@ -378,7 +410,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component,
	 * trigger function that handles the rest for the substream.
	 */
	if (pipeline->use_chain_dma)
		return sof_ipc4_chain_dma_trigger(sdev, substream->stream,
		return sof_ipc4_chain_dma_trigger(sdev, spcm, substream->stream,
						  pipeline_list, state, cmd);

	/* allocate memory for the pipeline data */
@@ -452,7 +484,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component,
		 * Invalidate the stream_start_offset to make sure that it is
		 * going to be updated if the stream resumes
		 */
		time_info = spcm->stream[substream->stream].private;
		time_info = sof_ipc4_sps_to_time_info(&spcm->stream[substream->stream]);
		if (time_info)
			time_info->stream_start_offset = SOF_IPC4_INVALID_STREAM_POSITION;

@@ -706,12 +738,16 @@ static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
static void sof_ipc4_pcm_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm)
{
	struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
	struct sof_ipc4_pcm_stream_priv *stream_priv;
	int stream;

	for_each_pcm_streams(stream) {
		pipeline_list = &spcm->stream[stream].pipeline_list;
		kfree(pipeline_list->pipelines);
		pipeline_list->pipelines = NULL;

		stream_priv = spcm->stream[stream].private;
		kfree(stream_priv->time_info);
		kfree(spcm->stream[stream].private);
		spcm->stream[stream].private = NULL;
	}
@@ -721,7 +757,8 @@ static int sof_ipc4_pcm_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm
{
	struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
	struct sof_ipc4_fw_data *ipc4_data = sdev->private;
	struct sof_ipc4_timestamp_info *stream_info;
	struct sof_ipc4_pcm_stream_priv *stream_priv;
	struct sof_ipc4_timestamp_info *time_info;
	bool support_info = true;
	u32 abi_version;
	u32 abi_offset;
@@ -749,33 +786,41 @@ static int sof_ipc4_pcm_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm
			return -ENOMEM;
		}

		stream_priv = kzalloc(sizeof(*stream_priv), GFP_KERNEL);
		if (!stream_priv) {
			sof_ipc4_pcm_free(sdev, spcm);
			return -ENOMEM;
		}

		spcm->stream[stream].private = stream_priv;

		if (!support_info)
			continue;

		stream_info = kzalloc(sizeof(*stream_info), GFP_KERNEL);
		if (!stream_info) {
		time_info = kzalloc(sizeof(*time_info), GFP_KERNEL);
		if (!time_info) {
			sof_ipc4_pcm_free(sdev, spcm);
			return -ENOMEM;
		}

		spcm->stream[stream].private = stream_info;
		stream_priv->time_info = time_info;
	}

	return 0;
}

static void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *spcm)
static void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *sps)
{
	struct sof_ipc4_copier *host_copier = NULL;
	struct sof_ipc4_copier *dai_copier = NULL;
	struct sof_ipc4_llp_reading_slot llp_slot;
	struct sof_ipc4_timestamp_info *info;
	struct sof_ipc4_timestamp_info *time_info;
	struct snd_soc_dapm_widget *widget;
	struct snd_sof_dai *dai;
	int i;

	/* find host & dai to locate info in memory window */
	for_each_dapm_widgets(spcm->list, i, widget) {
	for_each_dapm_widgets(sps->list, i, widget) {
		struct snd_sof_widget *swidget = widget->dobj.private;

		if (!swidget)
@@ -795,44 +840,44 @@ static void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pc
		return;
	}

	info = spcm->private;
	info->host_copier = host_copier;
	info->dai_copier = dai_copier;
	info->llp_offset = offsetof(struct sof_ipc4_fw_registers, llp_gpdma_reading_slots) +
				    sdev->fw_info_box.offset;
	time_info = sof_ipc4_sps_to_time_info(sps);
	time_info->host_copier = host_copier;
	time_info->dai_copier = dai_copier;
	time_info->llp_offset = offsetof(struct sof_ipc4_fw_registers,
					 llp_gpdma_reading_slots) + sdev->fw_info_box.offset;

	/* find llp slot used by current dai */
	for (i = 0; i < SOF_IPC4_MAX_LLP_GPDMA_READING_SLOTS; i++) {
		sof_mailbox_read(sdev, info->llp_offset, &llp_slot, sizeof(llp_slot));
		sof_mailbox_read(sdev, time_info->llp_offset, &llp_slot, sizeof(llp_slot));
		if (llp_slot.node_id == dai_copier->data.gtw_cfg.node_id)
			break;

		info->llp_offset += sizeof(llp_slot);
		time_info->llp_offset += sizeof(llp_slot);
	}

	if (i < SOF_IPC4_MAX_LLP_GPDMA_READING_SLOTS)
		return;

	/* if no llp gpdma slot is used, check aggregated sdw slot */
	info->llp_offset = offsetof(struct sof_ipc4_fw_registers, llp_sndw_reading_slots) +
					sdev->fw_info_box.offset;
	time_info->llp_offset = offsetof(struct sof_ipc4_fw_registers,
					 llp_sndw_reading_slots) + sdev->fw_info_box.offset;
	for (i = 0; i < SOF_IPC4_MAX_LLP_SNDW_READING_SLOTS; i++) {
		sof_mailbox_read(sdev, info->llp_offset, &llp_slot, sizeof(llp_slot));
		sof_mailbox_read(sdev, time_info->llp_offset, &llp_slot, sizeof(llp_slot));
		if (llp_slot.node_id == dai_copier->data.gtw_cfg.node_id)
			break;

		info->llp_offset += sizeof(llp_slot);
		time_info->llp_offset += sizeof(llp_slot);
	}

	if (i < SOF_IPC4_MAX_LLP_SNDW_READING_SLOTS)
		return;

	/* check EVAD slot */
	info->llp_offset = offsetof(struct sof_ipc4_fw_registers, llp_evad_reading_slot) +
					sdev->fw_info_box.offset;
	sof_mailbox_read(sdev, info->llp_offset, &llp_slot, sizeof(llp_slot));
	time_info->llp_offset = offsetof(struct sof_ipc4_fw_registers,
					 llp_evad_reading_slot) + sdev->fw_info_box.offset;
	sof_mailbox_read(sdev, time_info->llp_offset, &llp_slot, sizeof(llp_slot));
	if (llp_slot.node_id != dai_copier->data.gtw_cfg.node_id)
		info->llp_offset = 0;
		time_info->llp_offset = 0;
}

static int sof_ipc4_pcm_hw_params(struct snd_soc_component *component,
@@ -849,7 +894,7 @@ static int sof_ipc4_pcm_hw_params(struct snd_soc_component *component,
	if (!spcm)
		return -EINVAL;

	time_info = spcm->stream[substream->stream].private;
	time_info = sof_ipc4_sps_to_time_info(&spcm->stream[substream->stream]);
	/* delay calculation is not supported by current fw_reg ABI */
	if (!time_info)
		return 0;
@@ -864,7 +909,7 @@ static int sof_ipc4_pcm_hw_params(struct snd_soc_component *component,

static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
					    struct snd_pcm_substream *substream,
					    struct snd_sof_pcm_stream *stream,
					    struct snd_sof_pcm_stream *sps,
					    struct sof_ipc4_timestamp_info *time_info)
{
	struct sof_ipc4_copier *host_copier = time_info->host_copier;
@@ -918,7 +963,7 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
	struct sof_ipc4_timestamp_info *time_info;
	struct sof_ipc4_llp_reading_slot llp;
	snd_pcm_uframes_t head_cnt, tail_cnt;
	struct snd_sof_pcm_stream *stream;
	struct snd_sof_pcm_stream *sps;
	u64 dai_cnt, host_cnt, host_ptr;
	struct snd_sof_pcm *spcm;
	int ret;
@@ -927,8 +972,8 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
	if (!spcm)
		return -EOPNOTSUPP;

	stream = &spcm->stream[substream->stream];
	time_info = stream->private;
	sps = &spcm->stream[substream->stream];
	time_info = sof_ipc4_sps_to_time_info(sps);
	if (!time_info)
		return -EOPNOTSUPP;

@@ -938,7 +983,7 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
	 * the statistics is complete. And it will not change after the first initiailization.
	 */
	if (time_info->stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION) {
		ret = sof_ipc4_get_stream_start_offset(sdev, substream, stream, time_info);
		ret = sof_ipc4_get_stream_start_offset(sdev, substream, sps, time_info);
		if (ret < 0)
			return -EOPNOTSUPP;
	}
@@ -1030,15 +1075,13 @@ static snd_pcm_sframes_t sof_ipc4_pcm_delay(struct snd_soc_component *component,
{
	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
	struct sof_ipc4_timestamp_info *time_info;
	struct snd_sof_pcm_stream *stream;
	struct snd_sof_pcm *spcm;

	spcm = snd_sof_find_spcm_dai(component, rtd);
	if (!spcm)
		return 0;

	stream = &spcm->stream[substream->stream];
	time_info = stream->private;
	time_info = sof_ipc4_sps_to_time_info(&spcm->stream[substream->stream]);
	/*
	 * Report the stored delay value calculated in the pointer callback.
	 * In the unlikely event that the calculation was skipped/aborted, the