From d8231a707b578c314615ff16125b238798c5c079 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 19 Feb 2026 11:04:24 +0200 Subject: [PATCH 1/2] ASoC: SOF: ipc4-topology: Change DeepBuffer from static to dynamic mode Currently the DeepBuffer results static host DMA buffer and thus static minimum ALSA period size which can be a limiting factor for user space. DeepBuffer of static 100ms host DMA buffer can only be opened with at least 110ms ALSA period size, if for the same endpoint there is a need for smaller (or larger) buffer then a new PCM device must be created with different DeepBuffer configuration. This does not scale in real life. With Dynamic DeepBuffer the host DMA buffer size is calculated based on the requested ALSA period size (with a headroom between the two) using the DEEP_BUFFER token as a maximum limit for the host DMA buffer. This way applications can use the same DeepBuffer enabled PCM for different use cases and still benefit of the power saving of a bigger host DMA buffer. As an example, the DEEP_BUFFER in topology is set to 100ms (interpreted as maximum size with this patch): ALSA period time of 20ms will result 10ms host DMA Buffer - before the patch if 10ms host DMA buffer was desired, the minimum ALSA period size was 20ms ALSA period size of 50ms will result 40ms host DMA buffer - before the patch if 40ms host DMA buffer was desired, the minimum ALSA period size was 50ms ALSA period size of 110ms will result 100ms host DMA buffer - before the patch if 100ms host DMA buffer was desired, the minimum ALSA period size was 110ms ALSA period size of 500ms will result 100ms host DMA buffer - Like before this patch: 500ms ALSA period would use 100ms host DMA buffer The Dynamic DeepBuffer will give applications the means to choose between lower latency (small host DMA buffer) or higher power save (big host DMA buffer) with higher latency on the same device with topology providing a meaningful upper limit of the buffer size. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 116 +++++++++++++++++++++------------- sound/soc/sof/ipc4-topology.h | 8 +++ 2 files changed, 80 insertions(+), 44 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index c3c01d45663a79..6d65fbe19e2c33 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -669,22 +669,15 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) goto free_available_fmt; sps = &spcm->stream[dir]; - sof_update_ipc_object(scomp, &sps->dsp_max_burst_size_in_ms, - SOF_COPIER_DEEP_BUFFER_TOKENS, - swidget->tuples, - swidget->num_tuples, sizeof(u32), 1); - - /* Set default DMA buffer size if it is not specified in topology */ - if (!sps->dsp_max_burst_size_in_ms) { + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; struct sof_ipc4_pipeline *pipeline = pipe_widget->private; - if (dir == SNDRV_PCM_STREAM_PLAYBACK) - sps->dsp_max_burst_size_in_ms = pipeline->use_chain_dma ? - SOF_IPC4_CHAIN_DMA_BUFFER_SIZE : SOF_IPC4_MIN_DMA_BUFFER_SIZE; - else - /* Capture data is copied from DSP to host in 1ms bursts */ - sps->dsp_max_burst_size_in_ms = 1; + sps->dsp_max_burst_size_in_ms = pipeline->use_chain_dma ? + SOF_IPC4_CHAIN_DMA_BUFFER_SIZE : SOF_IPC4_MIN_DMA_BUFFER_SIZE; + } else { + /* Capture data is copied from DSP to host in 1ms bursts */ + sps->dsp_max_burst_size_in_ms = 1; } skip_gtw_cfg: @@ -2042,6 +2035,67 @@ static void sof_ipc4_host_config(struct snd_sof_dev *sdev, struct snd_sof_widget copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(host_dma_id); } +static void sof_ipc4_set_host_dma_buffer_size(struct snd_sof_widget *swidget, + unsigned int fe_period_bytes) +{ + unsigned int min_size, max_size, headroom, host_period_bytes; + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc4_copier_data *copier_data; + struct sof_ipc4_copier *ipc4_copier; + unsigned int deep_buffer_dma_ms = 0; + u32 buffer_bytes; + int ret; + + ipc4_copier = (struct sof_ipc4_copier *)swidget->private; + copier_data = &ipc4_copier->data; + + if (swidget->id == snd_soc_dapm_aif_in) + host_period_bytes = copier_data->base_config.ibs; + else + host_period_bytes = copier_data->base_config.obs; + + min_size = SOF_IPC4_MIN_DMA_BUFFER_SIZE * host_period_bytes; + headroom = min(SOF_IPC4_ALSA_PERIOD_MAX_HEADROOM_MS * host_period_bytes, + fe_period_bytes / 2); + + /* parse the deep buffer dma size */ + ret = sof_update_ipc_object(scomp, &deep_buffer_dma_ms, + SOF_COPIER_DEEP_BUFFER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(u32), 1); + if (ret) { + dev_dbg(scomp->dev, + "Failed to parse deep buffer dma size for %s\n", + swidget->widget->name); + buffer_bytes = min_size; + goto out; + } + + max_size = deep_buffer_dma_ms * host_period_bytes; + + /* + * Non Deepbuffer and small ALSA periods must use the minimal host DMA + * buffer size. + * Note: smaller than 2x the minimum host DMA buffer size for ALSA + * period is not allowed and should be protected by platform code with + * constraint + */ + if (deep_buffer_dma_ms <= SOF_IPC4_MIN_DMA_BUFFER_SIZE || + (min_size * 2) > fe_period_bytes) + buffer_bytes = min_size; + else + buffer_bytes = min(max_size, fe_period_bytes - headroom); + +out: + dev_dbg(scomp->dev, + "%s, dma buffer%s: %u ms (max: %u) / %u bytes, ALSA period: %u / %u\n", + swidget->widget->name, deep_buffer_dma_ms ? " (using Deep Buffer)" : "", + buffer_bytes / host_period_bytes, + deep_buffer_dma_ms ? deep_buffer_dma_ms : SOF_IPC4_MIN_DMA_BUFFER_SIZE, + buffer_bytes, fe_period_bytes / host_period_bytes, fe_period_bytes); + + copier_data->gtw_cfg.dma_buffer_size = buffer_bytes; +} + static int sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, @@ -2063,7 +2117,6 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, u32 **data; int ipc_size, ret, out_ref_valid_bits; u32 out_ref_rate, out_ref_channels, out_ref_type; - u32 deep_buffer_dma_ms = 0; bool single_output_bitdepth; int i; @@ -2081,16 +2134,6 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, str_yes_no(pipeline->use_chain_dma), platform_params->stream_tag); - /* parse the deep buffer dma size */ - ret = sof_update_ipc_object(scomp, &deep_buffer_dma_ms, - SOF_COPIER_DEEP_BUFFER_TOKENS, swidget->tuples, - swidget->num_tuples, sizeof(u32), 1); - if (ret) { - dev_err(scomp->dev, "Failed to parse deep buffer dma size for %s\n", - swidget->widget->name); - return ret; - } - ipc4_copier = (struct sof_ipc4_copier *)swidget->private; gtw_attr = ipc4_copier->gtw_attr; copier_data = &ipc4_copier->data; @@ -2425,34 +2468,19 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, * in topology. */ switch (swidget->id) { + case snd_soc_dapm_aif_in: + case snd_soc_dapm_aif_out: + sof_ipc4_set_host_dma_buffer_size(swidget, + params_period_bytes(fe_params)); + break; case snd_soc_dapm_dai_in: copier_data->gtw_cfg.dma_buffer_size = SOF_IPC4_MIN_DMA_BUFFER_SIZE * copier_data->base_config.ibs; break; - case snd_soc_dapm_aif_in: - copier_data->gtw_cfg.dma_buffer_size = - max((u32)SOF_IPC4_MIN_DMA_BUFFER_SIZE, deep_buffer_dma_ms) * - copier_data->base_config.ibs; - dev_dbg(sdev->dev, "copier %s, dma buffer%s: %u ms (%u bytes)", - swidget->widget->name, - deep_buffer_dma_ms ? " (using Deep Buffer)" : "", - max((u32)SOF_IPC4_MIN_DMA_BUFFER_SIZE, deep_buffer_dma_ms), - copier_data->gtw_cfg.dma_buffer_size); - break; case snd_soc_dapm_dai_out: copier_data->gtw_cfg.dma_buffer_size = SOF_IPC4_MIN_DMA_BUFFER_SIZE * copier_data->base_config.obs; break; - case snd_soc_dapm_aif_out: - copier_data->gtw_cfg.dma_buffer_size = - max((u32)SOF_IPC4_MIN_DMA_BUFFER_SIZE, deep_buffer_dma_ms) * - copier_data->base_config.obs; - dev_dbg(sdev->dev, "copier %s, dma buffer%s: %u ms (%u bytes)", - swidget->widget->name, - deep_buffer_dma_ms ? " (using Deep Buffer)" : "", - max((u32)SOF_IPC4_MIN_DMA_BUFFER_SIZE, deep_buffer_dma_ms), - copier_data->gtw_cfg.dma_buffer_size); - break; default: break; } diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index a289c1d8f3ff0e..90b567f6706af1 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -76,6 +76,14 @@ /* ChainDMA in fw uses 5ms DMA buffer */ #define SOF_IPC4_CHAIN_DMA_BUFFER_SIZE 5 +/* + * When Deep buffer is enabled for a device we need to keep a headroom between + * the host DMA buffer and ALSA period size to compensate for DMA bursts. + * The maximum headroom is 10ms, which is based on how the firmware moves data + * between host buffer and the rest of the pipeline. + */ +#define SOF_IPC4_ALSA_PERIOD_MAX_HEADROOM_MS 10 + /* * The base of multi-gateways. Multi-gateways addressing starts from * ALH_MULTI_GTW_BASE and there are ALH_MULTI_GTW_COUNT multi-sources From d53f6ea77f53bca16f9fda33ef6cf113d804ef67 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 19 Feb 2026 11:30:50 +0200 Subject: [PATCH 2/2] ASoC: SOF: ipc4/Intel: Rename dsp_max_burst_size_in_ms to dsp_min_burst_size_in_ms The meaning of the variable has changed with he Dynamic DeepBuffer and it reflects the smallest burst that the host DMA does. This can be used to set the minimum period time constraint to avoid overshooting by the DMA burst. Change the name and update the related code in Intel hda_dsp_pcm_open() to reflect the revised meaning. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/intel/hda-pcm.c | 31 +++++-------------------------- sound/soc/sof/ipc4-topology.c | 4 ++-- sound/soc/sof/sof-audio.h | 2 +- 3 files changed, 8 insertions(+), 29 deletions(-) diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c index da6c1e7263cde1..0718e338aca2be 100644 --- a/sound/soc/sof/intel/hda-pcm.c +++ b/sound/soc/sof/intel/hda-pcm.c @@ -29,8 +29,6 @@ #define SDnFMT_BITS(x) ((x) << 4) #define SDnFMT_CHAN(x) ((x) << 0) -#define HDA_MAX_PERIOD_TIME_HEADROOM 10 - static bool hda_always_enable_dmi_l1; module_param_named(always_enable_dmi_l1, hda_always_enable_dmi_l1, bool, 0444); MODULE_PARM_DESC(always_enable_dmi_l1, "SOF HDA always enable DMI l1"); @@ -287,36 +285,17 @@ int hda_dsp_pcm_open(struct snd_sof_dev *sdev, SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32); /* - * The dsp_max_burst_size_in_ms is the length of the maximum burst size + * The dsp_min_burst_size_in_ms is the length of the minimum burst size * of the host DMA in the ALSA buffer. * - * On playback start the DMA will transfer dsp_max_burst_size_in_ms - * amount of data in one initial burst to fill up the host DMA buffer. - * Consequent DMA burst sizes are shorter and their length can vary. - * To avoid immediate xrun by the initial burst we need to place - * constraint on the period size (via PERIOD_TIME) to cover the size of - * the host buffer. - * We need to add headroom of max 10ms as the firmware needs time to - * settle to the 1ms pacing and initially it can run faster for few - * internal periods. - * - * On capture the DMA will transfer 1ms chunks. + * Set a constraint to period time min to be at least twice as long as + * the minimum burst size to avoid DMA overruns */ - if (spcm->stream[direction].dsp_max_burst_size_in_ms) { - unsigned int period_time = spcm->stream[direction].dsp_max_burst_size_in_ms; - - /* - * add headroom over the maximum burst size to cover the time - * needed for the DMA pace to settle. - * Limit the headroom time to HDA_MAX_PERIOD_TIME_HEADROOM - */ - period_time += min(period_time, HDA_MAX_PERIOD_TIME_HEADROOM); - + if (spcm->stream[direction].dsp_min_burst_size_in_ms) snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, - period_time * USEC_PER_MSEC, + spcm->stream[direction].dsp_min_burst_size_in_ms * USEC_PER_MSEC, UINT_MAX); - } /* binding pcm substream to hda stream */ substream->runtime->private_data = &dsp_stream->hstream; diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 6d65fbe19e2c33..efa435b4498540 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -673,11 +673,11 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; struct sof_ipc4_pipeline *pipeline = pipe_widget->private; - sps->dsp_max_burst_size_in_ms = pipeline->use_chain_dma ? + sps->dsp_min_burst_size_in_ms = pipeline->use_chain_dma ? SOF_IPC4_CHAIN_DMA_BUFFER_SIZE : SOF_IPC4_MIN_DMA_BUFFER_SIZE; } else { /* Capture data is copied from DSP to host in 1ms bursts */ - sps->dsp_max_burst_size_in_ms = 1; + sps->dsp_min_burst_size_in_ms = 1; } skip_gtw_cfg: diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 36082e764bf99e..15fb4699cf3645 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -336,7 +336,7 @@ struct snd_sof_pcm_stream { struct snd_soc_dapm_widget_list *list; /* list of connected DAPM widgets */ bool d0i3_compatible; /* DSP can be in D0I3 when this pcm is opened */ bool pause_supported; /* PCM device supports PAUSE operation */ - unsigned int dsp_max_burst_size_in_ms; /* The maximum size of the host DMA burst in ms */ + unsigned int dsp_min_burst_size_in_ms; /* The minimum size of the host DMA burst in ms */ /* * flag to indicate that the DSP pipelines should be kept * active or not while suspending the stream