From b34bf9a6330277e3829b82e51674547400087807 Mon Sep 17 00:00:00 2001 From: Stefan Ghetu Date: Mon, 2 Nov 2026 15:38:42 +0200 Subject: [PATCH 1/2] ASoC: sof: imx: add SOF probes extraction client (IPC3) SND_SOC_SOF_DEBUG_PROBES was not selectable for i.MX and no auxiliary probes client device was registered. Add a hidden Kconfig symbol SND_SOC_SOF_IMX_PROBES (selected by SND_SOC_SOF_IMX8 when DEBUG_FS is on), a new imx-probes.c that registers the client, allocates a coherent DMA buffer and passes its bus address to the firmware in PROBE_INIT stream_tag. A 10 ms delayed work polls the DSP write counter, copies new data into the compress buffer and calls snd_compr_fragment_elapsed(). Known limits: polling only, single stream, 32-bit DMA address. Signed-off-by: Stefan Ghetu --- sound/soc/sof/imx/Kconfig | 9 ++ sound/soc/sof/imx/Makefile | 1 + sound/soc/sof/imx/imx-common.h | 3 + sound/soc/sof/imx/imx-probes.c | 190 ++++++++++++++++++++++++++++++ sound/soc/sof/imx/imx8.c | 5 + sound/soc/sof/sof-client-probes.c | 1 + 6 files changed, 209 insertions(+) create mode 100644 sound/soc/sof/imx/imx-probes.c diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig index 09d88ce5b9e6e5..1aa8d2c27a924f 100644 --- a/sound/soc/sof/imx/Kconfig +++ b/sound/soc/sof/imx/Kconfig @@ -22,11 +22,20 @@ config SND_SOC_SOF_IMX_COMMON This option is not user-selectable but automagically handled by 'select' statements at a higher level. +config SND_SOC_SOF_IMX_PROBES + tristate + select SND_SOC_SOF_DEBUG_PROBES + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. It pulls in the generic SOF + probes client (snd-sof-probes) for i.MX platforms. + config SND_SOC_SOF_IMX8 tristate "SOF support for i.MX8" depends on IMX_SCU depends on IMX_DSP select SND_SOC_SOF_IMX_COMMON + select SND_SOC_SOF_IMX_PROBES help This adds support for Sound Open Firmware for NXP i.MX8 platforms. Say Y if you have such a device. diff --git a/sound/soc/sof/imx/Makefile b/sound/soc/sof/imx/Makefile index 74b5ecad8fe8ce..b8d43ac7535b18 100644 --- a/sound/soc/sof/imx/Makefile +++ b/sound/soc/sof/imx/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) snd-sof-imx8-y := imx8.o +snd-sof-imx8-$(CONFIG_SND_SOC_SOF_IMX_PROBES) += imx-probes.o snd-sof-imx9-y := imx9.o snd-sof-imx-common-y := imx-common.o diff --git a/sound/soc/sof/imx/imx-common.h b/sound/soc/sof/imx/imx-common.h index 9bd711dbb5d03f..77d43550494c39 100644 --- a/sound/soc/sof/imx/imx-common.h +++ b/sound/soc/sof/imx/imx-common.h @@ -166,4 +166,7 @@ void imx8_dump(struct snd_sof_dev *sdev, u32 flags); extern const struct snd_sof_dsp_ops sof_imx_ops; +int imx_probes_register(struct snd_sof_dev *sdev); +void imx_probes_unregister(struct snd_sof_dev *sdev); + #endif diff --git a/sound/soc/sof/imx/imx-probes.c b/sound/soc/sof/imx/imx-probes.c new file mode 100644 index 00000000000000..20a45b262bdb3b --- /dev/null +++ b/sound/soc/sof/imx/imx-probes.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright 2026 NXP +// +// SOF probes client for i.MX (IPC3). The DSP cannot use a host DMA engine, so +// the host allocates a coherent buffer (header page + ring), gives its bus +// address to the firmware in PROBE_INIT (as stream_tag) and the firmware +// writes the extracted packets into the ring and publishes a byte counter in +// the first word of the header page. A delayed work copies new data into the +// compress buffer and wakes up the reader. + +#include +#include +#include +#include +#include +#include "../sof-priv.h" +#include "../sof-client-probes.h" +#include "../sof-client.h" +#include "imx-common.h" + +/* must match PROBE_HOST_HDR_SIZE in the firmware */ +#define IMX_PROBES_HDR_SIZE 4096 +#define IMX_PROBES_POLL_MS 10 + +struct imx_probes_stream { + struct delayed_work work; + struct snd_compr_stream *cstream; + struct device *dev; + void *vaddr; + dma_addr_t paddr; + size_t ring_size; + u32 sampling_rate; + u64 copied; +}; + +/* only one probes extraction stream exists */ +static struct imx_probes_stream imx_stream; + +static void imx_probes_work(struct work_struct *work) +{ + struct imx_probes_stream *s = &imx_stream; + struct snd_compr_runtime *rtd; + u8 *ring = s->vaddr + IMX_PROBES_HDR_SIZE; + u32 wr, n, off; + + if (!s->cstream || !s->vaddr) + return; + + rtd = s->cstream->runtime; + wr = readl(s->vaddr); + n = wr - (u32)s->copied; + + if (n > s->ring_size) { + /* reader too slow, drop the oldest data */ + s->copied = wr - s->ring_size; + n = s->ring_size; + } + + n = min_t(u32, n, rtd->buffer_size); + if (n) { + u32 src = (u32)s->copied % s->ring_size; + u32 dst = s->copied % rtd->buffer_size; + + off = src; + /* copy in chunks that do not wrap either buffer */ + while (n) { + u32 chunk = min3(n, (u32)(s->ring_size - off), + (u32)(rtd->buffer_size - dst)); + + memcpy(rtd->dma_area + dst, ring + off, chunk); + s->copied += chunk; + off = (off + chunk) % s->ring_size; + dst = (dst + chunk) % rtd->buffer_size; + n -= chunk; + } + snd_compr_fragment_elapsed(s->cstream); + } + + schedule_delayed_work(&s->work, msecs_to_jiffies(IMX_PROBES_POLL_MS)); +} + +static int imx_probes_compr_startup(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai, u32 *stream_id) +{ + /* real value (buffer address) is set in set_params */ + *stream_id = 0; + return 0; +} + +static void imx_probes_free(struct imx_probes_stream *s) +{ + cancel_delayed_work_sync(&s->work); + if (s->vaddr) + dma_free_coherent(s->dev, IMX_PROBES_HDR_SIZE + s->ring_size, + s->vaddr, s->paddr); + s->vaddr = NULL; + s->cstream = NULL; +} + +static int imx_probes_compr_shutdown(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + imx_probes_free(&imx_stream); + return 0; +} + +static int imx_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 sof_probes_priv *priv = cdev->data; + struct imx_probes_stream *s = &imx_stream; + struct snd_soc_pcm_stream *pstream = &dai->driver->capture; + + imx_probes_free(s); + + s->dev = sof_client_get_dma_dev(cdev); + /* DSP can only address 32-bit physical addresses */ + if (dma_set_coherent_mask(s->dev, DMA_BIT_MASK(32))) + return -ENOMEM; + s->ring_size = cstream->runtime->dma_bytes; + s->vaddr = dma_alloc_coherent(s->dev, IMX_PROBES_HDR_SIZE + s->ring_size, + &s->paddr, GFP_KERNEL); + if (!s->vaddr) + return -ENOMEM; + + s->cstream = cstream; + s->copied = 0; + s->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates); + INIT_DELAYED_WORK(&s->work, imx_probes_work); + + /* the firmware takes the buffer address from the stream_tag field */ + priv->extractor_stream_tag = (u32)s->paddr; + + return 0; +} + +static int imx_probes_compr_trigger(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + int cmd, struct snd_soc_dai *dai) +{ + struct imx_probes_stream *s = &imx_stream; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + schedule_delayed_work(&s->work, msecs_to_jiffies(IMX_PROBES_POLL_MS)); + break; + case SNDRV_PCM_TRIGGER_STOP: + cancel_delayed_work_sync(&s->work); + break; + } + + return 0; +} + +static int imx_probes_compr_pointer(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp, + struct snd_soc_dai *dai) +{ + tstamp->copied_total = imx_stream.copied; + tstamp->sampling_rate = imx_stream.sampling_rate; + + return 0; +} + +static const struct sof_probes_host_ops imx_probes_ops = { + .startup = imx_probes_compr_startup, + .shutdown = imx_probes_compr_shutdown, + .set_params = imx_probes_compr_set_params, + .trigger = imx_probes_compr_trigger, + .pointer = imx_probes_compr_pointer, +}; + +int imx_probes_register(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "imx-probes", 0, &imx_probes_ops, + sizeof(imx_probes_ops)); +} + +void imx_probes_unregister(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "imx-probes", 0); +} + +MODULE_IMPORT_NS("SND_SOC_SOF_CLIENT"); diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index 0820724dbb3491..3ce1f4fa6de3f5 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -295,6 +295,11 @@ static int imx8_ops_init(struct snd_sof_dev *sdev) sof_imx8_ops.debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem; + if (IS_ENABLED(CONFIG_SND_SOC_SOF_IMX_PROBES)) { + sof_imx8_ops.register_ipc_clients = imx_probes_register; + sof_imx8_ops.unregister_ipc_clients = imx_probes_unregister; + } + /* ... and finally set DAI driver */ sof_imx8_ops.drv = get_chip_info(sdev)->drv; sof_imx8_ops.num_drv = get_chip_info(sdev)->num_drv; diff --git a/sound/soc/sof/sof-client-probes.c b/sound/soc/sof/sof-client-probes.c index 124f5550815927..706b97e924af5f 100644 --- a/sound/soc/sof/sof-client-probes.c +++ b/sound/soc/sof/sof-client-probes.c @@ -582,6 +582,7 @@ static void sof_probes_client_remove(struct auxiliary_device *auxdev) static const struct auxiliary_device_id sof_probes_client_id_table[] = { { .name = "snd_sof.hda-probes", }, { .name = "snd_sof.acp-probes", }, + { .name = "snd_sof.imx-probes", }, {}, }; MODULE_DEVICE_TABLE(auxiliary, sof_probes_client_id_table); From 50dcd0f34f9a609101b29edbdd481a5ef9bd8032 Mon Sep 17 00:00:00 2001 From: Stefan Ghetu Date: Wed, 4 Nov 2026 10:14:26 +0200 Subject: [PATCH 2/2] ASoC: SOF: imx: probes: fix stream ownership and buffer accounting Reject a second open instead of freeing the buffer the DSP is writing to, and keep the active extractor tag when that happens. Initialize the work item at registration so cleanup can always cancel it. Track the DSP read cursor separately from the byte count reported to the compress core. Extend the 32-bit DSP counter across wraps, copy only into free space of the compress buffer, and reset the reported count on start and stop. Signed-off-by: Stefan Ghetu --- sound/soc/sof/imx/imx-probes.c | 95 ++++++++++++++++++++++--------- sound/soc/sof/sof-client-probes.c | 4 +- 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/sound/soc/sof/imx/imx-probes.c b/sound/soc/sof/imx/imx-probes.c index 20a45b262bdb3b..c383a7cf7c76a9 100644 --- a/sound/soc/sof/imx/imx-probes.c +++ b/sound/soc/sof/imx/imx-probes.c @@ -10,6 +10,8 @@ // compress buffer and wakes up the reader. #include +#include +#include #include #include #include @@ -31,50 +33,66 @@ struct imx_probes_stream { dma_addr_t paddr; size_t ring_size; u32 sampling_rate; - u64 copied; + u64 dsp_wr; /* bytes written by the DSP (32-bit counter extended) */ + u64 dsp_rd; /* bytes consumed from the DSP ring */ + u64 produced; /* bytes copied to the compress buffer in this run */ }; /* only one probes extraction stream exists */ static struct imx_probes_stream imx_stream; +static void imx_probes_sync_wr(struct imx_probes_stream *s) +{ + /* the DSP counter is 32-bit and wraps */ + s->dsp_wr += (u32)(readl(s->vaddr) - (u32)s->dsp_wr); +} + static void imx_probes_work(struct work_struct *work) { struct imx_probes_stream *s = &imx_stream; struct snd_compr_runtime *rtd; u8 *ring = s->vaddr + IMX_PROBES_HDR_SIZE; - u32 wr, n, off; + u64 pending, used; + u32 n, src, dst; if (!s->cstream || !s->vaddr) return; rtd = s->cstream->runtime; - wr = readl(s->vaddr); - n = wr - (u32)s->copied; - - if (n > s->ring_size) { - /* reader too slow, drop the oldest data */ - s->copied = wr - s->ring_size; - n = s->ring_size; + imx_probes_sync_wr(s); + + pending = s->dsp_wr - s->dsp_rd; + if (pending > s->ring_size) { + /* the DSP overwrote data we did not read, skip to the oldest valid */ + dev_warn_ratelimited(s->dev, "probes ring overrun, dropped %llu bytes\n", + pending - s->ring_size); + s->dsp_rd = s->dsp_wr - s->ring_size; + pending = s->ring_size; } - n = min_t(u32, n, rtd->buffer_size); + /* never overwrite data the reader has not consumed yet */ + used = s->produced - rtd->total_bytes_transferred; + n = min_t(u64, pending, rtd->buffer_size - used); if (n) { - u32 src = (u32)s->copied % s->ring_size; - u32 dst = s->copied % rtd->buffer_size; + bool copied = false; - off = src; + src = s->dsp_rd % s->ring_size; + dst = s->produced % rtd->buffer_size; /* copy in chunks that do not wrap either buffer */ while (n) { - u32 chunk = min3(n, (u32)(s->ring_size - off), + u32 chunk = min3(n, (u32)(s->ring_size - src), (u32)(rtd->buffer_size - dst)); - memcpy(rtd->dma_area + dst, ring + off, chunk); - s->copied += chunk; - off = (off + chunk) % s->ring_size; + memcpy(rtd->dma_area + dst, ring + src, chunk); + s->dsp_rd += chunk; + s->produced += chunk; + src = (src + chunk) % s->ring_size; dst = (dst + chunk) % rtd->buffer_size; n -= chunk; + copied = true; } - snd_compr_fragment_elapsed(s->cstream); + if (copied) + snd_compr_fragment_elapsed(s->cstream); } schedule_delayed_work(&s->work, msecs_to_jiffies(IMX_PROBES_POLL_MS)); @@ -84,26 +102,38 @@ static int imx_probes_compr_startup(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, struct snd_soc_dai *dai, u32 *stream_id) { + struct imx_probes_stream *s = &imx_stream; + + /* one stream only: do not touch the active stream's tag */ + if (s->cstream) + return -EBUSY; + + s->cstream = cstream; /* real value (buffer address) is set in set_params */ *stream_id = 0; return 0; } -static void imx_probes_free(struct imx_probes_stream *s) +static void imx_probes_free_buf(struct imx_probes_stream *s) { cancel_delayed_work_sync(&s->work); if (s->vaddr) dma_free_coherent(s->dev, IMX_PROBES_HDR_SIZE + s->ring_size, s->vaddr, s->paddr); s->vaddr = NULL; - s->cstream = NULL; } static int imx_probes_compr_shutdown(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, struct snd_soc_dai *dai) { - imx_probes_free(&imx_stream); + struct imx_probes_stream *s = &imx_stream; + + if (s->cstream != cstream) + return 0; + + imx_probes_free_buf(s); + s->cstream = NULL; return 0; } @@ -116,22 +146,26 @@ static int imx_probes_compr_set_params(struct sof_client_dev *cdev, struct imx_probes_stream *s = &imx_stream; struct snd_soc_pcm_stream *pstream = &dai->driver->capture; - imx_probes_free(s); + imx_probes_free_buf(s); s->dev = sof_client_get_dma_dev(cdev); /* DSP can only address 32-bit physical addresses */ if (dma_set_coherent_mask(s->dev, DMA_BIT_MASK(32))) return -ENOMEM; + /* firmware needs a power-of-two ring of at least 8 KiB */ + if (cstream->runtime->dma_bytes < SZ_8K || + !is_power_of_2(cstream->runtime->dma_bytes)) + return -EINVAL; s->ring_size = cstream->runtime->dma_bytes; s->vaddr = dma_alloc_coherent(s->dev, IMX_PROBES_HDR_SIZE + s->ring_size, &s->paddr, GFP_KERNEL); if (!s->vaddr) return -ENOMEM; - s->cstream = cstream; - s->copied = 0; + s->dsp_wr = 0; + s->dsp_rd = 0; + s->produced = 0; s->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates); - INIT_DELAYED_WORK(&s->work, imx_probes_work); /* the firmware takes the buffer address from the stream_tag field */ priv->extractor_stream_tag = (u32)s->paddr; @@ -147,10 +181,17 @@ static int imx_probes_compr_trigger(struct sof_client_dev *cdev, switch (cmd) { case SNDRV_PCM_TRIGGER_START: + /* a new run: the core restarts its counters, skip stale data */ + s->produced = 0; + if (s->vaddr) { + imx_probes_sync_wr(s); + s->dsp_rd = s->dsp_wr; + } schedule_delayed_work(&s->work, msecs_to_jiffies(IMX_PROBES_POLL_MS)); break; case SNDRV_PCM_TRIGGER_STOP: cancel_delayed_work_sync(&s->work); + s->produced = 0; break; } @@ -162,7 +203,7 @@ static int imx_probes_compr_pointer(struct sof_client_dev *cdev, struct snd_compr_tstamp64 *tstamp, struct snd_soc_dai *dai) { - tstamp->copied_total = imx_stream.copied; + tstamp->copied_total = imx_stream.produced; tstamp->sampling_rate = imx_stream.sampling_rate; return 0; @@ -178,6 +219,8 @@ static const struct sof_probes_host_ops imx_probes_ops = { int imx_probes_register(struct snd_sof_dev *sdev) { + INIT_DELAYED_WORK(&imx_stream.work, imx_probes_work); + return sof_client_dev_register(sdev, "imx-probes", 0, &imx_probes_ops, sizeof(imx_probes_ops)); } diff --git a/sound/soc/sof/sof-client-probes.c b/sound/soc/sof/sof-client-probes.c index 706b97e924af5f..a4604db16a1126 100644 --- a/sound/soc/sof/sof-client-probes.c +++ b/sound/soc/sof/sof-client-probes.c @@ -55,7 +55,9 @@ static int sof_probes_compr_startup(struct snd_compr_stream *cstream, ret = ops->startup(cdev, cstream, dai, &priv->extractor_stream_tag); if (ret) { dev_err(dai->dev, "Failed to startup probe stream: %d\n", ret); - priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + /* keep the tag of the stream that is already running */ + if (ret != -EBUSY) + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; sof_client_core_module_put(cdev); }