diff options
Diffstat (limited to 'sound/soc')
-rw-r--r-- | sound/soc/sof/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/sof/amd/acp-trace.c | 4 | ||||
-rw-r--r-- | sound/soc/sof/amd/acp.h | 2 | ||||
-rw-r--r-- | sound/soc/sof/core.c | 13 | ||||
-rw-r--r-- | sound/soc/sof/debug.c | 2 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda-dsp.c | 2 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda-trace.c | 4 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda.h | 2 | ||||
-rw-r--r-- | sound/soc/sof/ipc.c | 6 | ||||
-rw-r--r-- | sound/soc/sof/ipc3-dtrace.c | 649 | ||||
-rw-r--r-- | sound/soc/sof/ipc3-priv.h | 38 | ||||
-rw-r--r-- | sound/soc/sof/ipc3.c | 3 | ||||
-rw-r--r-- | sound/soc/sof/ops.c | 2 | ||||
-rw-r--r-- | sound/soc/sof/ops.h | 26 | ||||
-rw-r--r-- | sound/soc/sof/pm.c | 8 | ||||
-rw-r--r-- | sound/soc/sof/sof-priv.h | 53 | ||||
-rw-r--r-- | sound/soc/sof/trace.c | 621 |
17 files changed, 767 insertions, 669 deletions
diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 8a79f03207fe..92b5e83601be 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -3,6 +3,7 @@ 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\ ipc3-topology.o ipc3-control.o ipc3.o ipc3-pcm.o ipc3-loader.o\ + ipc3-dtrace.o\ ipc4.o ipc4-loader.o ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),) snd-sof-objs += sof-client.o diff --git a/sound/soc/sof/amd/acp-trace.c b/sound/soc/sof/amd/acp-trace.c index 903b6cc3dda3..c9482b27cbe3 100644 --- a/sound/soc/sof/amd/acp-trace.c +++ b/sound/soc/sof/amd/acp-trace.c @@ -34,7 +34,7 @@ int acp_sof_trace_release(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(acp_sof_trace_release, SND_SOC_SOF_AMD_COMMON); -int acp_sof_trace_init(struct snd_sof_dev *sdev, +int acp_sof_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, struct sof_ipc_dma_trace_params_ext *dtrace_params) { struct acp_dsp_stream *stream; @@ -46,7 +46,7 @@ int acp_sof_trace_init(struct snd_sof_dev *sdev, if (!stream) return -ENODEV; - stream->dmab = &sdev->dmatb; + stream->dmab = dmab; stream->num_pages = NUM_PAGES; ret = acp_dsp_stream_config(sdev, stream); diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index de526a1bce13..291b44c54bcc 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -212,7 +212,7 @@ extern struct snd_sof_dsp_ops sof_renoir_ops; int snd_amd_acp_find_config(struct pci_dev *pci); /* Trace */ -int acp_sof_trace_init(struct snd_sof_dev *sdev, +int acp_sof_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, struct sof_ipc_dma_trace_params_ext *dtrace_params); int acp_sof_trace_release(struct snd_sof_dev *sdev); diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 2d12e8bab769..53719c04658f 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -250,14 +250,13 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) } if (sof_debug_check_flag(SOF_DBG_ENABLE_TRACE)) { - sdev->dtrace_is_supported = true; + sdev->fw_trace_is_supported = true; - /* init DMA trace */ - ret = snd_sof_init_trace(sdev); + /* init firmware tracing */ + ret = sof_fw_trace_init(sdev); if (ret < 0) { /* non fatal */ - dev_warn(sdev->dev, - "warning: failed to initialize trace %d\n", + dev_warn(sdev->dev, "failed to initialize firmware tracing %d\n", ret); } } else { @@ -308,7 +307,7 @@ 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); + sof_fw_trace_free(sdev); fw_run_err: snd_sof_fw_unload(sdev); fw_load_err: @@ -447,7 +446,7 @@ int snd_sof_device_remove(struct device *dev) snd_sof_machine_unregister(sdev, pdata); if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { - snd_sof_free_trace(sdev); + sof_fw_trace_free(sdev); ret = snd_sof_dsp_power_down_notify(sdev); if (ret < 0) dev_warn(dev, "error: %d failed to prepare DSP for device removal", diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index 54d3643b46ad..cf1271eb29b2 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -443,6 +443,6 @@ void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev) snd_sof_ipc_dump(sdev); snd_sof_dsp_dbg_dump(sdev, "Firmware exception", SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); - snd_sof_trace_notify_for_error(sdev); + sof_fw_trace_fw_crashed(sdev); } EXPORT_SYMBOL(snd_sof_handle_fw_exception); diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index c068a3f2f6df..000ea906670c 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -432,7 +432,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->fw_trace_is_supported || !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 755ef1d835e0..cbb9bd7770e6 100644 --- a/sound/soc/sof/intel/hda-trace.c +++ b/sound/soc/sof/intel/hda-trace.c @@ -36,7 +36,7 @@ static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev, struct snd_dma_buffer return ret; } -int hda_dsp_trace_init(struct snd_sof_dev *sdev, +int hda_dsp_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, struct sof_ipc_dma_trace_params_ext *dtrace_params) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; @@ -57,7 +57,7 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, * 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); hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 535791c7d187..3e0f7b0c586a 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -658,7 +658,7 @@ static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; } /* * Trace Control. */ -int hda_dsp_trace_init(struct snd_sof_dev *sdev, +int hda_dsp_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, struct sof_ipc_dma_trace_params_ext *dtrace_params); int hda_dsp_trace_release(struct snd_sof_dev *sdev); int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd); diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 41f3a217be5d..c5aef5fc056b 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -184,6 +184,12 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) return NULL; } + if (ops->fw_tracing && (!ops->fw_tracing->init || !ops->fw_tracing->suspend || + !ops->fw_tracing->resume)) { + dev_err(sdev->dev, "Missing firmware tracing ops\n"); + return NULL; + } + return ipc; } EXPORT_SYMBOL(snd_sof_ipc_init); diff --git a/sound/soc/sof/ipc3-dtrace.c b/sound/soc/sof/ipc3-dtrace.c new file mode 100644 index 000000000000..b4e1343f9138 --- /dev/null +++ b/sound/soc/sof/ipc3-dtrace.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + +#include <linux/debugfs.h> +#include <linux/sched/signal.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ops.h" +#include "sof-utils.h" +#include "ipc3-priv.h" + +#define TRACE_FILTER_ELEMENTS_PER_ENTRY 4 +#define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024 + +enum sof_dtrace_state { + SOF_DTRACE_DISABLED, + SOF_DTRACE_STOPPED, + SOF_DTRACE_ENABLED, +}; + +struct sof_dtrace_priv { + 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_error; + bool dtrace_draining; + enum sof_dtrace_state dtrace_state; +}; + +static int trace_filter_append_elem(struct snd_sof_dev *sdev, 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 snd_sof_dev *sdev, const char *line, + struct sof_ipc_trace_filter_elem *elem, + int capacity, int *counter) +{ + int log_level, pipe_id, comp_id, read, ret; + 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(sdev->dev, "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, + "Parsing filter entry '%s' failed with %d\n", + entry, entry_len); + return -EINVAL; + } + } + + *out_elem_cnt = cnt; + + return 0; +} + +static int ipc3_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, "enabling device failed: %d\n", ret); + goto error; + } + ret = sof_ipc_tx_message(sdev->ipc, 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 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) + goto error; + + if (num_elems) { + ret = ipc3_trace_update_filter(sdev, num_elems, elems); + if (ret < 0) { + dev_err(sdev->dev, "Filter update failed: %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 = dfsentry_trace_filter_write, + .llseek = default_llseek, +}; + +static int debugfs_create_trace_filter(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_dtrace_avail(struct snd_sof_dev *sdev, + loff_t pos, size_t buffer_size) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_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_dtrace_avail(struct snd_sof_dev *sdev, loff_t pos, + size_t buffer_size) +{ + size_t ret = sof_dtrace_avail(sdev, pos, buffer_size); + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + wait_queue_entry_t wait; + + /* data immediately available */ + if (ret) + return ret; + + if (priv->dtrace_state != SOF_DTRACE_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->trace_sleep, &wait); + + if (!signal_pending(current)) { + /* set timeout to max value, no error code */ + schedule_timeout(MAX_SCHEDULE_TIMEOUT); + } + remove_wait_queue(&priv->trace_sleep, &wait); + + return sof_dtrace_avail(sdev, pos, buffer_size); +} + +static ssize_t dfsentry_dtrace_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 sof_dtrace_priv *priv = sdev->fw_trace_data; + 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 */ + 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_dtrace_avail(sdev, lpos, buffer_size); + if (priv->dtrace_error) { + dev_err(sdev->dev, "trace IO error\n"); + return -EIO; + } + + /* make sure count is <= avail */ + if (count > avail) + count = avail; + + /* + * make sure that all trace data is available for the CPU as the trace + * data buffer might be allocated from non consistent memory. + * Note: snd_dma_buffer_sync() is called for normal audio playback and + * capture streams also. + */ + snd_dma_buffer_sync(&priv->dmatb, SNDRV_DMA_SYNC_CPU); + /* 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 dfsentry_dtrace_release(struct inode *inode, struct file *file) +{ + struct snd_sof_dfsentry *dfse = inode->i_private; + struct snd_sof_dev *sdev = dfse->sdev; + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + /* avoid duplicate traces at next open */ + if (priv->dtrace_state != SOF_DTRACE_ENABLED) + priv->host_offset = 0; + + return 0; +} + +static const struct file_operations sof_dfs_dtrace_fops = { + .open = simple_open, + .read = dfsentry_dtrace_read, + .llseek = default_llseek, + .release = dfsentry_dtrace_release, +}; + +static int debugfs_create_dtrace(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv; + struct snd_sof_dfsentry *dfse; + int ret; + + if (!sdev) + return -EINVAL; + + priv = sdev->fw_trace_data; + + ret = debugfs_create_trace_filter(sdev); + if (ret < 0) + dev_warn(sdev->dev, "failed to create filter debugfs file: %d", ret); + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->buf = priv->dmatb.area; + dfse->size = priv->dmatb.bytes; + dfse->sdev = sdev; + + debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse, + &sof_dfs_dtrace_fops); + + return 0; +} + +static int ipc3_dtrace_enable(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + 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->fw_trace_is_supported) + return 0; + + if (priv->dtrace_state == SOF_DTRACE_ENABLED || !priv->dma_trace_pages) + return -EINVAL; + + if (priv->dtrace_state == SOF_DTRACE_STOPPED) + goto start; + + /* 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->dma_trace_pages; + params.stream_tag = 0; + + priv->host_offset = 0; + priv->dtrace_draining = false; + + ret = sof_dtrace_host_init(sdev, &priv->dmatb, ¶ms); + if (ret < 0) { + dev_err(sdev->dev, "Host dtrace init failed: %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, ¶ms, sizeof(params), &ipc_reply, sizeof(ipc_reply)); + if (ret < 0) { + dev_err(sdev->dev, "can't set params for DMA for trace %d\n", ret); + goto trace_release; + } + +start: + ret = sof_dtrace_host_trigger(sdev, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(sdev->dev, "Host dtrace trigger start failed: %d\n", ret); + goto trace_release; + } + + priv->dtrace_state = SOF_DTRACE_ENABLED; + + return 0; + +trace_release: + sof_dtrace_host_release(sdev); + return ret; +} + +static int ipc3_dtrace_init(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv; + int ret; + + /* dtrace is only supported with SOF_IPC */ + if (sdev->pdata->ipc_type != SOF_IPC) + return -EOPNOTSUPP; + + if (sdev->fw_trace_data) { + dev_err(sdev->dev, "fw_trace_data has been already allocated\n"); + return -EBUSY; + } + + priv = devm_kzalloc(sdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->fw_trace_data = priv; + + /* set false before start initialization */ + priv->dtrace_state = SOF_DTRACE_DISABLED; + + /* allocate trace page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, + PAGE_SIZE, &priv->dmatp); + if (ret < 0) { + dev_err(sdev->dev, "can't alloc page table for trace %d\n", ret); + return ret; + } + + /* allocate trace data buffer */ + ret = snd_dma_alloc_dir_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + DMA_FROM_DEVICE, DMA_BUF_SIZE_FOR_TRACE, + &priv->dmatb); + if (ret < 0) { + dev_err(sdev->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(sdev->dev, &priv->dmatb, + priv->dmatp.area, priv->dmatb.bytes); + if (ret < 0) + goto table_err; + + priv->dma_trace_pages = ret; + dev_dbg(sdev->dev, "%s: dma_trace_pages: %d\n", __func__, + priv->dma_trace_pages); + + if (sdev->first_boot) { + ret = debugfs_create_dtrace(sdev); + if (ret < 0) + goto table_err; + } + + init_waitqueue_head(&priv->trace_sleep); + + ret = ipc3_dtrace_enable(sdev); + if (ret < 0) + goto table_err; + + return 0; +table_err: + priv->dma_trace_pages = 0; + snd_dma_free_pages(&priv->dmatb); +page_err: + snd_dma_free_pages(&priv->dmatp); + return ret; +} + +int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + if (!sdev->fw_trace_is_supported) + return 0; + + if (priv->dtrace_state == SOF_DTRACE_ENABLED && + priv->host_offset != posn->host_offset) { + priv->host_offset = posn->host_offset; + wake_up(&priv->trace_sleep); + } + + if (posn->overflow != 0) + dev_err(sdev->dev, + "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 */ +static void ipc3_dtrace_fw_crashed(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + if (priv->dtrace_state == SOF_DTRACE_ENABLED) { + priv->dtrace_error = true; + wake_up(&priv->trace_sleep); + } +} + +static void ipc3_dtrace_release(struct snd_sof_dev *sdev, bool only_stop) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_reply ipc_reply; + int ret; + + if (!sdev->fw_trace_is_supported || priv->dtrace_state == SOF_DTRACE_DISABLED) + return; + + ret = sof_dtrace_host_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); + if (ret < 0) + dev_err(sdev->dev, "Host dtrace trigger stop failed: %d\n", ret); + priv->dtrace_state = SOF_DTRACE_STOPPED; + + /* + * stop and free trace DMA in the DSP. TRACE_DMA_FREE is only supported from + * ABI 3.20.0 onwards + */ + if (v->abi_version >= SOF_ABI_VER(3, 20, 0)) { + hdr.size = sizeof(hdr); + hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_FREE; + + ret = sof_ipc_tx_message(sdev->ipc, &hdr, hdr.size, + &ipc_reply, sizeof(ipc_reply)); + if (ret < 0) + dev_err(sdev->dev, "DMA_TRACE_FREE failed with error: %d\n", ret); + } + + if (only_stop) + goto out; + + ret = sof_dtrace_host_release(sdev); + if (ret < 0) + dev_err(sdev->dev, "Host dtrace release failed %d\n", ret); + + priv->dtrace_state = SOF_DTRACE_DISABLED; + +out: + priv->dtrace_draining = true; + wake_up(&priv->trace_sleep); +} + +static void ipc3_dtrace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state) +{ + ipc3_dtrace_release(sdev, pm_state.event == SOF_DSP_PM_D0); +} + +static int ipc3_dtrace_resume(struct snd_sof_dev *sdev) +{ + return ipc3_dtrace_enable(sdev); +} + +static void ipc3_dtrace_free(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + /* release trace */ + ipc3_dtrace_release(sdev, false); + + if (priv->dma_trace_pages) { + snd_dma_free_pages(&priv->dmatb); + snd_dma_free_pages(&priv->dmatp); + priv->dma_trace_pages = 0; + } +} + +const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops = { + .init = ipc3_dtrace_init, + .free = ipc3_dtrace_free, + .fw_crashed = ipc3_dtrace_fw_crashed, + .suspend = ipc3_dtrace_suspend, + .resume = ipc3_dtrace_resume, +}; diff --git a/sound/soc/sof/ipc3-priv.h b/sound/soc/sof/ipc3-priv.h index 82f9d0cbfb93..f5044202f3c5 100644 --- a/sound/soc/sof/ipc3-priv.h +++ b/sound/soc/sof/ipc3-priv.h @@ -16,6 +16,7 @@ extern const struct sof_ipc_pcm_ops ipc3_pcm_ops; extern const struct sof_ipc_tplg_ops ipc3_tplg_ops; extern const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops; extern const struct sof_ipc_fw_loader_ops ipc3_loader_ops; +extern const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops; /* helpers for fw_ready and ext_manifest parsing */ int sof_ipc3_get_ext_windows(struct snd_sof_dev *sdev, @@ -24,4 +25,41 @@ int sof_ipc3_get_cc_info(struct snd_sof_dev *sdev, const struct sof_ipc_ext_data_hdr *ext_hdr); int sof_ipc3_validate_fw_version(struct snd_sof_dev *sdev); +/* dtrace position update */ +int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn); + +/* dtrace platform callback wrappers */ +static inline int sof_dtrace_host_init(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmatb, + struct sof_ipc_dma_trace_params_ext *dtrace_params) +{ + struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops; + + if (dsp_ops->trace_init) + return dsp_ops->trace_init(sdev, dmatb, dtrace_params); + + return 0; +} + +static inline int sof_dtrace_host_release(struct snd_sof_dev *sdev) +{ + struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops; + + if (dsp_ops->trace_release) + return dsp_ops->trace_release(sdev); + + return 0; +} + +static inline int sof_dtrace_host_trigger(struct snd_sof_dev *sdev, int cmd) +{ + struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops; + + if (dsp_ops->trace_trigger) + return dsp_ops->trace_trigger(sdev, cmd); + + return 0; +} + #endif diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c index a8ffc4f99565..dff5feaad370 100644 --- a/sound/soc/sof/ipc3.c +++ b/sound/soc/sof/ipc3.c @@ -946,7 +946,7 @@ static void ipc3_trace_message(struct snd_sof_dev *sdev, void *msg_buf) switch (msg_type) { case SOF_IPC_TRACE_DMA_POSITION: - snd_sof_trace_update_pos(sdev, msg_buf); + ipc3_dtrace_posn_update(sdev, msg_buf); break; default: dev_err(sdev->dev, "unhandled trace message %#x\n", msg_type); @@ -1070,6 +1070,7 @@ const struct sof_ipc_ops ipc3_ops = { .pm = &ipc3_pm_ops, .pcm = &ipc3_pcm_ops, .fw_loader = &ipc3_loader_ops, + .fw_tracing = &ipc3_dtrace_ops, .tx_msg = sof_ipc3_tx_msg, .rx_msg = sof_ipc3_rx_msg, diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c index 235e2ef72178..ff066de4ceb9 100644 --- a/sound/soc/sof/ops.c +++ b/sound/soc/sof/ops.c @@ -177,7 +177,7 @@ void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset, bool non_recoverabl snd_sof_dsp_dbg_dump(sdev, "DSP panic!", SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); sof_set_fw_state(sdev, SOF_FW_CRASHED); - snd_sof_trace_notify_for_error(sdev); + sof_fw_trace_fw_crashed(sdev); } else { snd_sof_dsp_dbg_dump(sdev, "DSP panic (recovery will be attempted)", diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index aa64e3bd645f..b79ae4f66eba 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -375,32 +375,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, - struct sof_ipc_dma_trace_params_ext *dtrace_params) -{ - if (sof_ops(sdev)->trace_init) - return sof_ops(sdev)->trace_init(sdev, dtrace_params); - - 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 fa3f5514c00f..18eb327a57f0 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -107,7 +107,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) */ if (!runtime_resume && sof_ops(sdev)->set_power_state && old_state == SOF_DSP_PM_D0) { - ret = snd_sof_trace_resume(sdev); + ret = sof_fw_trace_resume(sdev); if (ret < 0) /* non fatal */ dev_warn(sdev->dev, @@ -143,7 +143,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) } /* resume DMA trace */ - ret = snd_sof_trace_resume(sdev); + ret = sof_fw_trace_resume(sdev); if (ret < 0) { /* non fatal */ dev_warn(sdev->dev, @@ -208,7 +208,7 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) /* Skip to platform-specific suspend if DSP is entering D0 */ if (target_state == SOF_DSP_PM_D0) { - snd_sof_trace_suspend(sdev, pm_state); + sof_fw_trace_suspend(sdev, pm_state); /* Notify clients not managed by pm framework about core suspend */ sof_suspend_clients(sdev, pm_state); goto suspend; @@ -218,7 +218,7 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) tplg_ops->tear_down_all_pipelines(sdev, false); /* suspend DMA trace */ - snd_sof_trace_suspend(sdev, pm_state); + sof_fw_trace_suspend(sdev, pm_state); /* Notify clients not managed by pm framework about core suspend */ sof_suspend_clients(sdev, pm_state); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 8ea196305e4b..9d7f53ff9c70 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -254,8 +254,9 @@ struct snd_sof_dsp_ops { size_t size, const char *name, enum sof_debugfs_access_type access_type); /* optional */ - /* host DMA trace initialization */ + /* host DMA trace (IPC3) */ int (*trace_init)(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmatb, struct sof_ipc_dma_trace_params_ext *dtrace_params); /* optional */ int (*trace_release)(struct snd_sof_dev *sdev); /* optional */ int (*trace_trigger)(struct snd_sof_dev *sdev, @@ -358,6 +359,22 @@ struct snd_sof_ipc_msg { }; /** + * struct sof_ipc_fw_tracing_ops - IPC-specific firmware tracing ops + * @init: Function pointer for initialization of the tracing + * @free: Optional function pointer for freeing of the tracing + * @fw_crashed: Optional function pointer to notify the tracing of a firmware crash + * @suspend: Function pointer for system/runtime suspend + * @resume: Function pointer for system/runtime resume + */ +struct sof_ipc_fw_tracing_ops { + int (*init)(struct snd_sof_dev *sdev); + void (*free)(struct snd_sof_dev *sdev); + void (*fw_crashed)(struct snd_sof_dev *sdev); + void (*suspend)(struct snd_sof_dev *sdev, pm_message_t pm_state); + int (*resume)(struct snd_sof_dev *sdev); +}; + +/** * struct sof_ipc_pm_ops - IPC-specific PM ops * @ctx_save: Function pointer for context save * @ctx_restore: Function pointer for context restore @@ -395,6 +412,7 @@ struct sof_ipc_pcm_ops; * @pm: Pointer to PM ops * @pcm: Pointer to PCM ops * @fw_loader: Pointer to Firmware Loader ops + * @fw_tracing: Pointer to Firmware tracing ops * * @tx_msg: Function pointer for sending a 'short' IPC message * @set_get_data: Function pointer for set/get data ('large' IPC message). This @@ -415,6 +433,7 @@ struct sof_ipc_ops { const struct sof_ipc_pm_ops *pm; const struct sof_ipc_pcm_ops *pcm; const struct sof_ipc_fw_loader_ops *fw_loader; + const struct sof_ipc_fw_tracing_ops *fw_tracing; int (*tx_msg)(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes, bool no_pm); @@ -442,12 +461,6 @@ struct snd_sof_ipc { const struct sof_ipc_ops *ops; }; -enum sof_dtrace_state { - SOF_DTRACE_DISABLED, - SOF_DTRACE_STOPPED, - SOF_DTRACE_ENABLED, -}; - /* * SOF Device Level. */ @@ -528,16 +541,9 @@ 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_error; - bool dtrace_draining; - enum sof_dtrace_state dtrace_state; + /* firmwre tracing */ + bool fw_trace_is_supported; /* set with Kconfig or module parameter */ + void *fw_trace_data; /* private data used by firmware tracing implementation */ bool msi_enabled; @@ -640,27 +646,26 @@ static inline void snd_sof_ipc_process_reply(struct snd_sof_dev *sdev, u32 msg_i /* * Trace/debug */ -int snd_sof_init_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 sof_print_oops_and_stack(struct snd_sof_dev *sdev, const char *level, u32 panic_code, u32 tracep_code, void *oops, struct sof_ipc_panic_info *panic_info, void *stack, size_t stack_words); -void snd_sof_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state); -int snd_sof_trace_resume(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, enum snd_sof_fw_blk_type blk_type, u32 offset, size_t size, const char *name, enum sof_debugfs_access_type access_type); +/* Firmware tracing */ +int sof_fw_trace_init(struct snd_sof_dev *sdev); +void sof_fw_trace_free(struct snd_sof_dev *sdev); +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); /* * DSP Architectures. diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c index 5d171bf8a5ea..6f662642d611 100644 --- a/sound/soc/sof/trace.c +++ b/sound/soc/sof/trace.c @@ -1,626 +1,51 @@ -// 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 <liam.r.girdwood@linux.intel.com> +// SPDX-License-Identifier: GPL-2.0-only // +// Copyright(c) 2022 Intel Corporation. All rights reserved. -#include <linux/debugfs.h> -#include <linux/sched/signal.h> #include "sof-priv.h" -#include "sof-audio.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, 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_state != SOF_DTRACE_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; - - /* - * make sure that all trace data is available for the CPU as the trace - * data buffer might be allocated from non consistent memory. - * Note: snd_dma_buffer_sync() is called for normal audio playback and - * capture streams also. - */ - snd_dma_buffer_sync(&sdev->dmatb, SNDRV_DMA_SYNC_CPU); - /* 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_state != SOF_DTRACE_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; -} - -static int snd_sof_enable_trace(struct snd_sof_dev *sdev) +int sof_fw_trace_init(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->ipc->ops->fw_tracing) { + dev_info(sdev->dev, "Firmware tracing is not available\n"); + sdev->fw_trace_is_supported = false; - if (!sdev->dtrace_is_supported) return 0; - - if (sdev->dtrace_state == SOF_DTRACE_ENABLED || !sdev->dma_trace_pages) - return -EINVAL; - - if (sdev->dtrace_state == SOF_DTRACE_STOPPED) - goto start; - - /* 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); - 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, ¶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; - } - -start: - 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_state = SOF_DTRACE_ENABLED; - - return 0; - -trace_release: - snd_sof_dma_trace_release(sdev); - return ret; + return sdev->ipc->ops->fw_tracing->init(sdev); } -int snd_sof_init_trace(struct snd_sof_dev *sdev) +void sof_fw_trace_free(struct snd_sof_dev *sdev) { - int ret; - - /* dtrace is only supported with SOF_IPC */ - if (sdev->pdata->ipc_type != SOF_IPC) - sdev->dtrace_is_supported = false; - - if (!sdev->dtrace_is_supported) - return 0; - - /* set false before start initialization */ - sdev->dtrace_state = SOF_DTRACE_DISABLED; - - /* 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_dir_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev, - DMA_FROM_DEVICE, 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_enable_trace(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_state == SOF_DTRACE_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) + if (!sdev->fw_trace_is_supported || !sdev->ipc->ops->fw_tracing) return; - if (sdev->dtrace_state == SOF_DTRACE_ENABLED) { - sdev->dtrace_error = true; - wake_up(&sdev->trace_sleep); - } + if (sdev->ipc->ops->fw_tracing->free) + sdev->ipc->ops->fw_tracing->free(sdev); } -EXPORT_SYMBOL(snd_sof_trace_notify_for_error); -static void snd_sof_release_trace(struct snd_sof_dev *sdev, bool only_stop) +void sof_fw_trace_fw_crashed(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_cmd_hdr hdr; - struct sof_ipc_reply ipc_reply; - int ret; - - if (!sdev->dtrace_is_supported || sdev->dtrace_state == SOF_DTRACE_DISABLED) + if (!sdev->fw_trace_is_supported) 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); - sdev->dtrace_state = SOF_DTRACE_STOPPED; - - /* - * stop and free trace DMA in the DSP. TRACE_DMA_FREE is only supported from - * ABI 3.20.0 onwards - */ - if (v->abi_version >= SOF_ABI_VER(3, 20, 0)) { - hdr.size = sizeof(hdr); - hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_FREE; - - ret = sof_ipc_tx_message(sdev->ipc, &hdr, hdr.size, - &ipc_reply, sizeof(ipc_reply)); - if (ret < 0) - dev_err(sdev->dev, "DMA_TRACE_FREE failed with error: %d\n", ret); - } - - if (only_stop) - goto out; - - 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_state = SOF_DTRACE_DISABLED; - -out: - sdev->dtrace_draining = true; - wake_up(&sdev->trace_sleep); + if (sdev->ipc->ops->fw_tracing->fw_crashed) + sdev->ipc->ops->fw_tracing->fw_crashed(sdev); } -void snd_sof_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state) +void sof_fw_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state) { - snd_sof_release_trace(sdev, pm_state.event == SOF_DSP_PM_D0); -} -EXPORT_SYMBOL(snd_sof_trace_suspend); + if (!sdev->fw_trace_is_supported) + return; -int snd_sof_trace_resume(struct snd_sof_dev *sdev) -{ - return snd_sof_enable_trace(sdev); + sdev->ipc->ops->fw_tracing->suspend(sdev, pm_state); } -EXPORT_SYMBOL(snd_sof_trace_resume); -void snd_sof_free_trace(struct snd_sof_dev *sdev) +int sof_fw_trace_resume(struct snd_sof_dev *sdev) { - if (!sdev->dtrace_is_supported) - return; - - /* release trace */ - snd_sof_release_trace(sdev, false); + if (!sdev->fw_trace_is_supported) + return 0; - if (sdev->dma_trace_pages) { - snd_dma_free_pages(&sdev->dmatb); - snd_dma_free_pages(&sdev->dmatp); - sdev->dma_trace_pages = 0; - } + return sdev->ipc->ops->fw_tracing->resume(sdev); } -EXPORT_SYMBOL(snd_sof_free_trace); |