From 72cb06c6173dc2e793529602c631216697fad315 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 25 Mar 2024 09:41:24 -0700 Subject: [PATCH 01/10] ASoC: SOF: Modify the signature of load_firmware op Modify the signature of the load_firmware op to take the firmware filename as an argument. The firmware filename should be include the full path of the firmware file to be loaded. This is in preparation to add a debug op that will be used to boot a firmware with the filename passed from userspace. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/amd/acp-loader.c | 2 +- sound/soc/sof/amd/acp.h | 2 +- sound/soc/sof/core.c | 2 +- sound/soc/sof/loader.c | 24 ++++++++++++++---------- sound/soc/sof/ops.h | 8 +++++--- sound/soc/sof/pm.c | 2 +- sound/soc/sof/sof-priv.h | 6 +++--- 7 files changed, 26 insertions(+), 20 deletions(-) diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c index 2d5e58846499d2..552d47c9813483 100644 --- a/sound/soc/sof/amd/acp-loader.c +++ b/sound/soc/sof/amd/acp-loader.c @@ -263,7 +263,7 @@ int acp_sof_dsp_run(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(acp_sof_dsp_run, SND_SOC_SOF_AMD_COMMON); -int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev) +int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev, const char *fw_name) { struct snd_sof_pdata *plat_data = sdev->pdata; struct acp_dev_data *adata = plat_data->hw_pdata; diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 87e79d500865a8..324718e4cd7d4b 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -270,7 +270,7 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev); /* DSP Loader callbacks */ int acp_sof_dsp_run(struct snd_sof_dev *sdev); int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev); -int acp_sof_load_signed_firmware(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); /* Block IO callbacks */ diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 83fe0401baf867..57ae255d8765e5 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -457,7 +457,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) } /* load the firmware */ - ret = snd_sof_load_firmware(sdev); + ret = snd_sof_load_firmware(sdev, NULL); if (ret < 0) { dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", ret); diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 0baf316b0064b6..6d6147167f7101 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -14,22 +14,25 @@ #include "sof-priv.h" #include "ops.h" -int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) +int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev, const char *fw_filename) { struct snd_sof_pdata *plat_data = sdev->pdata; - const char *fw_filename; ssize_t ext_man_size; + bool alloc = false; int ret; /* Don't request firmware again if firmware is already requested */ if (sdev->basefw.fw) return 0; - fw_filename = kasprintf(GFP_KERNEL, "%s/%s", - plat_data->fw_filename_prefix, - plat_data->fw_filename); - if (!fw_filename) - return -ENOMEM; + if (!fw_filename) { + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, + plat_data->fw_filename); + if (!fw_filename) + return -ENOMEM; + alloc = true; + } ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev); @@ -59,17 +62,18 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) } err: - kfree(fw_filename); + if (alloc) + kfree(fw_filename); return ret; } EXPORT_SYMBOL(snd_sof_load_firmware_raw); -int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) +int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev, const char *fw_filename) { int ret; - ret = snd_sof_load_firmware_raw(sdev); + ret = snd_sof_load_firmware_raw(sdev, fw_filename); if (ret < 0) return ret; diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 2584621c3b2d48..6961eba3a1061b 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -474,12 +474,14 @@ snd_sof_pcm_platform_trigger(struct snd_sof_dev *sdev, return 0; } -/* Firmware loading */ -static inline int snd_sof_load_firmware(struct snd_sof_dev *sdev) +/* Firmware loading. If the fw_filename argument is NULL, the firmware file set in the + * default profile will be loaded. Otherwise, it must contain the full path to the file. + */ +static inline int snd_sof_load_firmware(struct snd_sof_dev *sdev, const char *fw_filename) { dev_dbg(sdev->dev, "loading firmware\n"); - return sof_ops(sdev)->load_firmware(sdev); + return sof_ops(sdev)->load_firmware(sdev, fw_filename); } /* host DSP message data */ diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 8e3bcf602beb31..7f3c42662e724e 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -126,7 +126,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); /* load the firmware */ - ret = snd_sof_load_firmware(sdev); + ret = snd_sof_load_firmware(sdev, NULL); if (ret < 0) { dev_err(sdev->dev, "error: failed to load DSP firmware after resume %d\n", diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 4d6a1517f9b351..ef63af5944c298 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -228,7 +228,7 @@ struct snd_sof_dsp_ops { struct snd_sof_ipc_msg *msg); /* mandatory */ /* FW loading */ - int (*load_firmware)(struct snd_sof_dev *sof_dev); /* mandatory */ + int (*load_firmware)(struct snd_sof_dev *sof_dev, const char *fw_filename); /* mandatory */ int (*load_module)(struct snd_sof_dev *sof_dev, struct snd_sof_mod_hdr *hdr); /* optional */ @@ -727,8 +727,8 @@ int sof_create_ipc_file_profile(struct snd_sof_dev *sdev, /* * Firmware loading. */ -int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev); -int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev); +int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev, const char *fw_filename); +int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev, const char *fw_filename); int snd_sof_run_firmware(struct snd_sof_dev *sdev); void snd_sof_fw_unload(struct snd_sof_dev *sdev); From 0a1fb4d2c551dad666bff79b2a6ec28550304065 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 24 Apr 2024 08:51:41 -0700 Subject: [PATCH 02/10] ASoC: SOF: Set initial DSP device state to D3 Before the first firmware boot, set the initial state of the DSP to D3. The DSP D3 state is not the same as the SOF PCI device D3. Instead, it represents that the DSP is in the powered off state. After firmware boot is completed successfully, set the state to D0 to indicate that the DSP has been powered on. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/core.c | 9 ++++++--- sound/soc/sof/loader.c | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 57ae255d8765e5..70cdc2175924cd 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -418,8 +418,14 @@ static int sof_init_environment(struct snd_sof_dev *sdev) static int sof_probe_continue(struct snd_sof_dev *sdev) { struct snd_sof_pdata *plat_data = sdev->pdata; + struct sof_dsp_power_state state = { + .state = SOF_DSP_PM_D3, + }; int ret; + /* set initial power state to D3 */ + sdev->dsp_power_state = state; + /* Initialize loadable file paths and check the environment validity */ ret = sof_init_environment(sdev); if (ret) @@ -582,9 +588,6 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) /* initialize sof device */ sdev->dev = dev; - /* initialize default DSP power state */ - sdev->dsp_power_state.state = SOF_DSP_PM_D0; - sdev->pdata = plat_data; sdev->first_boot = true; dev_set_drvdata(dev, sdev); diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 6d6147167f7101..d24cca5b877013 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -112,6 +112,9 @@ EXPORT_SYMBOL(snd_sof_load_firmware_memcpy); int snd_sof_run_firmware(struct snd_sof_dev *sdev) { + struct sof_dsp_power_state state = { + .state = SOF_DSP_PM_D0, + }; int ret; init_waitqueue_head(&sdev->boot_wait); @@ -171,6 +174,9 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) dev_dbg(sdev->dev, "firmware boot complete\n"); sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE); + /* set the DSP power state to D0 after successful firmware boot */ + sdev->dsp_power_state = state; + /* perform post fw run operations */ ret = snd_sof_dsp_post_fw_run(sdev); if (ret < 0) { From a1ca0ca55ffed4ea7cdaef293bf1f79d6e2c2560 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 24 Apr 2024 09:02:14 -0700 Subject: [PATCH 03/10] ASoC: SOF: Intel: hda: Move DSP power step to the set_power_state op This will allow for the set_power_state op to be used for switching the DSP to the D3 state. The logic for powering up the DSP when setting the state to D0 also needs to be moved and will be done in a follow up. But for now, this will allow stress testing firmware boot. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/intel/hda-dsp.c | 49 ++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 4c88522d404847..c7a54698fa638f 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -684,8 +684,22 @@ static int hda_dsp_set_power_state(struct snd_sof_dev *sdev, case SOF_DSP_PM_D3: /* The only allowed transition is: D0I0 -> D3 */ if (sdev->dsp_power_state.state == SOF_DSP_PM_D0 && - sdev->dsp_power_state.substate == SOF_HDA_DSP_PM_D0I0) + sdev->dsp_power_state.substate == SOF_HDA_DSP_PM_D0I0) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + int ret, j; + + ret = chip->power_down_dsp(sdev); + if (ret < 0) { + dev_err(sdev->dev, "%s: failed to power down DSP\n", __func__); + return ret; + } + + /* reset ref counts for all cores */ + for (j = 0; j < chip->cores_num; j++) + sdev->dsp_core_ref_count[j] = 0; break; + } dev_err(sdev->dev, "error: transition from %d to %d not allowed\n", @@ -781,8 +795,11 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; struct hdac_bus *bus = sof_to_bus(sdev); + struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D3, + }; bool imr_lost = false; - int ret, j; + int ret; /* * The memory used for IMR boot loses its content in deeper than S3 @@ -820,15 +837,9 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) if (sdev->dspless_mode_selected) goto skip_dsp; - ret = chip->power_down_dsp(sdev); - if (ret < 0) { - dev_err(sdev->dev, "failed to power down DSP during suspend\n"); + ret = snd_sof_dsp_set_power_state(sdev, &target_state); + if (ret < 0) return ret; - } - - /* reset ref counts for all cores */ - for (j = 0; j < chip->cores_num; j++) - sdev->dsp_core_ref_count[j] = 0; /* disable ppcap interrupt */ hda_dsp_ctrl_ppcap_enable(sdev, false); @@ -988,10 +999,6 @@ EXPORT_SYMBOL_NS(hda_dsp_runtime_idle, SND_SOC_SOF_INTEL_HDA_COMMON); int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; - const struct sof_dsp_power_state target_state = { - .state = SOF_DSP_PM_D3, - }; - int ret; if (!sdev->dspless_mode_selected) { /* cancel any attempt for DSP D0I3 */ @@ -999,11 +1006,7 @@ int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev) } /* stop hda controller and power dsp off */ - ret = hda_suspend(sdev, true); - if (ret < 0) - return ret; - - return snd_sof_dsp_set_power_state(sdev, &target_state); + return hda_suspend(sdev, true); } EXPORT_SYMBOL_NS(hda_dsp_runtime_suspend, SND_SOC_SOF_INTEL_HDA_COMMON); @@ -1059,12 +1062,10 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state) /* stop hda controller and power dsp off */ ret = hda_suspend(sdev, false); - if (ret < 0) { - dev_err(bus->dev, "error: suspending dsp\n"); - return ret; - } + if (ret < 0) + dev_err(bus->dev, "failed to suspend dsp\n"); - return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); + return ret; } EXPORT_SYMBOL_NS(hda_dsp_suspend, SND_SOC_SOF_INTEL_HDA_COMMON); From 466d9271a65379f739ba79f9c806acdb66b24384 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Thu, 21 Mar 2024 16:23:54 -0700 Subject: [PATCH 04/10] ASoC: SOF: core: Introduce a new debug flag for DSP ops testing Introduce a new debug option to enable testing of DSP operations. With this ioption enabled, normal audio operations will be disabled. The API for the testing interface will be introduced in the follow up patches. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/core.c | 11 +++++++++++ sound/soc/sof/sof-priv.h | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 70cdc2175924cd..aa83f9fb400e78 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -462,6 +462,17 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto ipc_err; } + /* + * skip loading/booting firmware and registering the machine driver when DSP OPS testing + * is enabled with IPC4. Normal audio operations will be unavailable in this mode. + */ + if (sof_debug_check_flag(SOF_DBG_DSP_OPS_TEST_MODE) && + sdev->pdata->ipc_type == SOF_IPC_TYPE_4) { + sdev->dsp_test_mode_enabled = true; + sdev->probe_completed = true; + return 0; + } + /* load the firmware */ ret = snd_sof_load_firmware(sdev, NULL); if (ret < 0) { diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index ef63af5944c298..40616038bb45d3 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -52,6 +52,10 @@ struct snd_sof_pcm_stream; * dump the message payload also */ #define SOF_DBG_DSPLESS_MODE BIT(15) /* Do not initialize and use the DSP */ +#define SOF_DBG_DSP_OPS_TEST_MODE BIT(16) /* Used for DSP ops testing when + * DSPLESS_MODE is not set. + * No audio functionality when enabled. + */ /* Flag definitions used for controlling the DSP dump behavior */ #define SOF_DBG_DUMP_REGS BIT(0) @@ -565,6 +569,13 @@ struct snd_sof_dev { */ bool dspless_mode_selected; + /* + * Flag set when DSP ops testing is enabled. When this is set normal audio functionality + * will be disabled. Note that the DSP ops testing cannot be enabled when dspless mode is + * selected. + */ + bool dsp_test_mode_enabled; + /* Main, Base firmware image */ struct sof_firmware basefw; From 7e1bff401d7ddaa13312c3a203364a304eb37a41 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Thu, 21 Mar 2024 22:13:32 -0700 Subject: [PATCH 05/10] ASoC: SOF: debug: Add debugfs entries for testing DSP ops Add debugfs entries for testing DSP ops. This patch only adds the firmware filename and path entries to pass them from userspace. These will be saved in the newly added field for saving the test profile in struct snd_sof_dev. These will be used for testing DSP ops such as booting firmware and setting the DSP power state in the following patches. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/Makefile | 2 +- sound/soc/sof/debug-dsp-ops.c | 118 ++++++++++++++++++++++++++++++++++ sound/soc/sof/debug.c | 9 ++- sound/soc/sof/sof-priv.h | 5 ++ 4 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 sound/soc/sof/debug-dsp-ops.c diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index b0b22e6ebc03ff..fc29fff7b568be 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -2,7 +2,7 @@ snd-sof-y := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o\ - fw-file-profile.o + fw-file-profile.o debug-dsp-ops.o # IPC implementations ifneq ($(CONFIG_SND_SOC_SOF_IPC3),) diff --git a/sound/soc/sof/debug-dsp-ops.c b/sound/soc/sof/debug-dsp-ops.c new file mode 100644 index 00000000000000..c4ff71cee9aff5 --- /dev/null +++ b/sound/soc/sof/debug-dsp-ops.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2024 Intel Corporation. +// + +#include +#include +#include "sof-priv.h" +#include "ops.h" + +static ssize_t sof_dsp_ops_tester_dfs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + struct dentry *dentry; + const char *string; + size_t size_ret; + + /* return the FW filename or path */ + dentry = file->f_path.dentry; + if (!strcmp(dentry->d_name.name, "fw_filename")) + string = sdev->test_profile.fw_name; + else if (!strcmp(dentry->d_name.name, "fw_path")) + string = sdev->test_profile.fw_path; + else + return 0; + + if (*ppos || !string) + return 0; + + count = min_t(size_t, count, strlen(string)); + size_ret = copy_to_user(buffer, string, count); + if (size_ret) + return -EFAULT; + + *ppos += count; + + return count; +} + +static ssize_t sof_dsp_ops_tester_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct dentry *dentry = file->f_path.dentry; + struct snd_sof_dev *sdev = dfse->sdev; + size_t size; + char *string; + + if (strcmp(dentry->d_name.name, "fw_filename") && + strcmp(dentry->d_name.name, "fw_path")) + return 0; + + string = devm_kzalloc(sdev->dev, count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + size = simple_write_to_buffer(string, count, ppos, buffer, count); + + /* truncate the \n at the end */ + string[count - 1] = '\0'; + + if (!strcmp(dentry->d_name.name, "fw_filename")) { + if (sdev->test_profile.fw_name) + devm_kfree(sdev->dev, sdev->test_profile.fw_name); + sdev->test_profile.fw_name = string; + + return size; + } + + if (sdev->test_profile.fw_path) + devm_kfree(sdev->dev, sdev->test_profile.fw_path); + sdev->test_profile.fw_path = string; + + return size; +} + +static const struct file_operations sof_dsp_ops_tester_fops = { + .open = simple_open, + .write = sof_dsp_ops_tester_dfs_write, + .read = sof_dsp_ops_tester_dfs_read, +}; + +static int sof_dsp_dsp_ops_create_dfse(struct snd_sof_dev *sdev, const char *name, + struct dentry *parent, umode_t mode) +{ + struct snd_sof_dfsentry *dfse; + + /* create debugfs entry for FW filename */ + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->sdev = sdev; + debugfs_create_file(name, mode, parent, dfse, &sof_dsp_ops_tester_fops); + list_add(&dfse->list, &sdev->dfsentry_list); + + return 0; +} + +int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev) +{ + struct dentry *dsp_ops_debugfs; + int ret; + + /* debugfs root directory for DSP ops debug */ + dsp_ops_debugfs = debugfs_create_dir("fw_debug_ops", sdev->debugfs_root); + + /* create debugfs entry for FW filename */ + ret = sof_dsp_dsp_ops_create_dfse(sdev, "fw_filename", dsp_ops_debugfs, 0666); + if (ret < 0) + return ret; + + /* create debugfs entry for FW path */ + return sof_dsp_dsp_ops_create_dfse(sdev, "fw_path", dsp_ops_debugfs, 0666); +} diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index d0ffa1d7114504..ba99b777362f5e 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -371,9 +371,12 @@ int snd_sof_dbg_init(struct snd_sof_dev *sdev) return err; } - return snd_sof_debugfs_buf_item(sdev, &sdev->fw_state, - sizeof(sdev->fw_state), - "fw_state", 0444); + err = snd_sof_debugfs_buf_item(sdev, &sdev->fw_state, sizeof(sdev->fw_state), + "fw_state", 0444); + if (err < 0) + return err; + + return sof_dbg_dsp_ops_test_init(sdev); } EXPORT_SYMBOL_GPL(snd_sof_dbg_init); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 40616038bb45d3..a41567711bb8db 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -700,6 +700,9 @@ struct snd_sof_dev { bool mclk_id_override; u16 mclk_id_quirk; /* same size as in IPC3 definitions */ + /* used to store the FW filename and path passed from userspace for DSP ops testing */ + struct sof_loadable_file_profile test_profile; + void *private; /* core does not touch this */ }; @@ -909,4 +912,6 @@ static inline int sof_resume_clients(struct snd_sof_dev *sdev) extern const struct sof_ipc_ops ipc3_ops; extern const struct sof_ipc_ops ipc4_ops; +int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev); + #endif From 9d75165a7b42f7d8e95b61fd7681a9071c3a4fc6 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 25 Mar 2024 10:04:19 -0700 Subject: [PATCH 06/10] ASoC: SOF: debug-dsp-ops: Add a new op to test firmware boot Add a new op to test firmware boot. Make sure to the DSP is powered off before attempting to boot a new firmware. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/debug-dsp-ops.c | 52 ++++++++++++++++++++++++++++++++++- sound/soc/sof/intel/mtl.c | 15 ++++++---- sound/soc/sof/ipc4-loader.c | 11 +++++++- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/sound/soc/sof/debug-dsp-ops.c b/sound/soc/sof/debug-dsp-ops.c index c4ff71cee9aff5..0f521b08ca76d2 100644 --- a/sound/soc/sof/debug-dsp-ops.c +++ b/sound/soc/sof/debug-dsp-ops.c @@ -8,6 +8,37 @@ #include "sof-priv.h" #include "ops.h" +static int sof_dsp_ops_boot_firmware(struct snd_sof_dev *sdev) +{ + const char *fw_filename = NULL; + int ret; + + if (sdev->test_profile.fw_name && strlen(sdev->test_profile.fw_name)) { + if (sdev->test_profile.fw_path && strlen(sdev->test_profile.fw_path)) + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", sdev->test_profile.fw_path, + sdev->test_profile.fw_name); + else + fw_filename = kasprintf(GFP_KERNEL, "%s", sdev->test_profile.fw_name); + } + + /* If fw_filename is NULL the firmware from the default profile will be loaded */ + ret = snd_sof_load_firmware(sdev, fw_filename); + kfree(fw_filename); + if (ret < 0) + return ret; + + /* boot firmware */ + sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS); + + ret = snd_sof_run_firmware(sdev); + + /* set first_boot to false so subsequent boots will be from IMR if supported */ + if (!ret) + sdev->first_boot = false; + + return ret; +} + static ssize_t sof_dsp_ops_tester_dfs_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { @@ -48,6 +79,21 @@ static ssize_t sof_dsp_ops_tester_dfs_write(struct file *file, const char __user size_t size; char *string; + if (!strcmp(dentry->d_name.name, "boot_fw")) { + int ret; + string = kzalloc(count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + size = simple_write_to_buffer(string, count, ppos, buffer, count); + kfree(string); + + ret = sof_dsp_ops_boot_firmware(sdev); + if (ret < 0) + return ret; + return size; + } + if (strcmp(dentry->d_name.name, "fw_filename") && strcmp(dentry->d_name.name, "fw_path")) return 0; @@ -114,5 +160,9 @@ int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev) return ret; /* create debugfs entry for FW path */ - return sof_dsp_dsp_ops_create_dfse(sdev, "fw_path", dsp_ops_debugfs, 0666); + ret = sof_dsp_dsp_ops_create_dfse(sdev, "fw_path", dsp_ops_debugfs, 0666); + if (ret < 0) + return ret; + + return sof_dsp_dsp_ops_create_dfse(sdev, "boot_fw", dsp_ops_debugfs, 0222); } diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 2b9d22ccf345b8..e767de085f04e8 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -306,12 +306,6 @@ int mtl_dsp_post_fw_run(struct snd_sof_dev *sdev) if (sdev->first_boot) { struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; - ret = hda_sdw_startup(sdev); - if (ret < 0) { - dev_err(sdev->dev, "could not startup SoundWire links\n"); - return ret; - } - /* Check if IMR boot is usable */ if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT)) { hdev->imrboot_supported = true; @@ -319,6 +313,15 @@ int mtl_dsp_post_fw_run(struct snd_sof_dev *sdev) 0644, sdev->debugfs_root, &hdev->skip_imr_boot); } + + if (sdev->dsp_test_mode_enabled) + return 0; + + ret = hda_sdw_startup(sdev); + if (ret < 0) { + dev_err(sdev->dev, "could not startup SoundWire links\n"); + return ret; + } } hda_sdw_int_enable(sdev, true); diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c index bcdb33d0368209..456b9885d06ca1 100644 --- a/sound/soc/sof/ipc4-loader.c +++ b/sound/soc/sof/ipc4-loader.c @@ -149,13 +149,22 @@ static size_t sof_ipc4_fw_parse_basefw_ext_man(struct snd_sof_dev *sdev) ssize_t payload_offset; int ret; - fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL); + if (sdev->dsp_test_mode_enabled) + fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL); + else + fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL); if (!fw_lib) return -ENOMEM; fw_lib->sof_fw.fw = sdev->basefw.fw; payload_offset = sof_ipc4_fw_parse_ext_man(sdev, fw_lib); + if (sdev->dsp_test_mode_enabled) { + devm_kfree(sdev->dev, fw_lib->modules); + devm_kfree(sdev->dev, fw_lib); + return payload_offset; + } + if (payload_offset > 0) { fw_lib->sof_fw.payload_offset = payload_offset; From d18aca645dd8aa17a2f3506d74888b3dc69730b7 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 25 Mar 2024 10:10:50 -0700 Subject: [PATCH 07/10] ASoC: SOF: debug_dsp_ops: Add op to test DSP power state changes Add a new op to test DSP power state changes. For now only D3 is supported. More states will be added in follow up patches. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/debug-dsp-ops.c | 84 ++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/sound/soc/sof/debug-dsp-ops.c b/sound/soc/sof/debug-dsp-ops.c index 0f521b08ca76d2..a4c8237302ba0d 100644 --- a/sound/soc/sof/debug-dsp-ops.c +++ b/sound/soc/sof/debug-dsp-ops.c @@ -8,6 +8,44 @@ #include "sof-priv.h" #include "ops.h" +/* + * set dsp power state op by writing the requested power state. + * ex: echo D3 > dsp_power_state + */ +static int sof_dsp_ops_set_power_state(struct snd_sof_dev *sdev, char *state) +{ + /* only D3 supported for now */ + if (strcmp(state, "D3")) { + dev_err(sdev->dev, "Unsupported state %s\n", state); + return -EINVAL; + } + + /* power off the DSP */ + if (sdev->dsp_power_state.state == SOF_DSP_PM_D0) { + const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm); + struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D3, + }; + int ret; + + /* notify DSP of upcoming power down */ + if (pm_ops && pm_ops->ctx_save) { + ret = pm_ops->ctx_save(sdev); + if (ret < 0) + return ret; + } + + ret = snd_sof_dsp_set_power_state(sdev, &target_state); + if (ret < 0) + return ret; + + sdev->enabled_cores_mask = 0; + sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); + } + + return 0; +} + static int sof_dsp_ops_boot_firmware(struct snd_sof_dev *sdev) { const char *fw_filename = NULL; @@ -45,17 +83,29 @@ static ssize_t sof_dsp_ops_tester_dfs_read(struct file *file, char __user *buffe struct snd_sof_dfsentry *dfse = file->private_data; struct snd_sof_dev *sdev = dfse->sdev; struct dentry *dentry; - const char *string; + const char *string = NULL; size_t size_ret; /* return the FW filename or path */ dentry = file->f_path.dentry; - if (!strcmp(dentry->d_name.name, "fw_filename")) + if (!strcmp(dentry->d_name.name, "fw_filename")) { string = sdev->test_profile.fw_name; - else if (!strcmp(dentry->d_name.name, "fw_path")) + } else if (!strcmp(dentry->d_name.name, "fw_path")) { string = sdev->test_profile.fw_path; - else + } else if (!strcmp(dentry->d_name.name, "dsp_power_state")) { + switch (sdev->dsp_power_state.state) { + case SOF_DSP_PM_D0: + string = "D0"; + break; + case SOF_DSP_PM_D3: + string = "D3"; + break; + default: + break; + } + } else { return 0; + } if (*ppos || !string) return 0; @@ -94,6 +144,26 @@ static ssize_t sof_dsp_ops_tester_dfs_write(struct file *file, const char __user return size; } + /* set DSP power state */ + if (!strcmp(dentry->d_name.name, "dsp_power_state")) { + int ret; + + string = kzalloc(count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + size = simple_write_to_buffer(string, count, ppos, buffer, count); + + /* truncate the \n at the end */ + string[count - 1] = '\0'; + ret = sof_dsp_ops_set_power_state(sdev, string); + kfree(string); + if (ret < 0) + return ret; + + return size; + } + if (strcmp(dentry->d_name.name, "fw_filename") && strcmp(dentry->d_name.name, "fw_path")) return 0; @@ -164,5 +234,9 @@ int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev) if (ret < 0) return ret; - return sof_dsp_dsp_ops_create_dfse(sdev, "boot_fw", dsp_ops_debugfs, 0222); + ret = sof_dsp_dsp_ops_create_dfse(sdev, "boot_fw", dsp_ops_debugfs, 0222); + if (ret < 0) + return ret; + + return sof_dsp_dsp_ops_create_dfse(sdev, "dsp_power_state", dsp_ops_debugfs, 0666); } From 1972c982bdf9ceaf27c01e22e0f2cfc612d334d2 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 25 Mar 2024 10:32:50 -0700 Subject: [PATCH 08/10] ASoC: SOF: debug-dsp-ops: Add a debugfs entry to read current FW status Add a read-only debugfs entry to read the current FW boot state. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/debug-dsp-ops.c | 76 +++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/sound/soc/sof/debug-dsp-ops.c b/sound/soc/sof/debug-dsp-ops.c index a4c8237302ba0d..7763ae3f5f1a0d 100644 --- a/sound/soc/sof/debug-dsp-ops.c +++ b/sound/soc/sof/debug-dsp-ops.c @@ -8,6 +8,8 @@ #include "sof-priv.h" #include "ops.h" +#define MAX_FW_STATE_STRING_LEN 128 + /* * set dsp power state op by writing the requested power state. * ex: echo D3 > dsp_power_state @@ -77,13 +79,73 @@ static int sof_dsp_ops_boot_firmware(struct snd_sof_dev *sdev) return ret; } +static ssize_t sof_dsp_ops_fw_state_read(struct snd_sof_dev *sdev, char __user *buffer, + size_t count, loff_t *ppos) +{ + char string[MAX_FW_STATE_STRING_LEN]; + size_t size_ret; + + switch (sdev->fw_state) { + case SOF_FW_BOOT_NOT_STARTED: + snprintf(string, MAX_FW_STATE_STRING_LEN, + "%d: NOT STARTED\n", sdev->fw_state); + break; + case SOF_DSPLESS_MODE: + snprintf(string, MAX_FW_STATE_STRING_LEN, + "%d: DSPLESS MODE\n", sdev->fw_state); + break; + case SOF_FW_BOOT_PREPARE: + snprintf(string, MAX_FW_STATE_STRING_LEN, + "%d: PREPARE\n", sdev->fw_state); + break; + case SOF_FW_BOOT_IN_PROGRESS: + snprintf(string, MAX_FW_STATE_STRING_LEN, + "%d: BOOT IN PROGRESS\n", sdev->fw_state); + break; + case SOF_FW_BOOT_FAILED: + snprintf(string, MAX_FW_STATE_STRING_LEN, + "%d: FAILED\n", sdev->fw_state); + break; + case SOF_FW_BOOT_READY_FAILED: + snprintf(string, MAX_FW_STATE_STRING_LEN, + "%d: READY FAILED\n", sdev->fw_state); + break; + case SOF_FW_BOOT_READY_OK: + snprintf(string, MAX_FW_STATE_STRING_LEN, + "%d: READY OK\n", sdev->fw_state); + break; + case SOF_FW_BOOT_COMPLETE: + snprintf(string, MAX_FW_STATE_STRING_LEN, + "%d: COMPLETE\n", sdev->fw_state); + break; + case SOF_FW_CRASHED: + snprintf(string, MAX_FW_STATE_STRING_LEN, + "%d: CRASHED\n", sdev->fw_state); + break; + default: + break; + } + + if (*ppos) + return 0; + + count = min_t(size_t, count, strlen(string)); + size_ret = copy_to_user(buffer, string, count); + if (size_ret) + return -EFAULT; + + *ppos += count; + + return count; +} + static ssize_t sof_dsp_ops_tester_dfs_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { struct snd_sof_dfsentry *dfse = file->private_data; struct snd_sof_dev *sdev = dfse->sdev; struct dentry *dentry; - const char *string = NULL; + const char *string; size_t size_ret; /* return the FW filename or path */ @@ -95,14 +157,16 @@ static ssize_t sof_dsp_ops_tester_dfs_read(struct file *file, char __user *buffe } else if (!strcmp(dentry->d_name.name, "dsp_power_state")) { switch (sdev->dsp_power_state.state) { case SOF_DSP_PM_D0: - string = "D0"; + string = "D0\n"; break; case SOF_DSP_PM_D3: - string = "D3"; + string = "D3\n"; break; default: break; } + } else if (!strcmp(dentry->d_name.name, "fw_state")) { + return sof_dsp_ops_fw_state_read(sdev, buffer, count, ppos); } else { return 0; } @@ -238,5 +302,9 @@ int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev) if (ret < 0) return ret; - return sof_dsp_dsp_ops_create_dfse(sdev, "dsp_power_state", dsp_ops_debugfs, 0666); + ret = sof_dsp_dsp_ops_create_dfse(sdev, "dsp_power_state", dsp_ops_debugfs, 0666); + if (ret < 0) + return ret; + + return sof_dsp_dsp_ops_create_dfse(sdev, "fw_state", dsp_ops_debugfs, 0444); } From 95be13c642440b522c6c2f50552a838493b80957 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 17 Apr 2024 10:49:47 -0700 Subject: [PATCH 09/10] ASoC: SOF: debug-dsp-ops: Add op for unloading firmware Unload any existing firmware and set the fw_state to PREPARE in preparation for a subsequent firmware boot. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/debug-dsp-ops.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/sound/soc/sof/debug-dsp-ops.c b/sound/soc/sof/debug-dsp-ops.c index 7763ae3f5f1a0d..9f62fa4ab4fdfb 100644 --- a/sound/soc/sof/debug-dsp-ops.c +++ b/sound/soc/sof/debug-dsp-ops.c @@ -48,6 +48,15 @@ static int sof_dsp_ops_set_power_state(struct snd_sof_dev *sdev, char *state) return 0; } +static void sof_dsp_ops_unload_firmware(struct snd_sof_dev *sdev) +{ + /* release the currently loaded firmware */ + if (sdev->basefw.fw) + snd_sof_fw_unload(sdev); + + sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); +} + static int sof_dsp_ops_boot_firmware(struct snd_sof_dev *sdev) { const char *fw_filename = NULL; @@ -193,6 +202,17 @@ static ssize_t sof_dsp_ops_tester_dfs_write(struct file *file, const char __user size_t size; char *string; + if (!strcmp(dentry->d_name.name, "unload_fw")) { + string = kzalloc(count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + size = simple_write_to_buffer(string, count, ppos, buffer, count); + kfree(string); + sof_dsp_ops_unload_firmware(sdev); + return size; + } + if (!strcmp(dentry->d_name.name, "boot_fw")) { int ret; string = kzalloc(count + 1, GFP_KERNEL); @@ -306,5 +326,9 @@ int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev) if (ret < 0) return ret; - return sof_dsp_dsp_ops_create_dfse(sdev, "fw_state", dsp_ops_debugfs, 0444); + ret = sof_dsp_dsp_ops_create_dfse(sdev, "fw_state", dsp_ops_debugfs, 0444); + if (ret < 0) + return ret; + + return sof_dsp_dsp_ops_create_dfse(sdev, "unload_fw", dsp_ops_debugfs, 0222); } From bbac8319482634871a8a4d1d99818c5d6f7cc3de Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 17 Apr 2024 17:29:50 -0700 Subject: [PATCH 10/10] ASoC: SOF: debug-dsp-ops: Add ops for trace init/free These will be used to debugging purposes when testing the different DSP ops. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/debug-dsp-ops.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/sound/soc/sof/debug-dsp-ops.c b/sound/soc/sof/debug-dsp-ops.c index 9f62fa4ab4fdfb..02215f124cb5ad 100644 --- a/sound/soc/sof/debug-dsp-ops.c +++ b/sound/soc/sof/debug-dsp-ops.c @@ -48,6 +48,15 @@ static int sof_dsp_ops_set_power_state(struct snd_sof_dev *sdev, char *state) return 0; } +static int sof_dsp_ops_trace_init(struct snd_sof_dev *sdev, bool init) +{ + if (init) + return sof_fw_trace_init(sdev); + + sof_fw_trace_free(sdev); + return 0; +} + static void sof_dsp_ops_unload_firmware(struct snd_sof_dev *sdev) { /* release the currently loaded firmware */ @@ -202,6 +211,13 @@ static ssize_t sof_dsp_ops_tester_dfs_write(struct file *file, const char __user size_t size; char *string; + + if (!strcmp(dentry->d_name.name, "init_trace")) + return sof_dsp_ops_trace_init(sdev, true); + + if (!strcmp(dentry->d_name.name, "free_trace")) + return sof_dsp_ops_trace_init(sdev, false); + if (!strcmp(dentry->d_name.name, "unload_fw")) { string = kzalloc(count + 1, GFP_KERNEL); if (!string) @@ -330,5 +346,13 @@ int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev) if (ret < 0) return ret; - return sof_dsp_dsp_ops_create_dfse(sdev, "unload_fw", dsp_ops_debugfs, 0222); + ret = sof_dsp_dsp_ops_create_dfse(sdev, "unload_fw", dsp_ops_debugfs, 0222); + if (ret < 0) + return ret; + + ret = sof_dsp_dsp_ops_create_dfse(sdev, "init_trace", dsp_ops_debugfs, 0222); + if (ret < 0) + return ret; + + return sof_dsp_dsp_ops_create_dfse(sdev, "free_trace", dsp_ops_debugfs, 0222); }