[PATCH 0/9] ASoC: SOF: IPC client infrastructure
Hi,
The Linux SOF implementation is historically monolithic in a sense that all features accessible in the firmware can be used via the snd_sof_dev struct in one way or another.
Support for features can not be added or removed runtime and with the current way of things it is hard if not impossible to implement support for dynamic feature support when based on the firmware manifest we can easily enable/access independent modules with the SOF.
In order to be able to support such modularity this series introduces a small framework within SOF for client support using the Auxiliary bus.
Client drivers can be removed runtime and later re-loaded if needed without affecting the core's behaviour, but it is the core's and the platform's duty to create the Auxiliary devices usable in the platform and via the firmware.
There is still a need for SOF manifest update to convey information about features to really make the full dynamic client device creation.
The series will introduce the core SOF client support and converts the generic ipc flood test, ipc message injector and the probes (Intel HDA only) to a client driver.
Regards, Peter --- Peter Ujfalusi (8): ASoC: SOF: Drop unused DSP power states: D3_HOT and D3_COLD ASoC: SOF: Move the definition of enum sof_dsp_power_states to global header ASoC: SOF: ipc: Read and pass the whole message to handlers for IPC events ASoC: SOF: Split up utils.c into sof-utils and iomem-utils ASoC: SOF: Introduce IPC SOF client support ASoC: SOF: sof-client: Add support for clients not managed by pm framework ASoC: SOF: Convert the generic IPC message injector into SOF client ASoC: SOF: Convert the generic probe support to SOF client
Ranjani Sridharan (1): ASoC: SOF: Convert the generic IPC flood test into SOF client
include/sound/sof.h | 8 + sound/soc/sof/Kconfig | 33 +- sound/soc/sof/Makefile | 15 +- sound/soc/sof/core.c | 50 +- sound/soc/sof/debug.c | 565 -------------- sound/soc/sof/intel/Kconfig | 19 +- sound/soc/sof/intel/apl.c | 13 +- sound/soc/sof/intel/cnl.c | 13 +- sound/soc/sof/intel/hda-dai.c | 19 - sound/soc/sof/intel/hda-dsp.c | 6 - sound/soc/sof/intel/hda-probes.c | 72 +- sound/soc/sof/intel/hda.c | 10 + sound/soc/sof/intel/hda.h | 49 +- sound/soc/sof/intel/icl.c | 13 +- sound/soc/sof/intel/tgl.c | 13 +- sound/soc/sof/{utils.c => iomem-utils.c} | 61 +- sound/soc/sof/ipc.c | 88 ++- sound/soc/sof/ops.h | 43 - sound/soc/sof/pcm.c | 7 +- sound/soc/sof/pm.c | 13 +- sound/soc/sof/sof-client-ipc-flood-test.c | 396 ++++++++++ sound/soc/sof/sof-client-ipc-msg-injector.c | 192 +++++ sound/soc/sof/sof-client-probes.c | 821 ++++++++++++++++++++ sound/soc/sof/sof-client-probes.h | 31 + sound/soc/sof/sof-client.c | 469 +++++++++++ sound/soc/sof/sof-client.h | 67 ++ sound/soc/sof/sof-priv.h | 139 ++-- sound/soc/sof/sof-probes.c | 364 --------- sound/soc/sof/sof-probes.h | 38 - sound/soc/sof/sof-utils.c | 77 ++ sound/soc/sof/sof-utils.h | 19 + sound/soc/sof/trace.c | 1 + 32 files changed, 2415 insertions(+), 1309 deletions(-) rename sound/soc/sof/{utils.c => iomem-utils.c} (59%) create mode 100644 sound/soc/sof/sof-client-ipc-flood-test.c create mode 100644 sound/soc/sof/sof-client-ipc-msg-injector.c create mode 100644 sound/soc/sof/sof-client-probes.c create mode 100644 sound/soc/sof/sof-client-probes.h create mode 100644 sound/soc/sof/sof-client.c create mode 100644 sound/soc/sof/sof-client.h delete mode 100644 sound/soc/sof/sof-probes.c delete mode 100644 sound/soc/sof/sof-probes.h create mode 100644 sound/soc/sof/sof-utils.c create mode 100644 sound/soc/sof/sof-utils.h
The only reference to D3_HOT and D3_COLD DSP power state is in intel/hda-dsp.c in form of a dev_dbg() print.
Remove them as they are not used and even if they are they could be re-added via the substate.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/intel/hda-dsp.c | 6 ------ sound/soc/sof/sof-priv.h | 2 -- 2 files changed, 8 deletions(-)
diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 0fe522549c91..8ddde60c56b3 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -498,15 +498,9 @@ static void hda_dsp_state_log(struct snd_sof_dev *sdev) case SOF_DSP_PM_D2: dev_dbg(sdev->dev, "Current DSP power state: D2\n"); break; - case SOF_DSP_PM_D3_HOT: - dev_dbg(sdev->dev, "Current DSP power state: D3_HOT\n"); - break; case SOF_DSP_PM_D3: dev_dbg(sdev->dev, "Current DSP power state: D3\n"); break; - case SOF_DSP_PM_D3_COLD: - dev_dbg(sdev->dev, "Current DSP power state: D3_COLD\n"); - break; default: dev_dbg(sdev->dev, "Unknown DSP power state: %d\n", sdev->dsp_power_state.state); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index e48402ce4bdb..6358f8c84cce 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -84,9 +84,7 @@ enum sof_dsp_power_states { SOF_DSP_PM_D0, SOF_DSP_PM_D1, SOF_DSP_PM_D2, - SOF_DSP_PM_D3_HOT, SOF_DSP_PM_D3, - SOF_DSP_PM_D3_COLD, };
struct sof_dsp_power_state {
Move the enum sof_dsp_power_states to include/sound/sof.h to be accessible outside of the core SOF stack.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- include/sound/sof.h | 8 ++++++++ sound/soc/sof/sof-priv.h | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/include/sound/sof.h b/include/sound/sof.h index 813680ab9aad..7cdfc954df12 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -39,6 +39,14 @@ enum sof_fw_state { SOF_FW_CRASHED, };
+/* DSP power states */ +enum sof_dsp_power_states { + SOF_DSP_PM_D0, + SOF_DSP_PM_D1, + SOF_DSP_PM_D2, + SOF_DSP_PM_D3, +}; + /* * SOF Platform data. */ diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 6358f8c84cce..2e474048d708 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -79,14 +79,6 @@ bool sof_debug_check_flag(int mask); /* max number of DSP cores */ #define SOF_MAX_DSP_NUM_CORES 8
-/* DSP power state */ -enum sof_dsp_power_states { - SOF_DSP_PM_D0, - SOF_DSP_PM_D1, - SOF_DSP_PM_D2, - SOF_DSP_PM_D3, -}; - struct sof_dsp_power_state { u32 state; u32 substate; /* platform-specific */
Change the parameter list for the firmware initiated message (IPC event) handler functions to: handler(struct snd_sof_dev *sdev, void *full_msg);
Allocate memory and read the whole message in snd_sof_ipc_msgs_rx() then pass the pointer to the function handling the message. Do this only if we actually have a function which is tasked to process the given type.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/ipc.c | 85 +++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 41 deletions(-)
diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 16a0d7a059f3..ee56d4fa4053 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -18,8 +18,10 @@ #include "sof-audio.h" #include "ops.h"
-static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_type); -static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd); +typedef void (*ipc_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf); + +static void ipc_trace_message(struct snd_sof_dev *sdev, void *msg_buf); +static void ipc_stream_message(struct snd_sof_dev *sdev, void *msg_buf);
/* * IPC message Tx/Rx message handling. @@ -477,44 +479,30 @@ void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) } EXPORT_SYMBOL(snd_sof_ipc_reply);
-static void ipc_comp_notification(struct snd_sof_dev *sdev, - struct sof_ipc_cmd_hdr *hdr) +static void ipc_comp_notification(struct snd_sof_dev *sdev, void *msg_buf) { + struct sof_ipc_cmd_hdr *hdr = msg_buf; u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; - struct sof_ipc_ctrl_data *cdata; - int ret;
switch (msg_type) { case SOF_IPC_COMP_GET_VALUE: case SOF_IPC_COMP_GET_DATA: - cdata = kmalloc(hdr->size, GFP_KERNEL); - if (!cdata) - return; - - /* read back full message */ - ret = snd_sof_ipc_msg_data(sdev, NULL, cdata, hdr->size); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to read component event: %d\n", ret); - goto err; - } break; default: dev_err(sdev->dev, "error: unhandled component message %#x\n", msg_type); return; }
- snd_sof_control_notify(sdev, cdata); - -err: - kfree(cdata); + snd_sof_control_notify(sdev, msg_buf); }
/* DSP firmware has sent host a message */ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) { + ipc_rx_callback rx_callback = NULL; struct sof_ipc_cmd_hdr hdr; - u32 cmd, type; + void *msg_buf; + u32 cmd; int err;
/* read back header */ @@ -523,10 +511,15 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) dev_warn(sdev->dev, "failed to read IPC header: %d\n", err); return; } + + if (hdr.size < sizeof(hdr)) { + dev_err(sdev->dev, "The received message size is invalid\n"); + return; + } + ipc_log_header(sdev->dev, "ipc rx", hdr.cmd);
cmd = hdr.cmd & SOF_GLB_TYPE_MASK; - type = hdr.cmd & SOF_CMD_TYPE_MASK;
/* check message type */ switch (cmd) { @@ -551,20 +544,35 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) case SOF_IPC_GLB_PM_MSG: break; case SOF_IPC_GLB_COMP_MSG: - ipc_comp_notification(sdev, &hdr); + rx_callback = ipc_comp_notification; break; case SOF_IPC_GLB_STREAM_MSG: - /* need to pass msg id into the function */ - ipc_stream_message(sdev, hdr.cmd); + rx_callback = ipc_stream_message; break; case SOF_IPC_GLB_TRACE_MSG: - ipc_trace_message(sdev, type); + rx_callback = ipc_trace_message; break; default: - dev_err(sdev->dev, "error: unknown DSP message 0x%x\n", cmd); + dev_err(sdev->dev, "%s: Unknown DSP message: 0x%x\n", __func__, cmd); break; }
+ if (rx_callback) { + /* read the full message as we have rx handler for it */ + msg_buf = kmalloc(hdr.size, GFP_KERNEL); + if (!msg_buf) + return; + + err = snd_sof_ipc_msg_data(sdev, NULL, msg_buf, hdr.size); + if (err < 0) + dev_err(sdev->dev, "%s: Failed to read message: %d\n", + __func__, err); + else + rx_callback(sdev, msg_buf); + + kfree(msg_buf); + } + ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd); } EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); @@ -573,19 +581,14 @@ EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); * IPC trace mechanism. */
-static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_type) +static void ipc_trace_message(struct snd_sof_dev *sdev, void *msg_buf) { - struct sof_ipc_dma_trace_posn posn; - int ret; + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK;
switch (msg_type) { case SOF_IPC_TRACE_DMA_POSITION: - /* read back full message */ - ret = snd_sof_ipc_msg_data(sdev, NULL, &posn, sizeof(posn)); - if (ret < 0) - dev_warn(sdev->dev, "failed to read trace position: %d\n", ret); - else - snd_sof_trace_update_pos(sdev, &posn); + snd_sof_trace_update_pos(sdev, msg_buf); break; default: dev_err(sdev->dev, "error: unhandled trace message %#x\n", msg_type); @@ -667,11 +670,11 @@ static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) }
/* stream notifications from DSP FW */ -static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd) +static void ipc_stream_message(struct snd_sof_dev *sdev, void *msg_buf) { - /* get msg cmd type and msd id */ - u32 msg_type = msg_cmd & SOF_CMD_TYPE_MASK; - u32 msg_id = SOF_IPC_MESSAGE_ID(msg_cmd); + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; + u32 msg_id = SOF_IPC_MESSAGE_ID(hdr->cmd);
switch (msg_type) { case SOF_IPC_STREAM_POSITION:
The utils.c contains wrappers and implementation for accessing iomem mapped regions and a single unrelated function to create a compressed page table from snd_dma_buffer for firmware use.
The latter is used by the PCM and the dma trace code and it needs to be moved to a generic source/header for the client conversion to be possible.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/Makefile | 5 +- sound/soc/sof/{utils.c => iomem-utils.c} | 61 +------------------ sound/soc/sof/pcm.c | 1 + sound/soc/sof/sof-priv.h | 4 -- sound/soc/sof/sof-utils.c | 77 ++++++++++++++++++++++++ sound/soc/sof/sof-utils.h | 19 ++++++ sound/soc/sof/trace.c | 1 + 7 files changed, 103 insertions(+), 65 deletions(-) rename sound/soc/sof/{utils.c => iomem-utils.c} (59%) create mode 100644 sound/soc/sof/sof-utils.c create mode 100644 sound/soc/sof/sof-utils.h
diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 964b429146be..4b9fccacc2b7 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 utils.o sof-audio.o stream-ipc.o + control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o
snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += sof-probes.o snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o @@ -12,9 +12,12 @@ snd-sof-of-objs := sof-of-dev.o
snd-sof-nocodec-objs := nocodec.o
+snd-sof-utils-objs := sof-utils.o + obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o
+obj-$(CONFIG_SND_SOC_SOF) += snd-sof-utils.o
obj-$(CONFIG_SND_SOC_SOF_ACPI_DEV) += snd-sof-acpi.o obj-$(CONFIG_SND_SOC_SOF_OF_DEV) += snd-sof-of.o diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/iomem-utils.c similarity index 59% rename from sound/soc/sof/utils.c rename to sound/soc/sof/iomem-utils.c index 66fa6602fb67..3f57f6cf6542 100644 --- a/sound/soc/sof/utils.c +++ b/sound/soc/sof/iomem-utils.c @@ -3,7 +3,7 @@ // 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. +// Copyright(c) 2018-2022 Intel Corporation. All rights reserved. // // Author: Keyon Jie yang.jie@linux.intel.com // @@ -125,62 +125,3 @@ int sof_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, return 0; } EXPORT_SYMBOL(sof_block_read); - -/* - * Generic buffer page table creation. - * Take the each physical page address and drop the least significant unused - * bits from each (based on PAGE_SIZE). Then pack valid page address bits - * into compressed page table. - */ - -int snd_sof_create_page_table(struct device *dev, - struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size) -{ - int i, pages; - - pages = snd_sgbuf_aligned_pages(size); - - dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n", - dmab->area, size, pages); - - for (i = 0; i < pages; i++) { - /* - * The number of valid address bits for each page is 20. - * idx determines the byte position within page_table - * where the current page's address is stored - * in the compressed page_table. - * This can be calculated by multiplying the page number by 2.5. - */ - u32 idx = (5 * i) >> 1; - u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; - u8 *pg_table; - - dev_vdbg(dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); - - pg_table = (u8 *)(page_table + idx); - - /* - * pagetable compression: - * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 - * ___________pfn 0__________ __________pfn 1___________ _pfn 2... - * .... .... .... .... .... .... .... .... .... .... .... - * It is created by: - * 1. set current location to 0, PFN index i to 0 - * 2. put pfn[i] at current location in Little Endian byte order - * 3. calculate an intermediate value as - * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) - * 4. put x at offset (current location + 2) in LE byte order - * 5. increment current location by 5 bytes, increment i by 2 - * 6. continue to (2) - */ - if (i & 1) - put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, - pg_table); - else - put_unaligned_le32(pfn, pg_table); - } - - return pages; -} -EXPORT_SYMBOL(snd_sof_create_page_table); diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 37fb8e6cd493..62cb61655761 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -19,6 +19,7 @@ #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) #include "sof-probes.h" #endif +#include "sof-utils.h"
/* Create DMA buffer page table for DSP */ static int create_page_table(struct snd_soc_component *component, diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 2e474048d708..27d2f3ca2f06 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -502,10 +502,6 @@ void snd_sof_complete(struct device *dev);
void snd_sof_new_platform_drv(struct snd_sof_dev *sdev);
-int snd_sof_create_page_table(struct device *dev, - struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size); - /* * Firmware loading. */ diff --git a/sound/soc/sof/sof-utils.c b/sound/soc/sof/sof-utils.c new file mode 100644 index 000000000000..a3300ecee062 --- /dev/null +++ b/sound/soc/sof/sof-utils.c @@ -0,0 +1,77 @@ +// 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-2022 Intel Corporation. All rights reserved. +// +// Author: Keyon Jie yang.jie@linux.intel.com +// + +#include <asm/unaligned.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/device.h> +#include <sound/memalloc.h> +#include <linux/module.h> +#include "sof-utils.h" + +/* + * Generic buffer page table creation. + * Take the each physical page address and drop the least significant unused + * bits from each (based on PAGE_SIZE). Then pack valid page address bits + * into compressed page table. + */ + +int snd_sof_create_page_table(struct device *dev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size) +{ + int i, pages; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n", + dmab->area, size, pages); + + for (i = 0; i < pages; i++) { + /* + * The number of valid address bits for each page is 20. + * idx determines the byte position within page_table + * where the current page's address is stored + * in the compressed page_table. + * This can be calculated by multiplying the page number by 2.5. + */ + u32 idx = (5 * i) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u8 *pg_table; + + dev_vdbg(dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u8 *)(page_table + idx); + + /* + * pagetable compression: + * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 + * ___________pfn 0__________ __________pfn 1___________ _pfn 2... + * .... .... .... .... .... .... .... .... .... .... .... + * It is created by: + * 1. set current location to 0, PFN index i to 0 + * 2. put pfn[i] at current location in Little Endian byte order + * 3. calculate an intermediate value as + * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) + * 4. put x at offset (current location + 2) in LE byte order + * 5. increment current location by 5 bytes, increment i by 2 + * 6. continue to (2) + */ + if (i & 1) + put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, + pg_table); + else + put_unaligned_le32(pfn, pg_table); + } + + return pages; +} +EXPORT_SYMBOL(snd_sof_create_page_table); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/sof-utils.h b/sound/soc/sof/sof-utils.h new file mode 100644 index 000000000000..6f902893807e --- /dev/null +++ b/sound/soc/sof/sof-utils.h @@ -0,0 +1,19 @@ +/* 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) 2022 Intel Corporation. All rights reserved. + */ + +#ifndef __SOC_SOF_UTILS_H +#define __SOC_SOF_UTILS_H + +struct snd_dma_buffer; +struct device; + +int snd_sof_create_page_table(struct device *dev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size); + +#endif diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c index 2335d0f06d42..104388c551cb 100644 --- a/sound/soc/sof/trace.c +++ b/sound/soc/sof/trace.c @@ -12,6 +12,7 @@ #include <linux/sched/signal.h> #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
A client in the SOF (Sound Open Firmware) context is a driver that needs to communicate with the DSP via IPC messages. The SOF core is responsible for serializing the IPC messages to the DSP from the different clients.
One example of an SOF client would be an IPC test client that floods the DSP with test IPC messages to validate if the serialization works as expected.
Multi-client support will also add the ability to split the existing audio cards into multiple ones, so as to e.g. to deal with HDMI with a dedicated client instead of adding HDMI to all cards.
This patch introduces descriptors for SOF client driver and SOF client device along with APIs for registering and unregistering a SOF client driver, sending IPCs from a client device and accessing the SOF core debugfs root entry.
Along with this, add a couple of new members to struct snd_sof_dev that will be used for maintaining the list of clients.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Co-developed-by: Fred Oh fred.oh@linux.intel.com Signed-off-by: Fred Oh fred.oh@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/Kconfig | 7 + sound/soc/sof/Makefile | 1 + sound/soc/sof/core.c | 43 ++++- sound/soc/sof/ipc.c | 25 +-- sound/soc/sof/sof-client.c | 340 +++++++++++++++++++++++++++++++++++++ sound/soc/sof/sof-client.h | 67 ++++++++ sound/soc/sof/sof-priv.h | 78 ++++++++- 7 files changed, 540 insertions(+), 21 deletions(-) create mode 100644 sound/soc/sof/sof-client.c create mode 100644 sound/soc/sof/sof-client.h
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 1a7d6cefd3b7..468d7c113dae 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -61,6 +61,13 @@ config SND_SOC_SOF_DEBUG_PROBES Say Y if you want to enable probes. If unsure, select "N".
+config SND_SOC_SOF_CLIENT + tristate + select AUXILIARY_BUS + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + config SND_SOC_SOF_DEVELOPER_SUPPORT bool "SOF developer options support" depends on EXPERT && SND_SOC_SOF diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 4b9fccacc2b7..38ce2fe376fa 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -2,6 +2,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 +snd-sof-$(CONFIG_SND_SOC_SOF_CLIENT) += sof-client.o
snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += sof-probes.o snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 8f32b5b12b3e..2aabaac9007f 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -122,6 +122,27 @@ void sof_print_oops_and_stack(struct snd_sof_dev *sdev, const char *level, } EXPORT_SYMBOL(sof_print_oops_and_stack);
+/* Helper to manage DSP state */ +void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state) +{ + if (sdev->fw_state == new_state) + return; + + dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state); + sdev->fw_state = new_state; + + switch (new_state) { + case SOF_FW_BOOT_NOT_STARTED: + case SOF_FW_BOOT_COMPLETE: + case SOF_FW_CRASHED: + sof_client_fw_state_dispatcher(sdev); + fallthrough; + default: + break; + } +} +EXPORT_SYMBOL(sof_set_fw_state); + /* * FW Boot State Transition Diagram * @@ -266,6 +287,12 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto fw_trace_err; }
+ ret = sof_register_clients(sdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to register clients %d\n", ret); + goto sof_machine_err; + } + /* * Some platforms in SOF, ex: BYT, may not have their platform PM * callbacks set. Increment the usage count so as to @@ -281,6 +308,8 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
return 0;
+sof_machine_err: + snd_sof_machine_unregister(sdev, plat_data); fw_trace_err: snd_sof_free_trace(sdev); fw_run_err: @@ -329,7 +358,6 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data)
sdev->pdata = plat_data; sdev->first_boot = true; - sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; #endif @@ -350,9 +378,14 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) INIT_LIST_HEAD(&sdev->widget_list); INIT_LIST_HEAD(&sdev->dai_list); INIT_LIST_HEAD(&sdev->route_list); + INIT_LIST_HEAD(&sdev->ipc_client_list); + INIT_LIST_HEAD(&sdev->ipc_rx_handler_list); + INIT_LIST_HEAD(&sdev->fw_state_handler_list); spin_lock_init(&sdev->ipc_lock); spin_lock_init(&sdev->hw_lock); mutex_init(&sdev->power_state_access); + mutex_init(&sdev->ipc_client_mutex); + mutex_init(&sdev->client_event_handler_mutex);
/* set default timeouts if none provided */ if (plat_data->desc->ipc_timeout == 0) @@ -364,6 +397,8 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) else sdev->boot_timeout = plat_data->desc->boot_timeout;
+ sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { INIT_WORK(&sdev->probe_work, sof_probe_work); schedule_work(&sdev->probe_work); @@ -391,6 +426,12 @@ int snd_sof_device_remove(struct device *dev) if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) cancel_work_sync(&sdev->probe_work);
+ /* + * Unregister any registered client device first before IPC and debugfs + * to allow client drivers to be removed cleanly + */ + sof_unregister_clients(sdev); + /* * Unregister machine driver. This will unbind the snd_card which * will remove the component driver and unload the topology diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index ee56d4fa4053..c729bb7bf8c8 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -557,22 +557,25 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) break; }
- if (rx_callback) { - /* read the full message as we have rx handler for it */ - msg_buf = kmalloc(hdr.size, GFP_KERNEL); - if (!msg_buf) - return; + /* read the full message */ + msg_buf = kmalloc(hdr.size, GFP_KERNEL); + if (!msg_buf) + return;
- err = snd_sof_ipc_msg_data(sdev, NULL, msg_buf, hdr.size); - if (err < 0) - dev_err(sdev->dev, "%s: Failed to read message: %d\n", - __func__, err); - else + err = snd_sof_ipc_msg_data(sdev, NULL, msg_buf, hdr.size); + if (err < 0) { + dev_err(sdev->dev, "%s: Failed to read message: %d\n", __func__, err); + } else { + /* Call local handler for the message */ + if (rx_callback) rx_callback(sdev, msg_buf);
- kfree(msg_buf); + /* Notify registered clients */ + sof_client_ipc_rx_dispatcher(sdev, msg_buf); }
+ kfree(msg_buf); + ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd); } EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c new file mode 100644 index 000000000000..6f747d051b59 --- /dev/null +++ b/sound/soc/sof/sof-client.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Ranjani Sridharan ranjani.sridharan@linux.intel.com +// Peter Ujfalusi peter.ujfalusi@linux.intel.com +// + +#include <linux/debugfs.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include "ops.h" +#include "sof-client.h" +#include "sof-priv.h" + +/** + * struct sof_ipc_event_entry - IPC client event description + * @ipc_msg_type: IPC msg type of the event the client is interested + * @cdev: sof_client_dev of the requesting client + * @callback: Callback function of the client + * @list: item in SOF core client event list + */ +struct sof_ipc_event_entry { + u32 ipc_msg_type; + struct sof_client_dev *cdev; + sof_client_event_callback callback; + struct list_head list; +}; + +/** + * struct sof_state_event_entry - DSP panic event subscription entry + * @cdev: sof_client_dev of the requesting client + * @callback: Callback function of the client + * @list: item in SOF core client event list + */ +struct sof_state_event_entry { + struct sof_client_dev *cdev; + sof_client_fw_state_callback callback; + struct list_head list; +}; + +static void sof_client_auxdev_release(struct device *dev) +{ + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + + kfree(cdev->auxdev.dev.platform_data); + kfree(cdev); +} + +static int sof_client_dev_add_data(struct sof_client_dev *cdev, const void *data, + size_t size) +{ + void *d = NULL; + + if (data) { + d = kmemdup(data, size, GFP_KERNEL); + if (!d) + return -ENOMEM; + } + + cdev->auxdev.dev.platform_data = d; + return 0; +} + +int sof_register_clients(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients) + return sof_ops(sdev)->register_ipc_clients(sdev); + + return 0; +} + +void sof_unregister_clients(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev) && sof_ops(sdev)->unregister_ipc_clients) + sof_ops(sdev)->unregister_ipc_clients(sdev); +} + +int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, + const void *data, size_t size) +{ + struct auxiliary_device *auxdev; + struct sof_client_dev *cdev; + int ret; + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + cdev->sdev = sdev; + auxdev = &cdev->auxdev; + auxdev->name = name; + auxdev->dev.parent = sdev->dev; + auxdev->dev.release = sof_client_auxdev_release; + auxdev->id = id; + + ret = sof_client_dev_add_data(cdev, data, size); + if (ret < 0) + goto err_dev_add_data; + + ret = auxiliary_device_init(auxdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to initialize client dev %s.%d\n", name, id); + goto err_dev_init; + } + + ret = auxiliary_device_add(&cdev->auxdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to add client dev %s.%d\n", name, id); + /* + * sof_client_auxdev_release() will be invoked to free up memory + * allocations through put_device() + */ + auxiliary_device_uninit(&cdev->auxdev); + return ret; + } + + /* add to list of SOF client devices */ + mutex_lock(&sdev->ipc_client_mutex); + list_add(&cdev->list, &sdev->ipc_client_list); + mutex_unlock(&sdev->ipc_client_mutex); + + return 0; + +err_dev_init: + kfree(cdev->auxdev.dev.platform_data); + +err_dev_add_data: + kfree(cdev); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(sof_client_dev_register, SND_SOC_SOF_CLIENT); + +void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id) +{ + struct sof_client_dev *cdev; + + mutex_lock(&sdev->ipc_client_mutex); + + /* + * sof_client_auxdev_release() will be invoked to free up memory + * allocations through put_device() + */ + list_for_each_entry(cdev, &sdev->ipc_client_list, list) { + if (!strcmp(cdev->auxdev.name, name) && cdev->auxdev.id == id) { + list_del(&cdev->list); + auxiliary_device_delete(&cdev->auxdev); + auxiliary_device_uninit(&cdev->auxdev); + break; + } + } + + mutex_unlock(&sdev->ipc_client_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_dev_unregister, SND_SOC_SOF_CLIENT); + +int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, + void *reply_data, size_t reply_bytes) +{ + struct sof_ipc_cmd_hdr *hdr = ipc_msg; + + return sof_ipc_tx_message(cdev->sdev->ipc, hdr->cmd, ipc_msg, hdr->size, + reply_data, reply_bytes); +} +EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, SND_SOC_SOF_CLIENT); + +struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev) +{ + return cdev->sdev->debugfs_root; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_debugfs_root, SND_SOC_SOF_CLIENT); + +/* DMA buffer allocation in client drivers must use the core SOF device */ +struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev) +{ + return cdev->sdev->dev; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_dma_dev, SND_SOC_SOF_CLIENT); + +const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return &sdev->fw_ready.version; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_version, SND_SOC_SOF_CLIENT); + +/* module refcount management of SOF core */ +int sof_client_core_module_get(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + if (!try_module_get(sdev->dev->driver->owner)) + return -ENODEV; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_core_module_get, SND_SOC_SOF_CLIENT); + +void sof_client_core_module_put(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + module_put(sdev->dev->driver->owner); +} +EXPORT_SYMBOL_NS_GPL(sof_client_core_module_put, SND_SOC_SOF_CLIENT); + +/* IPC event handling */ +void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf) +{ + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_GLB_TYPE_MASK; + struct sof_ipc_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) { + if (event->ipc_msg_type == msg_type) + event->callback(event->cdev, msg_buf); + } + + mutex_unlock(&sdev->client_event_handler_mutex); +} + +int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type, + sof_client_event_callback callback) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_ipc_event_entry *event; + + if (!callback || !(ipc_msg_type & SOF_GLB_TYPE_MASK)) + return -EINVAL; + + event = kmalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + event->ipc_msg_type = ipc_msg_type; + event->cdev = cdev; + event->callback = callback; + + /* add to list of SOF client devices */ + mutex_lock(&sdev->client_event_handler_mutex); + list_add(&event->list, &sdev->ipc_rx_handler_list); + mutex_unlock(&sdev->client_event_handler_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_register_ipc_rx_handler, SND_SOC_SOF_CLIENT); + +void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_ipc_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) { + if (event->cdev == cdev && event->ipc_msg_type == ipc_msg_type) { + list_del(&event->list); + kfree(event); + break; + } + } + + mutex_unlock(&sdev->client_event_handler_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_unregister_ipc_rx_handler, SND_SOC_SOF_CLIENT); + +/*DSP state notification and query */ +void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev) +{ + struct sof_state_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->fw_state_handler_list, list) + event->callback(event->cdev, sdev->fw_state); + + mutex_unlock(&sdev->client_event_handler_mutex); +} + +int sof_client_register_fw_state_handler(struct sof_client_dev *cdev, + sof_client_fw_state_callback callback) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_state_event_entry *event; + + if (!callback) + return -EINVAL; + + event = kmalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + event->cdev = cdev; + event->callback = callback; + + /* add to list of SOF client devices */ + mutex_lock(&sdev->client_event_handler_mutex); + list_add(&event->list, &sdev->fw_state_handler_list); + mutex_unlock(&sdev->client_event_handler_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_register_fw_state_handler, SND_SOC_SOF_CLIENT); + +void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_state_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->fw_state_handler_list, list) { + if (event->cdev == cdev) { + list_del(&event->list); + kfree(event); + break; + } + } + + mutex_unlock(&sdev->client_event_handler_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_unregister_fw_state_handler, SND_SOC_SOF_CLIENT); + +enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return sdev->fw_state; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_state, SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client.h b/sound/soc/sof/sof-client.h new file mode 100644 index 000000000000..4b6394b4c694 --- /dev/null +++ b/sound/soc/sof/sof-client.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_SOF_CLIENT_H +#define __SOC_SOF_CLIENT_H + +#include <linux/auxiliary_bus.h> +#include <linux/device.h> +#include <linux/list.h> +#include <sound/sof.h> + +struct sof_ipc_fw_version; +struct sof_ipc_cmd_hdr; +struct snd_sof_dev; +struct dentry; + +/** + * struct sof_client_dev - SOF client device + * @auxdev: auxiliary device + * @sdev: pointer to SOF core device struct + * @list: item in SOF core client dev list + * @data: device specific data + */ +struct sof_client_dev { + struct auxiliary_device auxdev; + struct snd_sof_dev *sdev; + struct list_head list; + void *data; +}; + +#define sof_client_dev_to_sof_dev(cdev) ((cdev)->sdev) + +#define auxiliary_dev_to_sof_client_dev(auxiliary_dev) \ + container_of(auxiliary_dev, struct sof_client_dev, auxdev) + +#define dev_to_sof_client_dev(dev) \ + container_of(to_auxiliary_dev(dev), struct sof_client_dev, auxdev) + +int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, + void *reply_data, size_t reply_bytes); + +struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev); +struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev); +const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev); + +/* module refcount management of SOF core */ +int sof_client_core_module_get(struct sof_client_dev *cdev); +void sof_client_core_module_put(struct sof_client_dev *cdev); + +/* IPC notification */ +typedef void (*sof_client_event_callback)(struct sof_client_dev *cdev, void *msg_buf); + +int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type, + sof_client_event_callback callback); +void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type); + +/* DSP state notification and query */ +typedef void (*sof_client_fw_state_callback)(struct sof_client_dev *cdev, + enum sof_fw_state state); + +int sof_client_register_fw_state_handler(struct sof_client_dev *cdev, + sof_client_fw_state_callback callback); +void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev); +enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev); + +#endif /* __SOC_SOF_CLIENT_H */ diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 27d2f3ca2f06..f641833f3ff9 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -292,6 +292,10 @@ struct snd_sof_dsp_ops { void (*set_mach_params)(struct snd_soc_acpi_mach *mach, struct snd_sof_dev *sdev); /* optional */
+ /* IPC client ops */ + int (*register_ipc_clients)(struct snd_sof_dev *sdev); /* optional */ + void (*unregister_ipc_clients)(struct snd_sof_dev *sdev); /* optional */ + /* DAI ops */ struct snd_soc_dai_driver *drv; int num_drv; @@ -479,6 +483,30 @@ struct snd_sof_dev { */ int dsp_core_ref_count[SOF_MAX_DSP_NUM_CORES];
+ /* + * Used to keep track of registered IPC client devices so that they can + * be removed when the parent SOF module is removed. + */ + struct list_head ipc_client_list; + + /* mutex to protect client list */ + struct mutex ipc_client_mutex; + + /* + * Used for tracking the IPC client's RX registration for DSP initiated + * message handling. + */ + struct list_head ipc_rx_handler_list; + + /* + * Used for tracking the IPC client's registration for DSP state change + * notification + */ + struct list_head fw_state_handler_list; + + /* to protect the ipc_rx_handler_list and dsp_state_handler_list list */ + struct mutex client_event_handler_mutex; + void *private; /* core does not touch this */ };
@@ -582,15 +610,7 @@ extern const struct dsp_arch_ops sof_xtensa_arch_ops; /* * Firmware state tracking */ -static inline void sof_set_fw_state(struct snd_sof_dev *sdev, - enum sof_fw_state new_state) -{ - if (sdev->fw_state == new_state) - return; - - dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state); - sdev->fw_state = new_state; -} +void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state);
/* * Utilities @@ -623,4 +643,44 @@ int sof_stream_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream);
int sof_machine_check(struct snd_sof_dev *sdev); + +/* SOF client support */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_CLIENT) +int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, + const void *data, size_t size); +void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id); +int sof_register_clients(struct snd_sof_dev *sdev); +void sof_unregister_clients(struct snd_sof_dev *sdev); +void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf); +void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev); +#else /* CONFIG_SND_SOC_SOF_CLIENT */ +static inline int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, + u32 id, const void *data, size_t size) +{ + return 0; +} + +static inline void sof_client_dev_unregister(struct snd_sof_dev *sdev, + const char *name, u32 id) +{ +} + +static inline int sof_register_clients(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_clients(struct snd_sof_dev *sdev) +{ +} + +static inline void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf) +{ +} + +static inline void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev) +{ +} +#endif /* CONFIG_SND_SOC_SOF_CLIENT */ + #endif
Some SOF client can be of 'passive' type, meaning that they do not handle PM framework callbacks by themselves but rely on the auxiliary driver's suspend and resume callbacks to be notified about the core's suspend or resume event.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/pm.c | 13 ++++++++++- sound/soc/sof/sof-client.c | 46 ++++++++++++++++++++++++++++++++++++++ sound/soc/sof/sof-priv.h | 12 ++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 197a88695fef..7300ecadabd9 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -167,6 +167,9 @@ static int sof_resume(struct device *dev, bool runtime_resume) return ret; }
+ /* Notify clients not managed by pm framework about core resume */ + sof_resume_clients(sdev); + /* notify DSP of system resume */ ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); if (ret < 0) @@ -180,6 +183,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) static int sof_suspend(struct device *dev, bool runtime_suspend) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); + pm_message_t pm_state; u32 target_state = 0; int ret;
@@ -205,16 +209,23 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) }
target_state = snd_sof_dsp_power_target(sdev); + pm_state.event = target_state;
/* Skip to platform-specific suspend if DSP is entering D0 */ - if (target_state == SOF_DSP_PM_D0) + if (target_state == SOF_DSP_PM_D0) { + /* Notify clients not managed by pm framework about core suspend */ + sof_suspend_clients(sdev, pm_state); goto suspend; + }
sof_tear_down_pipelines(sdev, false);
/* release trace */ snd_sof_release_trace(sdev);
+ /* Notify clients not managed by pm framework about core suspend */ + sof_suspend_clients(sdev, pm_state); + #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) /* cache debugfs contents during runtime suspend */ if (runtime_suspend) diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index 6f747d051b59..932bdea49c24 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -169,6 +169,52 @@ int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, } EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, SND_SOC_SOF_CLIENT);
+int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state) +{ + struct auxiliary_driver *adrv; + struct sof_client_dev *cdev; + + mutex_lock(&sdev->ipc_client_mutex); + + list_for_each_entry(cdev, &sdev->ipc_client_list, list) { + /* Skip devices without loaded driver */ + if (!cdev->auxdev.dev.driver) + continue; + + adrv = to_auxiliary_drv(cdev->auxdev.dev.driver); + if (adrv->suspend) + adrv->suspend(&cdev->auxdev, state); + } + + mutex_unlock(&sdev->ipc_client_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_suspend_clients, SND_SOC_SOF_CLIENT); + +int sof_resume_clients(struct snd_sof_dev *sdev) +{ + struct auxiliary_driver *adrv; + struct sof_client_dev *cdev; + + mutex_lock(&sdev->ipc_client_mutex); + + list_for_each_entry(cdev, &sdev->ipc_client_list, list) { + /* Skip devices without loaded driver */ + if (!cdev->auxdev.dev.driver) + continue; + + adrv = to_auxiliary_drv(cdev->auxdev.dev.driver); + if (adrv->resume) + adrv->resume(&cdev->auxdev); + } + + mutex_unlock(&sdev->ipc_client_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_resume_clients, SND_SOC_SOF_CLIENT); + struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev) { return cdev->sdev->debugfs_root; diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index f641833f3ff9..39bbba5aeab2 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -653,6 +653,8 @@ int sof_register_clients(struct snd_sof_dev *sdev); void sof_unregister_clients(struct snd_sof_dev *sdev); void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf); void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev); +int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state); +int sof_resume_clients(struct snd_sof_dev *sdev); #else /* CONFIG_SND_SOC_SOF_CLIENT */ static inline int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, const void *data, size_t size) @@ -681,6 +683,16 @@ static inline void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void * static inline void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev) { } + +static inline int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state) +{ + return 0; +} + +static inline int sof_resume_clients(struct snd_sof_dev *sdev) +{ + return 0; +} #endif /* CONFIG_SND_SOC_SOF_CLIENT */
#endif
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Move the IPC flood test code out from the debug file as separate SOF client driver.
Based on the kernel configuration, the device registration for the new IPC flood test is going to happen in the core. With the separate client driver it is going to be possible to run multiple flood tests in parallel to increase the stress, the new Kconfig option can be used to select this (defaults to 1). In order to preserve backward compatibility with existing SW/scripts, the first IPC flood test's debugfs files have been linked to the old files.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Co-developed-by: Fred Oh fred.oh@linux.intel.com Signed-off-by: Fred Oh fred.oh@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/Kconfig | 16 +- sound/soc/sof/Makefile | 4 + sound/soc/sof/core.c | 1 + sound/soc/sof/debug.c | 231 ------------- sound/soc/sof/sof-client-ipc-flood-test.c | 396 ++++++++++++++++++++++ sound/soc/sof/sof-client.c | 56 ++- sound/soc/sof/sof-priv.h | 6 +- 7 files changed, 469 insertions(+), 241 deletions(-) create mode 100644 sound/soc/sof/sof-client-ipc-flood-test.c
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 468d7c113dae..3f8a2cadd2f8 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -194,13 +194,23 @@ config SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE If unsure, select "N".
config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST - bool "SOF enable IPC flood test" + tristate "SOF enable IPC flood test" + select SND_SOC_SOF_CLIENT help - This option enables the IPC flood test which can be used to flood - the DSP with test IPCs and gather stats about response times. + This option enables a separate client device for IPC flood test + which can be used to flood the DSP with test IPCs and gather stats + about response times. Say Y if you want to enable IPC flood test. If unsure, select "N".
+config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM + int "Number of IPC flood test clients" + range 1 32 + default 2 + depends on SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST + help + Select the number of IPC flood test clients to be created. + config SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR bool "SOF enable IPC message injector" help diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 38ce2fe376fa..964eff43c9ba 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -11,6 +11,8 @@ snd-sof-pci-objs := sof-pci-dev.o snd-sof-acpi-objs := sof-acpi-dev.o snd-sof-of-objs := sof-of-dev.o
+snd-sof-ipc-flood-test-objs := sof-client-ipc-flood-test.o + snd-sof-nocodec-objs := nocodec.o
snd-sof-utils-objs := sof-utils.o @@ -24,6 +26,8 @@ obj-$(CONFIG_SND_SOC_SOF_ACPI_DEV) += snd-sof-acpi.o obj-$(CONFIG_SND_SOC_SOF_OF_DEV) += snd-sof-of.o obj-$(CONFIG_SND_SOC_SOF_PCI_DEV) += snd-sof-pci.o
+obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) += snd-sof-ipc-flood-test.o + obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ obj-$(CONFIG_SND_SOC_SOF_AMD_TOPLEVEL) += amd/ diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 2aabaac9007f..f29fe573ec3c 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -484,3 +484,4 @@ MODULE_AUTHOR("Liam Girdwood"); MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_ALIAS("platform:sof-audio"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index 6d6757075f7c..e3a5f77bbd4d 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -234,107 +234,6 @@ static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev, } #endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) -#define MAX_IPC_FLOOD_DURATION_MS 1000 -#define MAX_IPC_FLOOD_COUNT 10000 -#define IPC_FLOOD_TEST_RESULT_LEN 512 - -static int sof_debug_ipc_flood_test(struct snd_sof_dev *sdev, - struct snd_sof_dfsentry *dfse, - bool flood_duration_test, - unsigned long ipc_duration_ms, - unsigned long ipc_count) -{ - struct sof_ipc_cmd_hdr hdr; - struct sof_ipc_reply reply; - u64 min_response_time = U64_MAX; - ktime_t start, end, test_end; - u64 avg_response_time = 0; - u64 max_response_time = 0; - u64 ipc_response_time; - int i = 0; - int ret; - - /* configure test IPC */ - hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; - hdr.size = sizeof(hdr); - - /* set test end time for duration flood test */ - if (flood_duration_test) - test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; - - /* send test IPC's */ - while (1) { - start = ktime_get(); - ret = sof_ipc_tx_message(sdev->ipc, hdr.cmd, &hdr, hdr.size, - &reply, sizeof(reply)); - end = ktime_get(); - - if (ret < 0) - break; - - /* compute min and max response times */ - ipc_response_time = ktime_to_ns(ktime_sub(end, start)); - min_response_time = min(min_response_time, ipc_response_time); - max_response_time = max(max_response_time, ipc_response_time); - - /* sum up response times */ - avg_response_time += ipc_response_time; - i++; - - /* test complete? */ - if (flood_duration_test) { - if (ktime_to_ns(end) >= test_end) - break; - } else { - if (i == ipc_count) - break; - } - } - - if (ret < 0) - dev_err(sdev->dev, - "error: ipc flood test failed at %d iterations\n", i); - - /* return if the first IPC fails */ - if (!i) - return ret; - - /* compute average response time */ - do_div(avg_response_time, i); - - /* clear previous test output */ - memset(dfse->cache_buf, 0, IPC_FLOOD_TEST_RESULT_LEN); - - if (flood_duration_test) { - dev_dbg(sdev->dev, "IPC Flood test duration: %lums\n", - ipc_duration_ms); - snprintf(dfse->cache_buf, IPC_FLOOD_TEST_RESULT_LEN, - "IPC Flood test duration: %lums\n", ipc_duration_ms); - } - - dev_dbg(sdev->dev, - "IPC Flood count: %d, Avg response time: %lluns\n", - i, avg_response_time); - dev_dbg(sdev->dev, "Max response time: %lluns\n", - max_response_time); - dev_dbg(sdev->dev, "Min response time: %lluns\n", - min_response_time); - - /* format output string */ - snprintf(dfse->cache_buf + strlen(dfse->cache_buf), - IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), - "IPC Flood count: %d\nAvg response time: %lluns\n", - i, avg_response_time); - - snprintf(dfse->cache_buf + strlen(dfse->cache_buf), - IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), - "Max response time: %lluns\nMin response time: %lluns\n", - max_response_time, min_response_time); - - return ret; -} -#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) static ssize_t msg_inject_read(struct file *file, char __user *buffer, @@ -437,15 +336,6 @@ static int snd_sof_debugfs_msg_inject_item(struct snd_sof_dev *sdev, static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - unsigned long ipc_duration_ms = 0; - bool flood_duration_test = false; - unsigned long ipc_count = 0; - struct dentry *dentry; - int err; -#endif size_t size; char *string; int ret; @@ -457,78 +347,6 @@ static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size = simple_write_to_buffer(string, count, ppos, buffer, count); ret = size;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - /* - * write op is only supported for ipc_flood_count or - * ipc_flood_duration_ms debugfs entries atm. - * ipc_flood_count floods the DSP with the number of IPC's specified. - * ipc_duration_ms test floods the DSP for the time specified - * in the debugfs entry. - */ - dentry = file->f_path.dentry; - if (strcmp(dentry->d_name.name, "ipc_flood_count") && - strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) { - ret = -EINVAL; - goto out; - } - - if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) - flood_duration_test = true; - - /* test completion criterion */ - if (flood_duration_test) - ret = kstrtoul(string, 0, &ipc_duration_ms); - else - ret = kstrtoul(string, 0, &ipc_count); - if (ret < 0) - goto out; - - /* limit max duration/ipc count for flood test */ - if (flood_duration_test) { - if (!ipc_duration_ms) { - ret = size; - goto out; - } - - /* find the minimum. min() is not used to avoid warnings */ - if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS) - ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS; - } else { - if (!ipc_count) { - ret = size; - goto out; - } - - /* find the minimum. min() is not used to avoid warnings */ - if (ipc_count > MAX_IPC_FLOOD_COUNT) - ipc_count = MAX_IPC_FLOOD_COUNT; - } - - ret = pm_runtime_get_sync(sdev->dev); - if (ret < 0 && ret != -EACCES) { - dev_err_ratelimited(sdev->dev, - "error: debugfs write failed to resume %d\n", - ret); - pm_runtime_put_noidle(sdev->dev); - goto out; - } - - /* flood test */ - ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test, - ipc_duration_ms, ipc_count); - - pm_runtime_mark_last_busy(sdev->dev); - err = pm_runtime_put_autosuspend(sdev->dev); - if (err < 0) - dev_err_ratelimited(sdev->dev, - "error: debugfs write failed to idle %d\n", - err); - - /* return size if test is successful */ - if (ret >= 0) - ret = size; -out: -#endif kfree(string); return ret; } @@ -544,24 +362,6 @@ static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, int size; u8 *buf;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - struct dentry *dentry; - - dentry = file->f_path.dentry; - if ((!strcmp(dentry->d_name.name, "ipc_flood_count") || - !strcmp(dentry->d_name.name, "ipc_flood_duration_ms"))) { - if (*ppos) - return 0; - - count = strlen(dfse->cache_buf); - size_ret = copy_to_user(buffer, dfse->cache_buf, count); - if (size_ret) - return -EFAULT; - - *ppos += count; - return count; - } -#endif size = dfse->size;
/* validate position & count */ @@ -719,19 +519,6 @@ int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, dfse->size = size; dfse->sdev = sdev;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - if (!strncmp(name, "ipc_flood", strlen("ipc_flood"))) { - /* - * cache_buf is unused for SOF_DFSENTRY_TYPE_BUF debugfs entries. - * So, use it to save the results of the last IPC flood test. - */ - dfse->cache_buf = devm_kzalloc(sdev->dev, IPC_FLOOD_TEST_RESULT_LEN, - GFP_KERNEL); - if (!dfse->cache_buf) - return -ENOMEM; - } -#endif - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, &sof_dfs_fops); /* add to dfsentry list */ @@ -892,24 +679,6 @@ int snd_sof_dbg_init(struct snd_sof_dev *sdev) return err; #endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - /* create read-write ipc_flood_count debugfs entry */ - err = snd_sof_debugfs_buf_item(sdev, NULL, 0, - "ipc_flood_count", 0666); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; - - /* create read-write ipc_flood_duration_ms debugfs entry */ - err = snd_sof_debugfs_buf_item(sdev, NULL, 0, - "ipc_flood_duration_ms", 0666); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; -#endif - #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) err = snd_sof_debugfs_msg_inject_item(sdev, "ipc_msg_inject", 0644, &msg_inject_fops); diff --git a/sound/soc/sof/sof-client-ipc-flood-test.c b/sound/soc/sof/sof-client-ipc-flood-test.c new file mode 100644 index 000000000000..db3a052c5dd2 --- /dev/null +++ b/sound/soc/sof/sof-client-ipc-flood-test.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Ranjani Sridharan ranjani.sridharan@linux.intel.com +// Peter Ujfalusi peter.ujfalusi@linux.intel.com +// + +#include <linux/auxiliary_bus.h> +#include <linux/completion.h> +#include <linux/debugfs.h> +#include <linux/ktime.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <sound/sof/header.h> + +#include "sof-client.h" + +#define MAX_IPC_FLOOD_DURATION_MS 1000 +#define MAX_IPC_FLOOD_COUNT 10000 +#define IPC_FLOOD_TEST_RESULT_LEN 512 +#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 + +#define DEBUGFS_IPC_FLOOD_COUNT "ipc_flood_count" +#define DEBUGFS_IPC_FLOOD_DURATION "ipc_flood_duration_ms" + +struct sof_ipc_flood_priv { + struct dentry *dfs_root; + struct dentry *dfs_link[2]; + char *buf; +}; + +static int sof_ipc_flood_dfs_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; +} + +/* + * helper function to perform the flood test. Only one of the two params, ipc_duration_ms + * or ipc_count, will be non-zero and will determine the type of test + */ +static int sof_debug_ipc_flood_test(struct sof_client_dev *cdev, + bool flood_duration_test, + unsigned long ipc_duration_ms, + unsigned long ipc_count) +{ + struct sof_ipc_flood_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_reply reply; + u64 min_response_time = U64_MAX; + ktime_t start, end, test_end; + u64 avg_response_time = 0; + u64 max_response_time = 0; + u64 ipc_response_time; + int i = 0; + int ret; + + /* configure test IPC */ + hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; + hdr.size = sizeof(hdr); + + /* set test end time for duration flood test */ + if (flood_duration_test) + test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; + + /* send test IPC's */ + while (1) { + start = ktime_get(); + ret = sof_client_ipc_tx_message(cdev, &hdr, &reply, sizeof(reply)); + end = ktime_get(); + + if (ret < 0) + break; + + /* compute min and max response times */ + ipc_response_time = ktime_to_ns(ktime_sub(end, start)); + min_response_time = min(min_response_time, ipc_response_time); + max_response_time = max(max_response_time, ipc_response_time); + + /* sum up response times */ + avg_response_time += ipc_response_time; + i++; + + /* test complete? */ + if (flood_duration_test) { + if (ktime_to_ns(end) >= test_end) + break; + } else { + if (i == ipc_count) + break; + } + } + + if (ret < 0) + dev_err(dev, "ipc flood test failed at %d iterations\n", i); + + /* return if the first IPC fails */ + if (!i) + return ret; + + /* compute average response time */ + do_div(avg_response_time, i); + + /* clear previous test output */ + memset(priv->buf, 0, IPC_FLOOD_TEST_RESULT_LEN); + + if (!ipc_count) { + dev_dbg(dev, "IPC Flood test duration: %lums\n", ipc_duration_ms); + snprintf(priv->buf, IPC_FLOOD_TEST_RESULT_LEN, + "IPC Flood test duration: %lums\n", ipc_duration_ms); + } + + dev_dbg(dev, "IPC Flood count: %d, Avg response time: %lluns\n", + i, avg_response_time); + dev_dbg(dev, "Max response time: %lluns\n", max_response_time); + dev_dbg(dev, "Min response time: %lluns\n", min_response_time); + + /* format output string and save test results */ + snprintf(priv->buf + strlen(priv->buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf), + "IPC Flood count: %d\nAvg response time: %lluns\n", + i, avg_response_time); + + snprintf(priv->buf + strlen(priv->buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf), + "Max response time: %lluns\nMin response time: %lluns\n", + max_response_time, min_response_time); + + return ret; +} + +/* + * Writing to the debugfs entry initiates the IPC flood test based on + * the IPC count or the duration specified by the user. + */ +static ssize_t sof_ipc_flood_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct device *dev = &cdev->auxdev.dev; + unsigned long ipc_duration_ms = 0; + bool flood_duration_test = false; + unsigned long ipc_count = 0; + struct dentry *dentry; + int err; + size_t size; + char *string; + int ret; + + string = kzalloc(count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + size = simple_write_to_buffer(string, count, ppos, buffer, count); + + /* + * write op is only supported for ipc_flood_count or + * ipc_flood_duration_ms debugfs entries atm. + * ipc_flood_count floods the DSP with the number of IPC's specified. + * ipc_duration_ms test floods the DSP for the time specified + * in the debugfs entry. + */ + dentry = file->f_path.dentry; + if (strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) && + strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) { + ret = -EINVAL; + goto out; + } + + if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) + flood_duration_test = true; + + /* test completion criterion */ + if (flood_duration_test) + ret = kstrtoul(string, 0, &ipc_duration_ms); + else + ret = kstrtoul(string, 0, &ipc_count); + if (ret < 0) + goto out; + + /* limit max duration/ipc count for flood test */ + if (flood_duration_test) { + if (!ipc_duration_ms) { + ret = size; + goto out; + } + + /* find the minimum. min() is not used to avoid warnings */ + if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS) + ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS; + } else { + if (!ipc_count) { + ret = size; + goto out; + } + + /* find the minimum. min() is not used to avoid warnings */ + if (ipc_count > MAX_IPC_FLOOD_COUNT) + ipc_count = MAX_IPC_FLOOD_COUNT; + } + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto out; + } + + /* flood test */ + ret = sof_debug_ipc_flood_test(cdev, flood_duration_test, + ipc_duration_ms, ipc_count); + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); + + /* return size if test is successful */ + if (ret >= 0) + ret = size; +out: + kfree(string); + return ret; +} + +/* return the result of the last IPC flood test */ +static ssize_t sof_ipc_flood_dfs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_ipc_flood_priv *priv = cdev->data; + size_t size_ret; + + struct dentry *dentry; + + dentry = file->f_path.dentry; + if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) || + !strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) { + if (*ppos) + return 0; + + count = min_t(size_t, count, strlen(priv->buf)); + size_ret = copy_to_user(buffer, priv->buf, count); + if (size_ret) + return -EFAULT; + + *ppos += count; + return count; + } + return count; +} + +static int sof_ipc_flood_dfs_release(struct inode *inode, struct file *file) +{ + debugfs_file_put(file->f_path.dentry); + + return 0; +} + +static const struct file_operations sof_ipc_flood_fops = { + .open = sof_ipc_flood_dfs_open, + .read = sof_ipc_flood_dfs_read, + .llseek = default_llseek, + .write = sof_ipc_flood_dfs_write, + .release = sof_ipc_flood_dfs_release, + + .owner = THIS_MODULE, +}; + +/* + * The IPC test client creates a couple of debugfs entries that will be used + * flood tests. Users can write to these entries to execute the IPC flood test + * by specifying either the number of IPCs to flood the DSP with or the duration + * (in ms) for which the DSP should be flooded with test IPCs. At the + * end of each test, the average, min and max response times are reported back. + * The results of the last flood test can be accessed by reading the debugfs + * entries. + */ +static int sof_ipc_flood_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 *debugfs_root = sof_client_get_debugfs_root(cdev); + struct device *dev = &auxdev->dev; + struct sof_ipc_flood_priv *priv; + + /* allocate memory for client data */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->buf = devm_kmalloc(dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL); + if (!priv->buf) + return -ENOMEM; + + cdev->data = priv; + + /* create debugfs root folder with device name under parent SOF dir */ + priv->dfs_root = debugfs_create_dir(dev_name(dev), debugfs_root); + if (!IS_ERR_OR_NULL(priv->dfs_root)) { + /* create read-write ipc_flood_count debugfs entry */ + debugfs_create_file(DEBUGFS_IPC_FLOOD_COUNT, 0644, priv->dfs_root, + cdev, &sof_ipc_flood_fops); + + /* create read-write ipc_flood_duration_ms debugfs entry */ + debugfs_create_file(DEBUGFS_IPC_FLOOD_DURATION, 0644, + priv->dfs_root, cdev, &sof_ipc_flood_fops); + + if (auxdev->id == 0) { + /* + * Create symlinks for backwards compatibility to the + * first IPC flood test instance + */ + char target[100]; + + snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_COUNT, + dev_name(dev)); + priv->dfs_link[0] = + debugfs_create_symlink(DEBUGFS_IPC_FLOOD_COUNT, + debugfs_root, target); + + snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_DURATION, + dev_name(dev)); + priv->dfs_link[1] = + debugfs_create_symlink(DEBUGFS_IPC_FLOOD_DURATION, + debugfs_root, target); + } + } + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_ipc_flood_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_ipc_flood_priv *priv = cdev->data; + + pm_runtime_disable(&auxdev->dev); + + if (auxdev->id == 0) { + debugfs_remove(priv->dfs_link[0]); + debugfs_remove(priv->dfs_link[1]); + } + + debugfs_remove_recursive(priv->dfs_root); +} + +static const struct auxiliary_device_id sof_ipc_flood_client_id_table[] = { + { .name = "snd_sof.ipc_flood" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_ipc_flood_client_id_table); + +/* + * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus + * type are enough to ensure that the parent SOF device resumes to bring the DSP + * back to D0. + * Driver name will be set based on KBUILD_MODNAME. + */ +static struct auxiliary_driver sof_ipc_flood_client_drv = { + .probe = sof_ipc_flood_probe, + .remove = sof_ipc_flood_remove, + + .id_table = sof_ipc_flood_client_id_table, +}; + +module_auxiliary_driver(sof_ipc_flood_client_drv); + +MODULE_DESCRIPTION("SOF IPC Flood Test Client Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index 932bdea49c24..0ffe7a26a19a 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -66,18 +66,70 @@ static int sof_client_dev_add_data(struct sof_client_dev *cdev, const void *data return 0; }
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) +static int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) +{ + int ret = 0; + int i; + + for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) { + ret = sof_client_dev_register(sdev, "ipc_flood", i, NULL, 0); + if (ret < 0) + break; + } + + if (ret) { + for (; i >= 0; --i) + sof_client_dev_unregister(sdev, "ipc_flood", i); + } + + return ret; +} + +static void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) +{ + int i; + + for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) + sof_client_dev_unregister(sdev, "ipc_flood", i); +} +#else +static inline int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) {} +#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST */ + int sof_register_clients(struct snd_sof_dev *sdev) { + int ret; + + /* Register platform independent client devices */ + ret = sof_register_ipc_flood_test(sdev); + if (ret) { + dev_err(sdev->dev, "IPC flood test client registration failed\n"); + return ret; + } + + /* Platform depndent client device registration */ + if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients) - return sof_ops(sdev)->register_ipc_clients(sdev); + ret = sof_ops(sdev)->register_ipc_clients(sdev);
- return 0; + if (ret) + sof_unregister_ipc_flood_test(sdev); + + return ret; }
void sof_unregister_clients(struct snd_sof_dev *sdev) { if (sof_ops(sdev) && sof_ops(sdev)->unregister_ipc_clients) sof_ops(sdev)->unregister_ipc_clients(sdev); + + sof_unregister_ipc_flood_test(sdev); }
int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 39bbba5aeab2..1af61ff89345 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -69,10 +69,6 @@ bool sof_debug_check_flag(int mask); #define SOF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_FLOAT)
-#define ENABLE_DEBUGFS_CACHEBUF \ - (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \ - IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)) - /* So far the primary core on all DSPs has ID 0 */ #define SOF_DSP_PRIMARY_CORE 0
@@ -326,7 +322,7 @@ struct snd_sof_dfsentry { * or if it is accessible only when the DSP is in D0. */ enum sof_debugfs_access_type access_type; -#if ENABLE_DEBUGFS_CACHEBUF +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) char *cache_buf; /* buffer to cache the contents of debugfs memory */ #endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR)
Move the IPC message injection code out from the debug file as separate SOF client driver.
Based on the kernel configuration, the device registration for the new IPC message injector is going to happen in the core.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/Kconfig | 3 +- sound/soc/sof/Makefile | 2 + sound/soc/sof/debug.c | 108 ----------- sound/soc/sof/sof-client-ipc-msg-injector.c | 192 ++++++++++++++++++++ sound/soc/sof/sof-client.c | 35 +++- sound/soc/sof/sof-priv.h | 4 - 6 files changed, 229 insertions(+), 115 deletions(-) create mode 100644 sound/soc/sof/sof-client-ipc-msg-injector.c
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 3f8a2cadd2f8..203b086ac22c 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -212,7 +212,8 @@ config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM Select the number of IPC flood test clients to be created.
config SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR - bool "SOF enable IPC message injector" + tristate "SOF enable IPC message injector" + select SND_SOC_SOF_CLIENT help This option enables the IPC message injector which can be used to send crafted IPC messages to the DSP to test its robustness. diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 964eff43c9ba..a2ae79ebf756 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -12,6 +12,7 @@ snd-sof-acpi-objs := sof-acpi-dev.o snd-sof-of-objs := sof-of-dev.o
snd-sof-ipc-flood-test-objs := sof-client-ipc-flood-test.o +snd-sof-ipc-msg-injector-objs := sof-client-ipc-msg-injector.o
snd-sof-nocodec-objs := nocodec.o
@@ -27,6 +28,7 @@ obj-$(CONFIG_SND_SOC_SOF_OF_DEV) += snd-sof-of.o obj-$(CONFIG_SND_SOC_SOF_PCI_DEV) += snd-sof-pci.o
obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) += snd-sof-ipc-flood-test.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) += snd-sof-ipc-msg-injector.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index e3a5f77bbd4d..937fe6e11d0d 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -234,105 +234,6 @@ static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev, } #endif
- -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) -static ssize_t msg_inject_read(struct file *file, char __user *buffer, - size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct sof_ipc_reply *rhdr = dfse->msg_inject_rx; - - if (!rhdr->hdr.size || !count || *ppos) - return 0; - - if (count > rhdr->hdr.size) - count = rhdr->hdr.size; - - if (copy_to_user(buffer, dfse->msg_inject_rx, count)) - return -EFAULT; - - *ppos += count; - return count; -} - -static ssize_t msg_inject_write(struct file *file, const char __user *buffer, - size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_ipc_cmd_hdr *hdr = dfse->msg_inject_tx; - size_t size; - int ret, err; - - if (*ppos) - return 0; - - size = simple_write_to_buffer(dfse->msg_inject_tx, SOF_IPC_MSG_MAX_SIZE, - ppos, buffer, count); - if (size != count) - return size > 0 ? -EFAULT : size; - - ret = pm_runtime_get_sync(sdev->dev); - if (ret < 0 && ret != -EACCES) { - dev_err_ratelimited(sdev->dev, "%s: DSP resume failed: %d\n", - __func__, ret); - pm_runtime_put_noidle(sdev->dev); - goto out; - } - - /* send the message */ - memset(dfse->msg_inject_rx, 0, SOF_IPC_MSG_MAX_SIZE); - ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, dfse->msg_inject_tx, count, - dfse->msg_inject_rx, SOF_IPC_MSG_MAX_SIZE); - - pm_runtime_mark_last_busy(sdev->dev); - err = pm_runtime_put_autosuspend(sdev->dev); - if (err < 0) - dev_err_ratelimited(sdev->dev, "%s: DSP idle failed: %d\n", - __func__, err); - - /* return size if test is successful */ - if (ret >= 0) - ret = size; - -out: - return ret; -} - -static const struct file_operations msg_inject_fops = { - .open = simple_open, - .read = msg_inject_read, - .write = msg_inject_write, - .llseek = default_llseek, -}; - -static int snd_sof_debugfs_msg_inject_item(struct snd_sof_dev *sdev, - const char *name, mode_t mode, - const struct file_operations *fops) -{ - struct snd_sof_dfsentry *dfse; - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - /* pre allocate the tx and rx buffers */ - dfse->msg_inject_tx = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - dfse->msg_inject_rx = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - if (!dfse->msg_inject_tx || !dfse->msg_inject_rx) - return -ENOMEM; - - dfse->type = SOF_DFSENTRY_TYPE_BUF; - dfse->sdev = sdev; - - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops); - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - - return 0; -} -#endif - static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { @@ -679,15 +580,6 @@ int snd_sof_dbg_init(struct snd_sof_dev *sdev) return err; #endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) - err = snd_sof_debugfs_msg_inject_item(sdev, "ipc_msg_inject", 0644, - &msg_inject_fops); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; -#endif - return 0; } EXPORT_SYMBOL_GPL(snd_sof_dbg_init); diff --git a/sound/soc/sof/sof-client-ipc-msg-injector.c b/sound/soc/sof/sof-client-ipc-msg-injector.c new file mode 100644 index 000000000000..bce103da4c49 --- /dev/null +++ b/sound/soc/sof/sof-client-ipc-msg-injector.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Peter Ujfalusi peter.ujfalusi@linux.intel.com +// + +#include <linux/auxiliary_bus.h> +#include <linux/completion.h> +#include <linux/debugfs.h> +#include <linux/ktime.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <sound/sof/header.h> + +#include "sof-client.h" + +#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 + +struct sof_msg_inject_priv { + struct dentry *dfs_file; + + void *tx_buffer; + void *rx_buffer; +}; + +static int sof_msg_inject_dfs_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_msg_inject_dfs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + struct sof_ipc_reply *rhdr = priv->rx_buffer; + + if (!rhdr->hdr.size || !count || *ppos) + return 0; + + if (count > rhdr->hdr.size) + count = rhdr->hdr.size; + + if (copy_to_user(buffer, priv->rx_buffer, count)) + return -EFAULT; + + *ppos += count; + return count; +} + +static ssize_t sof_msg_inject_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + int ret, err; + size_t size; + + if (*ppos) + return 0; + + size = simple_write_to_buffer(priv->tx_buffer, SOF_IPC_MSG_MAX_SIZE, + ppos, buffer, count); + if (size != count) + return size > 0 ? -EFAULT : size; + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + return ret; + } + + /* send the message */ + memset(priv->rx_buffer, 0, SOF_IPC_MSG_MAX_SIZE); + ret = sof_client_ipc_tx_message(cdev, priv->tx_buffer, priv->rx_buffer, + SOF_IPC_MSG_MAX_SIZE); + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); + + /* return size if test is successful */ + if (ret >= 0) + ret = size; + + return ret; +}; + +static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file) +{ + debugfs_file_put(file->f_path.dentry); + + return 0; +} + +static const struct file_operations sof_msg_inject_fops = { + .open = sof_msg_inject_dfs_open, + .read = sof_msg_inject_dfs_read, + .write = sof_msg_inject_dfs_write, + .llseek = default_llseek, + .release = sof_msg_inject_dfs_release, + + .owner = THIS_MODULE, +}; + +static int sof_msg_inject_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 *debugfs_root = sof_client_get_debugfs_root(cdev); + struct device *dev = &auxdev->dev; + struct sof_msg_inject_priv *priv; + + /* allocate memory for client data */ + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->tx_buffer = devm_kmalloc(dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + priv->rx_buffer = devm_kmalloc(dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!priv->tx_buffer || !priv->rx_buffer) + return -ENOMEM; + + cdev->data = priv; + + priv->dfs_file = debugfs_create_file("ipc_msg_inject", 0644, debugfs_root, + cdev, &sof_msg_inject_fops); + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_msg_inject_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_msg_inject_priv *priv = cdev->data; + + pm_runtime_disable(&auxdev->dev); + + debugfs_remove(priv->dfs_file); +} + +static const struct auxiliary_device_id sof_msg_inject_client_id_table[] = { + { .name = "snd_sof.msg_injector" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table); + +/* + * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus + * type are enough to ensure that the parent SOF device resumes to bring the DSP + * back to D0. + * Driver name will be set based on KBUILD_MODNAME. + */ +static struct auxiliary_driver sof_msg_inject_client_drv = { + .probe = sof_msg_inject_probe, + .remove = sof_msg_inject_remove, + + .id_table = sof_msg_inject_client_id_table, +}; + +module_auxiliary_driver(sof_msg_inject_client_drv); + +MODULE_DESCRIPTION("SOF IPC Message Injector Client Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index 0ffe7a26a19a..686ad0c3bb61 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -102,6 +102,25 @@ static inline int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) static inline void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) {} #endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) +static int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "msg_injector", 0, NULL, 0); +} + +static void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "msg_injector", 0); +} +#else +static inline int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) {} +#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR */ + int sof_register_clients(struct snd_sof_dev *sdev) { int ret; @@ -113,13 +132,24 @@ int sof_register_clients(struct snd_sof_dev *sdev) return ret; }
+ ret = sof_register_ipc_msg_injector(sdev); + if (ret) { + dev_err(sdev->dev, "IPC message injector client registration failed\n"); + goto err_msg_injector; + } + /* Platform depndent client device registration */
if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients) ret = sof_ops(sdev)->register_ipc_clients(sdev);
- if (ret) - sof_unregister_ipc_flood_test(sdev); + if (!ret) + return 0; + + sof_unregister_ipc_msg_injector(sdev); + +err_msg_injector: + sof_unregister_ipc_flood_test(sdev);
return ret; } @@ -129,6 +159,7 @@ void sof_unregister_clients(struct snd_sof_dev *sdev) if (sof_ops(sdev) && sof_ops(sdev)->unregister_ipc_clients) sof_ops(sdev)->unregister_ipc_clients(sdev);
+ sof_unregister_ipc_msg_injector(sdev); sof_unregister_ipc_flood_test(sdev); }
diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 1af61ff89345..2529408a4e90 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -324,10 +324,6 @@ struct snd_sof_dfsentry { enum sof_debugfs_access_type access_type; #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) char *cache_buf; /* buffer to cache the contents of debugfs memory */ -#endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) - void *msg_inject_tx; - void *msg_inject_rx; #endif struct snd_sof_dev *sdev; struct list_head list; /* list in sdev dfsentry list */
Add a new client driver for probes support and move all the probes-related code from the core to the client driver.
The probes client driver registers a component driver with one CPU DAI driver for extraction and creates a new sound card with one DUMMY DAI link with a dummy codec that will be used for extracting audio data from specific points in the audio pipeline.
The probes debugfs ops are based on the initial implementation by Cezary Rojewski and have been moved out of the SOF core into the client driver making it easier to maintain. This change will make it easier for the probes functionality to be added for all platforms without having the need to modify the existing(15+) machine drivers.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Signed-off-by: Peter Ujfalusi peter.ujfalusi@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com --- sound/soc/sof/Kconfig | 7 +- sound/soc/sof/Makefile | 3 +- sound/soc/sof/core.c | 6 - sound/soc/sof/debug.c | 226 -------- sound/soc/sof/intel/Kconfig | 19 +- sound/soc/sof/intel/apl.c | 13 +- sound/soc/sof/intel/cnl.c | 13 +- sound/soc/sof/intel/hda-dai.c | 19 - sound/soc/sof/intel/hda-probes.c | 72 ++- sound/soc/sof/intel/hda.c | 10 + sound/soc/sof/intel/hda.h | 49 +- sound/soc/sof/intel/icl.c | 13 +- sound/soc/sof/intel/tgl.c | 13 +- sound/soc/sof/ops.h | 43 -- sound/soc/sof/pcm.c | 8 +- sound/soc/sof/sof-client-probes.c | 821 ++++++++++++++++++++++++++++++ sound/soc/sof/sof-client-probes.h | 31 ++ sound/soc/sof/sof-priv.h | 25 - sound/soc/sof/sof-probes.c | 364 ------------- sound/soc/sof/sof-probes.h | 38 -- 20 files changed, 968 insertions(+), 825 deletions(-) create mode 100644 sound/soc/sof/sof-client-probes.c create mode 100644 sound/soc/sof/sof-client-probes.h delete mode 100644 sound/soc/sof/sof-probes.c delete mode 100644 sound/soc/sof/sof-probes.h
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 203b086ac22c..4542868cd730 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -53,13 +53,14 @@ config SND_SOC_SOF_COMPRESS select SND_SOC_COMPRESS
config SND_SOC_SOF_DEBUG_PROBES - bool "SOF enable data probing" + tristate + select SND_SOC_SOF_CLIENT select SND_SOC_COMPRESS help This option enables the data probing feature that can be used to gather data directly from specific points of the audio pipeline. - Say Y if you want to enable probes. - If unsure, select "N". + This option is not user-selectable but automagically handled by + 'select' statements at a higher level.
config SND_SOC_SOF_CLIENT tristate diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index a2ae79ebf756..4d31282c847d 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -4,7 +4,6 @@ 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 snd-sof-$(CONFIG_SND_SOC_SOF_CLIENT) += sof-client.o
-snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += sof-probes.o snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o
snd-sof-pci-objs := sof-pci-dev.o @@ -13,6 +12,7 @@ snd-sof-of-objs := sof-of-dev.o
snd-sof-ipc-flood-test-objs := sof-client-ipc-flood-test.o snd-sof-ipc-msg-injector-objs := sof-client-ipc-msg-injector.o +snd-sof-probes-objs := sof-client-probes.o
snd-sof-nocodec-objs := nocodec.o
@@ -29,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-flood-test.o obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) += snd-sof-ipc-msg-injector.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += snd-sof-probes.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 f29fe573ec3c..d99ecbb4282d 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -14,9 +14,6 @@ #include <sound/sof.h> #include "sof-priv.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "sof-probes.h" -#endif
/* see SOF_DBG_ flags */ static int sof_core_debug = IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE); @@ -358,9 +355,6 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data)
sdev->pdata = plat_data; sdev->first_boot = true; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; -#endif dev_set_drvdata(dev, sdev);
/* check all mandatory ops */ diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index 937fe6e11d0d..145fd0d1e166 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -19,221 +19,6 @@ #include "sof-priv.h" #include "ops.h"
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "sof-probes.h" - -/** - * strsplit_u32 - Split string into sequence of u32 tokens - * @buf: String to split into tokens. - * @delim: String containing delimiter characters. - * @tkns: Returned u32 sequence pointer. - * @num_tkns: Returned number of tokens obtained. - */ -static int -strsplit_u32(char **buf, const char *delim, u32 **tkns, size_t *num_tkns) -{ - char *s; - u32 *data, *tmp; - size_t count = 0; - size_t cap = 32; - int ret = 0; - - *tkns = NULL; - *num_tkns = 0; - data = kcalloc(cap, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - while ((s = strsep(buf, delim)) != NULL) { - ret = kstrtouint(s, 0, data + count); - if (ret) - goto exit; - if (++count >= cap) { - cap *= 2; - tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL); - if (!tmp) { - ret = -ENOMEM; - goto exit; - } - data = tmp; - } - } - - if (!count) - goto exit; - *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL); - if (*tkns == NULL) { - ret = -ENOMEM; - goto exit; - } - *num_tkns = count; - -exit: - kfree(data); - return ret; -} - -static int tokenize_input(const char __user *from, size_t count, - loff_t *ppos, u32 **tkns, size_t *num_tkns) -{ - char *buf; - int ret; - - buf = kmalloc(count + 1, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - ret = simple_write_to_buffer(buf, count, ppos, from, count); - if (ret != count) { - ret = ret >= 0 ? -EIO : ret; - goto exit; - } - - buf[count] = '\0'; - ret = strsplit_u32((char **)&buf, ",", tkns, num_tkns); -exit: - kfree(buf); - return ret; -} - -static ssize_t probe_points_read(struct file *file, - char __user *to, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_probe_point_desc *desc; - size_t num_desc, len = 0; - char *buf; - int i, ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - buf = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); - if (ret < 0) - goto exit; - - for (i = 0; i < num_desc; i++) { - ret = snprintf(buf + len, PAGE_SIZE - len, - "Id: %#010x Purpose: %d Node id: %#x\n", - desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); - if (ret < 0) - goto free_desc; - len += ret; - } - - ret = simple_read_from_buffer(to, count, ppos, buf, len); -free_desc: - kfree(desc); -exit: - kfree(buf); - return ret; -} - -static ssize_t probe_points_write(struct file *file, - const char __user *from, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_probe_point_desc *desc; - size_t num_tkns, bytes; - u32 *tkns; - int ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); - if (ret < 0) - return ret; - bytes = sizeof(*tkns) * num_tkns; - if (!num_tkns || (bytes % sizeof(*desc))) { - ret = -EINVAL; - goto exit; - } - - desc = (struct sof_probe_point_desc *)tkns; - ret = sof_ipc_probe_points_add(sdev, - desc, bytes / sizeof(*desc)); - if (!ret) - ret = count; -exit: - kfree(tkns); - return ret; -} - -static const struct file_operations probe_points_fops = { - .open = simple_open, - .read = probe_points_read, - .write = probe_points_write, - .llseek = default_llseek, -}; - -static ssize_t probe_points_remove_write(struct file *file, - const char __user *from, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - size_t num_tkns; - u32 *tkns; - int ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); - if (ret < 0) - return ret; - if (!num_tkns) { - ret = -EINVAL; - goto exit; - } - - ret = sof_ipc_probe_points_remove(sdev, tkns, num_tkns); - if (!ret) - ret = count; -exit: - kfree(tkns); - return ret; -} - -static const struct file_operations probe_points_remove_fops = { - .open = simple_open, - .write = probe_points_remove_write, - .llseek = default_llseek, -}; - -static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev, - const char *name, mode_t mode, - const struct file_operations *fops) -{ - struct snd_sof_dfsentry *dfse; - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->type = SOF_DFSENTRY_TYPE_BUF; - dfse->sdev = sdev; - - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops); - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - - return 0; -} -#endif - static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { @@ -569,17 +354,6 @@ int snd_sof_dbg_init(struct snd_sof_dev *sdev) return err; }
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - err = snd_sof_debugfs_probe_item(sdev, "probe_points", - 0644, &probe_points_fops); - if (err < 0) - return err; - err = snd_sof_debugfs_probe_item(sdev, "probe_points_remove", - 0200, &probe_points_remove_fops); - if (err < 0) - return err; -#endif - return 0; } EXPORT_SYMBOL_GPL(snd_sof_dbg_init); diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index 88b6176af021..b53f216d4ecc 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -215,6 +215,7 @@ config SND_SOC_SOF_HDA_COMMON select SND_SOC_SOF_PCI_DEV select SND_INTEL_DSP_CONFIG select SND_SOC_SOF_HDA_LINK_BASELINE + select SND_SOC_SOF_HDA_PROBES help This option is not user-selectable but automagically handled by 'select' statements at a higher level. @@ -240,15 +241,6 @@ config SND_SOC_SOF_HDA_AUDIO_CODEC Say Y if you want to enable HDAudio codecs with SOF. If unsure select "N".
-config SND_SOC_SOF_HDA_PROBES - bool "SOF enable probes over HDA" - depends on SND_SOC_SOF_DEBUG_PROBES - help - This option enables the data probing for Intel(R) - Skylake and newer platforms. - Say Y if you want to enable probes. - If unsure, select "N". - endif ## SND_SOC_SOF_HDA_COMMON
config SND_SOC_SOF_HDA_LINK_BASELINE @@ -266,6 +258,15 @@ config SND_SOC_SOF_HDA This option is not user-selectable but automagically handled by 'select' statements at a higher level.
+config SND_SOC_SOF_HDA_PROBES + bool + select SND_SOC_SOF_DEBUG_PROBES + help + The option enables the data probing for Intel(R) Skylake and newer + (HDA) platforms. + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE tristate select SOUNDWIRE_INTEL if SND_SOC_SOF_INTEL_SOUNDWIRE diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 810b8b6748a0..cd8d08c17561 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -80,15 +80,6 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack,
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - /* firmware loading */ .load_firmware = snd_sof_load_firmware_raw,
@@ -110,6 +101,10 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger,
+ /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 1911e104f113..bef27e8751f2 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -298,15 +298,6 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack,
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - /* firmware loading */ .load_firmware = snd_sof_load_firmware_raw,
@@ -328,6 +319,10 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger,
+ /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index af0c85e4e299..75063140ed0c 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -16,10 +16,6 @@
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "../sof-probes.h" -#endif - struct hda_pipe_params { u32 ch; u32 s_freq; @@ -737,20 +733,5 @@ struct snd_soc_dai_driver skl_dai[] = { .channels_max = 16, }, }, -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -{ - .name = "Probe Extraction CPU DAI", - .compress_new = snd_soc_new_compress, - .cops = &sof_probe_compr_ops, - .capture = { - .stream_name = "Probe Extraction", - .channels_min = 1, - .channels_max = 8, - .rates = SNDRV_PCM_RATE_48000, - .rate_min = 48000, - .rate_max = 48000, - }, -}, -#endif #endif }; diff --git a/sound/soc/sof/intel/hda-probes.c b/sound/soc/sof/intel/hda-probes.c index 35e548da3609..31e85d4aae8c 100644 --- a/sound/soc/sof/intel/hda-probes.c +++ b/sound/soc/sof/intel/hda-probes.c @@ -3,14 +3,20 @@ // 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) 2019-2020 Intel Corporation. All rights reserved. +// Copyright(c) 2019-2021 Intel Corporation. All rights reserved. // // Author: Cezary Rojewski cezary.rojewski@intel.com +// Converted to SOF client: +// Ranjani Sridharan ranjani.sridharan@linux.intel.com +// Peter Ujfalusi peter.ujfalusi@linux.intel.com //
+#include <linux/module.h> #include <sound/hdaudio_ext.h> #include <sound/soc.h> #include "../sof-priv.h" +#include "../sof-client-probes.h" +#include "../sof-client.h" #include "hda.h"
static inline struct hdac_ext_stream * @@ -19,10 +25,11 @@ hda_compr_get_stream(struct snd_compr_stream *cstream) return cstream->runtime->private_data; }
-int hda_probe_compr_assign(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) +static int hda_probes_compr_assign(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai, u32 *stream_id) { + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct hdac_ext_stream *hext_stream;
hext_stream = hda_dsp_stream_get(sdev, cstream->direction, 0); @@ -33,14 +40,17 @@ int hda_probe_compr_assign(struct snd_sof_dev *sdev, hdac_stream(hext_stream)->cstream = cstream; cstream->runtime->private_data = hext_stream;
- return hdac_stream(hext_stream)->stream_tag; + *stream_id = hdac_stream(hext_stream)->stream_tag; + + return 0; }
-int hda_probe_compr_free(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) +static int hda_probes_compr_free(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) { struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); int ret;
ret = hda_dsp_stream_put(sdev, cstream->direction, @@ -56,12 +66,13 @@ int hda_probe_compr_free(struct snd_sof_dev *sdev, return 0; }
-int hda_probe_compr_set_params(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai) +static int hda_probes_compr_set_params(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai) { struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct hdac_stream *hstream = hdac_stream(hext_stream); struct snd_dma_buffer *dmab; u32 bits, rate; @@ -89,19 +100,20 @@ int hda_probe_compr_set_params(struct snd_sof_dev *sdev, return 0; }
-int hda_probe_compr_trigger(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai) +static int hda_probes_compr_trigger(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + int cmd, struct snd_soc_dai *dai) { struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
return hda_dsp_stream_trigger(sdev, hext_stream, cmd); }
-int hda_probe_compr_pointer(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai) +static int hda_probes_compr_pointer(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai) { struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); struct snd_soc_pcm_stream *pstream; @@ -112,3 +124,25 @@ int hda_probe_compr_pointer(struct snd_sof_dev *sdev,
return 0; } + +/* SOF client implementation */ +static const struct sof_probes_host_ops hda_probes_ops = { + .assign = hda_probes_compr_assign, + .free = hda_probes_compr_free, + .set_params = hda_probes_compr_set_params, + .trigger = hda_probes_compr_trigger, + .pointer = hda_probes_compr_pointer, +}; + +int hda_probes_register(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "hda-probes", 0, &hda_probes_ops, + sizeof(hda_probes_ops)); +} + +void hda_probes_unregister(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "hda-probes", 0); +} + +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 8f6765317cfa..659fe9d1a542 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -1423,6 +1423,16 @@ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) } EXPORT_SYMBOL_NS(hda_pci_intel_probe, SND_SOC_SOF_INTEL_HDA_COMMON);
+int hda_register_clients(struct snd_sof_dev *sdev) +{ + return hda_probes_register(sdev); +} + +void hda_unregister_clients(struct snd_sof_dev *sdev) +{ + hda_probes_unregister(sdev); +} + MODULE_LICENSE("Dual BSD/GPL"); MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV); MODULE_IMPORT_NS(SND_SOC_SOF_HDA_AUDIO_CODEC); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 324418582044..21e34580a403 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -16,6 +16,7 @@ #include <sound/compress_driver.h> #include <sound/hda_codec.h> #include <sound/hdaudio_ext.h> +#include "../sof-client-probes.h" #include "shim.h"
/* PCI registers */ @@ -351,13 +352,7 @@
/* Number of DAIs */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -#define SOF_SKL_NUM_DAIS 16 -#else #define SOF_SKL_NUM_DAIS 15 -#endif - #else #define SOF_SKL_NUM_DAIS 8 #endif @@ -575,29 +570,6 @@ int hda_ipc_pcm_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, const struct sof_ipc_pcm_params_reply *reply);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -/* - * Probe Compress Operations. - */ -int hda_probe_compr_assign(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); -int hda_probe_compr_free(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); -int hda_probe_compr_set_params(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai); -int hda_probe_compr_trigger(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai); -int hda_probe_compr_pointer(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai); -#endif - /* * DSP IPC Operations. */ @@ -729,6 +701,25 @@ extern const struct sof_intel_dsp_desc ehl_chip_info; extern const struct sof_intel_dsp_desc jsl_chip_info; extern const struct sof_intel_dsp_desc adls_chip_info;
+/* Probes support */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) +int hda_probes_register(struct snd_sof_dev *sdev); +void hda_probes_unregister(struct snd_sof_dev *sdev); +#else +static inline int hda_probes_register(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void hda_probes_unregister(struct snd_sof_dev *sdev) +{ +} +#endif /* CONFIG_SND_SOC_SOF_HDA_PROBES */ + +/* SOF client registration for HDA platforms */ +int hda_register_clients(struct snd_sof_dev *sdev); +void hda_unregister_clients(struct snd_sof_dev *sdev); + /* machine driver select */ struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev); void hda_set_mach_params(struct snd_soc_acpi_mach *mach, diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index f75e3983969f..f20ab60e8a52 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -142,15 +142,6 @@ const struct snd_sof_dsp_ops sof_icl_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack,
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - /* firmware loading */ .load_firmware = snd_sof_load_firmware_raw,
@@ -173,6 +164,10 @@ const struct snd_sof_dsp_ops sof_icl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger,
+ /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 7f7929c5cb88..c7d1c244bc48 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -115,15 +115,6 @@ const struct snd_sof_dsp_ops sof_tgl_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack,
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - /* firmware loading */ .load_firmware = snd_sof_load_firmware_raw,
@@ -146,6 +137,10 @@ const struct snd_sof_dsp_ops sof_tgl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger,
+ /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 1f84d30296cf..999a36208b11 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -497,49 +497,6 @@ static inline int snd_sof_pcm_platform_ack(struct snd_sof_dev *sdev, return 0; }
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -static inline int -snd_sof_probe_compr_assign(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_assign(sdev, cstream, dai); -} - -static inline int -snd_sof_probe_compr_free(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_free(sdev, cstream, dai); -} - -static inline int -snd_sof_probe_compr_set_params(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_set_params(sdev, cstream, params, dai); -} - -static inline int -snd_sof_probe_compr_trigger(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_trigger(sdev, cstream, cmd, dai); -} - -static inline int -snd_sof_probe_compr_pointer(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai) -{ - if (sof_ops(sdev) && sof_ops(sdev)->probe_pointer) - return sof_ops(sdev)->probe_pointer(sdev, cstream, tstamp, dai); - - return 0; -} -#endif - /* machine driver */ static inline int snd_sof_machine_register(struct snd_sof_dev *sdev, void *pdata) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 62cb61655761..137f8ed71677 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -15,11 +15,8 @@ #include <sound/sof.h> #include "sof-priv.h" #include "sof-audio.h" -#include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "sof-probes.h" -#endif #include "sof-utils.h" +#include "ops.h"
/* Create DMA buffer page table for DSP */ static int create_page_table(struct snd_soc_component *component, @@ -925,9 +922,6 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->pointer = sof_pcm_pointer; pd->ack = sof_pcm_ack;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - pd->compress_ops = &sof_probe_compressed_ops; -#endif pd->pcm_construct = sof_pcm_new; pd->ignore_machine = drv_name; pd->be_hw_params_fixup = sof_pcm_dai_link_fixup; diff --git a/sound/soc/sof/sof-client-probes.c b/sound/soc/sof/sof-client-probes.c new file mode 100644 index 000000000000..797dedb26163 --- /dev/null +++ b/sound/soc/sof/sof-client-probes.c @@ -0,0 +1,821 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2019-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski cezary.rojewski@intel.com +// +// SOF client support: +// Ranjani Sridharan ranjani.sridharan@linux.intel.com +// Peter Ujfalusi peter.ujfalusi@linux.intel.com +// + +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/sof/header.h> +#include "sof-client.h" +#include "sof-client-probes.h" + +#define SOF_PROBES_SUSPEND_DELAY_MS 3000 +/* only extraction supported for now */ +#define SOF_PROBES_NUM_DAI_LINKS 1 + +#define SOF_PROBES_INVALID_NODE_ID UINT_MAX + +static bool __read_mostly sof_probes_enabled; +module_param_named(enable, sof_probes_enabled, bool, 0444); +MODULE_PARM_DESC(enable, "Enable SOF probes support"); + +struct sof_probes_priv { + struct dentry *dfs_points; + struct dentry *dfs_points_remove; + u32 extractor_stream_tag; + struct snd_soc_card card; + + const struct sof_probes_host_ops *host_ops; +}; + +struct sof_probe_point_desc { + unsigned int buffer_id; + unsigned int purpose; + unsigned int stream_tag; +} __packed; + +struct sof_probe_dma { + unsigned int stream_tag; + unsigned int dma_buffer_size; +} __packed; + +struct sof_ipc_probe_dma_add_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + struct sof_probe_dma dma[]; +} __packed; + +struct sof_ipc_probe_info_params { + struct sof_ipc_reply rhdr; + unsigned int num_elems; + union { + struct sof_probe_dma dma[0]; + struct sof_probe_point_desc desc[0]; + }; +} __packed; + +struct sof_ipc_probe_point_add_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + struct sof_probe_point_desc desc[]; +} __packed; + +struct sof_ipc_probe_point_remove_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + unsigned int buffer_id[]; +} __packed; + +/** + * sof_probes_init - initialize data probing + * @cdev: SOF client device + * @stream_tag: Extractor stream tag + * @buffer_size: DMA buffer size to set for extractor + * + * Host chooses whether extraction is supported or not by providing + * valid stream tag to DSP. Once specified, stream described by that + * tag will be tied to DSP for extraction for the entire lifetime of + * probe. + * + * Probing is initialized only once and each INIT request must be + * matched by DEINIT call. + */ +static int sof_probes_init(struct sof_client_dev *cdev, u32 stream_tag, + size_t buffer_size) +{ + struct sof_ipc_probe_dma_add_params *msg; + size_t size = struct_size(msg, dma, 1); + struct sof_ipc_reply reply; + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_INIT; + msg->num_elems = 1; + msg->dma[0].stream_tag = stream_tag; + msg->dma[0].dma_buffer_size = buffer_size; + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + kfree(msg); + return ret; +} + +/** + * sof_probes_deinit - cleanup after data probing + * @cdev: SOF client device + * + * Host sends DEINIT request to free previously initialized probe + * on DSP side once it is no longer needed. DEINIT only when there + * are no probes connected and with all injectors detached. + */ +static int sof_probes_deinit(struct sof_client_dev *cdev) +{ + struct sof_ipc_cmd_hdr msg; + struct sof_ipc_reply reply; + + msg.size = sizeof(msg); + msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT; + + return sof_client_ipc_tx_message(cdev, &msg, &reply, sizeof(reply)); +} + +static int sof_probes_info(struct sof_client_dev *cdev, unsigned int cmd, + void **params, size_t *num_params) +{ + struct sof_ipc_probe_info_params msg = {{{0}}}; + struct sof_ipc_probe_info_params *reply; + size_t bytes; + int ret; + + *params = NULL; + *num_params = 0; + + reply = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!reply) + return -ENOMEM; + msg.rhdr.hdr.size = sizeof(msg); + msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd; + + ret = sof_client_ipc_tx_message(cdev, &msg, reply, SOF_IPC_MSG_MAX_SIZE); + if (ret < 0 || reply->rhdr.error < 0) + goto exit; + + if (!reply->num_elems) + goto exit; + + if (cmd == SOF_IPC_PROBE_DMA_INFO) + bytes = sizeof(reply->dma[0]); + else + bytes = sizeof(reply->desc[0]); + bytes *= reply->num_elems; + *params = kmemdup(&reply->dma[0], bytes, GFP_KERNEL); + if (!*params) { + ret = -ENOMEM; + goto exit; + } + *num_params = reply->num_elems; + +exit: + kfree(reply); + return ret; +} + +/** + * sof_probes_points_info - retrieve list of active probe points + * @cdev: SOF client device + * @desc: Returned list of active probes + * @num_desc: Returned count of active probes + * + * Host sends PROBE_POINT_INFO request to obtain list of active probe + * points, valid for disconnection when given probe is no longer + * required. + */ +static int sof_probes_points_info(struct sof_client_dev *cdev, + struct sof_probe_point_desc **desc, + size_t *num_desc) +{ + return sof_probes_info(cdev, SOF_IPC_PROBE_POINT_INFO, + (void **)desc, num_desc); +} + +/** + * sof_probes_points_add - connect specified probes + * @cdev: SOF client device + * @desc: List of probe points to connect + * @num_desc: Number of elements in @desc + * + * Dynamically connects to provided set of endpoints. Immediately + * after connection is established, host must be prepared to + * transfer data from or to target stream given the probing purpose. + * + * Each probe point should be removed using PROBE_POINT_REMOVE + * request when no longer needed. + */ +static int sof_probes_points_add(struct sof_client_dev *cdev, + struct sof_probe_point_desc *desc, + size_t num_desc) +{ + struct sof_ipc_probe_point_add_params *msg; + size_t size = struct_size(msg, desc, num_desc); + struct sof_ipc_reply reply; + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_desc; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD; + memcpy(&msg->desc[0], desc, size - sizeof(*msg)); + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + kfree(msg); + return ret; +} + +/** + * sof_probes_points_remove - disconnect specified probes + * @cdev: SOF client device + * @buffer_id: List of probe points to disconnect + * @num_buffer_id: Number of elements in @desc + * + * Removes previously connected probes from list of active probe + * points and frees all resources on DSP side. + */ +static int sof_probes_points_remove(struct sof_client_dev *cdev, + unsigned int *buffer_id, size_t num_buffer_id) +{ + struct sof_ipc_probe_point_remove_params *msg; + size_t size = struct_size(msg, buffer_id, num_buffer_id); + struct sof_ipc_reply reply; + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_buffer_id; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE; + memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg)); + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + kfree(msg); + return ret; +} + +static int sof_probes_compr_startup(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + int ret; + + if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) + return -ENODEV; + + ret = sof_client_core_module_get(cdev); + if (ret) + return ret; + + ret = ops->assign(cdev, cstream, dai, &priv->extractor_stream_tag); + if (ret) { + dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret); + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + sof_client_core_module_put(cdev); + } + + return ret; +} + +static int sof_probes_compr_shutdown(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + struct sof_probe_point_desc *desc; + size_t num_desc; + int i, ret; + + /* disconnect all probe points */ + ret = sof_probes_points_info(cdev, &desc, &num_desc); + if (ret < 0) { + dev_err(dai->dev, "Failed to get probe points: %d\n", ret); + goto exit; + } + + for (i = 0; i < num_desc; i++) + sof_probes_points_remove(cdev, &desc[i].buffer_id, 1); + kfree(desc); + +exit: + ret = sof_probes_deinit(cdev); + if (ret < 0) + dev_err(dai->dev, "Failed to deinit probe: %d\n", ret); + + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + snd_compr_free_pages(cstream); + + ret = ops->free(cdev, cstream, dai); + + sof_client_core_module_put(cdev); + + return ret; +} + +static int sof_probes_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct snd_compr_runtime *rtd = cstream->runtime; + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + int ret; + + cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + cstream->dma_buffer.dev.dev = sof_client_get_dma_dev(cdev); + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; + + ret = ops->set_params(cdev, cstream, params, dai); + if (ret) + return ret; + + ret = sof_probes_init(cdev, priv->extractor_stream_tag, rtd->dma_bytes); + if (ret < 0) { + dev_err(dai->dev, "Failed to init probe: %d\n", ret); + return ret; + } + + return 0; +} + +static int sof_probes_compr_trigger(struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + + return ops->trigger(cdev, cstream, cmd, dai); +} + +static int sof_probes_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + + return ops->pointer(cdev, cstream, tstamp, dai); +} + +static const struct snd_soc_cdai_ops sof_probes_compr_ops = { + .startup = sof_probes_compr_startup, + .shutdown = sof_probes_compr_shutdown, + .set_params = sof_probes_compr_set_params, + .trigger = sof_probes_compr_trigger, + .pointer = sof_probes_compr_pointer, +}; + +static int sof_probes_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *rtd = cstream->runtime; + unsigned int offset, n; + void *ptr; + int ret; + + if (count > rtd->buffer_size) + count = rtd->buffer_size; + + div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset); + ptr = rtd->dma_area + offset; + n = rtd->buffer_size - offset; + + if (count < n) { + ret = copy_to_user(buf, ptr, count); + } else { + ret = copy_to_user(buf, ptr, n); + ret += copy_to_user(buf + n, rtd->dma_area, count - n); + } + + if (ret) + return count - ret; + return count; +} + +static const struct snd_compress_ops sof_probes_compressed_ops = { + .copy = sof_probes_compr_copy, +}; + +/** + * strsplit_u32 - Split string into sequence of u32 tokens + * @buf: String to split into tokens. + * @delim: String containing delimiter characters. + * @tkns: Returned u32 sequence pointer. + * @num_tkns: Returned number of tokens obtained. + */ +static int strsplit_u32(char *buf, const char *delim, u32 **tkns, size_t *num_tkns) +{ + char *s; + u32 *data, *tmp; + size_t count = 0; + size_t cap = 32; + int ret = 0; + + *tkns = NULL; + *num_tkns = 0; + data = kcalloc(cap, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + while ((s = strsep(&buf, delim)) != NULL) { + ret = kstrtouint(s, 0, data + count); + if (ret) + goto exit; + if (++count >= cap) { + cap *= 2; + tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL); + if (!tmp) { + ret = -ENOMEM; + goto exit; + } + data = tmp; + } + } + + if (!count) + goto exit; + *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL); + if (!(*tkns)) { + ret = -ENOMEM; + goto exit; + } + *num_tkns = count; + +exit: + kfree(data); + return ret; +} + +static int tokenize_input(const char __user *from, size_t count, + loff_t *ppos, u32 **tkns, size_t *num_tkns) +{ + char *buf; + int ret; + + buf = kmalloc(count + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = simple_write_to_buffer(buf, count, ppos, from, count); + if (ret != count) { + ret = ret >= 0 ? -EIO : ret; + goto exit; + } + + buf[count] = '\0'; + ret = strsplit_u32(buf, ",", tkns, num_tkns); +exit: + kfree(buf); + return ret; +} + +static ssize_t sof_probes_dfs_points_read(struct file *file, char __user *to, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_probe_point_desc *desc; + int remaining, offset; + size_t num_desc; + char *buf; + int i, ret, err; + + if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs read failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto exit; + } + + ret = sof_probes_points_info(cdev, &desc, &num_desc); + if (ret < 0) + goto exit; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs read failed to idle %d\n", err); + + for (i = 0; i < num_desc; i++) { + offset = strlen(buf); + remaining = PAGE_SIZE - offset; + ret = snprintf(buf + offset, remaining, + "Id: %#010x Purpose: %u Node id: %#x\n", + desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); + if (ret < 0 || ret >= remaining) { + /* truncate the output buffer at the last full line */ + buf[offset] = '\0'; + break; + } + } + + ret = simple_read_from_buffer(to, count, ppos, buf, strlen(buf)); + + kfree(desc); +exit: + kfree(buf); + return ret; +} + +static ssize_t +sof_probes_dfs_points_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_probe_point_desc *desc; + size_t num_tkns, bytes; + u32 *tkns; + int ret, err; + + if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); + if (ret < 0) + return ret; + bytes = sizeof(*tkns) * num_tkns; + if (!num_tkns || (bytes % sizeof(*desc))) { + ret = -EINVAL; + goto exit; + } + + desc = (struct sof_probe_point_desc *)tkns; + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto exit; + } + + ret = sof_probes_points_add(cdev, desc, bytes / sizeof(*desc)); + if (!ret) + ret = count; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); +exit: + kfree(tkns); + return ret; +} + +static const struct file_operations sof_probes_points_fops = { + .open = simple_open, + .read = sof_probes_dfs_points_read, + .write = sof_probes_dfs_points_write, + .llseek = default_llseek, + + .owner = THIS_MODULE, +}; + +static ssize_t +sof_probes_dfs_points_remove_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + size_t num_tkns; + u32 *tkns; + int ret, err; + + if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); + if (ret < 0) + return ret; + if (!num_tkns) { + ret = -EINVAL; + goto exit; + } + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto exit; + } + + ret = sof_probes_points_remove(cdev, tkns, num_tkns); + if (!ret) + ret = count; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); +exit: + kfree(tkns); + return ret; +} + +static const struct file_operations sof_probes_points_remove_fops = { + .open = simple_open, + .write = sof_probes_dfs_points_remove_write, + .llseek = default_llseek, + + .owner = THIS_MODULE, +}; + +static struct snd_soc_dai_driver sof_probes_dai_drv[] = { +{ + .name = "Probe Extraction CPU DAI", + .compress_new = snd_soc_new_compress, + .cops = &sof_probes_compr_ops, + .capture = { + .stream_name = "Probe Extraction", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + }, +}, +}; + +static const struct snd_soc_component_driver sof_probes_component = { + .name = "sof-probes-component", + .compress_ops = &sof_probes_compressed_ops, + .module_get_upon_open = 1, +}; + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static int sof_probes_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 *dev = &auxdev->dev; + struct snd_soc_dai_link_component platform_component[] = { + { + .name = dev_name(dev), + } + }; + struct snd_soc_card *card; + struct sof_probes_priv *priv; + struct snd_soc_dai_link_component *cpus; + struct sof_probes_host_ops *ops; + struct snd_soc_dai_link *links; + int ret; + + /* do not set up the probes support if it is not enabled */ + if (!sof_probes_enabled) + return -ENXIO; + + if (!dev->platform_data) { + dev_err(dev, "missing platform data\n"); + return -ENODEV; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ops = dev->platform_data; + + if (!ops->assign || !ops->free || !ops->set_params || !ops->trigger || + !ops->pointer) { + dev_err(dev, "missing platform callback(s)\n"); + return -ENODEV; + } + + priv->host_ops = ops; + cdev->data = priv; + + /* register probes component driver and dai */ + ret = devm_snd_soc_register_component(dev, &sof_probes_component, + sof_probes_dai_drv, + ARRAY_SIZE(sof_probes_dai_drv)); + if (ret < 0) { + dev_err(dev, "failed to register SOF probes DAI driver %d\n", ret); + return ret; + } + + /* set client data */ + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + + /* create read-write probes_points debugfs entry */ + priv->dfs_points = debugfs_create_file("probe_points", 0644, dfsroot, + cdev, &sof_probes_points_fops); + + /* create read-write probe_points_remove debugfs entry */ + priv->dfs_points_remove = debugfs_create_file("probe_points_remove", 0644, + dfsroot, cdev, + &sof_probes_points_remove_fops); + + links = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, sizeof(*links), GFP_KERNEL); + cpus = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, sizeof(*cpus), GFP_KERNEL); + if (!links || !cpus) { + debugfs_remove(priv->dfs_points); + debugfs_remove(priv->dfs_points_remove); + return -ENOMEM; + } + + /* extraction DAI link */ + links[0].name = "Compress Probe Capture"; + links[0].id = 0; + links[0].cpus = &cpus[0]; + links[0].num_cpus = 1; + links[0].cpus->dai_name = "Probe Extraction CPU DAI"; + links[0].codecs = dummy; + links[0].num_codecs = 1; + links[0].platforms = platform_component; + links[0].num_platforms = ARRAY_SIZE(platform_component); + links[0].nonatomic = 1; + + card = &priv->card; + + card->dev = dev; + card->name = "sof-probes"; + card->owner = THIS_MODULE; + card->num_links = SOF_PROBES_NUM_DAI_LINKS; + card->dai_link = links; + + /* set idle_bias_off to prevent the core from resuming the card->dev */ + card->dapm.idle_bias_off = true; + + snd_soc_card_set_drvdata(card, cdev); + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) { + debugfs_remove(priv->dfs_points); + debugfs_remove(priv->dfs_points_remove); + dev_err(dev, "Probes card register failed %d\n", ret); + return ret; + } + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_PROBES_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_probes_client_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_probes_priv *priv = cdev->data; + + if (!sof_probes_enabled) + return; + + pm_runtime_disable(&auxdev->dev); + debugfs_remove(priv->dfs_points); + debugfs_remove(priv->dfs_points_remove); +} + +static const struct auxiliary_device_id sof_probes_client_id_table[] = { + { .name = "snd_sof.hda-probes", }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_probes_client_id_table); + +/* driver name will be set based on KBUILD_MODNAME */ +static struct auxiliary_driver sof_probes_client_drv = { + .probe = sof_probes_client_probe, + .remove = sof_probes_client_remove, + + .id_table = sof_probes_client_id_table, +}; + +module_auxiliary_driver(sof_probes_client_drv); + +MODULE_DESCRIPTION("SOF Probes Client Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client-probes.h b/sound/soc/sof/sof-client-probes.h new file mode 100644 index 000000000000..0f9ed4569fd3 --- /dev/null +++ b/sound/soc/sof/sof-client-probes.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOF_CLIENT_PROBES_H +#define __SOF_CLIENT_PROBES_H + +struct snd_compr_stream; +struct snd_compr_tstamp; +struct snd_compr_params; +struct sof_client_dev; +struct snd_soc_dai; + +/* + * Callbacks used on platforms where the control for audio is split between + * DSP and host, like HDA. + */ +struct sof_probes_host_ops { + int (*assign)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_soc_dai *dai, u32 *stream_id); + int (*free)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); + int (*set_params)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai); + int (*trigger)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + int cmd, struct snd_soc_dai *dai); + int (*pointer)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai); +}; + +#endif diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 2529408a4e90..2c8e556cd5cc 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -201,27 +201,6 @@ struct snd_sof_dsp_ops { /* pcm ack */ int (*pcm_ack)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); /* optional */
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - /* Except for probe_pointer, all probe ops are mandatory */ - int (*probe_assign)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_free)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_set_params)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_trigger)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_pointer)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai); /* optional */ -#endif - /* host read DSP stream data */ int (*ipc_msg_data)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, @@ -446,10 +425,6 @@ struct snd_sof_dev { int ipc_timeout; int boot_timeout;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - unsigned int extractor_stream_tag; -#endif - /* DMA for Trace */ struct snd_dma_buffer dmatb; struct snd_dma_buffer dmatp; diff --git a/sound/soc/sof/sof-probes.c b/sound/soc/sof/sof-probes.c deleted file mode 100644 index c79026cdb8c7..000000000000 --- a/sound/soc/sof/sof-probes.c +++ /dev/null @@ -1,364 +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) 2019-2021 Intel Corporation. All rights reserved. -// Author: Cezary Rojewski cezary.rojewski@intel.com -// - -#include <sound/soc.h> -#include "ops.h" -#include "sof-priv.h" -#include "sof-probes.h" - -struct sof_probe_dma { - unsigned int stream_tag; - unsigned int dma_buffer_size; -} __packed; - -struct sof_ipc_probe_dma_add_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - struct sof_probe_dma dma[]; -} __packed; - -struct sof_ipc_probe_info_params { - struct sof_ipc_reply rhdr; - unsigned int num_elems; - union { - struct sof_probe_dma dma[0]; - struct sof_probe_point_desc desc[0]; - }; -} __packed; - -struct sof_ipc_probe_point_add_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - struct sof_probe_point_desc desc[]; -} __packed; - -struct sof_ipc_probe_point_remove_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - unsigned int buffer_id[]; -} __packed; - -/** - * sof_ipc_probe_init - initialize data probing - * @sdev: SOF sound device - * @stream_tag: Extractor stream tag - * @buffer_size: DMA buffer size to set for extractor - * - * Host chooses whether extraction is supported or not by providing - * valid stream tag to DSP. Once specified, stream described by that - * tag will be tied to DSP for extraction for the entire lifetime of - * probe. - * - * Probing is initialized only once and each INIT request must be - * matched by DEINIT call. - */ -static int sof_ipc_probe_init(struct snd_sof_dev *sdev, u32 stream_tag, - size_t buffer_size) -{ - struct sof_ipc_probe_dma_add_params *msg; - struct sof_ipc_reply reply; - size_t size = struct_size(msg, dma, 1); - int ret; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - msg->hdr.size = size; - msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_INIT; - msg->num_elems = 1; - msg->dma[0].stream_tag = stream_tag; - msg->dma[0].dma_buffer_size = buffer_size; - - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - kfree(msg); - return ret; -} - -/** - * sof_ipc_probe_deinit - cleanup after data probing - * @sdev: SOF sound device - * - * Host sends DEINIT request to free previously initialized probe - * on DSP side once it is no longer needed. DEINIT only when there - * are no probes connected and with all injectors detached. - */ -static int sof_ipc_probe_deinit(struct snd_sof_dev *sdev) -{ - struct sof_ipc_cmd_hdr msg; - struct sof_ipc_reply reply; - - msg.size = sizeof(msg); - msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT; - - return sof_ipc_tx_message(sdev->ipc, msg.cmd, &msg, msg.size, - &reply, sizeof(reply)); -} - -static int sof_ipc_probe_info(struct snd_sof_dev *sdev, unsigned int cmd, - void **params, size_t *num_params) -{ - struct sof_ipc_probe_info_params msg = {{{0}}}; - struct sof_ipc_probe_info_params *reply; - size_t bytes; - int ret; - - *params = NULL; - *num_params = 0; - - reply = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - if (!reply) - return -ENOMEM; - msg.rhdr.hdr.size = sizeof(msg); - msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd; - - ret = sof_ipc_tx_message(sdev->ipc, msg.rhdr.hdr.cmd, &msg, - msg.rhdr.hdr.size, reply, SOF_IPC_MSG_MAX_SIZE); - if (ret < 0 || reply->rhdr.error < 0) - goto exit; - - if (!reply->num_elems) - goto exit; - - if (cmd == SOF_IPC_PROBE_DMA_INFO) - bytes = sizeof(reply->dma[0]); - else - bytes = sizeof(reply->desc[0]); - bytes *= reply->num_elems; - *params = kmemdup(&reply->dma[0], bytes, GFP_KERNEL); - if (!*params) { - ret = -ENOMEM; - goto exit; - } - *num_params = reply->num_elems; - -exit: - kfree(reply); - return ret; -} - -/** - * sof_ipc_probe_points_info - retrieve list of active probe points - * @sdev: SOF sound device - * @desc: Returned list of active probes - * @num_desc: Returned count of active probes - * - * Host sends PROBE_POINT_INFO request to obtain list of active probe - * points, valid for disconnection when given probe is no longer - * required. - */ -int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, - struct sof_probe_point_desc **desc, - size_t *num_desc) -{ - return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_POINT_INFO, - (void **)desc, num_desc); -} -EXPORT_SYMBOL(sof_ipc_probe_points_info); - -/** - * sof_ipc_probe_points_add - connect specified probes - * @sdev: SOF sound device - * @desc: List of probe points to connect - * @num_desc: Number of elements in @desc - * - * Dynamically connects to provided set of endpoints. Immediately - * after connection is established, host must be prepared to - * transfer data from or to target stream given the probing purpose. - * - * Each probe point should be removed using PROBE_POINT_REMOVE - * request when no longer needed. - */ -int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, - struct sof_probe_point_desc *desc, size_t num_desc) -{ - struct sof_ipc_probe_point_add_params *msg; - struct sof_ipc_reply reply; - size_t size = struct_size(msg, desc, num_desc); - int ret; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - msg->hdr.size = size; - msg->num_elems = num_desc; - msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD; - memcpy(&msg->desc[0], desc, size - sizeof(*msg)); - - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - kfree(msg); - return ret; -} -EXPORT_SYMBOL(sof_ipc_probe_points_add); - -/** - * sof_ipc_probe_points_remove - disconnect specified probes - * @sdev: SOF sound device - * @buffer_id: List of probe points to disconnect - * @num_buffer_id: Number of elements in @desc - * - * Removes previously connected probes from list of active probe - * points and frees all resources on DSP side. - */ -int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, - unsigned int *buffer_id, size_t num_buffer_id) -{ - struct sof_ipc_probe_point_remove_params *msg; - struct sof_ipc_reply reply; - size_t size = struct_size(msg, buffer_id, num_buffer_id); - int ret; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - msg->hdr.size = size; - msg->num_elems = num_buffer_id; - msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE; - memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg)); - - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - kfree(msg); - return ret; -} -EXPORT_SYMBOL(sof_ipc_probe_points_remove); - -static int sof_probe_compr_startup(struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - int ret; - - ret = snd_sof_probe_compr_assign(sdev, cstream, dai); - if (ret < 0) { - dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret); - return ret; - } - - sdev->extractor_stream_tag = ret; - return 0; -} - -static int sof_probe_compr_shutdown(struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - struct sof_probe_point_desc *desc; - size_t num_desc; - int i, ret; - - /* disconnect all probe points */ - ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); - if (ret < 0) { - dev_err(dai->dev, "Failed to get probe points: %d\n", ret); - goto exit; - } - - for (i = 0; i < num_desc; i++) - sof_ipc_probe_points_remove(sdev, &desc[i].buffer_id, 1); - kfree(desc); - -exit: - ret = sof_ipc_probe_deinit(sdev); - if (ret < 0) - dev_err(dai->dev, "Failed to deinit probe: %d\n", ret); - - sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; - snd_compr_free_pages(cstream); - - return snd_sof_probe_compr_free(sdev, cstream, dai); -} - -static int sof_probe_compr_set_params(struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - struct snd_compr_runtime *rtd = cstream->runtime; - int ret; - - cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; - cstream->dma_buffer.dev.dev = sdev->dev; - ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); - if (ret < 0) - return ret; - - ret = snd_sof_probe_compr_set_params(sdev, cstream, params, dai); - if (ret < 0) - return ret; - - ret = sof_ipc_probe_init(sdev, sdev->extractor_stream_tag, - rtd->dma_bytes); - if (ret < 0) { - dev_err(dai->dev, "Failed to init probe: %d\n", ret); - return ret; - } - - return 0; -} - -static int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - - return snd_sof_probe_compr_trigger(sdev, cstream, cmd, dai); -} - -static int sof_probe_compr_pointer(struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - - return snd_sof_probe_compr_pointer(sdev, cstream, tstamp, dai); -} - -const struct snd_soc_cdai_ops sof_probe_compr_ops = { - .startup = sof_probe_compr_startup, - .shutdown = sof_probe_compr_shutdown, - .set_params = sof_probe_compr_set_params, - .trigger = sof_probe_compr_trigger, - .pointer = sof_probe_compr_pointer, -}; -EXPORT_SYMBOL(sof_probe_compr_ops); - -static int sof_probe_compr_copy(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - char __user *buf, size_t count) -{ - struct snd_compr_runtime *rtd = cstream->runtime; - unsigned int offset, n; - void *ptr; - int ret; - - if (count > rtd->buffer_size) - count = rtd->buffer_size; - - div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset); - ptr = rtd->dma_area + offset; - n = rtd->buffer_size - offset; - - if (count < n) { - ret = copy_to_user(buf, ptr, count); - } else { - ret = copy_to_user(buf, ptr, n); - ret += copy_to_user(buf + n, rtd->dma_area, count - n); - } - - if (ret) - return count - ret; - return count; -} - -const struct snd_compress_ops sof_probe_compressed_ops = { - .copy = sof_probe_compr_copy, -}; -EXPORT_SYMBOL(sof_probe_compressed_ops); diff --git a/sound/soc/sof/sof-probes.h b/sound/soc/sof/sof-probes.h deleted file mode 100644 index 4a1ed2942d28..000000000000 --- a/sound/soc/sof/sof-probes.h +++ /dev/null @@ -1,38 +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) 2019-2021 Intel Corporation. All rights reserved. - * Author: Cezary Rojewski cezary.rojewski@intel.com - */ - -#ifndef __SOF_PROBES_H -#define __SOF_PROBES_H - -#include <sound/compress_driver.h> -#include <sound/sof/header.h> - -struct snd_sof_dev; - -#define SOF_PROBE_INVALID_NODE_ID UINT_MAX - -struct sof_probe_point_desc { - unsigned int buffer_id; - unsigned int purpose; - unsigned int stream_tag; -} __packed; - -int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, - struct sof_probe_point_desc **desc, - size_t *num_desc); -int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, - struct sof_probe_point_desc *desc, - size_t num_desc); -int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, - unsigned int *buffer_id, size_t num_buffer_id); - -extern const struct snd_soc_cdai_ops sof_probe_compr_ops; -extern const struct snd_compress_ops sof_probe_compressed_ops; - -#endif
On Thu, Feb 10, 2022 at 12:55:19PM +0200, Peter Ujfalusi wrote:
Add a new client driver for probes support and move all the probes-related code from the core to the client driver.
This breaks an x86 allmodconfig build:
/mnt/kernel/sound/soc/sof/compress.c: In function 'create_page_table': /mnt/kernel/sound/soc/sof/compress.c:87:9: error: implicit declaration of function 'snd_sof_create_page_table'; did you mean 'create_page_table'? [-Werror=implicit-function-declaration] 87 | return snd_sof_create_page_table(component->dev, dmab, | ^~~~~~~~~~~~~~~~~~~~~~~~~ | create_page_table
On 10/02/2022 15:54, Mark Brown wrote:
On Thu, Feb 10, 2022 at 12:55:19PM +0200, Peter Ujfalusi wrote:
Add a new client driver for probes support and move all the probes-related code from the core to the client driver.
This breaks an x86 allmodconfig build:
/mnt/kernel/sound/soc/sof/compress.c: In function 'create_page_table': /mnt/kernel/sound/soc/sof/compress.c:87:9: error: implicit declaration of function 'snd_sof_create_page_table'; did you mean 'create_page_table'? [-Werror=implicit-function-declaration] 87 | return snd_sof_create_page_table(component->dev, dmab, | ^~~~~~~~~~~~~~~~~~~~~~~~~ | create_page_table
Interesting, we have this under constant builds for months and we certainly are building things as modules.
Let me check and fix this.
On 10/02/2022 16:41, Péter Ujfalusi wrote:
On 10/02/2022 15:54, Mark Brown wrote:
On Thu, Feb 10, 2022 at 12:55:19PM +0200, Peter Ujfalusi wrote:
Add a new client driver for probes support and move all the probes-related code from the core to the client driver.
This breaks an x86 allmodconfig build:
/mnt/kernel/sound/soc/sof/compress.c: In function 'create_page_table': /mnt/kernel/sound/soc/sof/compress.c:87:9: error: implicit declaration of function 'snd_sof_create_page_table'; did you mean 'create_page_table'? [-Werror=implicit-function-declaration] 87 | return snd_sof_create_page_table(component->dev, dmab, | ^~~~~~~~~~~~~~~~~~~~~~~~~ | create_page_table
Interesting, we have this under constant builds for months and we certainly are building things as modules.
Let me check and fix this.
This patch never got reviewed in SOF: ASoC: SOF: compr: Add compress ops implementation
and this introduced the use of snd_sof_create_page_table()
I don't sof-compress enabled, only imx and mediatek have it enabled.
Daniel, were there any reason to not send this to sof-dev prior?
On Thu, 10 Feb 2022 12:55:10 +0200, Peter Ujfalusi wrote:
The Linux SOF implementation is historically monolithic in a sense that all features accessible in the firmware can be used via the snd_sof_dev struct in one way or another.
Support for features can not be added or removed runtime and with the current way of things it is hard if not impossible to implement support for dynamic feature support when based on the firmware manifest we can easily enable/access independent modules with the SOF.
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[1/9] ASoC: SOF: Drop unused DSP power states: D3_HOT and D3_COLD commit: 2439a35508277922ea116c99ff4d4a32c607464c [2/9] ASoC: SOF: Move the definition of enum sof_dsp_power_states to global header commit: 5fdc1242453e2ae88b2cdb607e4eda6b687f084c [3/9] ASoC: SOF: ipc: Read and pass the whole message to handlers for IPC events commit: ab3a2189a3744527f54ace1be19eb13e6c3d24df [4/9] ASoC: SOF: Split up utils.c into sof-utils and iomem-utils commit: ee8443050b2bf06d80fdd2c78cc25cae2abdedcd [5/9] ASoC: SOF: Introduce IPC SOF client support commit: 6955d9512d0ea814f1c2761bef7ad7b3cedf4d68 [6/9] ASoC: SOF: sof-client: Add support for clients not managed by pm framework commit: 1069967afe1e6b728061682ff99ec534a55a5613 [7/9] ASoC: SOF: Convert the generic IPC flood test into SOF client commit: 6e9548cdb30e5d6724236dd7b89a79a270751485 [8/9] ASoC: SOF: Convert the generic IPC message injector into SOF client commit: cac0b0887e5304bddfda91a4a7106f9328c31318 [9/9] ASoC: SOF: Convert the generic probe support to SOF client commit: 3dc0d709177828a22dfc9d0072e3ac937ef90d06
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (2)
-
Mark Brown
-
Peter Ujfalusi