From 7e07fea36decae2d7ac10849e3f971b7293f6806 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 6 Jul 2021 16:55:11 +0300 Subject: [PATCH] ASoC: SOF: Convert the dma-trace support to SOF client Add a new client driver for dma trace support and move all related code from core to the new client driver. The dma trace is supported and used on all existing platform (intel and i.MX), thus the new client driver is selected by all platforms to be built and the CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE is used as a default value for enable/disable the functionality. The new module supports overriding the default state with 'enable' module parameter. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/Kconfig | 14 +- sound/soc/sof/Makefile | 4 +- sound/soc/sof/core.c | 23 +- sound/soc/sof/debug.c | 1 - sound/soc/sof/imx/Kconfig | 1 + sound/soc/sof/imx/imx-common.c | 14 + sound/soc/sof/imx/imx-common.h | 3 + sound/soc/sof/imx/imx8.c | 8 + sound/soc/sof/imx/imx8m.c | 4 + sound/soc/sof/intel/Kconfig | 9 + sound/soc/sof/intel/Makefile | 8 +- sound/soc/sof/intel/apl.c | 14 +- sound/soc/sof/intel/atom.c | 14 + sound/soc/sof/intel/atom.h | 3 + sound/soc/sof/intel/bdw.c | 16 + sound/soc/sof/intel/byt.c | 8 + sound/soc/sof/intel/cnl.c | 14 +- sound/soc/sof/intel/hda-dsp.c | 2 +- sound/soc/sof/intel/hda-trace.c | 78 ++- sound/soc/sof/intel/hda.h | 21 +- sound/soc/sof/intel/icl.c | 14 +- sound/soc/sof/intel/tgl.c | 14 +- sound/soc/sof/ipc.c | 24 +- sound/soc/sof/ops.c | 1 - sound/soc/sof/ops.h | 26 - sound/soc/sof/pm.c | 12 - sound/soc/sof/sof-client-dma-trace.c | 678 +++++++++++++++++++++++++++ sound/soc/sof/sof-client-dma-trace.h | 21 + sound/soc/sof/sof-priv.h | 29 +- sound/soc/sof/trace.c | 575 ----------------------- 30 files changed, 913 insertions(+), 740 deletions(-) create mode 100644 sound/soc/sof/sof-client-dma-trace.c create mode 100644 sound/soc/sof/sof-client-dma-trace.h delete mode 100644 sound/soc/sof/trace.c diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 0da33f0d98daf2..62ef2dcfaba1fb 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -60,6 +60,16 @@ config SND_SOC_SOF_DEBUG_PROBES This option is not user-selectable but automagically handled by 'select' statements at a higher level. +config SND_SOC_SOF_DEBUG_DMA_TRACE + tristate + select SND_SOC_SOF_CLIENT + help + This option enables the dma-trace feature that can be used to + gather trace information in close to real time from firmware, backed + by DMA. + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + config SND_SOC_SOF_CLIENT tristate select AUXILIARY_BUS @@ -188,8 +198,8 @@ config SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE bool "SOF enable firmware trace" help The firmware trace can be enabled either at build-time with - this option, or dynamically by setting flags in the SOF core - module parameter (similar to dynamic debug). + this option, or dynamically by setting the 'enable' module parameter + for the snd_sof_dma_trace module. If unsure, select "N". config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index d054f0294cf3ec..9a84fc29145506 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 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 + control.o iomem-utils.o sof-audio.o stream-ipc.o snd-sof-$(CONFIG_SND_SOC_SOF_CLIENT) += sof-client.o snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o @@ -12,6 +12,7 @@ snd-sof-of-objs := sof-of-dev.o snd-sof-ipc-test-objs := sof-client-ipc-test.o snd-sof-probes-objs := sof-client-probes.o +snd-sof-dma-trace-objs := sof-client-dma-trace.o snd-sof-nocodec-objs := nocodec.o @@ -28,6 +29,7 @@ obj-$(CONFIG_SND_SOC_SOF_PCI_DEV) += snd-sof-pci.o obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) += snd-sof-ipc-test.o obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += snd-sof-probes.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_DMA_TRACE) += snd-sof-dma-trace.o obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 2113386e5b13de..42e5ee200c9116 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -220,20 +220,8 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto fw_run_err; } - if (sof_core_debug & SOF_DBG_ENABLE_TRACE) { - sdev->dtrace_is_supported = true; - - /* init DMA trace */ - ret = snd_sof_init_trace(sdev); - if (ret < 0) { - /* non fatal */ - dev_warn(sdev->dev, - "warning: failed to initialize trace %d\n", - ret); - } - } else { - dev_dbg(sdev->dev, "SOF firmware trace disabled\n"); - } + if (sof_core_debug & SOF_DBG_ENABLE_TRACE) + dev_dbg(sdev->dev, "SOF_DBG_ENABLE_TRACE is no longer used\n"); /* hereafter all FW boot flows are for PM reasons */ sdev->first_boot = false; @@ -245,14 +233,14 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) if (ret < 0) { dev_err(sdev->dev, "error: failed to register DSP DAI driver %d\n", ret); - goto fw_trace_err; + goto fw_run_err; } ret = snd_sof_machine_register(sdev, plat_data); if (ret < 0) { dev_err(sdev->dev, "error: failed to register machine driver %d\n", ret); - goto fw_trace_err; + goto fw_run_err; } ret = sof_register_clients(sdev); @@ -278,8 +266,6 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) sof_machine_err: snd_sof_machine_unregister(sdev, plat_data); -fw_trace_err: - snd_sof_free_trace(sdev); fw_run_err: snd_sof_fw_unload(sdev); fw_load_err: @@ -412,7 +398,6 @@ int snd_sof_device_remove(struct device *dev) snd_sof_ipc_free(sdev); snd_sof_free_debug(sdev); - snd_sof_free_trace(sdev); } /* diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index aa5804a9042495..8d9515a83587df 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -408,6 +408,5 @@ void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev) snd_sof_ipc_dump(sdev); snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); sof_set_fw_state(sdev, SOF_FW_CRASHED); - snd_sof_trace_notify_for_error(sdev); } EXPORT_SYMBOL(snd_sof_handle_fw_exception); diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig index 34cf228c188f93..572e72ef009754 100644 --- a/sound/soc/sof/imx/Kconfig +++ b/sound/soc/sof/imx/Kconfig @@ -4,6 +4,7 @@ config SND_SOC_SOF_IMX_TOPLEVEL bool "SOF support for NXP i.MX audio DSPs" depends on ARM64|| COMPILE_TEST depends on SND_SOC_SOF_OF + select SND_SOC_SOF_DEBUG_DMA_TRACE help This adds support for Sound Open Firmware for NXP i.MX platforms. Say Y if you have such a device. diff --git a/sound/soc/sof/imx/imx-common.c b/sound/soc/sof/imx/imx-common.c index 8826ef94f04a33..26bf2b0e0cd0bf 100644 --- a/sound/soc/sof/imx/imx-common.c +++ b/sound/soc/sof/imx/imx-common.c @@ -7,6 +7,7 @@ #include #include #include "../ops.h" +#include "../sof-client-dma-trace.h" #include "imx-common.h" @@ -74,4 +75,17 @@ void imx8_dump(struct snd_sof_dev *sdev, u32 flags) } EXPORT_SYMBOL(imx8_dump); +int imx8_dma_trace_register(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "imx8-dma-trace", 0, NULL, 0); +} +EXPORT_SYMBOL(imx8_dma_trace_register); + +void imx8_dma_trace_unregister(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "imx8-dma-trace", 0); +} +EXPORT_SYMBOL(imx8_dma_trace_unregister); + +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/imx/imx-common.h b/sound/soc/sof/imx/imx-common.h index 1cc7d670418250..2c9bf3520e5079 100644 --- a/sound/soc/sof/imx/imx-common.h +++ b/sound/soc/sof/imx/imx-common.h @@ -13,4 +13,7 @@ void imx8_get_registers(struct snd_sof_dev *sdev, void imx8_dump(struct snd_sof_dev *sdev, u32 flags); +int imx8_dma_trace_register(struct snd_sof_dev *sdev); +void imx8_dma_trace_unregister(struct snd_sof_dev *sdev); + #endif diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index fc1720c211a3ea..5af512491abb67 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -433,6 +433,10 @@ struct snd_sof_dsp_ops sof_imx8_ops = { /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, + /* client ops */ + .register_ipc_clients = imx8_dma_trace_register, + .unregister_ipc_clients = imx8_dma_trace_unregister, + /* Debug information */ .dbg_dump = imx8_dump, .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, @@ -488,6 +492,10 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, + /* client ops */ + .register_ipc_clients = imx8_dma_trace_register, + .unregister_ipc_clients = imx8_dma_trace_unregister, + /* Debug information */ .dbg_dump = imx8_dump, .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, diff --git a/sound/soc/sof/imx/imx8m.c b/sound/soc/sof/imx/imx8m.c index 30624fafc632cd..cb8bdc3aa1d572 100644 --- a/sound/soc/sof/imx/imx8m.c +++ b/sound/soc/sof/imx/imx8m.c @@ -296,6 +296,10 @@ struct snd_sof_dsp_ops sof_imx8m_ops = { /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, + /* client ops */ + .register_ipc_clients = imx8_dma_trace_register, + .unregister_ipc_clients = imx8_dma_trace_unregister, + /* Debug information */ .dbg_dump = imx8_dump, .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index b53f216d4ecc32..11223566d0480b 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -15,6 +15,13 @@ config SND_SOC_SOF_INTEL_HIFI_EP_IPC This option is not user-selectable but automagically handled by 'select' statements at a higher level. +config SND_SOC_SOF_HDA_DMA_TRACE + bool + select SND_SOC_SOF_DEBUG_DMA_TRACE + help + This option is not user-selectable but following the higher level + SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE option. + config SND_SOC_SOF_INTEL_ATOM_HIFI_EP tristate select SND_SOC_SOF_INTEL_COMMON @@ -31,6 +38,7 @@ config SND_SOC_SOF_INTEL_COMMON select SND_SOC_INTEL_MACH select SND_SOC_ACPI if ACPI select SND_INTEL_DSP_CONFIG + select SND_SOC_SOF_DEBUG_DMA_TRACE help This option is not user-selectable but automagically handled by 'select' statements at a higher level. @@ -216,6 +224,7 @@ config SND_SOC_SOF_HDA_COMMON select SND_INTEL_DSP_CONFIG select SND_SOC_SOF_HDA_LINK_BASELINE select SND_SOC_SOF_HDA_PROBES + select SND_SOC_SOF_HDA_DMA_TRACE help This option is not user-selectable but automagically handled by 'select' statements at a higher level. diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile index 1f473d4d8416e4..26037ed634e22f 100644 --- a/sound/soc/sof/intel/Makefile +++ b/sound/soc/sof/intel/Makefile @@ -3,11 +3,11 @@ snd-sof-acpi-intel-byt-objs := byt.o snd-sof-acpi-intel-bdw-objs := bdw.o -snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \ - hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \ - hda-dai.o hda-bus.o \ - apl.o cnl.o tgl.o icl.o +snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-dsp.o \ + hda-ipc.o hda-ctrl.o hda-pcm.o hda-dai.o \ + hda-bus.o apl.o cnl.o tgl.o icl.o snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o +snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_DMA_TRACE) += hda-trace.o snd-sof-intel-hda-objs := hda-codec.o diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 5630d9b7fb66be..1ad0b771ed7921 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -27,12 +27,19 @@ static const struct snd_sof_debugfs_map apl_dsp_debugfs[] = { static int apl_register_clients(struct snd_sof_dev *sdev) { - return hda_probes_register(sdev); + int ret; + + ret = hda_probes_register(sdev); + if (ret) + return ret; + + return hda_dma_trace_register(sdev); } static void apl_unregister_clients(struct snd_sof_dev *sdev) { hda_probes_unregister(sdev); + hda_dma_trace_unregister(sdev); } /* apollolake ops */ @@ -107,11 +114,6 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .core_get = hda_dsp_core_get, .core_put = hda_dsp_core_put, - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - /* client ops */ .register_ipc_clients = apl_register_clients, .unregister_ipc_clients = apl_unregister_clients, diff --git a/sound/soc/sof/intel/atom.c b/sound/soc/sof/intel/atom.c index 74c630bb984718..88f7d448e3de2b 100644 --- a/sound/soc/sof/intel/atom.c +++ b/sound/soc/sof/intel/atom.c @@ -23,6 +23,7 @@ #include "atom.h" #include "../sof-acpi-dev.h" #include "../sof-audio.h" +#include "../sof-client-dma-trace.h" #include "../../intel/common/soc-intel-quirks.h" static void atom_host_done(struct snd_sof_dev *sdev); @@ -457,4 +458,17 @@ void atom_set_mach_params(const struct snd_soc_acpi_mach *mach, } EXPORT_SYMBOL_NS(atom_set_mach_params, SND_SOC_SOF_INTEL_ATOM_HIFI_EP); +int atom_dma_trace_register(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "atom-dma-trace", 0, NULL, 0); +} +EXPORT_SYMBOL_NS(atom_dma_trace_register, SND_SOC_SOF_INTEL_ATOM_HIFI_EP); + +void atom_dma_trace_unregister(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "atom-dma-trace", 0); +} +EXPORT_SYMBOL_NS(atom_dma_trace_unregister, SND_SOC_SOF_INTEL_ATOM_HIFI_EP); + +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/atom.h b/sound/soc/sof/intel/atom.h index 96a462c7a2e551..07f6f7cdb54f61 100644 --- a/sound/soc/sof/intel/atom.h +++ b/sound/soc/sof/intel/atom.h @@ -71,4 +71,7 @@ void atom_set_mach_params(const struct snd_soc_acpi_mach *mach, extern struct snd_soc_dai_driver atom_dai[]; +int atom_dma_trace_register(struct snd_sof_dev *sdev); +void atom_dma_trace_unregister(struct snd_sof_dev *sdev); + #endif diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c index 839f0efbab64f6..8e7f76b643ae72 100644 --- a/sound/soc/sof/intel/bdw.c +++ b/sound/soc/sof/intel/bdw.c @@ -22,6 +22,7 @@ #include "shim.h" #include "../sof-acpi-dev.h" #include "../sof-audio.h" +#include "../sof-client-dma-trace.h" /* BARs */ #define BDW_DSP_BAR 0 @@ -580,6 +581,16 @@ static void bdw_set_mach_params(const struct snd_soc_acpi_mach *mach, mach_params->dai_drivers = desc->ops->drv; } +static int bdw_dma_trace_register(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "bdw-dma-trace", 0, NULL, 0); +} + +static void bdw_dma_trace_unregister(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "bdw-dma-trace", 0); +} + /* Broadwell DAIs */ static struct snd_soc_dai_driver bdw_dai[] = { { @@ -660,6 +671,10 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, + /* client ops */ + .register_ipc_clients = bdw_dma_trace_register, + .unregister_ipc_clients = bdw_dma_trace_unregister, + /* DAI drivers */ .drv = bdw_dai, .num_drv = ARRAY_SIZE(bdw_dai), @@ -739,3 +754,4 @@ MODULE_LICENSE("Dual BSD/GPL"); MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC); MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); MODULE_IMPORT_NS(SND_SOC_SOF_ACPI_DEV); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c index dcfeaedb8fd5ff..f66b31e973de61 100644 --- a/sound/soc/sof/intel/byt.c +++ b/sound/soc/sof/intel/byt.c @@ -278,6 +278,10 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { .suspend = byt_suspend, .resume = byt_resume, + /* client ops */ + .register_ipc_clients = atom_dma_trace_register, + .unregister_ipc_clients = atom_dma_trace_unregister, + /* DAI drivers */ .drv = atom_dai, .num_drv = 3, /* we have only 3 SSPs on byt*/ @@ -360,6 +364,10 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { .suspend = byt_suspend, .resume = byt_resume, + /* client ops */ + .register_ipc_clients = atom_dma_trace_register, + .unregister_ipc_clients = atom_dma_trace_unregister, + /* DAI drivers */ .drv = atom_dai, /* all 6 SSPs may be available for cherrytrail */ diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index aeb4a14c738032..ab3ffd594850c3 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -232,12 +232,19 @@ void cnl_ipc_dump(struct snd_sof_dev *sdev) static int cnl_register_clients(struct snd_sof_dev *sdev) { - return hda_probes_register(sdev); + int ret; + + ret = hda_probes_register(sdev); + if (ret) + return ret; + + return hda_dma_trace_register(sdev); } static void cnl_unregister_clients(struct snd_sof_dev *sdev) { hda_probes_unregister(sdev); + hda_dma_trace_unregister(sdev); } /* cannonlake ops */ @@ -312,11 +319,6 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { /* firmware run */ .run = hda_dsp_cl_boot_firmware, - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - /* client ops */ .register_ipc_clients = cnl_register_clients, .unregister_ipc_clients = cnl_unregister_clients, diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index e03bb455f82a16..7c3a402bff020a 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -433,7 +433,7 @@ static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev, * when the DSP enters D0I3 while the system is in S0 * for debug purpose. */ - if (!sdev->dtrace_is_supported || + if (!sdev->dtrace_is_available || !hda_enable_trace_D0I3_S0 || sdev->system_suspend_target != SOF_SUSPEND_NONE) flags = HDA_PM_NO_DMA_TRACE; diff --git a/sound/soc/sof/intel/hda-trace.c b/sound/soc/sof/intel/hda-trace.c index c5dc833b57b8fc..697d8e85a3cede 100644 --- a/sound/soc/sof/intel/hda-trace.c +++ b/sound/soc/sof/intel/hda-trace.c @@ -17,6 +17,8 @@ #include #include "../ops.h" +#include "../sof-client-dma-trace.h" +#include "../sof-client.h" #include "hda.h" static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab) @@ -31,13 +33,15 @@ static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev, struct snd_dma_buffer ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL); if (ret < 0) - dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret); + dev_err(sdev->dev, "hdac prepare failed: %d\n", ret); return ret; } -int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) +static int hda_dma_trace_init(struct sof_client_dev *cdev, + struct snd_dma_buffer *dmab, u32 *stream_tag) { + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; int ret; @@ -45,8 +49,7 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) SOF_HDA_STREAM_DMI_L1_COMPATIBLE); if (!hda->dtrace_stream) { - dev_err(sdev->dev, - "error: no available capture stream for DMA trace\n"); + dev_err(sdev->dev, "no available capture stream for DMA trace\n"); return -ENODEV; } @@ -56,9 +59,9 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) * initialize capture stream, set BDL address and return corresponding * stream tag which will be sent to the firmware by IPC message. */ - ret = hda_dsp_trace_prepare(sdev, &sdev->dmatb); + ret = hda_dsp_trace_prepare(sdev, dmab); if (ret < 0) { - dev_err(sdev->dev, "error: hdac trace init failed: %d\n", ret); + dev_err(sdev->dev, "hdac trace init failed: %d\n", ret); hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, *stream_tag); hda->dtrace_stream = NULL; *stream_tag = 0; @@ -67,27 +70,68 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) return ret; } -int hda_dsp_trace_release(struct snd_sof_dev *sdev) +static int hda_dma_trace_release(struct sof_client_dev *cdev) { + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct hdac_stream *hstream; - if (hda->dtrace_stream) { - hstream = &hda->dtrace_stream->hstream; - hda_dsp_stream_put(sdev, - SNDRV_PCM_STREAM_CAPTURE, - hstream->stream_tag); - hda->dtrace_stream = NULL; - return 0; + if (!hda->dtrace_stream) { + dev_dbg(sdev->dev, "DMA trace stream is not opened!\n"); + return -ENODEV; } - dev_dbg(sdev->dev, "DMA trace stream is not opened!\n"); - return -ENODEV; + hstream = &hda->dtrace_stream->hstream; + hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, hstream->stream_tag); + hda->dtrace_stream = NULL; + + return 0; } -int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd) +static int hda_dsp_trace_trigger(struct sof_client_dev *cdev, int cmd) { + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; return hda_dsp_stream_trigger(sdev, hda->dtrace_stream, cmd); } + +static int hda_dma_trace_start(struct sof_client_dev *cdev) +{ + return hda_dsp_trace_trigger(cdev, SNDRV_PCM_TRIGGER_START); +} + +static int hda_dma_trace_stop(struct sof_client_dev *cdev) +{ + return hda_dsp_trace_trigger(cdev, SNDRV_PCM_TRIGGER_STOP); +} + +static void hda_dma_trace_is_available(struct sof_client_dev *cdev, + bool available) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + sdev->dtrace_is_available = available; +} + +static const struct sof_dma_trace_host_ops hda_dma_trace_ops = { + .init = hda_dma_trace_init, + .release = hda_dma_trace_release, + .start = hda_dma_trace_start, + .stop = hda_dma_trace_stop, + + .available = hda_dma_trace_is_available, +}; + +int hda_dma_trace_register(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "hda-dma-trace", 0, &hda_dma_trace_ops, + sizeof(hda_dma_trace_ops)); +} + +void hda_dma_trace_unregister(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "hda-dma-trace", 0); +} + +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 07bdf716a37cc3..deab247f9b473d 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -638,13 +638,6 @@ static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; } #endif -/* - * Trace Control. - */ -int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag); -int hda_dsp_trace_release(struct snd_sof_dev *sdev); -int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd); - /* * SoundWire support */ @@ -713,6 +706,20 @@ static inline void hda_probes_unregister(struct snd_sof_dev *sdev) } #endif /* CONFIG_SND_SOC_SOF_HDA_PROBES */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_DMA_TRACE) +int hda_dma_trace_register(struct snd_sof_dev *sdev); +void hda_dma_trace_unregister(struct snd_sof_dev *sdev); +#else +static inline int hda_dma_trace_register(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void hda_dma_trace_unregister(struct snd_sof_dev *sdev) +{ +} +#endif /* CONFIG_SND_SOC_SOF_HDA_DMA_TRACE */ + /* machine driver select */ void hda_machine_select(struct snd_sof_dev *sdev); void hda_set_mach_params(const struct snd_soc_acpi_mach *mach, diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index 04c3f9b98b3b41..3a310530730626 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -90,12 +90,19 @@ static int icl_dsp_post_fw_run(struct snd_sof_dev *sdev) static int icl_register_clients(struct snd_sof_dev *sdev) { - return hda_probes_register(sdev); + int ret; + + ret = hda_probes_register(sdev); + if (ret) + return ret; + + return hda_dma_trace_register(sdev); } static void icl_unregister_clients(struct snd_sof_dev *sdev) { hda_probes_unregister(sdev); + hda_dma_trace_unregister(sdev); } /* Icelake ops */ @@ -170,11 +177,6 @@ const struct snd_sof_dsp_ops sof_icl_ops = { .run = hda_dsp_cl_boot_firmware_iccmax, .stall = icl_dsp_core_stall, - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - /* client ops */ .register_ipc_clients = icl_register_clients, .unregister_ipc_clients = icl_unregister_clients, diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 9924a1c474fd4f..602d2f35b37754 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -62,12 +62,19 @@ static int tgl_dsp_core_put(struct snd_sof_dev *sdev, int core) static int tgl_register_clients(struct snd_sof_dev *sdev) { - return hda_probes_register(sdev); + int ret; + + ret = hda_probes_register(sdev); + if (ret) + return ret; + + return hda_dma_trace_register(sdev); } static void tgl_unregister_clients(struct snd_sof_dev *sdev) { hda_probes_unregister(sdev); + hda_dma_trace_unregister(sdev); } /* Tigerlake ops */ @@ -142,11 +149,6 @@ const struct snd_sof_dsp_ops sof_tgl_ops = { /* firmware run */ .run = hda_dsp_cl_boot_firmware_iccmax, - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - /* client ops */ .register_ipc_clients = tgl_register_clients, .unregister_ipc_clients = tgl_unregister_clients, diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index f38cb2a46c6b6b..6e67ff30b6465c 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -20,7 +20,6 @@ typedef void (*ipc_rx_callback)(struct snd_sof_dev *sdev, void *full_msg); -static void ipc_trace_message(struct snd_sof_dev *sdev, void *full_msg); static void ipc_stream_message(struct snd_sof_dev *sdev, void *full_msg); /* @@ -447,6 +446,7 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) wake_up(&sdev->boot_wait); } break; + case SOF_IPC_GLB_TRACE_MSG: case SOF_IPC_GLB_COMPOUND: case SOF_IPC_GLB_TPLG_MSG: case SOF_IPC_GLB_PM_MSG: @@ -457,9 +457,6 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) case SOF_IPC_GLB_STREAM_MSG: rx_callback = ipc_stream_message; break; - case SOF_IPC_GLB_TRACE_MSG: - rx_callback = ipc_trace_message; - break; default: dev_err(sdev->dev, "error: unknown DSP message 0x%x\n", cmd); break; @@ -488,25 +485,6 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) } EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); -/* - * IPC trace mechanism. - */ - -static void ipc_trace_message(struct snd_sof_dev *sdev, void *full_msg) -{ - struct sof_ipc_cmd_hdr *hdr = full_msg; - u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; - - switch (msg_type) { - case SOF_IPC_TRACE_DMA_POSITION: - snd_sof_trace_update_pos(sdev, full_msg); - break; - default: - dev_err(sdev->dev, "error: unhandled trace message %#x\n", msg_type); - break; - } -} - /* * IPC stream position. */ diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c index 92a64114bfd0ac..7c02ab686c0afb 100644 --- a/sound/soc/sof/ops.c +++ b/sound/soc/sof/ops.c @@ -159,6 +159,5 @@ void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset) snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); sof_set_fw_state(sdev, SOF_FW_CRASHED); - snd_sof_trace_notify_for_error(sdev); } EXPORT_SYMBOL(snd_sof_dsp_panic); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 2398b4e19ac996..9a43575b7d764a 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -363,32 +363,6 @@ static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev, return sof_ops(sdev)->send_msg(sdev, msg); } -/* host DMA trace */ -static inline int snd_sof_dma_trace_init(struct snd_sof_dev *sdev, - u32 *stream_tag) -{ - if (sof_ops(sdev)->trace_init) - return sof_ops(sdev)->trace_init(sdev, stream_tag); - - return 0; -} - -static inline int snd_sof_dma_trace_release(struct snd_sof_dev *sdev) -{ - if (sof_ops(sdev)->trace_release) - return sof_ops(sdev)->trace_release(sdev); - - return 0; -} - -static inline int snd_sof_dma_trace_trigger(struct snd_sof_dev *sdev, int cmd) -{ - if (sof_ops(sdev)->trace_trigger) - return sof_ops(sdev)->trace_trigger(sdev, cmd); - - return 0; -} - /* host PCM ops */ static inline int snd_sof_pcm_platform_open(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 0078b7864bfa75..aee9741dcf0891 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -147,15 +147,6 @@ static int sof_resume(struct device *dev, bool runtime_resume) return ret; } - /* resume DMA trace, only need send ipc */ - ret = snd_sof_init_trace_ipc(sdev); - if (ret < 0) { - /* non fatal */ - dev_warn(sdev->dev, - "warning: failed to init trace after resume %d\n", - ret); - } - /* restore pipelines */ ret = sof_set_up_pipelines(sdev, false); if (ret < 0) { @@ -214,9 +205,6 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) sof_tear_down_pipelines(sdev, false); - /* release trace */ - snd_sof_release_trace(sdev); - /* Notify clients about core suspend */ sof_suspend_clients(sdev, pm_state); diff --git a/sound/soc/sof/sof-client-dma-trace.c b/sound/soc/sof/sof-client-dma-trace.c new file mode 100644 index 00000000000000..e7e542d68e2d9c --- /dev/null +++ b/sound/soc/sof/sof-client-dma-trace.c @@ -0,0 +1,678 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2018-2021 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood +// +// SOF client version: +// Peter Ujfalusi +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sof-client.h" +#include "sof-client-dma-trace.h" +#include "sof-utils.h" + +/* DMA buffer size for trace */ +#define SOF_DTRACE_BUF_SIZE (PAGE_SIZE * 16) +#define SOF_DTRACE_SUSPEND_DELAY_MS 3000 + +#define TRACE_FILTER_ELEMENTS_PER_ENTRY 4 +#define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024 + +static bool __read_mostly sof_dtrace_enabled = + IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE); +module_param_named(enable, sof_dtrace_enabled, bool, 0444); +MODULE_PARM_DESC(enable, "Enable SOF dma-trace support"); + +struct sof_dtrace_priv { + const struct sof_dma_trace_host_ops *host_ops; + wait_queue_head_t dtrace_sleep; + struct snd_dma_buffer dmatb; + struct snd_dma_buffer dmatp; + struct dentry *dfs_trace; + struct dentry *dfs_filter; + struct device *dev; + + int dtrace_pages; + u32 host_offset; + bool dtrace_is_enabled; + bool dtrace_error; + bool dtrace_draining; +}; + +static int trace_filter_append_elem(u32 key, u32 value, + struct sof_ipc_trace_filter_elem *elem_list, + int capacity, int *counter) +{ + if (*counter >= capacity) + return -ENOMEM; + + elem_list[*counter].key = key; + elem_list[*counter].value = value; + ++*counter; + + return 0; +} + +static int trace_filter_parse_entry(struct sof_client_dev *cdev, const char *line, + struct sof_ipc_trace_filter_elem *elem, + int capacity, int *counter) +{ + int log_level, pipe_id, comp_id, read, ret; + struct sof_dtrace_priv *priv = cdev->data; + int len = strlen(line); + int cnt = *counter; + u32 uuid_id; + + /* ignore empty content */ + ret = sscanf(line, " %n", &read); + if (!ret && read == len) + return len; + + ret = sscanf(line, " %d %x %d %d %n", + &log_level, &uuid_id, &pipe_id, &comp_id, &read); + if (ret != TRACE_FILTER_ELEMENTS_PER_ENTRY || read != len) { + dev_err(priv->dev, "invalid trace filter entry '%s'\n", line); + return -EINVAL; + } + + if (uuid_id > 0) { + ret = trace_filter_append_elem(SOF_IPC_TRACE_FILTER_ELEM_BY_UUID, + uuid_id, elem, capacity, &cnt); + if (ret) + return ret; + } + if (pipe_id >= 0) { + ret = trace_filter_append_elem(SOF_IPC_TRACE_FILTER_ELEM_BY_PIPE, + pipe_id, elem, capacity, &cnt); + if (ret) + return ret; + } + if (comp_id >= 0) { + ret = trace_filter_append_elem(SOF_IPC_TRACE_FILTER_ELEM_BY_COMP, + comp_id, elem, capacity, &cnt); + if (ret) + return ret; + } + + ret = trace_filter_append_elem(SOF_IPC_TRACE_FILTER_ELEM_SET_LEVEL | + SOF_IPC_TRACE_FILTER_ELEM_FIN, + log_level, elem, capacity, &cnt); + if (ret) + return ret; + + /* update counter only when parsing whole entry passed */ + *counter = cnt; + + return len; +} + +static int trace_filter_parse(struct sof_client_dev *cdev, char *string, + int *out_elem_cnt, + struct sof_ipc_trace_filter_elem **out) +{ + struct sof_dtrace_priv *priv = cdev->data; + static const char entry_delimiter[] = ";"; + char *entry = string; + int capacity = 0; + int entry_len; + int cnt = 0; + + /* + * Each entry contains at least 1, up to TRACE_FILTER_ELEMENTS_PER_ENTRY + * IPC elements, depending on content. Calculate IPC elements capacity + * for the input string where each element is set. + */ + while (entry) { + capacity += TRACE_FILTER_ELEMENTS_PER_ENTRY; + entry = strchr(entry + 1, entry_delimiter[0]); + } + *out = kmalloc(capacity * sizeof(**out), GFP_KERNEL); + if (!*out) + return -ENOMEM; + + /* split input string by ';', and parse each entry separately */ + while ((entry = strsep(&string, entry_delimiter))) { + entry_len = trace_filter_parse_entry(cdev, entry, *out, capacity, &cnt); + if (entry_len < 0) { + dev_err(priv->dev, "%s failed for '%s', %d\n", __func__, + entry, entry_len); + return -EINVAL; + } + } + + *out_elem_cnt = cnt; + + return 0; +} + +static int sof_ipc_trace_update_filter(struct sof_client_dev *cdev, int num_elems, + struct sof_ipc_trace_filter_elem *elems) +{ + struct sof_ipc_trace_filter *msg; + struct sof_ipc_reply reply; + size_t size; + int ret; + + size = struct_size(msg, elems, num_elems); + if (size > SOF_IPC_MSG_MAX_SIZE) + return -EINVAL; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->hdr.size = size; + msg->hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_FILTER_UPDATE; + msg->elem_cnt = num_elems; + memcpy(&msg->elems[0], elems, num_elems * sizeof(*elems)); + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + + kfree(msg); + + return ret ? ret : reply.error; +} + +static ssize_t sof_dtrace_dfs_filter_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_ipc_trace_filter_elem *elems = NULL; + struct sof_dtrace_priv *priv = cdev->data; + int num_elems, ret; + loff_t pos = 0; + char *string; + + if (!priv->dtrace_is_enabled) { + dev_info(priv->dev, "filter can not be updated while suspended\n"); + return -EBUSY; + } + + if (count > TRACE_FILTER_MAX_CONFIG_STRING_LENGTH) { + dev_err(priv->dev, "%s too long input, %zu > %d\n", __func__, + count, TRACE_FILTER_MAX_CONFIG_STRING_LENGTH); + return -EINVAL; + } + + string = kmalloc(count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + /* assert null termination */ + string[count] = 0; + ret = simple_write_to_buffer(string, count, &pos, from, count); + if (ret < 0) + goto error; + + ret = trace_filter_parse(cdev, string, &num_elems, &elems); + if (ret < 0) + goto error; + + if (num_elems) { + ret = sof_ipc_trace_update_filter(cdev, num_elems, elems); + if (ret < 0) { + dev_err(priv->dev, "filter update failed: %d\n", ret); + goto error; + } + } + ret = count; + +error: + kfree(string); + kfree(elems); + return ret; +} + +static const struct file_operations sof_dtrace_filter_fops = { + .open = simple_open, + .write = sof_dtrace_dfs_filter_write, + .llseek = default_llseek, + + .owner = THIS_MODULE, +}; + +static size_t sof_trace_avail(struct sof_client_dev *cdev, loff_t pos, + size_t buffer_size) +{ + struct sof_dtrace_priv *priv = cdev->data; + loff_t host_offset = READ_ONCE(priv->host_offset); + + /* + * If host offset is less than local pos, it means write pointer of + * host DMA buffer has been wrapped. We should output the trace data + * at the end of host DMA buffer at first. + */ + if (host_offset < pos) + return buffer_size - pos; + + /* If there is available trace data now, it is unnecessary to wait. */ + if (host_offset > pos) + return host_offset - pos; + + return 0; +} + +static size_t sof_wait_trace_avail(struct sof_client_dev *cdev, loff_t pos, + size_t buffer_size) +{ + size_t ret = sof_trace_avail(cdev, pos, buffer_size); + struct sof_dtrace_priv *priv = cdev->data; + wait_queue_entry_t wait; + + /* data immediately available */ + if (ret) + return ret; + + if (!priv->dtrace_is_enabled && priv->dtrace_draining) { + /* + * tracing has ended and all traces have been + * read by client, return EOF + */ + priv->dtrace_draining = false; + return 0; + } + + /* wait for available trace data from FW */ + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&priv->dtrace_sleep, &wait); + + if (!signal_pending(current)) { + /* set timeout to max value, no error code */ + schedule_timeout(MAX_SCHEDULE_TIMEOUT); + } + remove_wait_queue(&priv->dtrace_sleep, &wait); + + return sof_trace_avail(cdev, pos, buffer_size); +} + +static int sof_dtrace_dfs_trace_open(struct inode *inode, struct file *file) +{ + struct sof_client_dev *cdev = inode->i_private; + int ret; + + if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) + return -ENODEV; + + ret = debugfs_file_get(file->f_path.dentry); + if (unlikely(ret)) + return ret; + + ret = simple_open(inode, file); + if (ret) + debugfs_file_put(file->f_path.dentry); + + return ret; +} + +static ssize_t sof_dtrace_dfs_trace_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_dtrace_priv *priv = cdev->data; + size_t buffer_size = priv->dmatb.bytes; + unsigned long rem; + loff_t lpos = *ppos; + size_t avail; + u64 lpos_64; + + /* make sure we know about any failures on the DSP side */ + priv->dtrace_error = false; + + /* check pos and count */ + if (lpos < 0) + return -EINVAL; + if (!count) + return 0; + + /* check for buffer wrap and count overflow */ + lpos_64 = lpos; + lpos = do_div(lpos_64, buffer_size); + + /* get available count based on current host offset */ + avail = sof_wait_trace_avail(cdev, lpos, buffer_size); + if (priv->dtrace_error) { + dev_err(priv->dev, "trace IO error\n"); + return -EIO; + } + + /* make sure count is <= avail */ + if (count > avail) + count = avail; + + /* copy available trace data to debugfs */ + rem = copy_to_user(buffer, ((u8 *)(priv->dmatb.area) + lpos), count); + if (rem) + return -EFAULT; + + *ppos += count; + + /* move debugfs reading position */ + return count; +} + +static int sof_dtrace_dfs_trace_release(struct inode *inode, struct file *file) +{ + struct sof_client_dev *cdev = inode->i_private; + struct sof_dtrace_priv *priv = cdev->data; + + /* avoid duplicate traces at next open */ + if (!priv->dtrace_is_enabled) + priv->host_offset = 0; + + debugfs_file_put(file->f_path.dentry); + return 0; +} + +static const struct file_operations sof_dtrace_trace_fops = { + .open = sof_dtrace_dfs_trace_open, + .read = sof_dtrace_dfs_trace_read, + .llseek = default_llseek, + .release = sof_dtrace_dfs_trace_release, + + .owner = THIS_MODULE, +}; + +static void snd_sof_dtrace_update_pos(struct sof_client_dev *cdev, void *full_msg) +{ + struct sof_ipc_dma_trace_posn *posn = full_msg; + u32 msg_type = posn->rhdr.hdr.cmd & SOF_CMD_TYPE_MASK; + struct sof_dtrace_priv *priv = cdev->data; + + if (msg_type != SOF_IPC_TRACE_DMA_POSITION) + dev_info(priv->dev, "unhandled trace message %#x\n", msg_type); + + if (priv->dtrace_is_enabled && priv->host_offset != posn->host_offset) { + priv->host_offset = posn->host_offset; + wake_up(&priv->dtrace_sleep); + } + + if (posn->overflow != 0) + dev_err(priv->dev, + "DSP trace buffer overflow %u bytes. Total messages %d\n", + posn->overflow, posn->messages); +} + +static void sof_dtrace_release(struct sof_client_dev *cdev) +{ + struct sof_dtrace_priv *priv = cdev->data; + const struct sof_dma_trace_host_ops *ops = priv->host_ops; + + if (!priv->dtrace_is_enabled) + return; + + if (ops) { + int ret = ops->stop(cdev); + + if (ret < 0) + dev_err(priv->dev, "host stop failed: %d\n", ret); + + ret = ops->release(cdev); + if (ret < 0) + dev_err(priv->dev, "host release failed: %d\n", ret); + } + + priv->dtrace_is_enabled = false; + priv->dtrace_draining = true; + wake_up(&priv->dtrace_sleep); +} + +static int sof_dtrace_init_ipc(struct sof_client_dev *cdev) +{ + const struct sof_ipc_fw_version *v = sof_client_get_fw_version(cdev); + struct sof_dtrace_priv *priv = cdev->data; + const struct sof_dma_trace_host_ops *ops = priv->host_ops; + struct sof_ipc_dma_trace_params_ext params; + struct sof_ipc_reply ipc_reply; + int ret; + + if (priv->dtrace_is_enabled) + return 0; + + /* set IPC parameters */ + params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG; + /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */ + if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) { + params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext); + params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT; + params.timestamp_ns = ktime_get(); /* in nanosecond */ + } else { + params.hdr.size = sizeof(struct sof_ipc_dma_trace_params); + params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS; + } + params.buffer.phy_addr = priv->dmatp.addr; + params.buffer.size = priv->dmatb.bytes; + params.buffer.pages = priv->dtrace_pages; + params.stream_tag = 0; + + priv->host_offset = 0; + priv->dtrace_draining = false; + + if (ops) { + ret = ops->init(cdev, &priv->dmatb, ¶ms.stream_tag); + if (ret < 0) { + dev_err(priv->dev, "host init failed: %d\n", ret); + return ret; + } + } + + dev_dbg(priv->dev, "stream_tag: %d\n", params.stream_tag); + + /* send IPC to the DSP */ + ret = sof_client_ipc_tx_message(cdev, ¶ms, &ipc_reply, sizeof(ipc_reply)); + if (ret < 0) { + dev_err(priv->dev, "can't set params for DMA for trace %d\n", ret); + goto trace_release; + } + + if (ops) { + ret = ops->start(cdev); + if (ret < 0) { + dev_err(priv->dev, "host start failed: %d\n", ret); + goto trace_release; + } + } + + priv->dtrace_is_enabled = true; + + return 0; + +trace_release: + if (ops) + ops->release(cdev); + + return ret; +} + +static void sof_dtrace_fw_state(struct sof_client_dev *cdev, + enum snd_sof_fw_state state) +{ + struct sof_dtrace_priv *priv = cdev->data; + + if (priv->dtrace_is_enabled && state == SOF_FW_CRASHED) { + priv->dtrace_error = true; + wake_up(&priv->dtrace_sleep); + } +} + +static int sof_dtrace_client_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct dentry *dfsroot = sof_client_get_debugfs_root(cdev); + struct device *dma_dev = sof_client_get_dma_dev(cdev); + struct sof_dma_trace_host_ops *ops; + struct device *dev = &auxdev->dev; + struct sof_dtrace_priv *priv; + int ret; + + /* do not set up the dma-trace support if it is not enabled */ + if (!sof_dtrace_enabled) + return -ENXIO; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ops = dev->platform_data; + + if (ops && (!ops->init || !ops->release || !ops->start || !ops->stop)) { + dev_err(dev, "missing platform callback(s)\n"); + return -ENODEV; + } + + /* + * dma-trace is power managed via auxdev suspend/resume callbacks by + * SOF core + */ + pm_runtime_no_callbacks(dev); + + priv->host_ops = ops; + priv->dev = dev; + cdev->data = priv; + + /* allocate trace page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, PAGE_SIZE, + &priv->dmatp); + if (ret < 0) { + dev_err(dev, "can't alloc page table for trace %d\n", ret); + return ret; + } + + /* allocate trace data buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, dma_dev, + SOF_DTRACE_BUF_SIZE, &priv->dmatb); + if (ret < 0) { + dev_err(dev, "can't alloc buffer for trace %d\n", ret); + goto page_err; + } + + /* create compressed page table for audio firmware */ + ret = snd_sof_create_page_table(dma_dev, &priv->dmatb, priv->dmatp.area, + priv->dmatb.bytes); + if (ret < 0) + goto table_err; + + priv->dtrace_pages = ret; + dev_dbg(dev, "dtrace_pages: %d\n", priv->dtrace_pages); + + priv->dfs_trace = debugfs_create_file("trace", 0444, dfsroot, cdev, + &sof_dtrace_trace_fops); + priv->dfs_filter = debugfs_create_file("filter", 0200, dfsroot, cdev, + &sof_dtrace_filter_fops); + + init_waitqueue_head(&priv->dtrace_sleep); + + ret = sof_client_register_ipc_rx_handler(cdev, SOF_IPC_GLB_TRACE_MSG, + snd_sof_dtrace_update_pos); + if (ret) + goto register_rx_err; + + ret = sof_client_register_fw_state_handler(cdev, sof_dtrace_fw_state); + if (ret) + goto register_fw_state_err; + + ret = sof_dtrace_init_ipc(cdev); + if (ret < 0) { + sof_client_unregister_ipc_rx_handler(cdev, SOF_IPC_GLB_TRACE_MSG); + goto ipc_err; + } + + if (ops && ops->available) + ops->available(cdev, true); + + return 0; + +ipc_err: + sof_client_unregister_fw_state_handler(cdev); +register_fw_state_err: + sof_client_unregister_ipc_rx_handler(cdev, SOF_IPC_GLB_TRACE_MSG); +register_rx_err: + debugfs_remove(priv->dfs_trace); + debugfs_remove(priv->dfs_filter); +table_err: + snd_dma_free_pages(&priv->dmatb); +page_err: + snd_dma_free_pages(&priv->dmatp); + + return ret; +} + +static void sof_dtrace_client_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_dtrace_priv *priv = cdev->data; + const struct sof_dma_trace_host_ops *ops = priv->host_ops; + + if (!sof_dtrace_enabled) + return; + + sof_dtrace_release(cdev); + + debugfs_remove(priv->dfs_filter); + debugfs_remove(priv->dfs_trace); + + sof_client_unregister_fw_state_handler(cdev); + sof_client_unregister_ipc_rx_handler(cdev, SOF_IPC_GLB_TRACE_MSG); + + if (ops && ops->available) + ops->available(cdev, false); + + snd_dma_free_pages(&priv->dmatb); + snd_dma_free_pages(&priv->dmatp); +} + +static int sof_dtrace_client_resume(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + + sof_dtrace_init_ipc(cdev); + + return 0; +} + +static int sof_dtrace_client_suspend(struct auxiliary_device *auxdev, + pm_message_t state) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + + sof_dtrace_release(cdev); + + return 0; +} + +static const struct auxiliary_device_id sof_dtrace_client_id_table[] = { + { .name = "snd_sof.atom-dma-trace", }, + { .name = "snd_sof.bdw-dma-trace", }, + { .name = "snd_sof.hda-dma-trace", }, + { .name = "snd_sof.imx8-dma-trace", }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_dtrace_client_id_table); + +/* driver name will be set based on KBUILD_MODNAME */ +static struct auxiliary_driver sof_dtrace_client_drv = { + .probe = sof_dtrace_client_probe, + .remove = sof_dtrace_client_remove, + .suspend = sof_dtrace_client_suspend, + .resume = sof_dtrace_client_resume, + + .id_table = sof_dtrace_client_id_table, +}; + +module_auxiliary_driver(sof_dtrace_client_drv); + +MODULE_DESCRIPTION("SOF DMA Trace Client Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client-dma-trace.h b/sound/soc/sof/sof-client-dma-trace.h new file mode 100644 index 00000000000000..ce83e6998d2902 --- /dev/null +++ b/sound/soc/sof/sof-client-dma-trace.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOF_CLIENT_DMA_TRACE_H +#define __SOF_CLIENT_DMA_TRACE_H + +struct snd_dma_buffer; +struct sof_client_dev; + +/* Platform callbacks */ +struct sof_dma_trace_host_ops { + int (*init)(struct sof_client_dev *cdev, struct snd_dma_buffer *dmab, + u32 *stream_tag); + int (*release)(struct sof_client_dev *cdev); + int (*start)(struct sof_client_dev *cdev); + int (*stop)(struct sof_client_dev *cdev); + + /* Optional */ + void (*available)(struct sof_client_dev *cdev, bool available); +}; + +#endif diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index ab4909c84be5e6..0e37ed36e76a4f 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -50,9 +50,6 @@ extern int sof_core_debug; /* time in ms for runtime suspend delay */ #define SND_SOF_SUSPEND_DELAY_MS 2000 -/* DMA buffer size for trace */ -#define DMA_BUF_SIZE_FOR_TRACE (PAGE_SIZE * 16) - #define SOF_IPC_DSP_REPLY 0 #define SOF_IPC_HOST_REPLY 1 @@ -249,13 +246,6 @@ struct snd_sof_dsp_ops { size_t size, const char *name, enum sof_debugfs_access_type access_type); /* optional */ - /* host DMA trace initialization */ - int (*trace_init)(struct snd_sof_dev *sdev, - u32 *stream_tag); /* optional */ - int (*trace_release)(struct snd_sof_dev *sdev); /* optional */ - int (*trace_trigger)(struct snd_sof_dev *sdev, - int cmd); /* optional */ - /* misc */ int (*get_bar_index)(struct snd_sof_dev *sdev, u32 type); /* optional */ @@ -430,16 +420,8 @@ struct snd_sof_dev { int ipc_timeout; int boot_timeout; - /* DMA for Trace */ - struct snd_dma_buffer dmatb; - struct snd_dma_buffer dmatp; - int dma_trace_pages; - wait_queue_head_t trace_sleep; - u32 host_offset; - bool dtrace_is_supported; /* set with Kconfig or module parameter */ - bool dtrace_is_enabled; - bool dtrace_error; - bool dtrace_draining; + /* dtrace client is available */ + bool dtrace_is_available; bool msi_enabled; @@ -533,22 +515,15 @@ int sof_ipc_init_msg_memory(struct snd_sof_dev *sdev); /* * Trace/debug */ -int snd_sof_init_trace(struct snd_sof_dev *sdev); -void snd_sof_release_trace(struct snd_sof_dev *sdev); -void snd_sof_free_trace(struct snd_sof_dev *sdev); int snd_sof_dbg_init(struct snd_sof_dev *sdev); void snd_sof_free_debug(struct snd_sof_dev *sdev); int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, void *base, size_t size, const char *name, mode_t mode); -int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, - struct sof_ipc_dma_trace_posn *posn); -void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev); void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, u32 tracep_code, void *oops, struct sof_ipc_panic_info *panic_info, void *stack, size_t stack_words); -int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev); int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev); int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c deleted file mode 100644 index a5f8b9e74cc2d6..00000000000000 --- a/sound/soc/sof/trace.c +++ /dev/null @@ -1,575 +0,0 @@ -// 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) 2018 Intel Corporation. All rights reserved. -// -// Author: Liam Girdwood -// - -#include -#include -#include "sof-priv.h" -#include "ops.h" -#include "sof-utils.h" - -#define TRACE_FILTER_ELEMENTS_PER_ENTRY 4 -#define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024 - -static int trace_filter_append_elem(struct snd_sof_dev *sdev, uint32_t key, uint32_t value, - struct sof_ipc_trace_filter_elem *elem_list, - int capacity, int *counter) -{ - if (*counter >= capacity) - return -ENOMEM; - - elem_list[*counter].key = key; - elem_list[*counter].value = value; - ++*counter; - - return 0; -} - -static int trace_filter_parse_entry(struct snd_sof_dev *sdev, const char *line, - struct sof_ipc_trace_filter_elem *elem, - int capacity, int *counter) -{ - int len = strlen(line); - int cnt = *counter; - uint32_t uuid_id; - int log_level; - int pipe_id; - int comp_id; - int read; - int ret; - - /* ignore empty content */ - ret = sscanf(line, " %n", &read); - if (!ret && read == len) - return len; - - ret = sscanf(line, " %d %x %d %d %n", &log_level, &uuid_id, &pipe_id, &comp_id, &read); - if (ret != TRACE_FILTER_ELEMENTS_PER_ENTRY || read != len) { - dev_err(sdev->dev, "error: invalid trace filter entry '%s'\n", line); - return -EINVAL; - } - - if (uuid_id > 0) { - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_UUID, - uuid_id, elem, capacity, &cnt); - if (ret) - return ret; - } - if (pipe_id >= 0) { - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_PIPE, - pipe_id, elem, capacity, &cnt); - if (ret) - return ret; - } - if (comp_id >= 0) { - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_COMP, - comp_id, elem, capacity, &cnt); - if (ret) - return ret; - } - - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_SET_LEVEL | - SOF_IPC_TRACE_FILTER_ELEM_FIN, - log_level, elem, capacity, &cnt); - if (ret) - return ret; - - /* update counter only when parsing whole entry passed */ - *counter = cnt; - - return len; -} - -static int trace_filter_parse(struct snd_sof_dev *sdev, char *string, - int *out_elem_cnt, - struct sof_ipc_trace_filter_elem **out) -{ - static const char entry_delimiter[] = ";"; - char *entry = string; - int capacity = 0; - int entry_len; - int cnt = 0; - - /* - * Each entry contains at least 1, up to TRACE_FILTER_ELEMENTS_PER_ENTRY - * IPC elements, depending on content. Calculate IPC elements capacity - * for the input string where each element is set. - */ - while (entry) { - capacity += TRACE_FILTER_ELEMENTS_PER_ENTRY; - entry = strchr(entry + 1, entry_delimiter[0]); - } - *out = kmalloc(capacity * sizeof(**out), GFP_KERNEL); - if (!*out) - return -ENOMEM; - - /* split input string by ';', and parse each entry separately in trace_filter_parse_entry */ - while ((entry = strsep(&string, entry_delimiter))) { - entry_len = trace_filter_parse_entry(sdev, entry, *out, capacity, &cnt); - if (entry_len < 0) { - dev_err(sdev->dev, "error: %s failed for '%s', %d\n", __func__, entry, - entry_len); - return -EINVAL; - } - } - - *out_elem_cnt = cnt; - - return 0; -} - -static int sof_ipc_trace_update_filter(struct snd_sof_dev *sdev, int num_elems, - struct sof_ipc_trace_filter_elem *elems) -{ - struct sof_ipc_trace_filter *msg; - struct sof_ipc_reply reply; - size_t size; - int ret; - - size = struct_size(msg, elems, num_elems); - if (size > SOF_IPC_MSG_MAX_SIZE) - return -EINVAL; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - - msg->hdr.size = size; - msg->hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_FILTER_UPDATE; - msg->elem_cnt = num_elems; - memcpy(&msg->elems[0], elems, num_elems * sizeof(*elems)); - - ret = pm_runtime_get_sync(sdev->dev); - if (ret < 0 && ret != -EACCES) { - pm_runtime_put_noidle(sdev->dev); - dev_err(sdev->dev, "error: enabling device failed: %d\n", ret); - goto error; - } - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - pm_runtime_mark_last_busy(sdev->dev); - pm_runtime_put_autosuspend(sdev->dev); - -error: - kfree(msg); - return ret ? ret : reply.error; -} - -static ssize_t sof_dfsentry_trace_filter_write(struct file *file, const char __user *from, - size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct sof_ipc_trace_filter_elem *elems = NULL; - struct snd_sof_dev *sdev = dfse->sdev; - loff_t pos = 0; - int num_elems; - char *string; - int ret; - - if (count > TRACE_FILTER_MAX_CONFIG_STRING_LENGTH) { - dev_err(sdev->dev, "%s too long input, %zu > %d\n", __func__, count, - TRACE_FILTER_MAX_CONFIG_STRING_LENGTH); - return -EINVAL; - } - - string = kmalloc(count + 1, GFP_KERNEL); - if (!string) - return -ENOMEM; - - /* assert null termination */ - string[count] = 0; - ret = simple_write_to_buffer(string, count, &pos, from, count); - if (ret < 0) - goto error; - - ret = trace_filter_parse(sdev, string, &num_elems, &elems); - if (ret < 0) { - dev_err(sdev->dev, "error: fail in trace_filter_parse, %d\n", ret); - goto error; - } - - if (num_elems) { - ret = sof_ipc_trace_update_filter(sdev, num_elems, elems); - if (ret < 0) { - dev_err(sdev->dev, "error: fail in sof_ipc_trace_update_filter %d\n", ret); - goto error; - } - } - ret = count; -error: - kfree(string); - kfree(elems); - return ret; -} - -static const struct file_operations sof_dfs_trace_filter_fops = { - .open = simple_open, - .write = sof_dfsentry_trace_filter_write, - .llseek = default_llseek, -}; - -static int trace_debugfs_filter_create(struct snd_sof_dev *sdev) -{ - struct snd_sof_dfsentry *dfse; - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->sdev = sdev; - dfse->type = SOF_DFSENTRY_TYPE_BUF; - - debugfs_create_file("filter", 0200, sdev->debugfs_root, dfse, - &sof_dfs_trace_filter_fops); - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - - return 0; -} - -static size_t sof_trace_avail(struct snd_sof_dev *sdev, - loff_t pos, size_t buffer_size) -{ - loff_t host_offset = READ_ONCE(sdev->host_offset); - - /* - * If host offset is less than local pos, it means write pointer of - * host DMA buffer has been wrapped. We should output the trace data - * at the end of host DMA buffer at first. - */ - if (host_offset < pos) - return buffer_size - pos; - - /* If there is available trace data now, it is unnecessary to wait. */ - if (host_offset > pos) - return host_offset - pos; - - return 0; -} - -static size_t sof_wait_trace_avail(struct snd_sof_dev *sdev, - loff_t pos, size_t buffer_size) -{ - wait_queue_entry_t wait; - size_t ret = sof_trace_avail(sdev, pos, buffer_size); - - /* data immediately available */ - if (ret) - return ret; - - if (!sdev->dtrace_is_enabled && sdev->dtrace_draining) { - /* - * tracing has ended and all traces have been - * read by client, return EOF - */ - sdev->dtrace_draining = false; - return 0; - } - - /* wait for available trace data from FW */ - init_waitqueue_entry(&wait, current); - set_current_state(TASK_INTERRUPTIBLE); - add_wait_queue(&sdev->trace_sleep, &wait); - - if (!signal_pending(current)) { - /* set timeout to max value, no error code */ - schedule_timeout(MAX_SCHEDULE_TIMEOUT); - } - remove_wait_queue(&sdev->trace_sleep, &wait); - - return sof_trace_avail(sdev, pos, buffer_size); -} - -static ssize_t sof_dfsentry_trace_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; - unsigned long rem; - loff_t lpos = *ppos; - size_t avail, buffer_size = dfse->size; - u64 lpos_64; - - /* make sure we know about any failures on the DSP side */ - sdev->dtrace_error = false; - - /* check pos and count */ - if (lpos < 0) - return -EINVAL; - if (!count) - return 0; - - /* check for buffer wrap and count overflow */ - lpos_64 = lpos; - lpos = do_div(lpos_64, buffer_size); - - /* get available count based on current host offset */ - avail = sof_wait_trace_avail(sdev, lpos, buffer_size); - if (sdev->dtrace_error) { - dev_err(sdev->dev, "error: trace IO error\n"); - return -EIO; - } - - /* make sure count is <= avail */ - if (count > avail) - count = avail; - - /* copy available trace data to debugfs */ - rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count); - if (rem) - return -EFAULT; - - *ppos += count; - - /* move debugfs reading position */ - return count; -} - -static int sof_dfsentry_trace_release(struct inode *inode, struct file *file) -{ - struct snd_sof_dfsentry *dfse = inode->i_private; - struct snd_sof_dev *sdev = dfse->sdev; - - /* avoid duplicate traces at next open */ - if (!sdev->dtrace_is_enabled) - sdev->host_offset = 0; - - return 0; -} - -static const struct file_operations sof_dfs_trace_fops = { - .open = simple_open, - .read = sof_dfsentry_trace_read, - .llseek = default_llseek, - .release = sof_dfsentry_trace_release, -}; - -static int trace_debugfs_create(struct snd_sof_dev *sdev) -{ - struct snd_sof_dfsentry *dfse; - int ret; - - if (!sdev) - return -EINVAL; - - ret = trace_debugfs_filter_create(sdev); - if (ret < 0) - dev_err(sdev->dev, "error: fail in %s, %d", __func__, ret); - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->type = SOF_DFSENTRY_TYPE_BUF; - dfse->buf = sdev->dmatb.area; - dfse->size = sdev->dmatb.bytes; - dfse->sdev = sdev; - - debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse, - &sof_dfs_trace_fops); - - return 0; -} - -int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev) -{ - struct sof_ipc_fw_ready *ready = &sdev->fw_ready; - struct sof_ipc_fw_version *v = &ready->version; - struct sof_ipc_dma_trace_params_ext params; - struct sof_ipc_reply ipc_reply; - int ret; - - if (!sdev->dtrace_is_supported) - return 0; - - if (sdev->dtrace_is_enabled || !sdev->dma_trace_pages) - return -EINVAL; - - /* set IPC parameters */ - params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG; - /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */ - if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) { - params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext); - params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT; - params.timestamp_ns = ktime_get(); /* in nanosecond */ - } else { - params.hdr.size = sizeof(struct sof_ipc_dma_trace_params); - params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS; - } - params.buffer.phy_addr = sdev->dmatp.addr; - params.buffer.size = sdev->dmatb.bytes; - params.buffer.pages = sdev->dma_trace_pages; - params.stream_tag = 0; - - sdev->host_offset = 0; - sdev->dtrace_draining = false; - - ret = snd_sof_dma_trace_init(sdev, ¶ms.stream_tag); - if (ret < 0) { - dev_err(sdev->dev, - "error: fail in snd_sof_dma_trace_init %d\n", ret); - return ret; - } - dev_dbg(sdev->dev, "%s: stream_tag: %d\n", __func__, params.stream_tag); - - /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, - params.hdr.cmd, ¶ms, sizeof(params), - &ipc_reply, sizeof(ipc_reply)); - if (ret < 0) { - dev_err(sdev->dev, - "error: can't set params for DMA for trace %d\n", ret); - goto trace_release; - } - - ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_START); - if (ret < 0) { - dev_err(sdev->dev, - "error: snd_sof_dma_trace_trigger: start: %d\n", ret); - goto trace_release; - } - - sdev->dtrace_is_enabled = true; - - return 0; - -trace_release: - snd_sof_dma_trace_release(sdev); - return ret; -} - -int snd_sof_init_trace(struct snd_sof_dev *sdev) -{ - int ret; - - if (!sdev->dtrace_is_supported) - return 0; - - /* set false before start initialization */ - sdev->dtrace_is_enabled = false; - - /* allocate trace page table buffer */ - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, - PAGE_SIZE, &sdev->dmatp); - if (ret < 0) { - dev_err(sdev->dev, - "error: can't alloc page table for trace %d\n", ret); - return ret; - } - - /* allocate trace data buffer */ - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev, - DMA_BUF_SIZE_FOR_TRACE, &sdev->dmatb); - if (ret < 0) { - dev_err(sdev->dev, - "error: can't alloc buffer for trace %d\n", ret); - goto page_err; - } - - /* create compressed page table for audio firmware */ - ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb, - sdev->dmatp.area, sdev->dmatb.bytes); - if (ret < 0) - goto table_err; - - sdev->dma_trace_pages = ret; - dev_dbg(sdev->dev, "%s: dma_trace_pages: %d\n", - __func__, sdev->dma_trace_pages); - - if (sdev->first_boot) { - ret = trace_debugfs_create(sdev); - if (ret < 0) - goto table_err; - } - - init_waitqueue_head(&sdev->trace_sleep); - - ret = snd_sof_init_trace_ipc(sdev); - if (ret < 0) - goto table_err; - - return 0; -table_err: - sdev->dma_trace_pages = 0; - snd_dma_free_pages(&sdev->dmatb); -page_err: - snd_dma_free_pages(&sdev->dmatp); - return ret; -} -EXPORT_SYMBOL(snd_sof_init_trace); - -int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, - struct sof_ipc_dma_trace_posn *posn) -{ - if (!sdev->dtrace_is_supported) - return 0; - - if (sdev->dtrace_is_enabled && sdev->host_offset != posn->host_offset) { - sdev->host_offset = posn->host_offset; - wake_up(&sdev->trace_sleep); - } - - if (posn->overflow != 0) - dev_err(sdev->dev, - "error: DSP trace buffer overflow %u bytes. Total messages %d\n", - posn->overflow, posn->messages); - - return 0; -} - -/* an error has occurred within the DSP that prevents further trace */ -void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev) -{ - if (!sdev->dtrace_is_supported) - return; - - if (sdev->dtrace_is_enabled) { - sdev->dtrace_error = true; - wake_up(&sdev->trace_sleep); - } -} -EXPORT_SYMBOL(snd_sof_trace_notify_for_error); - -void snd_sof_release_trace(struct snd_sof_dev *sdev) -{ - int ret; - - if (!sdev->dtrace_is_supported || !sdev->dtrace_is_enabled) - return; - - ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); - if (ret < 0) - dev_err(sdev->dev, - "error: snd_sof_dma_trace_trigger: stop: %d\n", ret); - - ret = snd_sof_dma_trace_release(sdev); - if (ret < 0) - dev_err(sdev->dev, - "error: fail in snd_sof_dma_trace_release %d\n", ret); - - sdev->dtrace_is_enabled = false; - sdev->dtrace_draining = true; - wake_up(&sdev->trace_sleep); -} -EXPORT_SYMBOL(snd_sof_release_trace); - -void snd_sof_free_trace(struct snd_sof_dev *sdev) -{ - if (!sdev->dtrace_is_supported) - return; - - snd_sof_release_trace(sdev); - - if (sdev->dma_trace_pages) { - snd_dma_free_pages(&sdev->dmatb); - snd_dma_free_pages(&sdev->dmatp); - sdev->dma_trace_pages = 0; - } -} -EXPORT_SYMBOL(snd_sof_free_trace);