Skip to content

Commit d0bfef8

Browse files
committed
ASoC: SOF: Intel: hda: Avoid ACE2+ link DMA stream allocation hazards
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>
1 parent da4a638 commit d0bfef8

6 files changed

Lines changed: 146 additions & 8 deletions

File tree

include/sound/hda-mlink.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@
99
struct hdac_bus;
1010
struct hdac_ext_link;
1111

12+
/**
13+
* enum hda_bus_ml_link_type - mlink link type, used by SOF link DMA
14+
* allocator constraints (see struct sof_intel_hda_dev).
15+
*
16+
* @HDA_BUS_ML_LINK_HDA: non-alt link, i.e. HDA codec or iDisp
17+
* @HDA_BUS_ML_LINK_SDW: alt link, SoundWire
18+
* @HDA_BUS_ML_LINK_UAOL: alt link, USB Audio Offload
19+
* @HDA_BUS_ML_LINK_OTHER: alt link, SSP or DMIC
20+
*/
21+
enum hda_bus_ml_link_type {
22+
HDA_BUS_ML_LINK_HDA,
23+
HDA_BUS_ML_LINK_SDW,
24+
HDA_BUS_ML_LINK_UAOL,
25+
HDA_BUS_ML_LINK_OTHER,
26+
};
27+
1228
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
1329

1430
int hda_bus_ml_init(struct hdac_bus *bus);
@@ -53,6 +69,8 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus);
5369
int hda_bus_ml_resume(struct hdac_bus *bus);
5470
int hda_bus_ml_suspend(struct hdac_bus *bus);
5571

72+
enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink);
73+
5674
struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus);
5775
struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus);
5876
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) { }
172190
static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; }
173191
static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; }
174192

193+
static inline enum hda_bus_ml_link_type
194+
hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) { return HDA_BUS_ML_LINK_HDA; }
195+
175196
static inline struct hdac_ext_link *
176197
hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; }
177198

sound/soc/sof/intel/hda-ctrl.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,24 @@ EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON"
186186
int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec)
187187
{
188188
struct hdac_bus *bus = sof_to_bus(sdev);
189+
struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
189190
struct hdac_stream *stream;
190191
int sd_offset, ret = 0;
191192
u32 gctl;
192193

193194
if (bus->chip_init)
194195
return 0;
195196

197+
/*
198+
* The controller reset clears the ACE2+ link DMA stream allocation
199+
* constraints; reset the masks to reflect this.
200+
*/
201+
memset(sof_hda->link_dma_active_sdw_mask, 0,
202+
sizeof(sof_hda->link_dma_active_sdw_mask));
203+
memset(sof_hda->link_dma_active_multi_mask, 0,
204+
sizeof(sof_hda->link_dma_active_multi_mask));
205+
sof_hda->link_dma_out_hda_used_mask = 0;
206+
196207
hda_codec_set_codec_wakeup(sdev, true);
197208

198209
hda_dsp_ctrl_misc_clock_gating(sdev, false);

sound/soc/sof/intel/hda-dai-ops.c

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
/* These ops are only applicable for the HDA DAI's in their current form */
2121
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
2222
/*
23-
* This function checks if the host dma channel corresponding
23+
* This function checks if the host DMA stream corresponding
2424
* to the link DMA stream_tag argument is assigned to one
2525
* of the FEs connected to the BE DAI.
2626
*/
@@ -42,23 +42,53 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
4242
}
4343

4444
static struct hdac_ext_stream *
45-
hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream)
45+
hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream,
46+
enum hda_bus_ml_link_type link_type)
4647
{
4748
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
49+
struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
4850
struct sof_intel_hda_stream *hda_stream;
4951
const struct sof_intel_dsp_desc *chip;
5052
struct snd_sof_dev *sdev;
5153
struct hdac_ext_stream *res = NULL;
5254
struct hdac_stream *hstream = NULL;
53-
5455
int stream_dir = substream->stream;
56+
bool is_multi = link_type == HDA_BUS_ML_LINK_HDA || link_type == HDA_BUS_ML_LINK_UAOL;
57+
bool is_play = stream_dir == SNDRV_PCM_STREAM_PLAYBACK;
58+
bool is_sdw = link_type == HDA_BUS_ML_LINK_SDW;
59+
bool is_hda = link_type == HDA_BUS_ML_LINK_HDA;
60+
u32 concur_block_mask = 0;
61+
u32 seq_block_mask = 0;
62+
unsigned int stream_idx;
5563

5664
if (!bus->ppcap) {
5765
dev_err(bus->dev, "stream type not supported\n");
5866
return NULL;
5967
}
6068

69+
/*
70+
* On ACE2+ the link DMA stream allocator must avoid two HW errata,
71+
* see the comment on struct sof_intel_hda_dev.
72+
*
73+
* - Concurrent cross-direction: SoundWire conflicts with HDA, iDisp
74+
* and UAOL on the same physical stream index; SSP and DMIC are safe.
75+
* - Sequential playback: a stream index previously used by an HDA/iDisp
76+
* link cannot drive any non-HDA/iDisp link in the same direction
77+
* until the next controller reset.
78+
*
79+
* The masks are protected by bus->reg_lock; sample them inside the
80+
* lock together with the stream walk to keep the decision atomic
81+
* with concurrent allocations and releases.
82+
*/
6183
guard(spinlock_irq)(&bus->reg_lock);
84+
85+
if (is_sdw)
86+
concur_block_mask = sof_hda->link_dma_active_multi_mask[!stream_dir];
87+
else if (is_multi)
88+
concur_block_mask = sof_hda->link_dma_active_sdw_mask[!stream_dir];
89+
if (is_play && !is_hda)
90+
seq_block_mask = sof_hda->link_dma_out_hda_used_mask;
91+
6292
list_for_each_entry(hstream, &bus->stream_list, list) {
6393
struct hdac_ext_stream *hext_stream =
6494
stream_to_hdac_ext_stream(hstream);
@@ -69,6 +99,12 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream
6999
sdev = hda_stream->sdev;
70100
chip = get_chip_info(sdev->pdata);
71101

102+
stream_idx = hstream->stream_tag - 1;
103+
104+
/* skip streams blocked by the ACE2+ allocator constraints */
105+
if ((concur_block_mask | seq_block_mask) & BIT(stream_idx))
106+
continue;
107+
72108
/* check if link is available */
73109
if (!hext_stream->link_locked) {
74110
/*
@@ -95,7 +131,7 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream
95131

96132
/*
97133
* This must be a hostless stream.
98-
* So reserve the host DMA channel.
134+
* So reserve the host DMA stream.
99135
*/
100136
hda_stream->host_reserved = 1;
101137
break;
@@ -109,6 +145,16 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream
109145

110146
res->link_locked = 1;
111147
res->link_substream = substream;
148+
149+
stream_idx = res->hstream.stream_tag - 1;
150+
if (is_sdw)
151+
sof_hda->link_dma_active_sdw_mask[stream_dir] |= BIT(stream_idx);
152+
else if (is_multi)
153+
sof_hda->link_dma_active_multi_mask[stream_dir] |= BIT(stream_idx);
154+
155+
/* persistent OUT HDA/iDisp shadow, cleared only on CRST# */
156+
if (is_hda && is_play)
157+
sof_hda->link_dma_out_hda_used_mask |= BIT(stream_idx);
112158
}
113159

114160
return res;
@@ -143,11 +189,13 @@ static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev
143189

144190
static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev,
145191
struct snd_soc_dai *cpu_dai,
146-
struct snd_pcm_substream *substream)
192+
struct snd_pcm_substream *substream,
193+
struct hdac_ext_link *hlink)
147194
{
148195
struct hdac_ext_stream *hext_stream;
196+
enum hda_bus_ml_link_type link_type = hda_bus_ml_link_get_type(hlink);
149197

150-
hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream);
198+
hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream, link_type);
151199
if (!hext_stream)
152200
return NULL;
153201

@@ -160,6 +208,22 @@ static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai
160208
struct snd_pcm_substream *substream)
161209
{
162210
struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream);
211+
struct sof_intel_hda_dev *sof_hda = sdev->pdata->hw_pdata;
212+
struct hdac_bus *bus = sof_to_bus(sdev);
213+
int dir = substream->stream;
214+
unsigned int stream_idx = hext_stream->hstream.stream_tag - 1;
215+
216+
/*
217+
* Drop the stream index from the per-direction active concurrency masks.
218+
* The two masks are mutually exclusive for a given stream/direction
219+
* (and a stream of the SSP/DMIC kind appears in neither), so a blind
220+
* clear of both is safe and lets us avoid having to remember the
221+
* link type at allocation time.
222+
*/
223+
scoped_guard(spinlock_irq, &bus->reg_lock) {
224+
sof_hda->link_dma_active_sdw_mask[dir] &= ~BIT(stream_idx);
225+
sof_hda->link_dma_active_multi_mask[dir] &= ~BIT(stream_idx);
226+
}
163227

164228
snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
165229
snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK);

sound/soc/sof/intel/hda-dai.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream,
188188

189189
if (!hext_stream) {
190190
if (ops->assign_hext_stream)
191-
hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream);
191+
hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream, hlink);
192192
}
193193

194194
if (!hext_stream)

sound/soc/sof/intel/hda-mlink.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,24 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
894894
}
895895
EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK");
896896

897+
enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink)
898+
{
899+
struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
900+
901+
if (!h2link->alt)
902+
return HDA_BUS_ML_LINK_HDA;
903+
904+
switch (h2link->elid) {
905+
case AZX_REG_ML_LEPTR_ID_SDW:
906+
return HDA_BUS_ML_LINK_SDW;
907+
case AZX_REG_ML_LEPTR_ID_INTEL_UAOL:
908+
return HDA_BUS_ML_LINK_UAOL;
909+
default:
910+
return HDA_BUS_ML_LINK_OTHER;
911+
}
912+
}
913+
EXPORT_SYMBOL_NS(hda_bus_ml_link_get_type, "SND_SOC_SOF_HDA_MLINK");
914+
897915
int hda_bus_ml_resume(struct hdac_bus *bus)
898916
{
899917
struct hdac_ext_link *hlink;

sound/soc/sof/intel/hda.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,29 @@ struct sof_intel_hda_dev {
523523
/* the maximum number of streams (playback + capture) supported */
524524
u32 stream_max;
525525

526+
/*
527+
* ACE2+ link DMA stream allocation constraints (stream index =
528+
* stream_tag - 1, shared between input and output directions). All
529+
* masks are cleared by hda_dsp_ctrl_init_chip() on controller reset
530+
* (CRST#).
531+
*
532+
* - Concurrent (cross-direction) constraint: a SoundWire stream and
533+
* a HDA/iDisp/UAOL stream cannot share a physical stream index
534+
* across directions, the resulting LLP/timestamp values are wrong.
535+
* link_dma_active_sdw_mask and link_dma_active_multi_mask
536+
* (indexed by SNDRV_PCM_STREAM_*) track currently allocated
537+
* streams per direction in each of the conflicting groups; SSP
538+
* and DMIC do not participate. Bits are cleared on stream release.
539+
*
540+
* - Sequential (playback only) constraint: once a HDA/iDisp link
541+
* has used a playback stream index, that index cannot drive a
542+
* non-HDA/iDisp link in the same direction until the next CRST#.
543+
* link_dma_out_hda_used_mask records this.
544+
*/
545+
u32 link_dma_active_sdw_mask[SNDRV_PCM_STREAM_LAST + 1];
546+
u32 link_dma_active_multi_mask[SNDRV_PCM_STREAM_LAST + 1];
547+
u32 link_dma_out_hda_used_mask;
548+
526549
/* PM related */
527550
bool l1_disabled;/* is DMI link L1 disabled? */
528551

@@ -1030,7 +1053,8 @@ struct hda_dai_widget_dma_ops {
10301053
struct snd_pcm_substream *substream);
10311054
struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev,
10321055
struct snd_soc_dai *cpu_dai,
1033-
struct snd_pcm_substream *substream);
1056+
struct snd_pcm_substream *substream,
1057+
struct hdac_ext_link *hlink);
10341058
void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
10351059
struct snd_pcm_substream *substream);
10361060
void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream,

0 commit comments

Comments
 (0)