diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 32ffd345e07fc4..ca9208efd9b5b2 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -266,6 +266,14 @@ config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT Say Y if you want to retain DSP context for FW exceptions. If unsure, select "N". +config SND_SOC_SOF_DEBUG_DSP_OPS_TEST + bool "SOF enable DSP ops testing" + help + This option is used for testing DSP operations such as load/unload FW, + set power state, enable/disable core etc. from userspace. + Say Y if you want to retain DSP context for FW exceptions. + If unsure, select "N". + endif ## SND_SOC_SOF_DEBUG endif ## SND_SOC_SOF_DEVELOPER_SUPPORT diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 3624124575afdf..a275b40762d9a0 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -4,6 +4,9 @@ snd-sof-objs := 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 +ifneq ($(CONFIG_SND_SOC_SOF_DEBUG_DSP_OPS_TEST),) +snd-sof-objs += debug-dsp-ops.o +endif # IPC implementations ifneq ($(CONFIG_SND_SOC_SOF_IPC3),) snd-sof-objs += ipc3.o ipc3-loader.o ipc3-topology.o ipc3-control.o ipc3-pcm.o\ diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 8ff0db0f51dc44..0543a3dab2518b 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -432,7 +432,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); @@ -472,6 +472,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) /* hereafter all FW boot flows are for PM reasons */ sdev->first_boot = false; +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_DSP_OPS_TEST) /* now register audio DSP platform driver and dai */ ret = devm_snd_soc_register_component(sdev->dev, &sdev->plat_drv, sof_ops(sdev)->drv, @@ -488,6 +489,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) "error: failed to register machine driver %d\n", ret); goto fw_trace_err; } +#endif ret = sof_register_clients(sdev); if (ret < 0) { @@ -511,8 +513,11 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) return 0; sof_machine_err: +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_DSP_OPS_TEST) snd_sof_machine_unregister(sdev, plat_data); + fw_trace_err: +#endif sof_fw_trace_free(sdev); fw_run_err: snd_sof_fw_unload(sdev); @@ -567,6 +572,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) if (sof_core_debug) dev_info(dev, "sof_debug value: %#x\n", sof_core_debug); +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_DSP_OPS_TEST) if (sof_debug_check_flag(SOF_DBG_DSPLESS_MODE)) { if (plat_data->desc->dspless_mode_supported) { dev_info(dev, "Switching to DSPless mode\n"); @@ -575,7 +581,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) dev_info(dev, "DSPless mode is not supported by the platform\n"); } } - +#endif /* Initialize sof_ops based on the initial selected IPC version */ ret = sof_init_sof_ops(sdev); if (ret) diff --git a/sound/soc/sof/debug-dsp-ops.c b/sound/soc/sof/debug-dsp-ops.c new file mode 100644 index 00000000000000..39d4f6b3f53eae --- /dev/null +++ b/sound/soc/sof/debug-dsp-ops.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2023 Intel Corporation. All rights reserved. +// + +#include +#include +#include "sof-priv.h" +#include "ops.h" + +/* + * set dsp power state op by writing the power state. + * ex: echo set_power_state,D3 > dsp_test_op + */ +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); + pm_message_t pm_state; + int ret; + + pm_state.event = SOF_DSP_PM_D3; + + /* suspend DMA trace */ + sof_fw_trace_suspend(sdev, pm_state); + + /* 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_runtime_suspend(sdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to power off DSP\n"); + return ret; + } + + sdev->enabled_cores_mask = 0; + sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); + } + + return 0; +} + +/* + * test firmware boot by passing the firmware file as the argument: + * ex: echo boot_firmware,intel/avs/tgl/community/dsp_basefw.bin > dsp_test_op + */ +static int sof_dsp_ops_boot_firmware(struct snd_sof_dev *sdev, char *fw_filename) +{ + int ret; + + /* power off the DSP */ + ret = sof_dsp_ops_set_power_state(sdev, "D3"); + if (ret < 0) + return ret; + + if (sdev->basefw.fw) + snd_sof_fw_unload(sdev); + + ret = snd_sof_dsp_runtime_resume(sdev); + if (ret < 0) + return ret; + + sdev->first_boot = true; + + /* load and boot firmware */ + ret = snd_sof_load_firmware(sdev, (const char *)fw_filename); + if (ret < 0) + return ret; + + sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS); + + ret = snd_sof_run_firmware(sdev); + if (ret < 0) + return ret; + + /* resume DMA trace */ + return sof_fw_trace_resume(sdev); +} + +/* ops are executed as "op_name,argument1,argument2...". For example, to set the DSP power state + * to D3: echo "load_firmware,/sof-tgl.ri" > dsp_test_op" + */ +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 snd_sof_dev *sdev = dfse->sdev; + size_t size; + const char *op_name; + char *string; + 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'; + + /* extract the name of the op to execute */ + op_name = strsep(&string, ","); + if (!op_name) + op_name = (const char *)string; + + if (!strcmp(op_name, "boot_firmware")) { + ret = sof_dsp_ops_boot_firmware(sdev, string); + if (ret < 0) + goto err; + } + + if (!strcmp(op_name, "set_power_state")) { + ret = sof_dsp_ops_set_power_state(sdev, string); + if (ret < 0) + goto err; + } + +err: + if (ret >= 0) + ret = size; + + kfree(string); + + return ret; +} + +static const struct file_operations sof_dsp_ops_tester_fops = { + .open = simple_open, + .write = sof_dsp_ops_tester_dfs_write, +}; + +int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev) +{ + struct snd_sof_dfsentry *dfse; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + /* no need to allocate dfse buffer */ + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->sdev = sdev; + + debugfs_create_file("dsp_test_op", 0222, sdev->debugfs_root, dfse, &sof_dsp_ops_tester_fops); + + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); + return 0; +} diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index d547318e0d32f4..afc1809490d5b0 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -353,9 +353,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), + 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/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c index eaa04762eb1122..e8eb2765f696df 100644 --- a/sound/soc/sof/ipc4-loader.c +++ b/sound/soc/sof/ipc4-loader.c @@ -136,10 +136,8 @@ static ssize_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev, static size_t sof_ipc4_fw_parse_basefw_ext_man(struct snd_sof_dev *sdev) { - struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct sof_ipc4_fw_library *fw_lib; ssize_t payload_offset; - int ret; fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL); if (!fw_lib) @@ -148,7 +146,11 @@ static size_t sof_ipc4_fw_parse_basefw_ext_man(struct snd_sof_dev *sdev) fw_lib->sof_fw.fw = sdev->basefw.fw; payload_offset = sof_ipc4_fw_parse_ext_man(sdev, fw_lib); +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_DSP_OPS_TEST) if (payload_offset > 0) { + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + int ret; + fw_lib->sof_fw.payload_offset = payload_offset; /* basefw ID is 0 */ @@ -157,7 +159,7 @@ static size_t sof_ipc4_fw_parse_basefw_ext_man(struct snd_sof_dev *sdev) if (ret) return ret; } - +#endif return payload_offset; } diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 2f8555f11c0364..57e35270cfcc97 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -14,10 +14,9 @@ #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; int ret; @@ -25,11 +24,13 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) 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; + } ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev); @@ -65,11 +66,11 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) } 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 6cf21e829e0727..663a836c5b071d 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -475,11 +475,11 @@ snd_sof_pcm_platform_trigger(struct snd_sof_dev *sdev, } /* Firmware loading */ -static inline int snd_sof_load_firmware(struct snd_sof_dev *sdev) +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 704b21413c7198..3b52b1ca7af09c 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-pci-dev.c b/sound/soc/sof/sof-pci-dev.c index aab5c900cecf83..341e36e3a95acf 100644 --- a/sound/soc/sof/sof-pci-dev.c +++ b/sound/soc/sof/sof-pci-dev.c @@ -171,6 +171,10 @@ static void sof_pci_probe_complete(struct device *dev) if (sof_pci_debug & SOF_PCI_DISABLE_PM_RUNTIME) return; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_DSP_OPS_TEST) + return; +#endif + /* allow runtime_pm */ pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 5e5c5a36c3c98e..2abe974aa1c72b 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 */ @@ -714,8 +714,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); @@ -779,6 +779,16 @@ void sof_fw_trace_fw_crashed(struct snd_sof_dev *sdev); void sof_fw_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state); int sof_fw_trace_resume(struct snd_sof_dev *sdev); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_DSP_OPS_TEST) +int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev); +#else +static inline int sof_dbg_dsp_ops_test_init(struct snd_sof_dev *sdev) +{ + return 0; +} +#endif + + /* * DSP Architectures. */