All of lore.kernel.org
 help / color / mirror / Atom feed
From: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
To: lgirdwood@gmail.com, broonie@kernel.org
Cc: linux-sound@vger.kernel.org, kai.vehmanen@linux.intel.com,
	yung-chuan.liao@linux.intel.com, pierre-louis.bossart@linux.dev,
	liam.r.girdwood@intel.com
Subject: [PATCH 4/4] ASoC: SOF: Intel: hda: Avoid ACE2+ link DMA stream allocation hazards
Date: Thu, 30 Jul 2026 15:51:30 +0300	[thread overview]
Message-ID: <20260730125130.29887-5-peter.ujfalusi@linux.intel.com> (raw)
In-Reply-To: <20260730125130.29887-1-peter.ujfalusi@linux.intel.com>

On ACE2+ platforms the link DMA stream allocator must avoid two
hardware errata in mlink-capable systems:

- Concurrent (cross-direction) hazard: when SoundWire shares a physical
  link DMA stream index with HDaudio, iDisp or UAOL across the two
  directions, the LLP and timestamp values for the affected stream are
  wrong. SSP and DMIC are not affected because every DMA request from
  those links carries one sample block.

- Sequential (playback only) hazard: once a HDaudio or iDisp link has
  used a playback stream index, that index cannot drive any non
  HDA/iDisp link in the same direction until the next controller
  reset (CRST#).

Track the active link type per direction in two masks (one for SoundWire,
one for HDA/iDisp/UAOL) and the persistent set of playback stream
indices touched by HDA/iDisp in a third mask. The link DMA allocator
skips streams that would violate either rule. Streams are released from
the active masks when the stream is released; all masks are cleared in
hda_dsp_ctrl_init_chip() because the CRST# performed there clears the
hardware state as well.

A new helper hda_bus_ml_link_get_type() returns the link type from the
existing extended link descriptor so the SOF allocator can tell
SoundWire, HDA/iDisp and UAOL apart without duplicating the parsing.

The implementation is generic. On platforms older than ACE2 every link
is reported as HDA, only the sequential mask is ever set and it has no
effect because no other link types are present, so behavior is
unchanged.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
Reviewed-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Reviewed-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Reviewed-by: Liam Girdwood <liam.r.girdwood@intel.com>
---
 include/sound/hda-mlink.h         | 21 +++++++++
 sound/soc/sof/intel/hda-ctrl.c    | 11 +++++
 sound/soc/sof/intel/hda-dai-ops.c | 76 ++++++++++++++++++++++++++++---
 sound/soc/sof/intel/hda-dai.c     |  2 +-
 sound/soc/sof/intel/hda-mlink.c   | 18 ++++++++
 sound/soc/sof/intel/hda.h         | 26 ++++++++++-
 6 files changed, 146 insertions(+), 8 deletions(-)

diff --git a/include/sound/hda-mlink.h b/include/sound/hda-mlink.h
index d9789b048c61..ba35f03576b9 100644
--- a/include/sound/hda-mlink.h
+++ b/include/sound/hda-mlink.h
@@ -9,6 +9,22 @@
 struct hdac_bus;
 struct hdac_ext_link;
 
+/**
+ * enum hda_bus_ml_link_type - mlink link type, used by SOF link DMA
+ *	allocator constraints (see struct sof_intel_hda_dev).
+ *
+ * @HDA_BUS_ML_LINK_HDA:  non-alt link, i.e. HDA codec or iDisp
+ * @HDA_BUS_ML_LINK_SDW:  alt link, SoundWire
+ * @HDA_BUS_ML_LINK_UAOL: alt link, USB Audio Offload
+ * @HDA_BUS_ML_LINK_OTHER: alt link, SSP or DMIC
+ */
+enum hda_bus_ml_link_type {
+	HDA_BUS_ML_LINK_HDA,
+	HDA_BUS_ML_LINK_SDW,
+	HDA_BUS_ML_LINK_UAOL,
+	HDA_BUS_ML_LINK_OTHER,
+};
+
 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
 
 int hda_bus_ml_init(struct hdac_bus *bus);
@@ -53,6 +69,8 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus);
 int hda_bus_ml_resume(struct hdac_bus *bus);
 int hda_bus_ml_suspend(struct hdac_bus *bus);
 
+enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink);
+
 struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus);
 struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus);
 struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus);
@@ -172,6 +190,9 @@ static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { }
 static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; }
 static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; }
 
+static inline enum hda_bus_ml_link_type
+hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) { return HDA_BUS_ML_LINK_HDA; }
+
 static inline struct hdac_ext_link *
 hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; }
 
diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c
index a9ead78d3fcb..aeb34310eebd 100644
--- a/sound/soc/sof/intel/hda-ctrl.c
+++ b/sound/soc/sof/intel/hda-ctrl.c
@@ -186,6 +186,7 @@ EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON"
 int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec)
 {
 	struct hdac_bus *bus = sof_to_bus(sdev);
+	struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
 	struct hdac_stream *stream;
 	int sd_offset, ret = 0;
 	u32 gctl;
@@ -193,6 +194,16 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec)
 	if (bus->chip_init)
 		return 0;
 
+	/*
+	 * The controller reset clears the ACE2+ link DMA stream allocation
+	 * constraints; reset the masks to reflect this.
+	 */
+	memset(sof_hda->link_dma_active_sdw_mask, 0,
+	       sizeof(sof_hda->link_dma_active_sdw_mask));
+	memset(sof_hda->link_dma_active_multi_mask, 0,
+	       sizeof(sof_hda->link_dma_active_multi_mask));
+	sof_hda->link_dma_out_hda_used_mask = 0;
+
 	hda_codec_set_codec_wakeup(sdev, true);
 
 	hda_dsp_ctrl_misc_clock_gating(sdev, false);
diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c
index b2c559559962..f0be42048db3 100644
--- a/sound/soc/sof/intel/hda-dai-ops.c
+++ b/sound/soc/sof/intel/hda-dai-ops.c
@@ -20,7 +20,7 @@
 /* These ops are only applicable for the HDA DAI's in their current form */
 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
 /*
- * This function checks if the host dma channel corresponding
+ * This function checks if the host DMA stream corresponding
  * to the link DMA stream_tag argument is assigned to one
  * of the FEs connected to the BE DAI.
  */
@@ -42,23 +42,53 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
 }
 
 static struct hdac_ext_stream *
-hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream)
+hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream,
+		       enum hda_bus_ml_link_type link_type)
 {
 	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
 	struct sof_intel_hda_stream *hda_stream;
 	const struct sof_intel_dsp_desc *chip;
 	struct snd_sof_dev *sdev;
 	struct hdac_ext_stream *res = NULL;
 	struct hdac_stream *hstream = NULL;
-
 	int stream_dir = substream->stream;
+	bool is_multi = link_type == HDA_BUS_ML_LINK_HDA || link_type == HDA_BUS_ML_LINK_UAOL;
+	bool is_play = stream_dir == SNDRV_PCM_STREAM_PLAYBACK;
+	bool is_sdw = link_type == HDA_BUS_ML_LINK_SDW;
+	bool is_hda = link_type == HDA_BUS_ML_LINK_HDA;
+	u32 concur_block_mask = 0;
+	u32 seq_block_mask = 0;
+	unsigned int stream_idx;
 
 	if (!bus->ppcap) {
 		dev_err(bus->dev, "stream type not supported\n");
 		return NULL;
 	}
 
+	/*
+	 * On ACE2+ the link DMA stream allocator must avoid two HW errata,
+	 * see the comment on struct sof_intel_hda_dev.
+	 *
+	 * - Concurrent cross-direction: SoundWire conflicts with HDA, iDisp
+	 *   and UAOL on the same physical stream index; SSP and DMIC are safe.
+	 * - Sequential playback: a stream index previously used by an HDA/iDisp
+	 *   link cannot drive any non-HDA/iDisp link in the same direction
+	 *   until the next controller reset.
+	 *
+	 * The masks are protected by bus->reg_lock; sample them inside the
+	 * lock together with the stream walk to keep the decision atomic
+	 * with concurrent allocations and releases.
+	 */
 	guard(spinlock_irq)(&bus->reg_lock);
+
+	if (is_sdw)
+		concur_block_mask = sof_hda->link_dma_active_multi_mask[!stream_dir];
+	else if (is_multi)
+		concur_block_mask = sof_hda->link_dma_active_sdw_mask[!stream_dir];
+	if (is_play && !is_hda)
+		seq_block_mask = sof_hda->link_dma_out_hda_used_mask;
+
 	list_for_each_entry(hstream, &bus->stream_list, list) {
 		struct hdac_ext_stream *hext_stream =
 			stream_to_hdac_ext_stream(hstream);
@@ -69,6 +99,12 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream
 		sdev = hda_stream->sdev;
 		chip = get_chip_info(sdev->pdata);
 
+		stream_idx = hstream->stream_tag - 1;
+
+		/* skip streams blocked by the ACE2+ allocator constraints */
+		if ((concur_block_mask | seq_block_mask) & BIT(stream_idx))
+			continue;
+
 		/* check if link is available */
 		if (!hext_stream->link_locked) {
 			/*
@@ -95,7 +131,7 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream
 
 				/*
 				 * This must be a hostless stream.
-				 * So reserve the host DMA channel.
+				 * So reserve the host DMA stream.
 				 */
 				hda_stream->host_reserved = 1;
 				break;
@@ -109,6 +145,16 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream
 
 		res->link_locked = 1;
 		res->link_substream = substream;
+
+		stream_idx = res->hstream.stream_tag - 1;
+		if (is_sdw)
+			sof_hda->link_dma_active_sdw_mask[stream_dir] |= BIT(stream_idx);
+		else if (is_multi)
+			sof_hda->link_dma_active_multi_mask[stream_dir] |= BIT(stream_idx);
+
+		/* persistent OUT HDA/iDisp shadow, cleared only on CRST# */
+		if (is_hda && is_play)
+			sof_hda->link_dma_out_hda_used_mask |= BIT(stream_idx);
 	}
 
 	return res;
@@ -143,11 +189,13 @@ static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev
 
 static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev,
 						      struct snd_soc_dai *cpu_dai,
-						      struct snd_pcm_substream *substream)
+						      struct snd_pcm_substream *substream,
+						      struct hdac_ext_link *hlink)
 {
 	struct hdac_ext_stream *hext_stream;
+	enum hda_bus_ml_link_type link_type = hda_bus_ml_link_get_type(hlink);
 
-	hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream);
+	hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream, link_type);
 	if (!hext_stream)
 		return NULL;
 
@@ -160,6 +208,22 @@ static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai
 				    struct snd_pcm_substream *substream)
 {
 	struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream);
+	struct sof_intel_hda_dev *sof_hda = sdev->pdata->hw_pdata;
+	struct hdac_bus *bus = sof_to_bus(sdev);
+	int dir = substream->stream;
+	unsigned int stream_idx = hext_stream->hstream.stream_tag - 1;
+
+	/*
+	 * Drop the stream index from the per-direction active concurrency masks.
+	 * The two masks are mutually exclusive for a given stream/direction
+	 * (and a stream of the SSP/DMIC kind appears in neither), so a blind
+	 * clear of both is safe and lets us avoid having to remember the
+	 * link type at allocation time.
+	 */
+	scoped_guard(spinlock_irq, &bus->reg_lock) {
+		sof_hda->link_dma_active_sdw_mask[dir] &= ~BIT(stream_idx);
+		sof_hda->link_dma_active_multi_mask[dir] &= ~BIT(stream_idx);
+	}
 
 	snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
 	snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK);
diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c
index 15faedeec16d..bb44d4f8a4da 100644
--- a/sound/soc/sof/intel/hda-dai.c
+++ b/sound/soc/sof/intel/hda-dai.c
@@ -188,7 +188,7 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream,
 
 	if (!hext_stream) {
 		if (ops->assign_hext_stream)
-			hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream);
+			hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream, hlink);
 	}
 
 	if (!hext_stream)
diff --git a/sound/soc/sof/intel/hda-mlink.c b/sound/soc/sof/intel/hda-mlink.c
index e0107cbd06e6..6f02fb5b70ce 100644
--- a/sound/soc/sof/intel/hda-mlink.c
+++ b/sound/soc/sof/intel/hda-mlink.c
@@ -894,6 +894,24 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
 }
 EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK");
 
+enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink)
+{
+	struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+	if (!h2link->alt)
+		return HDA_BUS_ML_LINK_HDA;
+
+	switch (h2link->elid) {
+	case AZX_REG_ML_LEPTR_ID_SDW:
+		return HDA_BUS_ML_LINK_SDW;
+	case AZX_REG_ML_LEPTR_ID_INTEL_UAOL:
+		return HDA_BUS_ML_LINK_UAOL;
+	default:
+		return HDA_BUS_ML_LINK_OTHER;
+	}
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_link_get_type, "SND_SOC_SOF_HDA_MLINK");
+
 int hda_bus_ml_resume(struct hdac_bus *bus)
 {
 	struct hdac_ext_link *hlink;
diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h
index 799e49539b4a..1609589929a1 100644
--- a/sound/soc/sof/intel/hda.h
+++ b/sound/soc/sof/intel/hda.h
@@ -523,6 +523,29 @@ struct sof_intel_hda_dev {
 	/* the maximum number of streams (playback + capture) supported */
 	u32 stream_max;
 
+	/*
+	 * ACE2+ link DMA stream allocation constraints (stream index =
+	 * stream_tag - 1, shared between input and output directions). All
+	 * masks are cleared by hda_dsp_ctrl_init_chip() on controller reset
+	 * (CRST#).
+	 *
+	 * - Concurrent (cross-direction) constraint: a SoundWire stream and
+	 *   a HDA/iDisp/UAOL stream cannot share a physical stream index
+	 *   across directions, the resulting LLP/timestamp values are wrong.
+	 *   link_dma_active_sdw_mask and link_dma_active_multi_mask
+	 *   (indexed by SNDRV_PCM_STREAM_*) track currently allocated
+	 *   streams per direction in each of the conflicting groups; SSP
+	 *   and DMIC do not participate. Bits are cleared on stream release.
+	 *
+	 * - Sequential (playback only) constraint: once a HDA/iDisp link
+	 *   has used a playback stream index, that index cannot drive a
+	 *   non-HDA/iDisp link in the same direction until the next CRST#.
+	 *   link_dma_out_hda_used_mask records this.
+	 */
+	u32 link_dma_active_sdw_mask[SNDRV_PCM_STREAM_LAST + 1];
+	u32 link_dma_active_multi_mask[SNDRV_PCM_STREAM_LAST + 1];
+	u32 link_dma_out_hda_used_mask;
+
 	/* PM related */
 	bool l1_disabled;/* is DMI link L1 disabled? */
 
@@ -1031,7 +1054,8 @@ struct hda_dai_widget_dma_ops {
 						   struct snd_pcm_substream *substream);
 	struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev,
 						      struct snd_soc_dai *cpu_dai,
-						      struct snd_pcm_substream *substream);
+						      struct snd_pcm_substream *substream,
+						      struct hdac_ext_link *hlink);
 	void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
 				    struct snd_pcm_substream *substream);
 	void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream,
-- 
2.55.0


      parent reply	other threads:[~2026-07-30 12:51 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-30 12:51 [PATCH 0/4] ASoC: SOF: Intel: Handle ACE2+ link DMA allocation restrictions Peter Ujfalusi
2026-07-30 12:51 ` [PATCH 1/4] ASoC: SOF: Intel: hda: Fold mlink enumeration into hda_dsp_ctrl_init_chip() Peter Ujfalusi
2026-07-30 12:51 ` [PATCH 2/4] ASoC: SOF: Intel: hda: Keep non-alt mlinks powered at probe on ACE2+ Peter Ujfalusi
2026-07-30 12:51 ` [PATCH 3/4] ASoC: SOF: Intel: hda: Remove unused hda_bus_ml_put_all() Peter Ujfalusi
2026-07-30 12:51 ` Peter Ujfalusi [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260730125130.29887-5-peter.ujfalusi@linux.intel.com \
    --to=peter.ujfalusi@linux.intel.com \
    --cc=broonie@kernel.org \
    --cc=kai.vehmanen@linux.intel.com \
    --cc=lgirdwood@gmail.com \
    --cc=liam.r.girdwood@intel.com \
    --cc=linux-sound@vger.kernel.org \
    --cc=pierre-louis.bossart@linux.dev \
    --cc=yung-chuan.liao@linux.intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.