From 746de48c3a250f0c264105dcbfcdf0c5001e970b Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 8 Oct 2026 12:01:30 +0300 Subject: [PATCH 01/20] ASoC: SOF: ipc4-topology: Fix shift-out-of-bounds for aggregated ALH capture When ch_count is smaller than blob->alh_cfg.device_count (e.g. a mono capture stream, such as a SmartMic, aggregated across multiple SDW links), step = ch_count / blob->alh_cfg.device_count truncates to 0. The subsequent GENMASK(step - 1, 0) then underflows step - 1 (u32) to UINT_MAX, causing GENMASK()'s internal shift to use an out-of-bounds exponent: UBSAN: shift-out-of-bounds in sound/soc/sof/ipc4-topology.c:2363:13 shift exponent 18446744069414584384 is too large for 64-bit type 'long unsigned int' Treat ch_count < device_count the same way as the existing "channels equal to output reference channels" case: apply the same ch_mask to every aggregated device instead of trying to split channels that can't be evenly divided. Fixes: 0390a102cc18 ("ASoC: SOF: ipc4-topology: use different channel mask for each sdw amp feedback") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Reviewed-by: Bard Liao Link: https://patch.msgid.link/20261008090130.15135-1-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 24a1175dd690..22c15fbb93f2 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -2458,11 +2458,14 @@ _sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ch_map >>= 4; } - if (swidget->id == snd_soc_dapm_dai_in && ch_count == out_ref_channels) { + if ((swidget->id == snd_soc_dapm_dai_in && ch_count == out_ref_channels) || + ch_count < blob->alh_cfg.device_count) { /* * For playback DAI widgets where the channel number is equal to - * the output reference channels, set the step = 0 to ensure all - * the ch_mask is applied to all alh mappings. + * the output reference channels, or when there are fewer + * channels than aggregated devices (e.g. a mono capture + * stream split across multiple links), set step = 0 to + * ensure all the ch_mask is applied to all alh mappings. */ mask = ch_mask; step = 0; From f90f8afb61beb268ca8092e7f3a826b520d3e8da Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 8 Oct 2026 12:01:43 +0300 Subject: [PATCH 02/20] ASoC: SOF: sof-client: Use event-handler mutex consistently Protect IPC RX and FW state handler list unregister/dispatch paths with client_event_handler_mutex to match list registration and locking comments. Fixes: 5c19da34df02 ("ASoC: SOF: Use guard()/scoped_guard() for mutex locks where it makes sense") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Reviewed-by: Bard Liao Link: https://patch.msgid.link/20261008090143.15158-1-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/sof-client.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index 64da8df15bf2..21084cd98e43 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -576,7 +576,7 @@ void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev, struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct sof_ipc_event_entry *event; - guard(mutex)(&sdev->ipc_client_mutex); + guard(mutex)(&sdev->client_event_handler_mutex); list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) { if (event->cdev == cdev && event->ipc_msg_type == ipc_msg_type) { @@ -593,7 +593,7 @@ void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev) { struct sof_state_event_entry *event; - guard(mutex)(&sdev->ipc_client_mutex); + guard(mutex)(&sdev->client_event_handler_mutex); list_for_each_entry(event, &sdev->fw_state_handler_list, list) event->callback(event->cdev, sdev->fw_state); @@ -628,7 +628,7 @@ void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev) struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct sof_state_event_entry *event; - guard(mutex)(&sdev->ipc_client_mutex); + guard(mutex)(&sdev->client_event_handler_mutex); list_for_each_entry(event, &sdev->fw_state_handler_list, list) { if (event->cdev == cdev) { From f6be9f558a83fe30a03bd3adae461c821a0cef8c Mon Sep 17 00:00:00 2001 From: Sergey Tiraspolsky Date: Sat, 10 Oct 2026 03:37:53 -0700 Subject: [PATCH 03/20] ASoC: Intel: avs: Reset the stream unless it is already prepared Commit cfca1637bc2b ("ASoC: Intel: avs: Unprepare a stream when XRUN occurs") resets the HDA stream when ->prepare() is called in the XRUN state. User space does not always recover that way. PipeWire's ALSA backend recovers from an underrun with snd_pcm_drop(), snd_pcm_prepare() and a new start, so ->prepare() runs in the SETUP state, hdac_stream->prepared is still set and the reset is skipped. Every recovery attempt then fails the same way: spa.alsa: hw:avsmax98927,1p: snd_pcm_avail after recover: Broken pipe On a Google Pixelbook (Kaby Lake, max98927 speakers) this happens after an occasional underrun during normal playback and silences the speakers for one to four minutes, until the PCM is closed. It can be provoked by stopping PipeWire with SIGSTOP for 0.5 s during playback: in one run that single underrun led to 1297 failed recoveries over two minutes, until the sink was suspended. A plain alsa-lib program that recovers with snd_pcm_drop() and snd_pcm_prepare() hits the same loop, while snd_pcm_prepare() straight from XRUN recovers at once. Skip the reset only when the stream is already PREPARED, the repeated ->prepare() case the check exists for, and reset it otherwise. With this change both kinds of recovery succeed with a single restart. Fixes: cfca1637bc2b ("ASoC: Intel: avs: Unprepare a stream when XRUN occurs") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Sergey Tiraspolsky Acked-by: Cezary Rojewski Link: https://patch.msgid.link/20261010103753.16969-1-stiraspo@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index ad25bd355769..880cd7a9fb34 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -756,7 +756,7 @@ static int avs_dai_fe_prepare(struct snd_pcm_substream *substream, struct snd_so data = snd_soc_dai_get_dma_data(dai, substream); host_stream = data->host_stream; - if (runtime->state == SNDRV_PCM_STATE_XRUN) + if (runtime->state != SNDRV_PCM_STATE_PREPARED) hdac_stream(host_stream)->prepared = false; if (hdac_stream(host_stream)->prepared) return 0; From 0936bbd35a247665c64886135835faa3c3f4f29f Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Sun, 11 Oct 2026 06:54:59 +0300 Subject: [PATCH 04/20] ASoC: stm32: spdifrx: fix synchronization state check The interrupt handler uses a logical AND to check whether SPDIFRX was in the SYNC state before clearing errors. Since both SYNC (1) and ENABLE (3) are nonzero, this also treats ENABLE as SYNC (and any other non-zero value that might be added). The logical && basically just converts this to check for != 0. So errors occurring while SPDIFRX is enabled cause the driver to retry synch instead of disconnecting the PCM stream. Fix is to change the logical && to equality (==) and only retry when exactly in sync state. Fixes: 0c93c291321f2 ("ASoC: stm32: spdifrx: retry synchronization in sync state") Signed-off-by: Avi Weiss Reviewed-by: Olivier Moysan Link: https://patch.msgid.link/20261011035459.2289403-1-thnkslprpt@gmail.com Signed-off-by: Mark Brown --- sound/soc/stm/stm32_spdifrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index e0fef47a227e..13a07ef93240 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -720,7 +720,7 @@ static irqreturn_t stm32_spdifrx_isr(int irq, void *devid) if (err) { regmap_read(spdifrx->regmap, STM32_SPDIFRX_CR, &cr); - sync_state = FIELD_GET(SPDIFRX_CR_SPDIFEN_MASK, cr) && + sync_state = FIELD_GET(SPDIFRX_CR_SPDIFEN_MASK, cr) == SPDIFRX_SPDIFEN_SYNC; /* SPDIFRX is in STATE_STOP. Disable SPDIFRX to clear errors */ From 203436d2aeb7093290459e344e1912a418a6ebfd Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 8 Oct 2026 22:09:47 +0900 Subject: [PATCH 05/20] ASoC: cs42l56: Put the Limiter Cushion control on the limiter register The Limiter Cushion control writes bits 4:2 of the ALC Thresholds register. Those bits are ALCMIN, which the ALC MIN control owns. The datasheet puts CUSH at bits 4:2 of the Limit Thresholds Control register, next to LMAX, which the Limiter MAX control already uses. So setting the limiter cushion changes the ALC minimum threshold, and the cushion itself cannot be set. Use CS42L56_LIM_THRESHOLD_CTL. Fixes: 272b5edd3b8f ("ASoC: Add support for CS42L56 CODEC") Link: https://statics.cirrus.com/pubs/proDatasheet/CS42L56_F2.pdf Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20261008130953.2446692-2-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l56.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs42l56.c b/sound/soc/codecs/cs42l56.c index 4d9a22a1029c..f0ea5309a6d4 100644 --- a/sound/soc/codecs/cs42l56.c +++ b/sound/soc/codecs/cs42l56.c @@ -481,7 +481,7 @@ static const struct snd_kcontrol_new cs42l56_snd_controls[] = { 0, 0x3f, 0, 0), SOC_SINGLE_TLV("Limiter MAX", CS42L56_LIM_THRESHOLD_CTL, 5, 0x07, 1, alc_tlv), - SOC_SINGLE_TLV("Limiter Cushion", CS42L56_ALC_THRESHOLD, + SOC_SINGLE_TLV("Limiter Cushion", CS42L56_LIM_THRESHOLD_CTL, 2, 0x07, 1, alc_tlv), SOC_SINGLE("NG Switch", CS42L56_NOISE_GATE_CTL, 6, 1, 1), From 8b08b98796d2da103f2432550c14a7c687e72080 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 8 Oct 2026 22:09:48 +0900 Subject: [PATCH 06/20] ASoC: cs42l52: Fix the NG Type Switch bit The NG Type Switch enum selects between "Apply Specific" and "Apply All", which is the NGALL bit. The datasheet puts NGALL at bit 7 of the Noise Gate Control register. The enum uses bit 6, which is NG, the noise gate enable owned by the NG Enable Switch. cs42l52.h names it CS42L52_NG_ENABLE_SHIFT. cs42l56 has the same Noise Gate register and puts its NG All Switch at bit 7. So changing the noise gate type turns the noise gate on or off, and NGALL cannot be set. Use bit 7. Fixes: dfe0f98b8d2e ("ASoC: Add support for CS42L52 Codec") Link: https://statics.cirrus.com/pubs/proDatasheet/CS42L52_F2.pdf Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20261008130953.2446692-3-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l52.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs42l52.c b/sound/soc/codecs/cs42l52.c index 9d6bcfbbf0b2..c55ae3532682 100644 --- a/sound/soc/codecs/cs42l52.c +++ b/sound/soc/codecs/cs42l52.c @@ -290,7 +290,7 @@ static const char * const cs42l52_ng_type_text[] = { }; static SOC_ENUM_SINGLE_DECL(ng_type_enum, - CS42L52_NOISE_GATE_CTL, 6, + CS42L52_NOISE_GATE_CTL, 7, cs42l52_ng_type_text); static const char * const left_swap_text[] = { From b26edc925c1cfa0184cd270ed81d4833db045ce0 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 8 Oct 2026 22:09:49 +0900 Subject: [PATCH 07/20] ASoC: pcm3168a: Fix the DAC zero flag polarity bit The DAC Zero Flag Polarity enum uses PCM3168A_DAC_ATSPDA_SHIFT, which is bit 6 of register 70. That bit is ATSPDA, the attenuation speed, which the DAC Volume Rate Multiplier enum already owns. The datasheet puts ZREV, the zero flag polarity, at bit 0. The header defines PCM3168A_DAC_ZREV_SHIFT for it, but nothing uses it. So changing the zero flag polarity changes the attenuation speed, and ZREV cannot be set. Use PCM3168A_DAC_ZREV_SHIFT. Fixes: a9b17a638af5 ("ASoC: pcm3168a: Add driver for pcm3168a codec") Link: https://www.ti.com/lit/ds/symlink/pcm3168a.pdf Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20261008130953.2446692-4-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm3168a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c index cb6a6f08f2f8..6f45ea4869a7 100644 --- a/sound/soc/codecs/pcm3168a.c +++ b/sound/soc/codecs/pcm3168a.c @@ -103,7 +103,7 @@ static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_func, PCM3168A_DAC_ATT_DEMP_ZF, static const char *const pcm3168a_pol[] = { "Active High", "Active Low" }; static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_pol, PCM3168A_DAC_ATT_DEMP_ZF, - PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_pol); + PCM3168A_DAC_ZREV_SHIFT, pcm3168a_pol); static const char *const pcm3168a_con[] = { "Differential", "Single-Ended" }; From f7934340c56a84fc1b246ebf6b2e806d775f3b54 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 8 Oct 2026 22:09:50 +0900 Subject: [PATCH 08/20] ASoC: max98371: Fix the Speaker DHT Min Volume bits Register 0x31 holds two 4-bit fields. The datasheet puts SPK_GAIN_MIN at bits 7:4 and DHT_VROT_PNT at bits 3:0. The Speaker DHT Min Volume and Speaker DHT Rotation Volume controls both use bits 3:0. So setting the minimum speaker gain changes the DHT rotation point, and SPK_GAIN_MIN cannot be set. Move Speaker DHT Min Volume to bits 7:4. Fixes: ca2cd6bc6663 ("ASoC: Add max98371 codec driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20261008130953.2446692-5-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98371.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/max98371.c b/sound/soc/codecs/max98371.c index c8d4c68af562..324270cc5cb2 100644 --- a/sound/soc/codecs/max98371.c +++ b/sound/soc/codecs/max98371.c @@ -166,7 +166,7 @@ static const struct snd_kcontrol_new max98371_snd_controls[] = { 0, (1< Date: Thu, 8 Oct 2026 22:09:51 +0900 Subject: [PATCH 09/20] ASoC: wm8978: Fix the 128x oversampling switch bits The DAC and ADC 128x Oversampling Switches use bit 5 of R10 and R14. Table 66 of the WM8978 datasheet puts DACOSR128 and ADCOSR128 at bit 3 of those registers. In R14, bit 5 is inside HPFCUT[6:4], which the High Pass Cut Off control owns. So turning on ADC oversampling changes the high pass cut-off, and the oversampling bit cannot be set. In R10, bit 5 is reserved, so the DAC switch has no effect. Use bit 3 for both. Fixes: 823dba519122 ("ASoC: Fix broken bitfield definitions in WM8978") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20261008130953.2446692-6-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8978.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index ad8064bbaaac..e40ba4d43ada 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -255,9 +255,9 @@ static const struct snd_kcontrol_new wm8978_snd_controls[] = { /* DAC / ADC oversampling */ SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL, - 5, 1, 0), + 3, 1, 0), SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL, - 5, 1, 0), + 3, 1, 0), }; /* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */ From 4db45fc1da3d62d0fa6e8f1e457ae92e8741ed07 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 8 Oct 2026 22:09:52 +0900 Subject: [PATCH 10/20] ASoC: nau8822: Fix the 128x oversampling switch bits The DAC and ADC 128x Oversampling Switches use bit 5 of R10 and R14. The NAU8822 datasheet register map puts DACOS and ADCOS at bit 3 of those registers. In R14, bit 5 is inside HPF[6:4], which the High Pass Cut Off control owns. So turning on ADC oversampling changes the high pass cut-off, and the oversampling bit cannot be set. In R10, bit 5 is reserved, so the DAC switch has no effect. Use bit 3 for both. Fixes: 1e3cb6c321be ("ASoC: nau8822: new codec driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20261008130953.2446692-7-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8822.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/nau8822.c b/sound/soc/codecs/nau8822.c index 830164e991a7..b4ae269873c4 100644 --- a/sound/soc/codecs/nau8822.c +++ b/sound/soc/codecs/nau8822.c @@ -383,9 +383,9 @@ static const struct snd_kcontrol_new nau8822_snd_controls[] = { NAU8822_REG_RIGHT_ADC_BOOST_CONTROL, 0, 7, 0, boost_tlv), SOC_SINGLE("DAC 128x Oversampling Switch", - NAU8822_REG_DAC_CONTROL, 5, 1, 0), + NAU8822_REG_DAC_CONTROL, 3, 1, 0), SOC_SINGLE("ADC 128x Oversampling Switch", - NAU8822_REG_ADC_CONTROL, 5, 1, 0), + NAU8822_REG_ADC_CONTROL, 3, 1, 0), }; /* LMAIN and RMAIN Mixer */ From 2a31b5378c6db9c4a8d399e5d10d32cd4b732bef Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 13 Oct 2026 07:54:23 -0500 Subject: [PATCH 11/20] ASoC: amd: yc: disable MSI on resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BIOS/firmware may re-enable MSI in PCI config space during system resume even though this driver only uses legacy INTx interrupts. If MSI is left enabled with stale address and data registers, the device can write interrupts to a bogus address and cause an IOMMU IO_PAGE_FAULT. Clear the MSI Enable bit before reinitializing ACP hardware on system resume. Keep runtime resume separate because firmware does not restore PCI configuration on that path. Fixes: c8212df7bc0f ("ASoC: amd: add acp6x pci driver pm ops") Reported-by: Marek Borůvka Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222119 Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20261013125425.3541206-1-mario.limonciello@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/pci-acp6x.c | 38 ++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/sound/soc/amd/yc/pci-acp6x.c b/sound/soc/amd/yc/pci-acp6x.c index 671443204e04..bacbfc55c598 100644 --- a/sound/soc/amd/yc/pci-acp6x.c +++ b/sound/soc/amd/yc/pci-acp6x.c @@ -301,7 +301,7 @@ static int snd_acp6x_suspend(struct device *dev) return ret; } -static int snd_acp6x_resume(struct device *dev) +static int snd_acp6x_runtime_resume(struct device *dev) { struct acp6x_dev_data *adata; int ret; @@ -313,8 +313,42 @@ static int snd_acp6x_resume(struct device *dev) return ret; } +static void acp6x_disable_msi_on_resume(struct pci_dev *pdev) +{ + u16 control; + + if (!pdev->msi_cap) + return; + + pci_read_config_word(pdev, pdev->msi_cap + PCI_MSI_FLAGS, &control); + if (control & PCI_MSI_FLAGS_ENABLE) { + dev_warn(&pdev->dev, + "ACP: MSI unexpectedly enabled after resume (flags=0x%04x), disabling\n", + control); + control &= ~PCI_MSI_FLAGS_ENABLE; + pci_write_config_word(pdev, pdev->msi_cap + PCI_MSI_FLAGS, control); + } +} + +static int snd_acp6x_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + /* + * BIOS/firmware may re-enable MSI in PCI config space during + * system resume even though this driver only uses legacy INTx + * interrupts. If MSI is left enabled with stale address/data + * registers, the device will write interrupts to a bogus address + * causing IOMMU IO_PAGE_FAULT and interrupt delivery failure. + * Explicitly clear the MSI Enable bit before reinitializing + * the ACP hardware. + */ + acp6x_disable_msi_on_resume(pdev); + return snd_acp6x_runtime_resume(dev); +} + static const struct dev_pm_ops acp6x_pm = { - RUNTIME_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume, NULL) + RUNTIME_PM_OPS(snd_acp6x_suspend, snd_acp6x_runtime_resume, NULL) SYSTEM_SLEEP_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume) }; From 929e433b8cbc1f3c0fe90847124fe1eced5ef4d4 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:33 +0530 Subject: [PATCH 12/20] ASoC: SOF: amd: rename acp7x machine table and descriptor to acp7f The existing acp7x machine table, chip descriptor, firmware, and topology files all target ACP7.F hardware. Rename them to acp7f to reflect the actual hardware variant. No functional change. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 10 +++++----- sound/soc/amd/mach-config.h | 2 +- sound/soc/sof/amd/pci-acp7x.c | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 3d48bb0f100f..d40600e12d4f 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -428,17 +428,17 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[] = { }; EXPORT_SYMBOL(snd_soc_acpi_amd_acp70_sof_machines); -struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[] = { +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[] = { { .id = "AMDI1010", - .drv_name = "acp7x-dsp", + .drv_name = "acp7f-dsp", .pdata = &acp_quirk_data, - .fw_filename = "sof-acp7x.ri", - .sof_tplg_filename = "sof-acp7x.tplg", + .fw_filename = "sof-acp7f.ri", + .sof_tplg_filename = "sof-acp7f.tplg", }, {}, }; -EXPORT_SYMBOL(snd_soc_acpi_amd_acp7x_sof_machines); +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7f_sof_machines); MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h index b602a983feb7..2dafd742dc01 100644 --- a/sound/soc/amd/mach-config.h +++ b/sound/soc/amd/mach-config.h @@ -28,7 +28,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; -extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[]; struct config_entry { u32 flags; diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 0594c1f3deb6..5949aefe26bc 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -26,8 +26,8 @@ #define ACP7X_REG_START 0x1240000 #define ACP7X_REG_END 0x125C000 -static const struct sof_amd_acp_desc acp7x_chip_info = { - .name = "acp7x", +static const struct sof_amd_acp_desc acp7f_chip_info = { + .name = "acp7f", .pgfsm_base = ACP7X_PGFSM_BASE, .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, @@ -45,13 +45,13 @@ static const struct sof_amd_acp_desc acp7x_chip_info = { .reg_end_addr = ACP7X_REG_END, }; -static const struct sof_dev_desc acp7x_desc = { - .machines = snd_soc_acpi_amd_acp7x_sof_machines, +static const struct sof_dev_desc acp7f_desc = { + .machines = snd_soc_acpi_amd_acp7f_sof_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .chip_info = &acp7x_chip_info, + .chip_info = &acp7f_chip_info, .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), .ipc_default = SOF_IPC_TYPE_3, .default_fw_path = { @@ -61,7 +61,7 @@ static const struct sof_dev_desc acp7x_desc = { [SOF_IPC_TYPE_3] = "amd/sof-tplg", }, .default_fw_filename = { - [SOF_IPC_TYPE_3] = "sof-acp7x.ri", + [SOF_IPC_TYPE_3] = "sof-acp7f.ri", }, .nocodec_tplg_filename = "sof-acp.tplg", .ops = &sof_acp7x_ops, @@ -95,7 +95,7 @@ static void acp7x_pci_remove(struct pci_dev *pci) /* PCI IDs */ static const struct pci_device_id acp7x_pci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), - .driver_data = (unsigned long)&acp7x_desc}, + .driver_data = (unsigned long)&acp7f_desc}, { 0, } }; MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); From f84363861e442581ecfb9d0fc54c7f2330f3b41b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:34 +0530 Subject: [PATCH 13/20] ASoC: SOF: amd: select descriptor per PCI revision in acp7x probe Add a static acp7f_pci_id entry carrying the acp7f_desc descriptor. Update acp7x_pci_probe() to check the config flag before the revision switch and return the per-revision pci_device_id to sof_pci_probe() rather than passing the table entry. Remove driver_data from acp7x_pci_ids[] since the descriptor is now selected in the probe callback. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/pci-acp7x.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 5949aefe26bc..7261377c615b 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -68,23 +68,25 @@ static const struct sof_dev_desc acp7f_desc = { .ops_init = sof_acp7x_ops_init, }; +static const struct pci_device_id acp7f_pci_id = { + PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&acp7f_desc, +}; + static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { unsigned int flag; - switch (pci->revision) { - case ACP7B_PCI_ID: - case ACP7F_PCI_ID: - break; - default: - return -ENODEV; - } - flag = snd_amd_acp_find_config(pci); if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) return -ENODEV; - return sof_pci_probe(pci, pci_id); + switch (pci->revision) { + case ACP7F_PCI_ID: + return sof_pci_probe(pci, &acp7f_pci_id); + default: + return -ENODEV; + } } static void acp7x_pci_remove(struct pci_dev *pci) @@ -94,8 +96,7 @@ static void acp7x_pci_remove(struct pci_dev *pci) /* PCI IDs */ static const struct pci_device_id acp7x_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), - .driver_data = (unsigned long)&acp7f_desc}, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID) }, { 0, } }; MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); From 9246ac5b5c99bac520ae130d21716cde350a8753 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:35 +0530 Subject: [PATCH 14/20] ASoC: amd: add I2S machine table for ACP7.B Add snd_soc_acpi_amd_acp7b_sof_machines with the ACP7.B-specific driver name, firmware image, and topology file. Declare it in mach-config.h alongside the existing acp7f entry. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-4-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 12 ++++++++++++ sound/soc/amd/mach-config.h | 1 + 2 files changed, 13 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index d40600e12d4f..30e01bc47ee0 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -440,5 +440,17 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[] = { }; EXPORT_SYMBOL(snd_soc_acpi_amd_acp7f_sof_machines); +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[] = { + { + .id = "AMDI1010", + .drv_name = "acp7b-dsp", + .pdata = &acp_quirk_data, + .fw_filename = "sof-acp7b.ri", + .sof_tplg_filename = "sof-acp7b.tplg", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7b_sof_machines); + MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h index 2dafd742dc01..02ce044bcbf7 100644 --- a/sound/soc/amd/mach-config.h +++ b/sound/soc/amd/mach-config.h @@ -28,6 +28,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[]; struct config_entry { From 44f8d28191f13c56b453ca6ea0c1cfe195a58d9c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:36 +0530 Subject: [PATCH 15/20] ASoC: SOF: amd: add ACP7.B chip descriptor and wire per-revision probe Add acp7b_chip_info, acp7b_desc and acp7b_pci_id for ACP7.B platforms. Extend acp7x_pci_probe() to select acp7b_desc for ACP7B_PCI_ID revisions alongside the existing acp7f_desc path. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-5-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/pci-acp7x.c | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 7261377c615b..5a21ab8ebb4b 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -26,6 +26,48 @@ #define ACP7X_REG_START 0x1240000 #define ACP7X_REG_END 0x125C000 +static const struct sof_amd_acp_desc acp7b_chip_info = { + .name = "acp7b", + .pgfsm_base = ACP7X_PGFSM_BASE, + .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, + .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, + .ext_intr_stat = ACP7X_EXT_INTR_STAT, + .ext_intr_stat1 = ACP7X_EXT_INTR_STAT1, + .dsp_intr_base = ACP7X_DSP_SW_INTR_BASE, + .acp_error_stat = ACP7X_ERROR_STATUS, + .sram_pte_offset = ACP7X_SRAM_PTE_OFFSET, + .hw_semaphore_offset = ACP7X_AXI2DAGB_SEM_0, + .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, + .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, + .reg_start_addr = ACP7X_REG_START, + .sdw_max_link_count = ACP7X_SDW_MAX_MANAGER_COUNT, + .sdw_acpi_dev_addr = SDW_ACPI_ADDR_ACP7X, + .reg_end_addr = ACP7X_REG_END, +}; + +static const struct sof_dev_desc acp7b_desc = { + .machines = snd_soc_acpi_amd_acp7b_sof_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &acp7b_chip_info, + .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), + .ipc_default = SOF_IPC_TYPE_3, + .default_fw_path = { + [SOF_IPC_TYPE_3] = "amd/sof", + }, + .default_tplg_path = { + [SOF_IPC_TYPE_3] = "amd/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC_TYPE_3] = "sof-acp7b.ri", + }, + .nocodec_tplg_filename = "sof-acp.tplg", + .ops = &sof_acp7x_ops, + .ops_init = sof_acp7x_ops_init, +}; + static const struct sof_amd_acp_desc acp7f_chip_info = { .name = "acp7f", .pgfsm_base = ACP7X_PGFSM_BASE, @@ -68,6 +110,11 @@ static const struct sof_dev_desc acp7f_desc = { .ops_init = sof_acp7x_ops_init, }; +static const struct pci_device_id acp7b_pci_id = { + PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&acp7b_desc, +}; + static const struct pci_device_id acp7f_pci_id = { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), .driver_data = (unsigned long)&acp7f_desc, @@ -82,6 +129,8 @@ static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_ return -ENODEV; switch (pci->revision) { + case ACP7B_PCI_ID: + return sof_pci_probe(pci, &acp7b_pci_id); case ACP7F_PCI_ID: return sof_pci_probe(pci, &acp7f_pci_id); default: From b2dc10ee0b51f52a5b9c3dceec848c619ec63082 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:37 +0530 Subject: [PATCH 16/20] ASoC: SOF: amd: Propagate PCI subsystem Vendor and Device IDs Extend the AMD SOF machine driver to propagate the PCI subsystem Vendor and Device IDs so that they may be subsequently used as an SSID. Store the subsystem IDs in acp_dev_data during probe (both amd_sof_acp_probe and amd_sof_acp7x_probe), and propagate them to mach_params in the SoundWire machine select path. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-6-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp-common.c | 6 ++++++ sound/soc/sof/amd/acp.c | 4 ++++ sound/soc/sof/amd/acp.h | 2 ++ 3 files changed, 12 insertions(+) diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index 33540f7c421b..60e5c851c874 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -155,9 +155,15 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * } if (mach && mach->link_mask) { mach->mach_params.subsystem_rev = acp_data->pci_rev; + mach->mach_params.subsystem_vendor = acp_data->subsystem_vendor; + mach->mach_params.subsystem_device = acp_data->subsystem_device; + mach->mach_params.subsystem_id_set = true; mach->mach_params.links = mach->links; mach->mach_params.link_mask = mach->link_mask; mach->mach_params.platform = dev_name(sdev->dev); + + dev_dbg(sdev->dev, "SSID %x%04x\n", mach->mach_params.subsystem_vendor, + mach->mach_params.subsystem_device); return mach; } } diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index b059039c9e0d..71c39eb36194 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1198,6 +1198,8 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) adata->addr = addr; adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; adata->pci_rev = pci->revision; + adata->subsystem_vendor = pci->subsystem_vendor; + adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); sdev->pdata->hw_pdata = adata; @@ -1335,6 +1337,8 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) adata->addr = addr; adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; adata->pci_rev = pci->revision; + adata->subsystem_vendor = pci->subsystem_vendor; + adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); sdev->pdata->hw_pdata = adata; diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 1bc6b572448d..929251772526 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -296,6 +296,8 @@ struct acp_dev_data { unsigned int pdm_sel; bool is_sdw_dev; unsigned int pci_rev; + u32 subsystem_vendor; + u32 subsystem_device; int acp_sof_signed_firmware_image; }; From b23e9b1f3b4c8f1be4e359cd576eda082391d0cd Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:38 +0530 Subject: [PATCH 17/20] ASoC: SOF: amd: consolidate local variable declarations Use u32 instead of unsigned int for hardware register variables and combine related declarations onto single lines in sof_amd_check_and_handle_acp7x_sdw_wake_irq() and handle_amd_sof_acp7x_sdw_pme_event(). No functional change. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-7-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 71c39eb36194..790af65be34a 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -674,9 +674,8 @@ static int sof_amd_check_and_handle_acp7x_sdw_wake_irq(struct snd_sof_dev *sdev) { struct acp_dev_data *adata = sdev->pdata->hw_pdata; const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); - unsigned int ext_intr_stat1; - unsigned int sdw_pme_stat, sdw_wake_en; - unsigned int i; + u32 ext_intr_stat1, sdw_pme_stat, sdw_wake_en; + u32 i; bool sdw_wake_irq = false; ext_intr_stat1 = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat1); @@ -1445,10 +1444,7 @@ static void handle_amd_sof_acp7x_sdw_pme_event(struct snd_sof_dev *sdev) { struct acp_dev_data *adata; struct amd_sdw_manager *amd_manager; - u32 sdw_pme_stat; - u32 sdw_wake_en; - u32 pme_reg; - u32 wake_mask; + u32 sdw_pme_stat, sdw_wake_en, pme_reg, wake_mask; unsigned int instance; adata = sdev->pdata->hw_pdata; From 0e4c4081169bb7bbe755de62c2d09c4af80fd61a Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:39 +0530 Subject: [PATCH 18/20] ASoC: amd: acp: add acp-mach-common.h for shared machine driver types ACP7.B/7.F platforms expose a PDM controller selection via the acp-audio-ep-port ACPI _DSD property. struct amd_pdm_pdata carries this selection and is required by multiple machine driver variants - the legacy (non-DSP) and SOF stacks - for both I2S and SoundWire paths. Currently the struct lives in soc_amd_sdw_common.h, which is SoundWire-specific, making it unavailable to I2S machine drivers without an inappropriate header dependency. Add acp-mach-common.h as a minimal header for types shared across all AMD ACP machine drivers. Move struct amd_pdm_pdata there and retain an #include of it in soc_amd_sdw_common.h so that no call sites require modification. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-8-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-mach-common.h | 25 +++++++++++++++++++++++++ sound/soc/amd/acp/soc_amd_sdw_common.h | 14 +------------- 2 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 sound/soc/amd/acp/acp-mach-common.h diff --git a/sound/soc/amd/acp/acp-mach-common.h b/sound/soc/amd/acp/acp-mach-common.h new file mode 100644 index 000000000000..a2cb25be6bf8 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach-common.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved + */ +/* + * acp-mach-common.h - Structures shared across all AMD ACP machine drivers + * (I2S, SoundWire, and SOF paths). + */ + +#ifndef __ACP_MACH_COMMON_H +#define __ACP_MACH_COMMON_H + +/** + * struct amd_pdm_pdata - PDM controller platform data passed via mach->pdata + * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), + * non-zero when a PDM controller was identified via ACPI _DSD for + * ACP7.B/7.F platforms. + * + * Passed via mach->pdata by machine select logic to both I2S and SoundWire + * machine drivers so each can configure the correct DMIC DAI link. + */ +struct amd_pdm_pdata { + unsigned int pdm_sel; +}; + +#endif /* __ACP_MACH_COMMON_H */ diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 17e4e97fb3d1..034c9ab6e4ea 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -13,6 +13,7 @@ #include #include #include +#include "acp-mach-common.h" #define ACP63_SDW_MAX_CPU_DAIS 8 #define ACP63_SDW_MAX_LINKS 2 @@ -23,19 +24,6 @@ #define ACP71_PCI_REV 0x71 #define ACP72_PCI_REV 0x72 -/** - * struct amd_pdm_pdata - platform data passed via mach->pdata to machine driver - * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), - * non-zero when a PDM controller was identified via ACPI _DSD - * - * Carries the PDM controller selection for ACP7.B/7.F platforms, derived - * from the acp-audio-ep-port ACPI _DSD property and passed via mach->pdata - * to the machine driver. - */ -struct amd_pdm_pdata { - unsigned int pdm_sel; -}; - #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) #define ASOC_SDW_ACP_DMIC BIT(5) From 439c43d9628ee38c72fdc5933f7b6091461441b6 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:40 +0530 Subject: [PATCH 19/20] ASoC: SOF: amd: propagate pdm_sel to SDW machine driver via mach->pdata When amd_sof_sdw_machine_select() returns a SoundWire machine, it did not carry the PDM controller selection to the machine driver. Without this, acp-sdw-sof-mach.c cannot configure the correct DMIC DAI link for ACP7.B/7.F platforms. Allocate amd_pdm_pdata and assign it to mach->pdata in the SDW machine select return path when acp_data->pdm_sel is non-zero, mirroring the existing handling in amd_sof_machine_select(). Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-9-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp-common.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index 60e5c851c874..3bb1de8c0376 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -132,6 +132,7 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * struct snd_soc_acpi_mach *mach; const struct snd_soc_acpi_link_adr *link; struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; + struct amd_pdm_pdata *pdm_pdata; int ret, i; if (acp_data->info.count) { @@ -162,6 +163,16 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * mach->mach_params.link_mask = mach->link_mask; mach->mach_params.platform = dev_name(sdev->dev); + if (acp_data->pdm_sel) { + pdm_pdata = devm_kzalloc(sdev->dev, + sizeof(*pdm_pdata), + GFP_KERNEL); + if (!pdm_pdata) + return NULL; + pdm_pdata->pdm_sel = acp_data->pdm_sel; + mach->pdata = pdm_pdata; + } + dev_dbg(sdev->dev, "SSID %x%04x\n", mach->mach_params.subsystem_vendor, mach->mach_params.subsystem_device); return mach; From cec3598305a6df306fc4eb0c49a72e0f9949c06f Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:41 +0530 Subject: [PATCH 20/20] ASoC: SOF: amd: add ASP carveout firmware loading for ACP7.B/7.F ACP7.B and ACP7.F require signed firmware to be authenticated by the AMD Security Processor (ASP) before the DSP can execute it. Implement a new two-stage firmware load path via the ASP MPASP mailbox. Stage 1 (probe time): query ASP for the physical base address and ATU group assignment of a pre-allocated 32 MB MALL carveout region. If ASP does not support carveout, probe fails immediately. Stage 2 (load time): load the signed image from disk, send it to ASP via VALIDATE_IMAGE; ASP authenticates the image and deposits the raw binary into the carveout. The driver then SHA-DMAs the raw binary from the carveout into DSP IRAM. On D3->D0 resume the raw binary is preserved in the carveout; the driver skips the filesystem load and ASP authentication and restores directly from the saved carveout address. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-10-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/Makefile | 2 +- sound/soc/sof/amd/acp-dsp-offset.h | 58 +++ sound/soc/sof/amd/acp-loader.c | 64 ++++ sound/soc/sof/amd/acp-stream.c | 9 - sound/soc/sof/amd/acp.c | 77 +++- sound/soc/sof/amd/acp.h | 105 ++++++ sound/soc/sof/amd/acp7x.c | 11 +- sound/soc/sof/amd/acp_asp_mailbox.c | 559 ++++++++++++++++++++++++++++ 8 files changed, 869 insertions(+), 16 deletions(-) create mode 100644 sound/soc/sof/amd/acp_asp_mailbox.c diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile index bb1c0ddef69d..3a7d230a4c46 100644 --- a/sound/soc/sof/amd/Makefile +++ b/sound/soc/sof/amd/Makefile @@ -4,7 +4,7 @@ # # Copyright(c) 2021, 2023, 2024 Advanced Micro Devices, Inc. All rights reserved. -snd-sof-amd-acp-y := acp.o acp-loader.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o +snd-sof-amd-acp-y := acp.o acp-loader.o acp_asp_mailbox.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o snd-sof-amd-acp-$(CONFIG_SND_SOC_SOF_ACP_PROBES) += acp-probes.o snd-sof-amd-renoir-y := pci-rn.o renoir.o snd-sof-amd-rembrandt-y := pci-rmb.o rembrandt.o diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h index 3984cd627db6..0bcccf27d133 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -37,6 +37,30 @@ /* Registers from ACP_DSP_0 block */ #define ACP_DSP0_RUNSTALL 0x414 +/* + * ATU group registers for groups 9 to 16. + * ACPAXI2AXI_ATU_CTRL (0xC40) sits between GRP_8 and GRP_9, + * so groups 9 to 16 are offset by 4 bytes relative to a simple stride. + */ +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_9 0xC44 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_9 0xC48 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_10 0xC4C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_10 0xC50 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_11 0xC54 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_11 0xC58 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_12 0xC5C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_12 0xC60 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_13 0xC64 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_13 0xC68 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_14 0xC6C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_14 0xC70 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_15 0xC74 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_15 0xC78 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_16 0xC7C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_16 0xC80 + +/* ATU page size value for 2 MB pages - used for carveout groups */ +#define PAGE_SIZE_2M_ENABLE 0x0 /* Registers from ACP_AXI2AXIATU block */ #define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 0xC00 @@ -132,6 +156,40 @@ #define ACP_SHA_DMA_INCLUDE_HDR 0x1CCC #define ACP_SHA_PSP_ACK 0x1C74 +/* ACP7.B/7.F MALL/Carveout configuration registers (ACP MMIO offsets) */ +#define ACP7X_MALL_ADDR_VALID 0x114C +#define ACP7X_MALL_BASE_ADDR_LOW 0x1150 +#define ACP7X_MALL_BASE_ADDR_HIGH 0x1154 +#define ACP7X_MALL_SIZE 0x1158 +#define ACP7X_MALL_VALID 0x115C + +/* ASP MPASP mailbox SMN addresses for ACP7.B/7.F carveout firmware load */ +#define MPASP_C2PMSG_173_REG 0x03810BB4 +#define MPASP_C2PMSG_174_REG 0x03810BB8 +#define MPASP_C2PMSG_175_REG 0x03810BBC +#define MPASP_C2PMSG_73_REG 0x03810A24 + +/* ASP mailbox command IDs */ +#define ASP_MBOX_CMD_VALIDATE_IMAGE 0x0001 +#define ASP_MBOX_CMD_CALC_HMAC 0x0002 +#define ASP_MBOX_CMD_VALIDATE_HMAC 0x0003 +#define ASP_MBOX_CMD_GET_CARVEOUT_ADDR 0x0004 + +/* MPASP_C2PMSG_173 control register bit definitions */ +#define ASP_MBOX_READY_BIT BIT(31) +#define ASP_MBOX_CMD_ID_MASK 0xFFFF + +/* ASP mailbox timeout: 5 seconds per ASP firmware load specification */ +#define ASP_MBOX_TIMEOUT_US 5000000 +/* Short timeout for the initial GET_CARVEOUT_ADDR probe: fail fast when + * ASP does not support carveout rather than blocking probe for 5 seconds. + */ +#define ASP_MBOX_PROBE_TIMEOUT_US 50000 +#define ASP_MBOX_POLL_INTERVAL_US 1000 + +/* ASP mailbox payload cookie "ACPB" */ +#define ASP_MBOX_COOKIE 0x41435042 + #define ACP_SCRATCH_REG_0 0x10000 #define ACP6X_DSP_FUSION_RUNSTALL 0x0644 #define ACP70_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c index f1ec85694323..abd080484804 100644 --- a/sound/soc/sof/amd/acp-loader.c +++ b/sound/soc/sof/amd/acp-loader.c @@ -168,12 +168,76 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) struct pci_dev *pci = to_pci_dev(sdev->dev); const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); struct acp_dev_data *adata; + u64 carveout_offset; unsigned int src_addr, size_fw, dest_addr; u32 page_count, dma_size; + u32 acp_logical_addr; int ret; adata = sdev->pdata->hw_pdata; + if (adata->asp_carveout_base) { + size_fw = adata->fw_code_raw_size; + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; + + if (adata->fw_code_carveout_addr < adata->asp_carveout_base || + adata->fw_code_carveout_addr - adata->asp_carveout_base + + size_fw > adata->asp_carveout_size) { + dev_err(sdev->dev, + "carveout addr 0x%llx out of window [0x%llx+0x%x]\n", + adata->fw_code_carveout_addr, + adata->asp_carveout_base, + adata->asp_carveout_size); + return -EINVAL; + } + carveout_offset = adata->fw_code_carveout_addr - adata->asp_carveout_base; + acp_logical_addr = ACP_SYSTEM_MEMORY_WINDOW + + (adata->asp_carveout_group_start * 8 * SZ_1M) + + (u32)carveout_offset; + + ret = configure_and_run_sha_dma(adata, NULL, + acp_logical_addr, + ACP_IRAM_BASE_ADDRESS, size_fw); + if (ret < 0) { + dev_err(sdev->dev, "SHA DMA from carveout failed: %d\n", ret); + return ret; + } + + if (adata->is_sram_in_use) { + configure_pte_for_fw_loading(FW_SRAM_DATA_BIN, + ACP_SRAM_PAGE_COUNT, adata); + src_addr = ACP_SYSTEM_MEMORY_WINDOW + ACP_DEFAULT_SRAM_LENGTH + + (page_count * ACP_PAGE_SIZE); + dest_addr = ACP7X_SRAM_BASE_ADDRESS; + ret = configure_and_run_dma(adata, src_addr, dest_addr, + adata->fw_sram_data_bin_size); + if (ret < 0) { + dev_err(sdev->dev, "SRAM DMA failed: %d\n", ret); + dma_free_coherent(&pci->dev, ACP_DEFAULT_SRAM_LENGTH, + adata->sram_data_buf, adata->sram_dma_addr); + adata->sram_data_buf = NULL; + return ret; + } + ret = acp_dma_status(adata, 0); + if (ret < 0) + dev_err(sdev->dev, "SRAM DMA status error: %d\n", ret); + } + + /* Enable cache window */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_OFFSET0, + desc->sram_pte_offset); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_SIZE0, + SRAM1_SIZE | BIT(31)); + + if (adata->is_sram_in_use) { + dma_free_coherent(&pci->dev, ACP_DEFAULT_SRAM_LENGTH, + adata->sram_data_buf, adata->sram_dma_addr); + adata->sram_data_buf = NULL; + } + return ret; + } + if (adata->pci_rev >= ACP7B_PCI_ID) { if (adata->acp_sof_signed_firmware_image) { if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { diff --git a/sound/soc/sof/amd/acp-stream.c b/sound/soc/sof/amd/acp-stream.c index 9212a3137cfd..a751ffdb0adf 100644 --- a/sound/soc/sof/amd/acp-stream.c +++ b/sound/soc/sof/amd/acp-stream.c @@ -15,15 +15,6 @@ #include "acp-dsp-offset.h" #include "acp.h" -#define PTE_GRP1_OFFSET 0x00000000 -#define PTE_GRP2_OFFSET 0x00800000 -#define PTE_GRP3_OFFSET 0x01000000 -#define PTE_GRP4_OFFSET 0x01800000 -#define PTE_GRP5_OFFSET 0x02000000 -#define PTE_GRP6_OFFSET 0x02800000 -#define PTE_GRP7_OFFSET 0x03000000 -#define PTE_GRP8_OFFSET 0x03800000 - int acp_dsp_stream_config(struct snd_sof_dev *sdev, struct acp_dsp_stream *stream) { const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 790af65be34a..86e2402b8ffd 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -279,11 +279,18 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, unsigned int tx_count, fw_qualifier, val; int ret; - if (!image_addr) { + if (!image_addr && + !adata->asp_carveout_base) { dev_err(sdev->dev, "SHA DMA image address is NULL\n"); return -EINVAL; } + /* + * Flush the ATU cache before programming SHA DMA registers + * to guarantee coherency at SHA DMA start time. + */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD); if (val & ACP_SHA_RUN) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RESET); @@ -297,8 +304,13 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, } } - if ((adata->quirks && adata->quirks->signed_fw_image) || - adata->acp_sof_signed_firmware_image) + /* + * The signing header must NOT be included for the carveout path: ASP + * stripped it when depositing the raw binary during VALIDATE_IMAGE. + */ + if (((adata->quirks && adata->quirks->signed_fw_image) || + adata->acp_sof_signed_firmware_image) && + !adata->asp_carveout_base) snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); @@ -316,7 +328,17 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, if (ret) return ret; } - snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN); + /* + * SHA_IOC_En triggers an interrupt-on-completion to ASP for source + * address validation. Only set it on the carveout path; enabling it + * on the legacy ATU-window path breaks PSP validation. + */ + if (adata->asp_carveout_base) { + reinit_completion(&adata->sha_dma_complete); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN_WITH_IOC); + } else { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN); + } ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_TRANSFER_BYTE_CNT, tx_count, tx_count == image_length, @@ -333,6 +355,23 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, return ret; } + if (adata->asp_carveout_base) { + /* + * Wait for the SHA IOC interrupt which fires after ASP validates + * the source address. Use ASP_MBOX_TIMEOUT_US to match the + * budget used for all other ASP mailbox operations. + * usecs_to_jiffies() can round to 0 at low HZ so clamp to at + * least 1 jiffy. + */ + unsigned long timeout_jiffies = + max(1UL, usecs_to_jiffies(ASP_MBOX_TIMEOUT_US)); + + if (!wait_for_completion_timeout(&adata->sha_dma_complete, + timeout_jiffies)) + dev_warn(sdev->dev, + "SHA DMA interrupt not received within timeout, continuing\n"); + } + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_DSP_FW_QUALIFIER, fw_qualifier, fw_qualifier & DSP_FW_RUN_ENABLE, ACP_REG_POLL_INTERVAL, ACP_DMA_COMPLETE_TIMEOUT_US); @@ -768,6 +807,20 @@ static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) if (adata->sdw) wake_irq_flag = sof_amd_check_and_handle_acp7x_sdw_wake_irq(sdev); + /* + * SHA DMA completion interrupt (ACP_SHA_STAT, bit 15 of ext_intr_stat). + * ACP_SHA_STAT has no dedicated mask bit in ACP_EXTERNAL_INTR_CNTL; + * it is always enabled by default and fires only when SHA DMA completes + * with SHA_IOC_En set. SHA DMA is triggered exclusively during firmware + * loading, so this interrupt is only asserted in that context. + * Acknowledge and signal sha_dma_complete so configure_and_run_sha_dma() + * can proceed to poll ACP_SHA_DSP_FW_QUALIFIER. + */ + if (ext_intr_stat & ACP_SHA_STAT) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_SHA_STAT); + complete(&adata->sha_dma_complete); + irq_flag = 1; + } if (ext_intr_stat & ACP7X_ERROR_IRQ) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP7X_ERROR_IRQ); @@ -1200,6 +1253,7 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) adata->subsystem_vendor = pci->subsystem_vendor; adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); + init_completion(&adata->sha_dma_complete); sdev->pdata->hw_pdata = adata; ret = acp_init(sdev); @@ -1339,6 +1393,7 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) adata->subsystem_vendor = pci->subsystem_vendor; adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); + init_completion(&adata->sha_dma_complete); sdev->pdata->hw_pdata = adata; ret = acp_init(sdev); @@ -1411,6 +1466,20 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) acp_memory_init(sdev); acp_dsp_stream_init(sdev); + /* + * Carveout is the only supported signed firmware load path on + * ACP7.B/7.F. Query ASP now when signed firmware is enabled so + * probe fails cleanly if carveout is unavailable. + */ + if (adata->acp_sof_signed_firmware_image) { + ret = acp7x_query_asp_carveout(sdev); + if (ret) { + dev_err(sdev->dev, + "ASP carveout unavailable, cannot load firmware: %d\n", ret); + goto free_ipc_irq; + } + } + return 0; free_ipc_irq: diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 929251772526..4eefc9cdba1b 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -49,10 +49,14 @@ #define ACP_PAGE_SIZE 0x1000 #define ACP_DMA_CH_RUN 0x02 #define ACP_MAX_DESC_CNT 0x02 +#define ACP_DSP_RUN 0x00 #define DSP_FW_RUN_ENABLE 0x01 #define ACP_SHA_RUN 0x01 #define ACP_SHA_RESET 0x02 #define ACP_SHA_HEADER 0x01 +/* SHA_IOC_En (bit 2): interrupt-on-completion to ASP; must be set alongside sha_run */ +#define ACP_SHA_IOC_EN 0x04 +#define ACP_SHA_RUN_WITH_IOC (ACP_SHA_RUN | ACP_SHA_IOC_EN) #define ACP_DMA_CH_RST 0x01 #define ACP_DMA_CH_GRACEFUL_RST_EN 0x10 #define ACP_ATU_CACHE_INVALID 0x01 @@ -115,6 +119,28 @@ #define ACP_FIRMWARE_SIGNATURE 0x100 #define ACP_IMAGE_HEADER_SIZE ACP_FIRMWARE_SIGNATURE #define ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF 0x14 +#define ACP_ASP_SIGNATURE_LENGTH 512 + +/* + * ACP7.B/7.F carveout: 32 MB region mapped at 2 MB page granularity. + * Use AXI2AXIATU_PAGE_SIZE_Mask2MB (value 0x0) for carveout ATU + * groups giving 32MB / 2MB = 16 PTEs. 4KB pages would require 8192 PTEs and + * is not how the hardware carveout path is configured. + */ +#define ACP7X_CARVEOUT_MAX_SIZE (32 * SZ_1M) +#define ACP7X_CARVEOUT_PAGE_SIZE (2 * SZ_1M) +#define ACP7X_CARVEOUT_PAGE_COUNT (ACP7X_CARVEOUT_MAX_SIZE / ACP7X_CARVEOUT_PAGE_SIZE) +/* + * PTE high-word flags for ATU entries: + * bit[31]: PAGE_Enable - entry is valid + * bit[30]: MALL_Enable - route accesses through carveout (MALL) memory, + * not standard DRAM. Required for ASP to recognise the SHA DMA + * source as within the carveout and grant ACP_SHA_DSP_FW_QUALIFIER. + * (PAGE_Enable | MALL_Enable ATU entry flags) + */ +#define ACP_ATU_PTE_PAGE_ENABLE BIT(31) +#define ACP_ATU_PTE_MALL_ENABLE BIT(30) +#define ACP_ATU_PTE_CARVEOUT_FLAGS (ACP_ATU_PTE_PAGE_ENABLE | ACP_ATU_PTE_MALL_ENABLE) #define ACP_ERROR_IRQ_MASK BIT(29) #define ACP_SDW0_IRQ_MASK BIT(21) @@ -139,6 +165,30 @@ #define ACP_DSP_MSG_SET 1 #define ACP_DSP_ACK_SET 1 +/* + * ATU PTE group ACP logical address offsets. + * Each group covers 8 MB (0x00800000). Groups 1-8 are used for PCM + * streams; groups 9-16 are extended groups. + * + * GRP_N_OFFSET = (N - 1) * 8 MB + */ +#define PTE_GRP1_OFFSET 0x00000000 +#define PTE_GRP2_OFFSET 0x00800000 +#define PTE_GRP3_OFFSET 0x01000000 +#define PTE_GRP4_OFFSET 0x01800000 +#define PTE_GRP5_OFFSET 0x02000000 +#define PTE_GRP6_OFFSET 0x02800000 +#define PTE_GRP7_OFFSET 0x03000000 +#define PTE_GRP8_OFFSET 0x03800000 +#define PTE_GRP9_OFFSET 0x04000000 +#define PTE_GRP10_OFFSET 0x04800000 +#define PTE_GRP11_OFFSET 0x05000000 +#define PTE_GRP12_OFFSET 0x05800000 +#define PTE_GRP13_OFFSET 0x06000000 +#define PTE_GRP14_OFFSET 0x06800000 +#define PTE_GRP15_OFFSET 0x07000000 +#define PTE_GRP16_OFFSET 0x07800000 + enum clock_source { ACP_CLOCK_96M = 0, ACP_CLOCK_48M, @@ -152,6 +202,29 @@ struct acp_atu_grp_pte { u32 high; }; +/* ASP mailbox payload for GET_CARVEOUT_ADDR command (0x04) */ +struct asp_get_carveout_payload { + u32 cookie; /* [0x00] Driver: ASP_MBOX_COOKIE */ + u32 status; /* [0x04] ASP: 0 = success */ + u64 carveout_addr; /* [0x08] ASP: carveout physical base address */ + u32 carveout_size; /* [0x10] ASP: ACP_MALL_SIZE register value */ + u32 mall_addr_valid; /* [0x14] ASP: ACP_MALL_ADDR_VALID register value */ + u32 mall_valid; /* [0x18] ASP: ACP_MALL_VALID register value */ +}; + +/* + * ASP mailbox payload for VALIDATE_IMAGE command (0x01). + * Driver fills src/dest fields; ASP fills carveout_dest_addr and status. + */ +struct asp_validate_image_payload { + u32 cookie; /* [0x00] Driver: ASP_MBOX_COOKIE */ + u32 status; /* [0x04] ASP: 0 = success */ + u64 carveout_dest_addr; /* [0x08] ASP: physical addr of raw binary in carveout */ + u64 dest_offset; /* [0x10] Driver: destination offset within carveout */ + u64 src_phys_addr; /* [0x18] Driver: signed FW source physical addr in DDR */ + u32 fw_image_size; /* [0x20] Driver: total signed image size in bytes */ +}; + union dma_tx_cnt { struct { unsigned int count : 19; @@ -299,6 +372,33 @@ struct acp_dev_data { u32 subsystem_vendor; u32 subsystem_device; int acp_sof_signed_firmware_image; + /* ACP7.F ASP carveout firmware load state */ + u64 asp_carveout_base; + u32 asp_carveout_size; + u32 asp_carveout_group_start; /* first ATU group index for carveout (from ASP) */ + u32 asp_carveout_group_count; /* number of consecutive ATU groups for carveout */ + /* + * Physical address and raw size of each validated DSP code binary + * within the carveout region (returned by ASP VALIDATE_IMAGE). + * Preserved across suspend/resume so that SHA DMA can reload DSP IRAM + * from the carveout without re-authenticating from disk. + */ + u64 fw_code_carveout_addr; + u32 fw_code_raw_size; + /* Real CPU physical address of the ASP MPASP mailbox payload page */ + phys_addr_t asp_mbox_buf_phys; + void *asp_mbox_buf; + /* + * SHA DMA completion for the carveout path. + * ACP_SHA_STAT (bit 15 of ACP_EXTERNAL_INTR_STAT) has no dedicated + * mask bit in ACP_EXTERNAL_INTR_CNTL - it is always enabled by default. + * SHA DMA is triggered exclusively during firmware loading, so no + * spurious ACP_SHA_STAT interrupts are expected outside that window. + * acp7x_irq_handler signals sha_dma_complete when ACP_SHA_STAT fires; + * configure_and_run_sha_dma() waits on it before polling + * ACP_SHA_DSP_FW_QUALIFIER to ensure ASP has validated the source. + */ + struct completion sha_dma_complete; }; void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); @@ -321,6 +421,11 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev); int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev); int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type); +/* ACP7.B/7.F ASP carveout firmware load helper functions */ +int acp7x_query_asp_carveout(struct snd_sof_dev *sdev); +int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev); +int acp7x_configure_carveout_pte(struct snd_sof_dev *sdev); + /* Block IO callbacks */ int acp_dsp_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, u32 offset, void *src, size_t size); diff --git a/sound/soc/sof/amd/acp7x.c b/sound/soc/sof/amd/acp7x.c index b6722d11168c..521b7c00c761 100644 --- a/sound/soc/sof/amd/acp7x.c +++ b/sound/soc/sof/amd/acp7x.c @@ -170,8 +170,15 @@ int sof_acp7x_ops_init(struct snd_sof_dev *sdev) acp_sof_post_fw_run_delay = obj->integer.value; } - if (acp_sof_signed_firmware_image) - sof_acp7x_ops.load_firmware = acp_sof_load_signed_firmware; + if (acp_sof_signed_firmware_image) { + /* + * This driver handles only ACP7.B/7.F which both use the ASP + * carveout firmware load path. Assign unconditionally rather + * than rechecking the revision so the correct loader is always + * selected when signed firmware is enabled. + */ + sof_acp7x_ops.load_firmware = acp7x_load_firmware_carveout; + } if (acp_sof_post_fw_run_delay) sof_acp7x_ops.post_fw_run = sof_acp7x_post_fw_run_delay; diff --git a/sound/soc/sof/amd/acp_asp_mailbox.c b/sound/soc/sof/amd/acp_asp_mailbox.c new file mode 100644 index 000000000000..a9c0edfe0c3f --- /dev/null +++ b/sound/soc/sof/amd/acp_asp_mailbox.c @@ -0,0 +1,559 @@ +// 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) 2026 Advanced Micro Devices, Inc. All rights reserved. +// +// Authors: Vijendar Mukunda + +/* + * ASP (AMD Security Processor) mailbox communication and ACP7.B/7.F carveout + * memory firmware load/restore for the ACP7.B/7.F platform. + */ + +#include +#include +#include +#include +#include +#include + +#include "../ops.h" +#include "acp-dsp-offset.h" +#include "acp.h" + +/* + * acp7x_asp_send_cmd - send one ASP mailbox command via the ACPI AMSG method. + * + * The ACPI method "AMSG" (stored as 'GSMA' LE) is used to + * proxy commands to ASP. Direct SMN writes to MPASP_C2PMSG_* do not work + * because the BIOS gates host access to those registers and requires all + * ASP mailbox traffic to go through this ACPI method. + * + * The caller must fill adata->asp_mbox_buf with the payload before calling. + * On return the same buffer contains ASP's response fields. + * + * ACPI method signature: AMSG(CommandId, PayloadPhysAddrHi, PayloadPhysAddrLo) + * GET_CARVEOUT_ADDR uses a short 50 ms probe timeout; other commands use 5 s. + */ +static int acp7x_asp_send_cmd(struct snd_sof_dev *sdev, u16 cmd_id) +{ + struct pci_dev *pci = to_pci_dev(sdev->dev); + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct acpi_device *adev = ACPI_COMPANION(&pci->dev); + union acpi_object args[3]; + struct acpi_object_list arg_list = { ARRAY_SIZE(args), args }; + unsigned long timeout; + acpi_handle handle; + acpi_status status; + unsigned int ready_timeout; + u32 ctrl; + int ret, smn_ret; + + if (!adev) { + dev_err(sdev->dev, "ASP cmd 0x%04x: no ACPI companion device\n", cmd_id); + return -ENODEV; + } + + handle = adev->handle; + + /* Short timeout for GET_CARVEOUT_ADDR to fail fast if ASP lacks carveout support. */ + ready_timeout = (cmd_id == ASP_MBOX_CMD_GET_CARVEOUT_ADDR) ? + ASP_MBOX_PROBE_TIMEOUT_US : ASP_MBOX_TIMEOUT_US; + + timeout = jiffies + usecs_to_jiffies(ready_timeout); + ret = -ETIMEDOUT; + do { + smn_ret = amd_smn_read(0, MPASP_C2PMSG_173_REG, &ctrl); + if (smn_ret) { + dev_err(sdev->dev, "ASP mailbox SMN read failed: %d\n", smn_ret); + return smn_ret; + } + if (ctrl & ASP_MBOX_READY_BIT) { + ret = 0; + break; + } + usleep_range(ASP_MBOX_POLL_INTERVAL_US, ASP_MBOX_POLL_INTERVAL_US + 100); + } while (!time_after(jiffies, timeout)); + + if (ret) { + dev_err(sdev->dev, "ASP mailbox not ready before cmd 0x%04x\n", cmd_id); + return -ETIMEDOUT; + } + + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = cmd_id; + args[1].type = ACPI_TYPE_INTEGER; + args[1].integer.value = upper_32_bits(adata->asp_mbox_buf_phys); + args[2].type = ACPI_TYPE_INTEGER; + args[2].integer.value = lower_32_bits(adata->asp_mbox_buf_phys); + + status = acpi_evaluate_object(handle, "AMSG", &arg_list, NULL); + if (ACPI_FAILURE(status)) { + dev_err(sdev->dev, "ASP AMSG cmd 0x%04x: ACPI method failed: %s\n", + cmd_id, acpi_format_exception(status)); + return -EIO; + } + return 0; +} + +/* + * acp7x_configure_carveout_pte - program ATU groups for ACP7.B/7.F carveout. + * + * Programs PAGE_SIZE, BASE_ADDR and PTE entries for each ATU group covering + * the carveout region using 2 MB pages with PAGE_Enable | MALL_Enable flags. + * Must be called after GET_CARVEOUT_ADDR and on every D0 resume since + * acp_init() resets the scratch SRAM and ATU registers. + */ +int acp7x_configure_carveout_pte(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u64 phys_addr; + u32 grp_start = adata->asp_carveout_group_start; + u32 grp_count = adata->asp_carveout_group_count; + u32 total_pages, pages_per_group; + u32 grp, page_in_grp, page_idx; + u32 pte_scratch_offset, page_size_reg, base_addr_reg, reg_val; + u32 pte_lo_addr, pte_hi_addr; + u32 low, high; + u32 abs_grp; + + if (!adata->asp_carveout_base) { + dev_err(sdev->dev, "carveout PTE: no valid carveout address\n"); + return -EINVAL; + } + + if (!grp_count || grp_start < 8 || grp_start + grp_count > 16) { + dev_err(sdev->dev, "carveout PTE: invalid ATU group range %u+%u\n", + grp_start, grp_count); + return -EINVAL; + } + + /* + * ASP is expected to allocate exactly four contiguous 8 MB ATU groups + * for the fixed 32 MB carveout. Warn if the response differs so that + * unexpected firmware behaviour is clearly visible in the log. + */ + if (grp_count != 4) + dev_warn(sdev->dev, "carveout PTE: expected 4 ATU groups, got %u\n", + grp_count); + + total_pages = adata->asp_carveout_size / ACP7X_CARVEOUT_PAGE_SIZE; + pages_per_group = total_pages / grp_count; + page_idx = 0; + + for (grp = 0; grp < grp_count; grp++) { + abs_grp = grp_start + grp; + + /* + * asp_carveout_group_start is the 0-based index from + * __ffs(mall_addr_valid): bit N in MallGroupEntries represents + * hardware GRP_(N+1), so abs_grp is 0-based and GRP number is + * (abs_grp + 1). Example: bits 11-14 set -> abs_grp 11..14 -> + * programs GRP_12..GRP_15 (registers 0xC5C..0xC74). + * ATU_CTRL (0xC40) sits between GRP_8 and GRP_9, adding a + * 4-byte gap for abs_grp >= 8 (i.e. GRP_9 and above). + */ + if (abs_grp < 8) + page_size_reg = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 + abs_grp * 8; + else + page_size_reg = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 + abs_grp * 8 + 4; + + base_addr_reg = page_size_reg + 4; + + pte_scratch_offset = abs_grp * 0x20; + reg_val = desc->sram_pte_offset + pte_scratch_offset; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, page_size_reg, PAGE_SIZE_2M_ENABLE); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base_addr_reg, reg_val | BIT(31)); + + dev_dbg(sdev->dev, + "carveout ATU: abs_grp=%u PAGE_SIZE_reg=0x%x BASE_ADDR_reg=0x%x val=0x%x\n", + abs_grp, page_size_reg, base_addr_reg, + (u32)(reg_val | BIT(31))); + + for (page_in_grp = 0; page_in_grp < pages_per_group; + page_in_grp++, page_idx++) { + pte_lo_addr = ACP_SCRATCH_REG_0 + pte_scratch_offset + (page_in_grp * 8); + pte_hi_addr = pte_lo_addr + 4; + phys_addr = adata->asp_carveout_base + + ((u64)page_idx * ACP7X_CARVEOUT_PAGE_SIZE); + low = lower_32_bits(phys_addr); + high = upper_32_bits(phys_addr) | ACP_ATU_PTE_CARVEOUT_FLAGS; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_lo_addr, low); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_hi_addr, high); + } + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); + + dev_dbg(sdev->dev, + "carveout PTE done: %u groups [%u..%u] covering 0x%llx..0x%llx\n", + grp_count, grp_start, grp_start + grp_count - 1, + adata->asp_carveout_base, + adata->asp_carveout_base + (u64)adata->asp_carveout_size); + return 0; +} +EXPORT_SYMBOL_NS(acp7x_configure_carveout_pte, "SND_SOC_SOF_AMD_COMMON"); + +/* + * acp7x_alloc_asp_payload - allocate a physically contiguous buffer below 4 GB. + * + * ASP accesses memory via the SoC fabric, bypassing the IOMMU, so the buffer + * must be below 4 GB and page_to_phys() must return the real CPU physical + * address. Use __GFP_DMA32/__GFP_DMA as hard zone constraints (not hints) + * to guarantee the physical address fits in 32 bits. + */ +static void *acp7x_alloc_asp_payload(struct snd_sof_dev *sdev, phys_addr_t *phys_out) +{ + struct page *page; + void *vaddr; + + page = alloc_page(GFP_KERNEL | __GFP_DMA32 | __GFP_ZERO); + if (!page) + page = alloc_page(GFP_KERNEL | __GFP_DMA | __GFP_ZERO); + if (!page) { + dev_err(sdev->dev, "ASP mbox: failed to allocate below-4GB payload page\n"); + return NULL; + } + + vaddr = page_address(page); + *phys_out = page_to_phys(page); + + if (*phys_out > 0xFFFFFFFFULL) { + dev_err(sdev->dev, "ASP mbox: page at phys=0x%llx still exceeds 4 GB\n", + (u64)*phys_out); + __free_page(page); + return NULL; + } + + return vaddr; +} + +static void acp7x_free_asp_payload(void *vaddr) +{ + if (vaddr) + __free_page(virt_to_page(vaddr)); +} + +int acp7x_query_asp_carveout(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct asp_get_carveout_payload *co_payload; + phys_addr_t phys; + u32 group_bits, first_bit, count; + int ret = 0; + + co_payload = acp7x_alloc_asp_payload(sdev, &phys); + if (!co_payload) + return -ENOMEM; + + co_payload->cookie = ASP_MBOX_COOKIE; + /* Sentinel: ASP overwrites on success; 0xFFFFFFFF after the call means no carveout */ + co_payload->carveout_size = 0xFFFFFFFF; + co_payload->mall_addr_valid = 0xFFFFFFFF; + co_payload->mall_valid = 0xFFFFFFFF; + + adata->asp_mbox_buf = co_payload; + adata->asp_mbox_buf_phys = phys; + + ret = acp7x_asp_send_cmd(sdev, ASP_MBOX_CMD_GET_CARVEOUT_ADDR); + + dev_dbg(sdev->dev, + "GET_CARVEOUT response: status=0x%x addr=0x%llx size=0x%x mall_valid=0x%x mall_addr_valid=0x%x\n", + co_payload->status, co_payload->carveout_addr, + co_payload->carveout_size, co_payload->mall_valid, + co_payload->mall_addr_valid); + + if (ret) + goto out_free; + + if (co_payload->status || !co_payload->carveout_addr || + !co_payload->carveout_size || co_payload->carveout_size == 0xFFFFFFFF) { + dev_warn(sdev->dev, + "ASP GET_CARVEOUT_ADDR: status=0x%x addr=0x%llx size=0x%x - no carveout\n", + co_payload->status, co_payload->carveout_addr, + co_payload->carveout_size); + ret = -ENODATA; + goto out_free; + } + + if (!IS_ALIGNED(co_payload->carveout_addr, ACP7X_CARVEOUT_PAGE_SIZE) || + co_payload->carveout_size != ACP7X_CARVEOUT_MAX_SIZE) { + dev_warn(sdev->dev, + "ASP carveout: unexpected base alignment or size (addr=0x%llx size=0x%x, expected 0x%x)\n", + co_payload->carveout_addr, co_payload->carveout_size, + ACP7X_CARVEOUT_MAX_SIZE); + ret = -ENODATA; + goto out_free; + } + /* + * Extract ATU group start and count from MallGroupEntries bits[15:0]. + * Scan to find first set bit (start), then count + * contiguous set bits. Example: 0x7800 -> start=11, count=4. + * Defer committing asp_carveout_base/size until all validation passes + * so adata is never left with a non-zero base but zero group_count. + */ + group_bits = co_payload->mall_addr_valid & 0xFFFF; + first_bit = 0; + count = 0; + + if (group_bits) { + first_bit = __ffs(group_bits); + while ((group_bits >> (first_bit + count)) & 1) + count++; + /* Validate contiguous run - reject non-contiguous masks */ + if (group_bits != (((1u << count) - 1) << first_bit)) { + dev_warn(sdev->dev, + "ASP carveout: non-contiguous mall_addr_valid 0x%x unsupported\n", + group_bits); + ret = -ENODATA; + goto out_free; + } + } + if (!count) { + dev_warn(sdev->dev, "ASP carveout: no valid ATU groups in mall_addr_valid\n"); + ret = -ENODATA; + goto out_free; + } + adata->asp_carveout_base = co_payload->carveout_addr; + adata->asp_carveout_size = co_payload->carveout_size; + adata->asp_carveout_group_start = first_bit; + adata->asp_carveout_group_count = count; + + dev_dbg(sdev->dev, "ASP carveout: base=0x%llx size=0x%x grp_start=%u grp_count=%u\n", + adata->asp_carveout_base, adata->asp_carveout_size, + adata->asp_carveout_group_start, adata->asp_carveout_group_count); + +out_free: + adata->asp_mbox_buf = NULL; + adata->asp_mbox_buf_phys = 0; + acp7x_free_asp_payload(co_payload); + return ret; +} +EXPORT_SYMBOL_NS(acp7x_query_asp_carveout, "SND_SOC_SOF_AMD_COMMON"); + +int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + struct acp_dev_data *adata = plat_data->hw_pdata; + struct asp_validate_image_payload *vi_payload; + const char *fw_filename; + struct page *fw_page; + phys_addr_t vi_phys; + u64 dest; + u32 page_count, dma_size; + u32 size_fw_signed; + unsigned int order; + int ret; + + /* + * Carveout query was already done in amd_sof_acp7x_probe() via + * acp7x_query_asp_carveout(). If ASP did not return a valid carveout + * address, fail immediately - no fallback to legacy path. + */ + + /* Program PTEs for the entire carveout region before any DMA into it. + * acp_init() in resume clears the scratch SRAM so PTEs must be reprogrammed + * on every boot including D3->D0 resume. + */ + ret = acp7x_configure_carveout_pte(sdev); + if (ret) + return ret; + + /* + * On D3->D0 resume the raw binary is already in the carveout region + * (placed by ASP during the previous VALIDATE_IMAGE call). Skip the + * filesystem load and VALIDATE_IMAGE - go directly to the data binary + * path. acp_dsp_pre_fw_run() will SHA-DMA from the preserved carveout + * address into DSP IRAM. + * + * On first boot fw_code_carveout_addr is 0 so the condition below is + * false and the full firmware load path executes. + */ + if (adata->fw_code_carveout_addr) + goto load_data_binary; + + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, adata->fw_code_bin); + if (!fw_filename) + return -ENOMEM; + + /* Release any previously loaded firmware before requesting again */ + if (sdev->basefw.fw) { + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + } + + ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev); + kfree(fw_filename); + if (ret < 0) { + dev_err(sdev->dev, "request_firmware %s failed: %d\n", + adata->fw_code_bin, ret); + return ret; + } + + page_count = PAGE_ALIGN(sdev->basefw.fw->size) >> PAGE_SHIFT; + dma_size = page_count * ACP_PAGE_SIZE; + + /* + * Allocate the firmware staging buffer below 4 GB using alloc_pages so + * virt_to_phys() gives the real CPU physical address that ASP can read + * via the SoC fabric (bypassing the IOMMU). dma_alloc_coherent() gives + * an IOMMU-remapped DMA address which ASP cannot use. + * + * get_order() gives the smallest power-of-2 number of pages covering + * dma_size; alloc_pages() allocates that contiguous range. + */ + order = get_order(dma_size); + fw_page = alloc_pages(GFP_KERNEL | __GFP_DMA32 | __GFP_ZERO, order); + if (!fw_page) + fw_page = alloc_pages(GFP_KERNEL | __GFP_DMA | __GFP_ZERO, order); + if (!fw_page) { + dev_err(sdev->dev, "failed to allocate FW staging buffer\n"); + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + adata->bin_buf = page_address(fw_page); + adata->sha_dma_addr = page_to_phys(fw_page); + + if (adata->sha_dma_addr > 0xFFFFFFFFULL) { + dev_err(sdev->dev, + "FW staging buffer at phys=0x%llx exceeds 4 GB\n", + (u64)adata->sha_dma_addr); + __free_pages(fw_page, order); + adata->bin_buf = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + + memcpy(adata->bin_buf, sdev->basefw.fw->data, sdev->basefw.fw->size); + adata->fw_bin_size = sdev->basefw.fw->size; + + if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { + dev_err(sdev->dev, "signed FW too small: %u\n", adata->fw_bin_size); + ret = -EINVAL; + goto free_bin; + } + /* + * SizeFWSigned (header[0x14]) is the size of the signed payload + * including the ASP signature trailer. ASP strips the ACP image + * header (0x100 bytes) but preserves the signature, so the raw + * binary deposited in carveout has size = SizeFWSigned and the + * SHA DMA length must equal SizeFWSigned. + */ + size_fw_signed = get_unaligned_le32(adata->bin_buf + ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF); + + if (size_fw_signed <= ACP_ASP_SIGNATURE_LENGTH || + size_fw_signed > adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE) { + dev_err(sdev->dev, + "invalid SizeFWSigned 0x%x in carveout firmware header\n", + size_fw_signed); + ret = -EINVAL; + goto free_bin; + } + adata->fw_code_raw_size = size_fw_signed; + vi_payload = acp7x_alloc_asp_payload(sdev, &vi_phys); + if (!vi_payload) { + ret = -ENOMEM; + goto free_bin; + } + + vi_payload->cookie = ASP_MBOX_COOKIE; + vi_payload->dest_offset = 0; + /* + * ASP bypasses the CPU IOMMU and accesses memory via the SoC + * fabric directly, so it must be given a real CPU physical + * address for the staging buffer (not an IOMMU/DMA-translated + * address). Use virt_to_phys(bin_buf) which returns the real + * CPU physical address, same as for mailbox payload buffers. + */ + vi_payload->src_phys_addr = (u64)virt_to_phys(adata->bin_buf); + vi_payload->fw_image_size = adata->fw_bin_size; + + adata->asp_mbox_buf = vi_payload; + adata->asp_mbox_buf_phys = vi_phys; + + dev_dbg(sdev->dev, "sending VALIDATE_IMAGE(0x01): src=0x%llx size=0x%x dest_offset=0x%llx phys=0x%llx\n", + vi_payload->src_phys_addr, vi_payload->fw_image_size, + vi_payload->dest_offset, (u64)adata->asp_mbox_buf_phys); + + ret = acp7x_asp_send_cmd(sdev, ASP_MBOX_CMD_VALIDATE_IMAGE); + + /* Read result before freeing payload */ + if (!ret && !vi_payload->status) { + dest = vi_payload->carveout_dest_addr; + + if (dest < adata->asp_carveout_base || + dest - adata->asp_carveout_base + adata->fw_code_raw_size > + adata->asp_carveout_size) { + dev_err(sdev->dev, + "VALIDATE_IMAGE: dest 0x%llx outside carveout window\n", + dest); + ret = -EIO; + } else { + adata->fw_code_carveout_addr = dest; + } + } else if (!ret && vi_payload->status) { + ret = -EIO; + } + + adata->asp_mbox_buf = NULL; + adata->asp_mbox_buf_phys = 0; + acp7x_free_asp_payload(vi_payload); + + if (ret) { + if (ret == -EIO) + dev_err(sdev->dev, "ASP VALIDATE_IMAGE failed\n"); + goto free_bin; + } + + dev_dbg(sdev->dev, "ASP VALIDATE_IMAGE: raw binary at carveout 0x%llx size 0x%x\n", + adata->fw_code_carveout_addr, adata->fw_code_raw_size); + + /* DDR staging buffer is no longer needed - raw binary is in carveout */ + free_pages((unsigned long)adata->bin_buf, get_order(dma_size)); + adata->bin_buf = NULL; + +load_data_binary: + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, adata->fw_data_bin); + if (!fw_filename) { + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + + ret = request_firmware(&adata->fw_dbin, fw_filename, sdev->dev); + kfree(fw_filename); + if (ret < 0) { + dev_err(sdev->dev, "request_firmware %s failed: %d\n", + adata->fw_data_bin, ret); + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; + } + + ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_SRAM, 0, + (void *)adata->fw_dbin->data, + adata->fw_dbin->size); + release_firmware(adata->fw_dbin); + adata->fw_dbin = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; + +free_bin: + free_pages((unsigned long)adata->bin_buf, get_order(dma_size)); + adata->bin_buf = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; +} +EXPORT_SYMBOL_NS(acp7x_load_firmware_carveout, "SND_SOC_SOF_AMD_COMMON"); +