From 7d0f3981d2f88255d23514cc2486fe3e1a031462 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Sat, 26 Sep 2026 15:52:05 +0530 Subject: [PATCH 1/9] 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 --- 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 b3b73096ac74a6..13bcd131f37b3f 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -421,17 +421,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 b602a983feb7f3..2dafd742dc01c4 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 0594c1f3deb6bb..5949aefe26bc32 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 7aa121300f2b0990f5363ed2dd11da66878d1748 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Sat, 26 Sep 2026 16:13:59 +0530 Subject: [PATCH 2/9] 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 --- sound/soc/sof/amd/pci-acp7x.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 5949aefe26bc32..7261377c615b7a 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; + flag = snd_amd_acp_find_config(pci); + if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) + return -ENODEV; + switch (pci->revision) { - case ACP7B_PCI_ID: case ACP7F_PCI_ID: - break; + return sof_pci_probe(pci, &acp7f_pci_id); 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); } 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 5573921930d7d5ba74a4a95b20ae58b63db5daaa Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Sat, 26 Sep 2026 16:15:35 +0530 Subject: [PATCH 3/9] 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 --- sound/soc/amd/acp-config.c | 15 +++++++++++++++ sound/soc/amd/mach-config.h | 1 + 2 files changed, 16 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 13bcd131f37b3f..5f676f3825bbc5 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -435,3 +435,18 @@ EXPORT_SYMBOL(snd_soc_acpi_amd_acp7f_sof_machines); MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); MODULE_LICENSE("Dual BSD/GPL"); + +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 2dafd742dc01c4..02ce044bcbf722 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 a27a56246eddf62f9321a0c2ec4425526659351b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Sat, 26 Sep 2026 15:41:33 +0530 Subject: [PATCH 4/9] 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 --- 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 7261377c615b7a..5a21ab8ebb4b9b 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 67cf3643224ae70c3df225c82154d0a78c68bd4d Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:14:38 +0530 Subject: [PATCH 5/9] 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 --- 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 33540f7c421b19..60e5c851c8743c 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 b059039c9e0d1e..71c39eb36194e9 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 5a887959461a26..ae194af70b2b7f 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 f32491d391d1b0365952dbca13c475b334f727e9 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Sat, 26 Sep 2026 16:33:24 +0530 Subject: [PATCH 6/9] 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 --- 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 71c39eb36194e9..790af65be34a2f 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 368ad810607bd6e56a2a5b597833f655e9a99909 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 24 Sep 2026 18:12:08 +0530 Subject: [PATCH 7/9] 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 --- sound/soc/amd/acp/acp-mach-common.h | 25 +++++++++++++++++++++++++ sound/soc/amd/acp/soc_amd_sdw_common.h | 14 ++------------ 2 files changed, 27 insertions(+), 12 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 00000000000000..a2cb25be6bf8f8 --- /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 17e4e97fb3d1d6..e047b0d4cc3d30 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -23,18 +23,8 @@ #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; -}; +/* amd_pdm_pdata is defined in acp-mach-common.h */ +#include "acp-mach-common.h" #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) From dd8ddeac1c47e73574f4f92803f1e26edda5d818 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 24 Sep 2026 18:12:09 +0530 Subject: [PATCH 8/9] 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 --- 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 60e5c851c8743c..3bb1de8c03762f 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 063a38ce8163ed96a6ae481f721c6dd7ad31ec6e Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 29 Sep 2026 17:35:28 +0530 Subject: [PATCH 9/9] 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 --- sound/soc/sof/amd/Makefile | 2 +- sound/soc/sof/amd/acp-dsp-offset.h | 58 +++ sound/soc/sof/amd/acp-loader.c | 71 +++- sound/soc/sof/amd/acp-stream.c | 9 - sound/soc/sof/amd/acp.c | 92 ++++- sound/soc/sof/amd/acp.h | 109 ++++++ sound/soc/sof/amd/acp7x.c | 11 +- sound/soc/sof/amd/acp_asp_mailbox.c | 549 ++++++++++++++++++++++++++++ 8 files changed, 882 insertions(+), 19 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 bb1c0ddef69d77..3a7d230a4c4612 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 3984cd627db6df..4845de0e9f66f0 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–16. + * ACPAXI2AXI_ATU_CTRL (0xC40) sits between GRP_8 and GRP_9, + * so groups 9–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 1dc6fec6ce57d4..cc7eb92b9ab7fe 100644 --- a/sound/soc/sof/amd/acp-loader.c +++ b/sound/soc/sof/amd/acp-loader.c @@ -27,8 +27,6 @@ #define FW_BIN_PTE_OFFSET 0x00 #define FW_DATA_BIN_PTE_OFFSET 0x08 -#define ACP_DSP_RUN 0x00 - int acp_dsp_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, u32 offset, void *dest, size_t size) { @@ -168,12 +166,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) { @@ -195,6 +257,11 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) size_fw = adata->fw_bin_size; } } else if (adata->quirks && adata->quirks->signed_fw_image) { + if (adata->fw_bin_size <= ACP_FIRMWARE_SIGNATURE) { + dev_err(sdev->dev, "Invalid signed firmware size %u\n", + adata->fw_bin_size); + return -EINVAL; + } size_fw = adata->fw_bin_size - ACP_FIRMWARE_SIGNATURE; } else { size_fw = adata->fw_bin_size; diff --git a/sound/soc/sof/amd/acp-stream.c b/sound/soc/sof/amd/acp-stream.c index 9212a3137cfd2b..a751ffdb0adf3c 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 790af65be34a2f..4fa3f96353ebbc 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -279,11 +279,21 @@ 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. + * + * For the carveout path the PTE programming + * in acp7x_configure_carveout_pte() already flushed, but flush again + * here 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 +307,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 +331,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 +358,34 @@ 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"); + } + + /* + * For unsigned firmware images PSP does not set ACP_SHA_DSP_FW_QUALIFIER, + * so pre-write DSP_FW_RUN_ENABLE to let the poll succeed immediately. + * For signed images PSP sets the qualifier after authentication, so the + * poll waits for the real hardware acknowledgment. + */ + if (!(adata->quirks && adata->quirks->signed_fw_image) && + !adata->acp_sof_signed_firmware_image) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DSP_FW_QUALIFIER, + DSP_FW_RUN_ENABLE); + 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 +821,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 +1267,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 +1407,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); @@ -1382,7 +1451,6 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) ACPI_TYPE_INTEGER, &obj)) adata->acp_sof_signed_firmware_image = obj->integer.value; } - sdev->dsp_box.offset = 0; sdev->dsp_box.size = BOX_SIZE_512; @@ -1411,6 +1479,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 ae194af70b2b7f..9f3c56836b4ed8 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,33 @@ struct acp_atu_grp_pte { u32 high; }; +/* + * ASP mailbox payload for GET_CARVEOUT_ADDR command (0x04). + * Placed in a DMA-coherent page whose physical address is written to + * MPASP_C2PMSG_174 (HI) / MPASP_C2PMSG_175 (LO) before the command. + */ +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 +376,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 +425,11 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev); int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev, const char *fw_filename); int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type); +/* ACP7.F ASP carveout firmware load and restore */ +int acp7x_query_asp_carveout(struct snd_sof_dev *sdev); +int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev, const char *fw_filename); +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 b6722d11168cbb..521b7c00c76128 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 00000000000000..d5e503df8a3463 --- /dev/null +++ b/sound/soc/sof/amd/acp_asp_mailbox.c @@ -0,0 +1,549 @@ +// 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; + } + + 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, const char *fw_filename) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + struct acp_dev_data *adata = plat_data->hw_pdata; + struct asp_validate_image_payload *vi_payload; + 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"); +