diff mbox series

[3/6] ASoC: SOF: Create client driver for IPC test

Message ID 20200930225051.889607-4-david.m.ertman@intel.com (mailing list archive)
State Superseded
Headers show
Series Ancillary bus implementation and SOF multi-client support | expand

Commit Message

Dave Ertman Sept. 30, 2020, 10:50 p.m. UTC
From: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>

Create an SOF client driver for IPC flood test. This
driver is used to set up the debugfs entries and the
read/write ops for initiating the IPC flood test that
would be used to measure the min/max/avg response times
for sending IPCs to the DSP.

Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
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: Dave Ertman <david.m.ertman@intel.com>
---
 sound/soc/sof/Kconfig               |  10 +
 sound/soc/sof/Makefile              |   4 +
 sound/soc/sof/sof-ipc-test-client.c | 314 ++++++++++++++++++++++++++++
 3 files changed, 328 insertions(+)
 create mode 100644 sound/soc/sof/sof-ipc-test-client.c

Comments

Greg Kroah-Hartman Oct. 1, 2020, 1:04 p.m. UTC | #1
On Wed, Sep 30, 2020 at 03:50:48PM -0700, Dave Ertman wrote:
> +/*
> + * 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_test_probe(struct ancillary_device *ancildev,
> +			      const struct ancillary_device_id *id)
> +{
> +	struct sof_client_dev *cdev = ancillary_dev_to_sof_client_dev(ancildev);
> +	struct sof_ipc_client_data *ipc_client_data;
> +
> +	/*
> +	 * The ancillary device has a usage count of 0 even before runtime PM
> +	 * is enabled. So, increment the usage count to let the device
> +	 * suspend after probe is complete.
> +	 */
> +	pm_runtime_get_noresume(&ancildev->dev);
> +
> +	/* allocate memory for client data */
> +	ipc_client_data = devm_kzalloc(&ancildev->dev, sizeof(*ipc_client_data), GFP_KERNEL);
> +	if (!ipc_client_data)
> +		return -ENOMEM;
> +
> +	ipc_client_data->buf = devm_kzalloc(&ancildev->dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL);
> +	if (!ipc_client_data->buf)
> +		return -ENOMEM;
> +
> +	cdev->data = ipc_client_data;
> +
> +	/* create debugfs root folder with device name under parent SOF dir */
> +	ipc_client_data->dfs_root = debugfs_create_dir(dev_name(&ancildev->dev),
> +						       sof_client_get_debugfs_root(cdev));
> +
> +	/* create read-write ipc_flood_count debugfs entry */
> +	debugfs_create_file("ipc_flood_count", 0644, ipc_client_data->dfs_root,
> +			    cdev, &sof_ipc_dfs_fops);
> +
> +	/* create read-write ipc_flood_duration_ms debugfs entry */
> +	debugfs_create_file("ipc_flood_duration_ms", 0644, ipc_client_data->dfs_root,
> +			    cdev, &sof_ipc_dfs_fops);

These debugfs files are never removed, why not?

thanks,

greg k-h
Greg Kroah-Hartman Oct. 1, 2020, 1:09 p.m. UTC | #2
On Wed, Sep 30, 2020 at 03:50:48PM -0700, Dave Ertman wrote:
> From: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
> 
> Create an SOF client driver for IPC flood test. This
> driver is used to set up the debugfs entries and the
> read/write ops for initiating the IPC flood test that
> would be used to measure the min/max/avg response times
> for sending IPCs to the DSP.
> 
> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
> 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: Dave Ertman <david.m.ertman@intel.com>

Am I reading this series correct in that this is the only "user" of the
new ancilicary bus/driver code?

If so, why is it even needed?  These are just debugfs files for testing,
why does that need to be in a separate device?  What is being "shared"
here that needs multiple struct devices to handle?

confused,

greg k-h
Pierre-Louis Bossart Oct. 1, 2020, 1:55 p.m. UTC | #3
Thanks for the review Greg.

On 10/1/20 8:09 AM, Greg KH wrote:
> On Wed, Sep 30, 2020 at 03:50:48PM -0700, Dave Ertman wrote:
>> From: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
>>
>> Create an SOF client driver for IPC flood test. This
>> driver is used to set up the debugfs entries and the
>> read/write ops for initiating the IPC flood test that
>> would be used to measure the min/max/avg response times
>> for sending IPCs to the DSP.
>>
>> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
>> 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: Dave Ertman <david.m.ertman@intel.com>
> 
> Am I reading this series correct in that this is the only "user" of the
> new ancilicary bus/driver code?

This is the first user, and it was meant to demonstrate how the client 
is instantiated and communicates with hardware controlled by the parent. 
The next users will be 'audio probes', which provides the ability to 
extract/inject data into the DSP. We also want to split the existing 
audio cards into several pieces, e.g. the HDMI parts should really be 
presented as a separate card.

The other users will be networking/RDMA, which were actually the first 
to suggest this bus.

> If so, why is it even needed?  These are just debugfs files for testing,
> why does that need to be in a separate device?  What is being "shared"
> here that needs multiple struct devices to handle?
> 
> confused,

The parent PCI device provides access to the DSP firmware/hardware and 
is in complete control of the IPC with the DSP firmware. The parent 
plays the role of a 'server' providing shared hardware access to 
multiple clients.

Why is this needed?

With the current audio solutions, we have a monolithic solution that has 
proven difficult to maintain. We'd really like to expose unrelated DSP 
functionality with different devices.

The best example is really HDMI. HDMI/DP audio interfaces are controlled 
by the same hardware, but are logically independent. What we end-up 
doing is re-adding the same solution over and over for each machine driver:

sound/soc/intel/boards$ git grep hda_dsp_hdmi_build_controls
bxt_da7219_max98357a.c:         return hda_dsp_hdmi_build_controls(card, 
component);
bxt_rt298.c:            return hda_dsp_hdmi_build_controls(card, component);
cml_rt1011_rt5682.c:            return hda_dsp_hdmi_build_controls(card, 
component);
ehl_rt5660.c:   return hda_dsp_hdmi_build_controls(card, 
pcm->codec_dai->component);
glk_rt5682_max98357a.c:         return hda_dsp_hdmi_build_controls(card, 
component);
hda_dsp_common.c:int hda_dsp_hdmi_build_controls(struct snd_soc_card *card,
hda_dsp_common.h:int hda_dsp_hdmi_build_controls(struct snd_soc_card *card,
hda_dsp_common.h:static inline int hda_dsp_hdmi_build_controls(struct 
snd_soc_card *card,
skl_hda_dsp_common.h:   return hda_dsp_hdmi_build_controls(card, component);
sof_da7219_max98373.c:          return hda_dsp_hdmi_build_controls(card,
sof_pcm512x.c:  return hda_dsp_hdmi_build_controls(card, 
pcm->codec_dai->component);
sof_rt5682.c:           return hda_dsp_hdmi_build_controls(card, component);
sof_sdw_hdmi.c:         return hda_dsp_hdmi_build_controls(card, component);

and we also keep adding HDMI-related ASoC topology definitions for all 
the cards.

It would make a lot more sense if we could have ONE HDMI/DP card which 
is created, instead of managing HDMI/DP from the card that is supposed 
to deal with local accessories based on HDaudio/DMIC/SoundWire/I2S.

The audio probes are similar, we want to have a single probe client 
instead of adding audio probes to every single card we have to maintain.

On platforms where the DSP can deal with sensors, this would also allow 
the parent to expose IIO and HID clients.

Going back to this IPC test, maybe the commit message is unclear: we 
already have this functionality in the mainline, it's been very helpful 
for stress tests. What this patch shows is that moving the functionality 
to a client makes it possible to scale to 2 or more clients with a 
simple set of register/unregister. The device model makes it really easy 
to scale.

So yes, you are correct that for now there is a single user with very 
limited functionality. This is intentional to make the reviews simpler, 
but if/when this bus is part of the mainline we'll have additional 
users, and not just from Intel if you look at the reviewed-by tags.

We might even remove the platform devices used for the SoundWire master 
and use this instead :-)

Does this help?
Mark Brown Oct. 1, 2020, 1:59 p.m. UTC | #4
On Wed, Sep 30, 2020 at 03:50:48PM -0700, Dave Ertman wrote:

> +/* helper function to perform the flood 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)

Again, some word wrapping would be nice.

The flood_duration_test parameter is boolean which is often a warning
sign for uncler interfaces and...

> +	if (flood_duration_test) {
> +		dev_dbg(dev, "IPC Flood test duration: %lums\n", ipc_duration_ms);
> +		snprintf(ipc_client_data->buf, IPC_FLOOD_TEST_RESULT_LEN,
> +			 "IPC Flood test duration: %lums\n", ipc_duration_ms);
> +	}

...appears to only control this debug print which I'd never have guessed
from the name.

> +	/* set test completion criterion */
> +	ret = flood_duration_test ? kstrtoul(string, 0, &ipc_duration_ms) :
> +			kstrtoul(string, 0, &ipc_count);
> +	if (ret < 0)
> +		goto out;

Please write normal conditional statements for legibility.
Sridharan, Ranjani Oct. 1, 2020, 4:46 p.m. UTC | #5
On Thu, 2020-10-01 at 15:04 +0200, Greg KH wrote:
> On Wed, Sep 30, 2020 at 03:50:48PM -0700, Dave Ertman wrote:
> > +/*
> > + * 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_test_probe(struct ancillary_device *ancildev,
> > +			      const struct ancillary_device_id *id)
> > +{
> > +	struct sof_client_dev *cdev =
> > ancillary_dev_to_sof_client_dev(ancildev);
> > +	struct sof_ipc_client_data *ipc_client_data;
> > +
> > +	/*
> > +	 * The ancillary device has a usage count of 0 even before
> > runtime PM
> > +	 * is enabled. So, increment the usage count to let the device
> > +	 * suspend after probe is complete.
> > +	 */
> > +	pm_runtime_get_noresume(&ancildev->dev);
> > +
> > +	/* allocate memory for client data */
> > +	ipc_client_data = devm_kzalloc(&ancildev->dev,
> > sizeof(*ipc_client_data), GFP_KERNEL);
> > +	if (!ipc_client_data)
> > +		return -ENOMEM;
> > +
> > +	ipc_client_data->buf = devm_kzalloc(&ancildev->dev,
> > IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL);
> > +	if (!ipc_client_data->buf)
> > +		return -ENOMEM;
> > +
> > +	cdev->data = ipc_client_data;
> > +
> > +	/* create debugfs root folder with device name under parent SOF
> > dir */
> > +	ipc_client_data->dfs_root =
> > debugfs_create_dir(dev_name(&ancildev->dev),
> > +						       sof_client_get_d
> > ebugfs_root(cdev));
> > +
> > +	/* create read-write ipc_flood_count debugfs entry */
> > +	debugfs_create_file("ipc_flood_count", 0644, ipc_client_data-
> > >dfs_root,
> > +			    cdev, &sof_ipc_dfs_fops);
> > +
> > +	/* create read-write ipc_flood_duration_ms debugfs entry */
> > +	debugfs_create_file("ipc_flood_duration_ms", 0644,
> > ipc_client_data->dfs_root,
> > +			    cdev, &sof_ipc_dfs_fops);
> 
> These debugfs files are never removed, why not?
Looks like a miss. Will fix.

Thanks,
Ranjani
Dave Ertman Oct. 1, 2020, 4:48 p.m. UTC | #6
> -----Original Message-----
> From: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
> Sent: Thursday, October 1, 2020 6:55 AM
> To: Greg KH <gregkh@linuxfoundation.org>; Ertman, David M
> <david.m.ertman@intel.com>
> Cc: alsa-devel@alsa-project.org; tiwai@suse.de; broonie@kernel.org;
> Sridharan, Ranjani <ranjani.sridharan@intel.com>; jgg@nvidia.com;
> parav@nvidia.com; Ranjani Sridharan <ranjani.sridharan@linux.intel.com>;
> Fred Oh <fred.oh@linux.intel.com>
> Subject: Re: [PATCH 3/6] ASoC: SOF: Create client driver for IPC test
> 
> Thanks for the review Greg.
> 
> On 10/1/20 8:09 AM, Greg KH wrote:
> > On Wed, Sep 30, 2020 at 03:50:48PM -0700, Dave Ertman wrote:
> >> From: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
> >>
> >> Create an SOF client driver for IPC flood test. This
> >> driver is used to set up the debugfs entries and the
> >> read/write ops for initiating the IPC flood test that
> >> would be used to measure the min/max/avg response times
> >> for sending IPCs to the DSP.
> >>
> >> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
> >> 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: Dave Ertman <david.m.ertman@intel.com>
> >
> > Am I reading this series correct in that this is the only "user" of the
> > new ancilicary bus/driver code?
> 
> This is the first user, and it was meant to demonstrate how the client
> is instantiated and communicates with hardware controlled by the parent.
> The next users will be 'audio probes', which provides the ability to
> extract/inject data into the DSP. We also want to split the existing
> audio cards into several pieces, e.g. the HDMI parts should really be
> presented as a separate card.
> 
> The other users will be networking/RDMA, which were actually the first
> to suggest this bus.
> 
> > If so, why is it even needed?  These are just debugfs files for testing,
> > why does that need to be in a separate device?  What is being "shared"
> > here that needs multiple struct devices to handle?
> >
> > confused,
> 
> The parent PCI device provides access to the DSP firmware/hardware and
> is in complete control of the IPC with the DSP firmware. The parent
> plays the role of a 'server' providing shared hardware access to
> multiple clients.
> 
> Why is this needed?
> 
> With the current audio solutions, we have a monolithic solution that has
> proven difficult to maintain. We'd really like to expose unrelated DSP
> functionality with different devices.
> 
> The best example is really HDMI. HDMI/DP audio interfaces are controlled
> by the same hardware, but are logically independent. What we end-up
> doing is re-adding the same solution over and over for each machine driver:
> 
> sound/soc/intel/boards$ git grep hda_dsp_hdmi_build_controls
> bxt_da7219_max98357a.c:         return hda_dsp_hdmi_build_controls(card,
> component);
> bxt_rt298.c:            return hda_dsp_hdmi_build_controls(card, component);
> cml_rt1011_rt5682.c:            return hda_dsp_hdmi_build_controls(card,
> component);
> ehl_rt5660.c:   return hda_dsp_hdmi_build_controls(card,
> pcm->codec_dai->component);
> glk_rt5682_max98357a.c:         return hda_dsp_hdmi_build_controls(card,
> component);
> hda_dsp_common.c:int hda_dsp_hdmi_build_controls(struct snd_soc_card
> *card,
> hda_dsp_common.h:int hda_dsp_hdmi_build_controls(struct snd_soc_card
> *card,
> hda_dsp_common.h:static inline int hda_dsp_hdmi_build_controls(struct
> snd_soc_card *card,
> skl_hda_dsp_common.h:   return hda_dsp_hdmi_build_controls(card,
> component);
> sof_da7219_max98373.c:          return hda_dsp_hdmi_build_controls(card,
> sof_pcm512x.c:  return hda_dsp_hdmi_build_controls(card,
> pcm->codec_dai->component);
> sof_rt5682.c:           return hda_dsp_hdmi_build_controls(card, component);
> sof_sdw_hdmi.c:         return hda_dsp_hdmi_build_controls(card,
> component);
> 
> and we also keep adding HDMI-related ASoC topology definitions for all
> the cards.
> 
> It would make a lot more sense if we could have ONE HDMI/DP card which
> is created, instead of managing HDMI/DP from the card that is supposed
> to deal with local accessories based on HDaudio/DMIC/SoundWire/I2S.
> 
> The audio probes are similar, we want to have a single probe client
> instead of adding audio probes to every single card we have to maintain.
> 
> On platforms where the DSP can deal with sensors, this would also allow
> the parent to expose IIO and HID clients.
> 
> Going back to this IPC test, maybe the commit message is unclear: we
> already have this functionality in the mainline, it's been very helpful
> for stress tests. What this patch shows is that moving the functionality
> to a client makes it possible to scale to 2 or more clients with a
> simple set of register/unregister. The device model makes it really easy
> to scale.
> 
> So yes, you are correct that for now there is a single user with very
> limited functionality. This is intentional to make the reviews simpler,
> but if/when this bus is part of the mainline we'll have additional
> users, and not just from Intel if you look at the reviewed-by tags.
> 
> We might even remove the platform devices used for the SoundWire master
> and use this instead :-)
> 
> Does this help?
> 

The reason we switched from using RDMA/LAN as an example was the impasse
in trying to get patches for all three moving parts  up through two different trees
at the same time (netdev and rdma).  It was becoming a huge mess.  The Intel
sound driver folks provided a consumer in a single tree to make submission smoother.

The patches for the LAN driver and RDMA are waiting to see what the final form of
ancillary bus will be so that they can match to the final API.  We still have the same
use case in the LAN/RDMA model.

-DaveE
diff mbox series

Patch

diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig
index cea7efedafef..55a2a20c3ec9 100644
--- a/sound/soc/sof/Kconfig
+++ b/sound/soc/sof/Kconfig
@@ -190,6 +190,16 @@  config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST
 	  Say Y if you want to enable IPC flood test.
 	  If unsure, select "N".
 
+config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_CLIENT
+	tristate "SOF enable IPC flood test client"
+	depends on SND_SOC_SOF_CLIENT
+	help
+	  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_RETAIN_DSP_CONTEXT
 	bool "SOF retain DSP context on any FW exceptions"
 	help
diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile
index 5e46f25a3851..baa93fe2cc9a 100644
--- a/sound/soc/sof/Makefile
+++ b/sound/soc/sof/Makefile
@@ -9,6 +9,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-test-objs := sof-ipc-test-client.o
+
 snd-sof-nocodec-objs := nocodec.o
 
 obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o
@@ -21,6 +23,8 @@  obj-$(CONFIG_SND_SOC_SOF_PCI) += snd-sof-pci.o
 
 obj-$(CONFIG_SND_SOC_SOF_CLIENT) += snd-sof-client.o
 
+obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_CLIENT) += snd-sof-ipc-test.o
+
 obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/
 obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/
 obj-$(CONFIG_SND_SOC_SOF_XTENSA) += xtensa/
diff --git a/sound/soc/sof/sof-ipc-test-client.c b/sound/soc/sof/sof-ipc-test-client.c
new file mode 100644
index 000000000000..c39d5009c75b
--- /dev/null
+++ b/sound/soc/sof/sof-ipc-test-client.c
@@ -0,0 +1,314 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//
+
+#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 <linux/ancillary_bus.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
+
+struct sof_ipc_client_data {
+	struct dentry *dfs_root;
+	char *buf;
+};
+
+/* helper function to perform the flood 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_client_data *ipc_client_data = cdev->data;
+	struct device *dev = &cdev->ancildev.dev;
+	struct sof_ipc_cmd_hdr hdr;
+	struct sof_ipc_reply reply;
+	u64 min_response_time = U64_MAX;
+	u64 avg_response_time = 0;
+	u64 max_response_time = 0;
+	ktime_t cur = ktime_get();
+	ktime_t test_end;
+	int i = 0;
+	int ret = 0;
+
+	/* 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 */
+	test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC;
+
+	/* send test IPC's */
+	for (i = 0; flood_duration_test ? ktime_to_ns(cur) < test_end : i < ipc_count; i++) {
+		ktime_t start;
+		u64 ipc_response_time;
+
+		start = ktime_get();
+		ret = sof_client_ipc_tx_message(cdev, hdr.cmd,
+						&hdr, hdr.size, &reply,
+						sizeof(reply));
+		if (ret < 0)
+			break;
+		cur = ktime_get();
+
+		/* compute min and max response times */
+		ipc_response_time = ktime_to_ns(ktime_sub(cur, 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;
+	}
+
+	if (ret < 0)
+		return ret;
+
+	/* return if the first IPC fails */
+	if (!i)
+		return ret;
+
+	/* compute average response time */
+	DIV_ROUND_CLOSEST(avg_response_time, i);
+
+	/* clear previous test output */
+	memset(ipc_client_data->buf, 0, IPC_FLOOD_TEST_RESULT_LEN);
+
+	if (flood_duration_test) {
+		dev_dbg(dev, "IPC Flood test duration: %lums\n", ipc_duration_ms);
+		snprintf(ipc_client_data->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(ipc_client_data->buf + strlen(ipc_client_data->buf),
+		 IPC_FLOOD_TEST_RESULT_LEN - strlen(ipc_client_data->buf),
+		 "IPC Flood count: %d\nAvg response time: %lluns\n", i, avg_response_time);
+
+	snprintf(ipc_client_data->buf + strlen(ipc_client_data->buf),
+		 IPC_FLOOD_TEST_RESULT_LEN - strlen(ipc_client_data->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_dfsentry_write(struct file *file, const char __user *buffer,
+				      size_t count, loff_t *ppos)
+{
+	struct dentry *dentry = file->f_path.dentry;
+	struct sof_client_dev *cdev = file->private_data;
+	struct device *dev = &cdev->ancildev.dev;
+	unsigned long ipc_duration_ms = 0;
+	bool flood_duration_test;
+	unsigned long ipc_count = 0;
+	char *string;
+	size_t size;
+	int err;
+	int ret;
+
+	string = kzalloc(count, GFP_KERNEL);
+	if (!string)
+		return -ENOMEM;
+
+	size = simple_write_to_buffer(string, count, ppos, buffer, count);
+
+	flood_duration_test = !strcmp(dentry->d_name.name, "ipc_flood_duration_ms");
+
+	/* set test completion criterion */
+	ret = flood_duration_test ? kstrtoul(string, 0, &ipc_duration_ms) :
+			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;
+		}
+
+		ipc_duration_ms = min_t(unsigned long, ipc_duration_ms, MAX_IPC_FLOOD_DURATION_MS);
+	} else {
+		if (!ipc_count) {
+			ret = size;
+			goto out;
+		}
+
+		ipc_count = min_t(unsigned long, ipc_count, MAX_IPC_FLOOD_COUNT);
+	}
+
+	ret = pm_runtime_get_sync(dev);
+	if (ret < 0 && ret != -EACCES) {
+		dev_err_ratelimited(dev, "error: 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) {
+		ret = err;
+		goto out;
+	}
+
+	/* 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_dfsentry_read(struct file *file, char __user *buffer,
+				     size_t count, loff_t *ppos)
+{
+	struct sof_client_dev *cdev = file->private_data;
+	struct sof_ipc_client_data *ipc_client_data = cdev->data;
+	size_t size_ret;
+
+	if (*ppos)
+		return 0;
+
+	/* return results of the last IPC test */
+	count = min_t(size_t, count, strlen(ipc_client_data->buf));
+	size_ret = copy_to_user(buffer, ipc_client_data->buf, count);
+	if (size_ret)
+		return -EFAULT;
+
+	*ppos += count;
+	return count;
+}
+
+static const struct file_operations sof_ipc_dfs_fops = {
+	.open = simple_open,
+	.read = sof_ipc_dfsentry_read,
+	.llseek = default_llseek,
+	.write = sof_ipc_dfsentry_write,
+};
+
+/*
+ * 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_test_probe(struct ancillary_device *ancildev,
+			      const struct ancillary_device_id *id)
+{
+	struct sof_client_dev *cdev = ancillary_dev_to_sof_client_dev(ancildev);
+	struct sof_ipc_client_data *ipc_client_data;
+
+	/*
+	 * The ancillary device has a usage count of 0 even before runtime PM
+	 * is enabled. So, increment the usage count to let the device
+	 * suspend after probe is complete.
+	 */
+	pm_runtime_get_noresume(&ancildev->dev);
+
+	/* allocate memory for client data */
+	ipc_client_data = devm_kzalloc(&ancildev->dev, sizeof(*ipc_client_data), GFP_KERNEL);
+	if (!ipc_client_data)
+		return -ENOMEM;
+
+	ipc_client_data->buf = devm_kzalloc(&ancildev->dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL);
+	if (!ipc_client_data->buf)
+		return -ENOMEM;
+
+	cdev->data = ipc_client_data;
+
+	/* create debugfs root folder with device name under parent SOF dir */
+	ipc_client_data->dfs_root = debugfs_create_dir(dev_name(&ancildev->dev),
+						       sof_client_get_debugfs_root(cdev));
+
+	/* create read-write ipc_flood_count debugfs entry */
+	debugfs_create_file("ipc_flood_count", 0644, ipc_client_data->dfs_root,
+			    cdev, &sof_ipc_dfs_fops);
+
+	/* create read-write ipc_flood_duration_ms debugfs entry */
+	debugfs_create_file("ipc_flood_duration_ms", 0644, ipc_client_data->dfs_root,
+			    cdev, &sof_ipc_dfs_fops);
+
+	/* enable runtime PM */
+	pm_runtime_set_autosuspend_delay(&ancildev->dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS);
+	pm_runtime_use_autosuspend(&ancildev->dev);
+	pm_runtime_set_active(&ancildev->dev);
+	pm_runtime_enable(&ancildev->dev);
+	pm_runtime_mark_last_busy(&ancildev->dev);
+	pm_runtime_put_autosuspend(&ancildev->dev);
+
+	return 0;
+}
+
+static int sof_ipc_test_cleanup(struct ancillary_device *ancildev)
+{
+	pm_runtime_disable(&ancildev->dev);
+
+	return 0;
+}
+
+static int sof_ipc_test_remove(struct ancillary_device *ancildev)
+{
+	return sof_ipc_test_cleanup(ancildev);
+}
+
+static void sof_ipc_test_shutdown(struct ancillary_device *ancildev)
+{
+	sof_ipc_test_cleanup(ancildev);
+}
+
+static const struct ancillary_device_id sof_ipc_ancilbus_id_table[] = {
+	{ .name = "snd_sof_client.ipc_test" },
+	{},
+};
+MODULE_DEVICE_TABLE(ancillary, sof_ipc_ancilbus_id_table);
+
+/*
+ * No need for driver pm_ops as the generic pm callbacks in the ancillary bus type are enough to
+ * ensure that the parent SOF device resumes to bring the DSP back to D0.
+ */
+static struct sof_client_drv sof_ipc_test_client_drv = {
+	.name = "sof-ipc-test-client-drv",
+	.ancillary_drv = {
+		.driver = {
+			.name = "sof-ipc-test-ancilbus-drv",
+		},
+		.id_table = sof_ipc_ancilbus_id_table,
+		.probe = sof_ipc_test_probe,
+		.remove = sof_ipc_test_remove,
+		.shutdown = sof_ipc_test_shutdown,
+	},
+};
+
+module_sof_client_driver(sof_ipc_test_client_drv);
+
+MODULE_DESCRIPTION("SOF IPC Test Client Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);