Skip to content

Stop PHRASE_DETECTED notification flood and update the test tools - #8

Closed
ujfalusi wants to merge 249 commits into
lgirdwood:mainfrom
ujfalusi:peter/topic/wov-d0i3-phrase_flood_fix
Closed

ujfalusi wants to merge 249 commits into
lgirdwood:mainfrom
ujfalusi:peter/topic/wov-d0i3-phrase_flood_fix

Conversation

@ujfalusi

@ujfalusi ujfalusi commented Oct 9, 2026

Copy link
Copy Markdown

Send one notification and drop the NO_PERIOD_WAKE from the tinyalsa / alsa-lib tools

The alsa-lib would break after 15s wait, it would need a polling thread to be able to work properly
The tinyalsa version was tight-loop polling in PCM_NOIRQ mode, which burns CPU. Waiting for period elapsed is better.

softwarecki and others added 30 commits July 23, 2026 16:31
Rework the RTNR module to only use the sink/source API to prepare SOF
for the full transition to pipeline 2.0.

Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
…l send

The primary-core watchdog timeout notification used the deprecated
old-interface call intel_adsp_ipc_send_message_emergency() together with
the Intel-specific INTEL_ADSP_IPC_HOST_DEV device handle.

Route it instead through the generic ipc_platform_send_msg_direct(), which
maps to ipc_service_send_critical() and the backend critical/emergency
send, identical register behaviour (busy-wait, no ack, bypasses flow
control), but expressed with the backend-agnostic ipc_service API and a
struct ipc_msg, mirroring the secondary-core path.

This removes the watchdog's dependency on the old interface and on the
Intel host IPC device macro, dropping the intel_adsp_ipc*.h includes.

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
The IPC4 fuzzer hit an out-of-bounds read in
ipc4_find_all_dma_configs_tlvs_only() while parsing a copier gateway
config blob as a TLV sequence. AddressSanitizer reports a SEGV on a wild
address ~16 MB below the config buffer.

Root cause: the per-TLV bounds check and the iterator both do pointer
arithmetic on the host-controlled tlvs->length without guarding against
overflow. On 32-bit targets (native_sim and the Xtensa DSPs) a large
length wraps the pointer:

	if ((uintptr_t)tlvs->value + tlvs->length > end_addr)   /* wraps */
		return IPC4_INVALID_REQUEST;
	...
	tlvs = tlv_next(tlvs);   /* tlvs + 8 + length, also wraps */

With length = 0xff000000 and a 32-byte buffer, tlvs->value + length
wraps to a value below end_addr, so the check passes; tlv_next() wraps to
the same address, which is still below end_addr, so the loop continues and
dereferences that wild pointer. config_length is already bounded to the
init payload (so the buffer itself is small and valid) - this is a
separate overflow inside the TLV walk, on the length field embedded in the
blob.

Compare tlvs->length against the remaining space using subtraction, which
cannot overflow because the (tightened) loop condition guarantees the TLV
header - and therefore tlvs->value - is within the buffer. tlv_next() is
thus bounded by end_addr as well. The loop condition now also requires the
full 8-byte header to fit before it is dereferenced.

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
The debug_stream.py tool outputs exception records in a format that
differs from the QEMU format the decoder originally targeted:

 - Register values can be (nil) instead of 0x0, e.g. 'A4 (nil)'
 - Backtrace entries are on separate lines after 'Backtrace:' rather
   than all on a single line
 - Additional registers LCOUNT and THREADPTR are present

Update the parser to handle both formats:

 - Fix (nil) regex: the trailing word boundary anchor failed on the
   closing parenthesis; split hex and decimal into separate capture
   groups to avoid the issue
 - Parse backtrace entries across multiple lines after the Backtrace:
   header, stopping when a line contains non-backtrace content
 - Add LCOUNT and THREADPTR to the accepted register list

The existing single-line backtrace and QEMU register formats continue
to work unchanged.

Signed-off-by: Jyri Sarha <jyri.sarha@linux.intel.com>
This reverts commit 48cdc4d. struct
sys_sem cannot be on heap and we can have many LL tasks so using a
statically allocated pool of them doesn't look like a good idea
either. Using k_sem is slower, but so far there don't seem to be good
alternatives to that.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
Total of 821 commits.

Changes include:

21a30f3f9a62 llext: correct got entry reads in xtensa elf
e0f04c284729 Revert "kernel: timeout: add sys_clock_idle_enter() for
             low-power idle handoff"
7d0bb85adc29 kernel: timeout: add sys_clock_idle_enter() for low-power
             idle handoff
3e93f462ac41 soc: nxp: use diamond include for non-local header files
a77fc1e6e870 boards: nxp: use diamond include for non-local header files
eec6a8f52a63 arch: x86, xtensa: support the idle-hook CPU load backend
25d08114e92e arch: gate idle notification hooks on SYS_IDLE_HOOKS
b8585ef254c1 soc: intel: use diamond include for non-local header files
715e6c494266 dts: arm: use diamond include for non-local header files
6e245d5d3704 lib/sys: add sys prefix to word granular access functions
439718a59814 xtensa: remove CPU mask pin only special code path
539fec76a346 logging: widen raw timestamp format to full uint width
c1c448133b57 modules: hal_nxp: fix i.MX943 Cortex-A device build
5c1758aed791 west.yml: remove unused files from hal_nxp
11b534905147 drivers: timer: declare 64-bit counter widths to the
             generic core
0bbd4843faa3 drivers: timer: fix the equality-match compare catch-up
             livelock
72d14283bda2 drivers: timer: intel_adsp_timer: use the generic timer
             core
e8163a10525e drivers: timer: native_sim_timer: use the generic timer
             core
4b0877cebe95 drivers: timer: xtensa_sys_timer: use the generic timer
             core
945af04840a2 lib: os: cpu_load: close the idle window at ISR entry
3244b759ddf3 Revert "west.yml: update hal nxp to the latest version"
a9f8e1a2391f west.yml: update hal nxp to the latest version

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
…URE_CH

The BE DAI and all widgets in the capture path should honor the number of
capture channels set by SDW_JACK_CAPTURE_CH.

Before this change, the value of SDW_JACK_CAPTURE_CH only affected the
min/max channels of the FE DAI but all the other widgets in the pipeline
were defaulting to 2-channel. Because of this, the codec DAI was always
asked for a 2-channel capture, which won't work on codecs that only
support 1 channel. And the host-copier output was 2-channel audio that
could not be connected to the 1-channel DAI.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
module_set_large_config() reads a SET_LARGE_CONFIG payload out of the
fixed-size HOSTBOX mailbox. For a single-fragment config (init_block &&
final_block) the number of bytes copied, fragment_size, is set verbatim
to the host-controlled data_off_size (a 20-bit field, up to ~1 MB),
without bounding it to the mailbox. The .set_configuration handler
(e.g. comp_data_blob_set() -> memcpy_s()) then copies that many bytes
from the ~4 KB mailbox; memcpy_s() only validates the destination size,
so the source over-read is not caught and reads past
MAILBOX_HOSTBOX_BASE.

A fuzzer-crafted request (data_off_size = 64778 from a 4096-byte
mailbox) triggers a global-buffer-overflow read reported by
AddressSanitizer.

Reject a single fragment larger than MAILBOX_HOSTBOX_SIZE, mirroring the
range check already present on the GET path (commit f72dbb2) and the
mailbox bound used at module init.

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
This fixes a regression introduced by
bde2726 ("ipc4: add user-space command-forwarding context")
which replaced

    ipc_buf.comp.core = cpu_get_id();

with

    ipc_buf.comp.core = src->ipc_config.core;

The replacement was needed to prepare the function to be called from
user space, where cpu_get_id() is privileged.

However, it broke the cross-core bind case: such a bind is always
processed on the primary core, so the two expressions are not
equivalent there.

Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
k_thread_cpu_pin() must not be called on a running thread, and the
worker must not execute while its memory domain is being modified.
Suspend the worker thread before pinning it to the module core and
before the module memory domain is set up, and resume it once the
domain has been configured.

This fixes a crash in tests with a userspace DP module on a secondary
core: the worker thread started by k_work_user_queue_start() was still
running on another core and had not yet reached its queue wait
condition by the time k_thread_cpu_pin() was called.

Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
The existing sof-ptl-rt713-l3-rt1320-l1.tplg includes 2ch PCH DMIC. And
the '-2ch' suffix will be added in the Linux driver. Rename
sof-ptl-rt713-l3-rt1320-l1 to sof-ptl-rt713-l3-rt1320-l1-2ch.
It is fine because the sof-ptl-rt713-l3-rt1320-l1.tplg was not released
to sof-bin yet.

Signed-off-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Remove XTENSA_BACKTRACE_EXCEPTION_DUMP option from
debug_stream_overlay.conf since its removed from Zephyr in commit:

ba8bb9a20f97 arch: xtensa: change backtrace to use EXCEPTION_DUMP

Signed-off-by: Jyri Sarha <jyri.sarha@linux.intel.com>
Rework the multiband drc module to only use the sink/source api to prepare
sof for the full transition to pipeline 2.0.

Signed-off-by: Piotr Hoppe <piotr.hoppe@intel.com>
Eliminate multiple instances of IPC4 data copying, use simple type-
casts instead. This removes stack objects and replaces run-time
copying with compile-time pointer substitution.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
copier_alh_assign_dai_index() reads alh_cfg.count from the host-supplied
gateway config blob and walks alh_cfg.mapping[0..count). Count was only
bounded by the mapping[] array size, not the actual blob size, so a malformed
blob could make the mapping walk read past config_data and underflow
dma_config_length in the HDA branch.

Validate that the blob contains the fixed header before reading count, then
ensure count is within the mapping[] bound and that its computed ALH
configuration size fits in the blob. This rejects malformed configurations
before any mapping[] access.

HDA always reads mapping[0], including for a single gateway. Reject a zero
count in this path so a header-only blob cannot cause that out-of-bounds read.
Keep zero-count behavior unchanged for single ALH, which does not access
mapping[]. Use %zu when logging blob_size to preserve size_t values on
64-bit builds.

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
Update Zephyr to include 24f16b4f886c ("lib: os: p4wq: skip
k_thread_cpu_mask_clear() in PIN_ONLY mode").

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
This patch fixes the issue with the Phase Vocoder module producing
strong rattle with other than s16 format decoded output from compress
decoders such as MP3 and AAC.

The pipeline bind path only propagates the container frame_fmt into the
shared buffer between two modules; it does not populate valid_sample_fmt.
As a result source_get_valid_fmt() on the phase vocoder's input returned
the zero-initialised value (S16_LE), so the wrong processing variant was
selected whenever the topology declared a valid bit depth other than 16.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
The .cold section contains executable code, while .coldrodata
contains data. Therefore their MMU flags have to be different,
particularly when mapping for userspace. Add a page-size alignment to
account for such cases.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
When the user changes the speed control the component preserves the
polar analysis state across the reset so that the interpolation can
continue smoothly, but the synthesis phase accumulator output_phase
was left drifting. Each output IFFT in stft_do_fft_ifft() advances
output_phase by an interpolated one-analysis-hop delta of the form
(1 - frac) * angle_delta_prev + frac * angle_delta, where frac is
the current interpolation position between two consecutive analysis
frames. That delta is added irrespective of the current speed, so at
non-unity speed output_phase runs faster than the true polar angle.
On top of that, the first post-reset IFFT does not consume a new
input FFT yet still applies its phase interpolation step (with
frac = 0, so the added term equals angle_delta_prev), which adds one
extra angle_delta_prev per reset. After a repeated excursion (for
example 1.0 -> 0.5 -> 1.0 with several intermediate steps) both
effects accumulate as a random per-bin phase offset that persists
after returning to unity speed. Perceptually this smears transients
and dulls the sound even though the audio is being processed at
speed 1.0 again.

The steady-state invariant at speed 1.0 is that after each IFFT's
phase interpolation step output_phase equals polar_prev.angle. To
preserve this invariant across a speed change, re-anchor output_phase
to polar_prev.angle minus angle_delta_prev in reset_for_new_speed().
The first post-reset IFFT then lands output_phase exactly on
polar_prev.angle, and all subsequent IFFTs advance normally with no
cumulative offset. Existing NULL and channel-count guards keep the
init-time call from touching buffers before they are allocated.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Without an explicit teardown a fuzz testcase that successfully creates
components, buffers or pipelines leaves them registered in
global_ipc->comp_list. The next LLVMFuzzerTestOneInput() then sees a
non-empty topology it never asked for, which both hides bugs (crashes
that depend on freshly-empty state are missed) and fabricates them
(crashes that only occur because of carry-over from a previous case are
unreproducible when the artifact is replayed on its own).

Add a posix-only teardown helper, called from posix_fuzz_case_begin()
once at the start of every testcase before the input is staged. The
helper:

  * Runs a pre-pass that forces every COMP_TYPE_COMPONENT to
    COMP_STATE_READY and initialises any NULL bsource_list/bsink_list
    pointers, because ipc_comp_free() returns -EINVAL (and silently
    leaks the entry) for a component that is not READY or whose buffer
    lists were never list_init()'d - the latter happens when a
    component was registered but its init failed partway through. The
    pre-pass also cancels any active pipeline pipe_task so
    ipc_pipeline_free() does not stall waiting for it (up to 100 LL
    periods on native_sim).
  * Drains each type in batches: it snapshots up to POSIX_TEARDOWN_BATCH
    ids, frees them with the typed helper, and repeats until no entry of
    that type remains, so the number of objects a testcase creates is not
    bounded by the snapshot size (walking and freeing comp_list in one
    pass is unsafe because the SOF free helpers unlink each entry). A
    batch that frees nothing ends the loop so it cannot spin forever.
  * Frees in dependency order COMPONENT -> BUFFER -> PIPELINE so the
    topology layer never dereferences an already-freed parent. The
    BUFFER pass is compiled out for IPC4 because ipc4/helper.c never
    stores COMP_TYPE_BUFFER and ipc_buffer_free() does not exist there.
  * Ends with a force-drain pass that walks the whole residual list with
    list_for_item_safe() and removes each entry with list_item_del() +
    rfree(), so comp_list is guaranteed empty on return regardless of how
    many objects remained or of any future COMP_TYPE_* the typed passes
    do not cover.

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
The LL scheduler semaphore is used by the userspace, so it has to be
allocated dynamically. On the other hand dynamic object freeing from
the userspace is unsupported by design. To securely free the
semaphore object we add two syscalls that guarantee, that object
freeing cannot be abused.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
ipc4_get_comp_drv() returned NULL unconditionally on native_sim because
the RIMAGE_MANIFEST guard (Intel ADSP ACE/CAVS only) was the sole path
to a driver UUID lookup. Every INIT_INSTANCE call therefore failed with
IPC4_MOD_NOT_INITIALIZED before creating any comp_dev, making the entire
module instance verb surface (CONFIG_GET/SET, LARGE_CONFIG on real
modules, BIND, UNBIND, DELETE_INSTANCE) unreachable from the fuzzer.

Resolve module drivers on native_sim from a small fuzzer-only helper,
ipc4_get_fuzzer_drv(), called from the CONFIG_ARCH_POSIX_LIBFUZZER branch
of ipc4_get_comp_drv() so the heavily-used manifest lookup path is left
untouched. It mirrors the IPC3 whitebox hack in posix/ipc.c: module_id is
a 1-based index into the runtime component driver list, counting only
instantiable module-adapter drivers so the fuzzer's module_id space
matches what a real signed manifest exposes. Drivers with no ops.create,
and internal gateway drivers (SOF_COMP_HOST/DAI) that are never IPC4
modules, are skipped (otherwise a module_id could land on an
unconfigurable component or a NULL create op).

module_id 0 (BaseFW) intentionally resolves to NULL: BaseFW is never
created as a pipeline component, its messages are dispatched directly by
base_fw.c. A tr_err() logs any module_id that maps to no driver to help
developers and agents triage. The change is inactive in all non-fuzz
builds.

Measured impact (UBSan IPC4, seed=1, 20 s, small corpus):
  cov ~558 -> ~3974  (~7x lift from previously dead module instance paths)

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
The IPC fuzzer hit a NULL pointer dereference in the IPC3 path while
triggering a pipeline that contains a selector component. Both
AddressSanitizer and UBSan report a READ SEGV at address 0x30 in
dev_comp_type() (src/include/sof/audio/component.h). 0x30 is the offset
of comp_dev::ipc_config.type, i.e. the accessor was called on a NULL
comp_dev.

Root cause: a comp_buffer links a producer on its ->source side to a
consumer on its ->sink side, and buffers are zero-initialised, so an
unconnected side stays NULL. selector_trigger() checks only that its
source buffer exists and then passes that buffer's producer component
straight to dev_comp_type() to look for an upstream KPB:

	type = dev_comp_type(comp_buffer_get_source_component(sourceb));

When the source buffer is attached to the selector but never attached to
an upstream producer, comp_buffer::source is NULL and dev_comp_type()
dereferences it. The mirror-image lookup in kpb.c already guards against a
missing peer component; selector_trigger() was the only site doing this
without the check.

Look up the producer component first and treat a missing producer as
"not a KPB" instead of dereferencing it. Also honour the comp_set_state()
return value: the crashing trigger was a PAUSE issued from
COMP_STATE_READY, which comp_set_state() already rejects with -EINVAL, but
selector_trigger() ignored the error and fell through to the dereference.
Bail out on error like every other component trigger handler.

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
The IPC fuzzer built an IPC3 pipeline whose component owned a
half-connected buffer - present on the component's buffer list but with
no component attached to its opposite end - and then issued a
SOF_IPC_STREAM_POSITION request. ipc_stream_position() calls
pipeline_get_timestamp(), which walks the graph with
pipeline_get_dai_comp() to locate the DAI endpoint. For each hop the walk
takes the buffer's peer component via buffer_get_comp() and immediately
dereferences comp->pipeline. For a dangling buffer buffer_get_comp()
returns NULL, so the read faults at NULL + 8 (the ->pipeline field).

A comp_buffer only carries a peer on a side once both ends are attached,
but the IPC3 graph is assembled incrementally (each COMP_CONNECT attaches
a single end) so a buffer can be left half-connected. The STREAM_POSITION
path reaches this walk directly, independent of the trigger path, and
does not go through the pipeline-run connectivity checks: a pipeline can
be completed and then have a half-connected buffer attached by a later
COMP_CONNECT, and a subsequent POSITION request walks the dangling edge.

Guard the peer before dereferencing it, mirroring the existing checks in
the tree: the IPC4 sibling pipeline_get_dai_comp_latency() already checks
"if (!source || !source->pipeline)", and the XRUN rewind walk in
pipeline_trigger_xrun() checks "if (!buffer_comp || !buffer_comp->pipeline)".
A fully-connected buffer always has a non-NULL peer and a cross-pipeline
peer keeps a non-NULL pointer, so only a genuinely dangling buffer is
rejected and valid topologies are unaffected. pipeline_get_timestamp()
already tolerates a NULL DAI result.

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
Qualify the source pointer of circular buffer as const in cir_buf_copy()
and cir_buf_bytes_without_wrap() functions.

Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
Rework the pcm conversion and dma copy paths to operate on circular buffer
source and sink descriptors instead of the legacy audio stream buffers.

Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
scheduler_dp_ll_tick() is currently registered as a notifier
callback, but it's always triggered deterministically, always with
the same-core-only flag, which leads to it being called immediately.
So the notifier only adds a layer of indirection and reduces clarity.
Replace it with a direct function call.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
elf_error() negates its argument, so an already negative code was turned
into a positive value that callers testing "ret < 0" take for success.

Signed-off-by: Adrian Bonislawski <adrian.bonislawski@intel.com>
cpu_get_id() ultimately reads a privileged special register via
arch_proc_id() (e.g. the Xtensa PRID register). When low-atency
pipelines run in user-space threads (CONFIG_SOF_USERSPACE_LL), this read
is issued from user mode and faults. While many direct usages of
cpu_get_id() have been removed from SOF codebase, multiple usages
remain. As the remaining usages are mostly on less frequently used code
paths (most via cpu_is_me() call, which is used in IPC handling), opt to
keep the remaining cpu_get_id() calls and make the function available as
a system call.

The system call machinery is gated on CONFIG_SOF_FULL_ZEPHYR_APPLICATION
so unit-test builds keep the plain inline definition.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
When CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP isn't selected, dynamically
allocated driver objects should still be accessible to the userspace.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
lrgirdwo and others added 28 commits October 2, 2026 13:26
Add an ALSA mixer switch control 'ecns_kpb_test_signal' and IPC4 handler
to enable generating an incrementing-by-1 monotonic 16-bit integer test signal
on the ECNS clean mono output pin (Pin 0) instead of microphone data.

This provides an end-to-end mechanism to verify sample continuity and detect
any sample drops, skips, or buffer corruption between ECNS, KPB, WoV arbiter,
and host capture DMA.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
… on drain

Explicitly configure sel_sink and host_sink buffer parameters to mono S16
during kpb_prepare(), preventing default 2-channel buffer configuration
from halving draining throughput.

Eliminate the transient KPB_STATE_BUFFERING state transition in
kpb_buffer_data() so asynchronous keyword triggers arriving during the 1ms
buffering cycle are not rejected with 'wrong KPB state'.

Subscribe to NOTIFIER_ID_WOV_CTRL to handle WOV_ARB_CMD_RESUME, cancelling
active draining tasks and resetting the history buffer cleanly back to
KPB_STATE_RUN on stream reset. Also accept COMP_STATE_PREPARE in
is_sink_ready, add buffer_stream_writeback() on sink updates, and align
pacing calculations.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
…ilence fill

Configure stream parameters on sink and Pin 0 source buffers during
wov_arb_prepare() to guarantee mono S16 layout.

In wov_arb_copy(), simplify active source buffer discovery, compute copy
frames using MIN(src_frames, dst_frames) with exact audio_stream_frame_bytes(),
and add buffer_stream_invalidate() / buffer_stream_writeback() for cache
coherency.

Remove synthetic silence insertion (fill_bytes), which injected zero frames
when the source momentarily had fewer frames than the sink had free space,
corrupting continuous audio data streams.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
…n 20-run suite

Set avail_min to 160 frames (10ms) via snd_pcm_sw_params_set_avail_min()
in wov_blocking_read.c to wake poll() promptly on drained audio arrival under
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, and adopt a read-first capture loop before
sleeping on -EAGAIN.

In run_wov_10x.py, add check_wav_continuity() to mathematically verify 0
sample skips in recorded test signal WAV files across all S0 and D0i3
iterations, automatically enable ecns_kpb_test_signal, make control resets
conditional, and tune runtime PM settling delays.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
When the host deletes a pipeline under IPC4 (e.g. during stream teardown),
components may still be in PREPARE or ACTIVE state if an explicit RESET
trigger was not issued beforehand. ipc_comp_free() previously rejected
freeing any component not in COMP_STATE_READY with -EINVAL, causing
SOF_IPC4_GLB_DELETE_PIPELINE to fail with IPC4_INVALID_RESOURCE_STATE (12).

Attempt to stop and reset the component to release its resources and
transition it to COMP_STATE_READY before proceeding with component
freeing.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
…esume

- During draining of pre-roll history, newly buffered real-time frames
  (*rt_stream_update) need to be drained directly into the sink buffer
  before switching to HOST_COPY state so no frames are dropped at the
  history-to-realtime boundary.
- Avoid forcefully setting kpb->sel_sink component state to COMP_STATE_ACTIVE
  on WOV_ARB_CMD_RESUME when selector copy was never paused by KPB.
- Ensure host copier comp_copy is invoked after buffer production during
  drain bursts to update stream pointers in no-IRQ mode.
- Prevent duplicate notifier registrations across prepare and reset cycles.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
…port 32-bit audio

- Implement wall-clock rate limiting (10ms) on phrase detection notifications
  to the host driver using sof_cycle_get_64() to prevent mailbox queue
  overflow and timeout during fast drain bursts.
- Add support for 32-bit sample depth audio streams and channel/width
  conversion between 32-bit KPB drain buffers and host copier sinks.
- In IPC4 mode, translate base module configuration to stream params
  directly via ipc4_base_module_cfg_to_stream_params().

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
…pins

- Add support for 32-bit sample formats across both output pins:
  * Pin 0: Mono clean stream feeding 32-bit KPB circular buffer.
  * Pin 1: Stereo clean stream feeding Host Copier 10 (supporting
    S16_LE, S24_LE, S32_LE).
- Implement flexible sample width conversions between 16-bit and 32-bit
  containers across input DMIC DAI copiers and downstream consumers.
- Ensure correct frame consumption tracking across active pins.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
…multi-slot WoV

With 32-bit audio internalized across ECNS and KPB, the 2000ms KPB circular
pre-roll history buffer requires 128KB of heap space. Instantiating 7 pipelines,
3 parallel microWakeWord instances, and 32-bit DMA buffers exceeded the default
256KB heap (resulting in buffer_alloc_range failures for DAI DMA buffers).

Increase CONFIG_SOF_ZEPHYR_HEAP_SIZE to 0x80000 (512KB), matching
intel_adsp_ace30_ptl.conf, and enable runtime heap statistics and arena sizing.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
- Update DMIC WoV feature and standalone PTL/WCL 4-channel manifests to
  internalize 32-bit audio data across DMIC DAI copier, ECNS, KPB, and
  WOV Arbiter pipelines.
- Add S16_LE, S24_LE, and S32_LE multi-format capture capabilities on
  PCM 10 (ECNS) and PCM 11 (DMIC Multi-WOV).
- Update prebuilt topology binaries for PTL and WCL in build/.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
- In wov_blocking_read, recover from -EPIPE on no-period-wakeup streams
  and retry active slot kcontrol query to ensure robust slot matching
  upon receiving audio.
- In run_wov_10x.py, report subprogram stdout and stderr on test failure
  to aid rapid diagnosis.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add PCAN (Per-Channel AGC Normalization) fixed-point math library for
normalizing mel filterbank energies. PCAN estimates per-channel noise
background, applies non-linear wide-dynamic-range AGC compression with
fixed-point LUT gain lookup, and delivers scaled features for streaming
keyword-spotting networks.

Include ztest test suite with MATLAB reference models covering gain
lookup, noise estimation, dynamic shrinking, stream processing, and
corner cases.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Add 8-bit PCAN-normalized Mel feature extraction to the MFCC module.
When CONFIG_COMP_MFCC_PCAN is enabled, MFCC applies Per-Channel Energy
Normalization to its Mel filterbank outputs and quantizes the result to
int8, matching microWakeWord input requirements.

Ensure continuous frame delivery in compress mode by removing silence
suppression from mfcc_output_compress(), as downstream streaming wake-word
detectors require continuous feature hops without gaps. Update the MFCC
setup tuning scripts and topology component configurations to provide
10 ms PCAN and non-PCAN configurations with VAD and DTX disabled.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Allow phrase detection modules to operate without an audio sink consumer.
Detection modules emit event notifications rather than audio data streams.

Guard against division by zero in module_adapter_calculate_dp_period()
when sinks have no frame bytes or sample rate defined. Allow NULL sinks
in module_adapter_prepare() when mod->max_sinks is zero, enabling
event-only modules to prepare and run cleanly.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Update microWakeWord to consume 8-bit PCAN-normalized Mel features directly
from the upstream MFCC module. Add built-in TFLM fruit model headers
(strawberry, banana, orange) as well as hey_jarvis and hi_intel, and
update the model loading and inference pipeline.

Fix input data draining when paused by requesting available frames with
source_get_data() before calling source_release_data(). Lower the warmup
requirement and consecutive detect threshold to allow responsive detection.
Clean up macro redefinitions in detect_test.c and update tuning scripts.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Add a Kconfig choice (CONFIG_ECNS_KPB_MONO_AVG_*) to select which input
channels are averaged into the mono detection output sent to KPB. This
allows boards where the first microphone pair is degraded to select the
higher quality pair.

Round and saturate 32-to-16 bit sample conversions so precision is preserved
from the 32-bit detection path. Update output buffer format declarations
to match topology requirements. Keep pin 0 (16 kHz detection) and pin 1
(48 kHz host) processing independent, and continuously drain unconsumed
input pins when downstream consumer sinks are inactive or full to prevent
DMIC hardware FIFO overflow.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
When downstream detector mixout buffers are full, KPB previously
halted without buffering into its history ring or consuming from
its source. This back-propagated full buffers upstream and froze
the detection path.

Consume available source frames and buffer them into the circular
history buffer even if the selector sink has no free space. Forward
samples to the selector sink when space is available, keeping both
history recording and upstream data flow active. Remove the unused
static function kpb_clear_history_buffer().

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
…trol

Setting wov_active_slot via kcontrol previously marked the chosen slot
as active, transitioning the arbiter into draining mode and causing
it to reject subsequent keyword detections.

Leave the arbiter in listening mode (WOV_ARB_NO_ACTIVE) when setting a slot
filter via kcontrol. The targeted slot remains armed while sibling slots
are paused to reduce DSP load, allowing natural keyword detections to be
accepted and processed.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
When a capture stream is starved or waiting for detection audio, logging
every 20 ms floods the trace buffer and causes diagnostic log messages
to be dropped.

Increase SOF_MIN_NO_BYTES_INTERVAL_MS from 20 ms to 1000 ms so that
trace transport bandwidth is conserved during idle capture periods.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Enable CONFIG_COMP_MFCC_PCAN, CONFIG_COMP_MWW_PCAN, and debug tracing
on intel_adsp_cavs25, intel_adsp_cavs25_tgph, intel_adsp_ace30_ptl, and
intel_adsp_ace30_wcl.

On cavs25, select CONFIG_ECNS_KPB_MONO_AVG_CH_3_4 to average microphone
channels 3 and 4 into the mono KPB output, avoiding degraded microphone
pairs. Increase CONFIG_IDC_TIMEOUT_US to 100000 on cavs25 and cavs25_tgph
to prevent secondary core bring-up timeouts during SMP boot.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Configure dmic-wov-multi topologies and manifests to support 4-channel
DMIC input on the detection path (dmic16k, eqiir, and ECNS pin 0) using
32-bit container formats. Offer the single 4-channel 32-bit input format
matching the NHLT blob on dmic01, resolving channel duplication.

Add conditional inclusion of 10 ms PCAN MFCC configuration blobs via
IncludeByKey.MWW_PCAN. Update Brya targets in tplg-targets.cmake with
DMIC_WOV_DAI_CHANNELS=4, and add a dmic16k 4-channel passthrough capture
target for microphone diagnostics.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
When pipelines are cancelled via pipeline_schedule_cancel() during
pause or stop triggers, sa_set_panic_on_delay() is called to enable
panic on delay. In host testbench and library builds, the system agent
timer is not initialized, leaving the sa pointer NULL.

Add a NULL check in sa_set_panic_on_delay() before dereferencing the sa
pointer so host testbench runs do not crash with a segmentation fault.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
When running host-testbench.sh inside containerized or snap-based editor
environments, GTK_PATH may point to bundled GTK libraries from an older
runtime environment. This can cause Octave to fail during startup with
dynamic library symbol lookup errors.

Unset GTK_PATH at the start of host-testbench.sh to prevent library
path conflicts.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
…nces

Lowering the startup warmup threshold previously allowed the model to
evaluate detections on initial unprimed temporal buffer states during
pipeline start. This produced a false trigger at t = 1s on startup
transients before audio playback began.

Restore MWW_WARMUP_INFERENCES to 33 inferences (~1 sec) and restore
MWW_CONSECUTIVE_DETECTS_REQUIRED to 3 to align with training verification
and suppress startup false triggers.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
mod_ipc_msg_w_ext_init() allocates the ipc_msg and its tx_data with
mod_zalloc(), which uses SOF_MEM_FLAG_USER only and thus returns
cached memory. Before the conversion the message was allocated with
SOF_MEM_FLAG_COHERENT.

msg->list is linked into the uncached ipc->msg_list by the module
(possibly on a secondary core or DP thread) and unlinked by the IPC
send worker on the primary core. With a cached msg the worker operates
on stale list pointers, list_item_del() fails to unlink the message
and the same notification is sent to the host over and over again.

Allocate the message and its payload with SOF_MEM_FLAG_COHERENT, as
it was done before.

Fixes: 97dd710 ("ipc: make IPC message allocation userspace-safe")
Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
(cherry picked from commit a5cb815)
Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
…copy

wov_arb_copy() resent SOF_IPC4_NOTIFY_PHRASE_DETECTED every 10ms (rate
limited, but still continuous) for as long as a slot stayed active, i.e. for
the entire remaining lifetime of the host PCM stream. This flooded the IPC
mailbox and host dmesg (hundreds of notifications per capture) even though
the notification has no functional purpose there: only a substream that
actually opted into SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP depends on it to
call snd_sof_pcm_period_elapsed(); a normal interrupt-driven capture is
already woken by its regular hw pointer updates and ignores it.

Drop the periodic resend and the last_notify_time throttle state it needed.
PHRASE_DETECTED is now sent exactly once per detection, from
arb_on_detect(), matching its role as a one-shot wake rather than a
substitute for the host's own period-elapsed mechanism.

Verified on DUT: arecord + wovdebug_111 trigger now produces exactly one
notification (2 dmesg lines) instead of ~600 over a 5s capture, and the
no-period-wakeup test tool (wov_blocking_read_tinyalsa) still unblocks
immediately on trigger.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
The tinyalsa reader arms wovdebug_* before pcm_readi(). With
start_threshold=1, tinyalsa may defer starting the PCM until that
read, allowing the fake detection to race ahead of the host
pipeline. Start the PCM explicitly before arming the control.

PCM_NOIRQ is not needed for MMAP capture and makes tinyalsa wake
on timed polls instead of normal period notifications. Drop it
so the reader can wait for period wakeups.

On the DUT, wovdebug_111 returned all 4000 frames with normal
period wakeups in 0.258s.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
The alsa-lib capture tool opens the PCM in nonblocking mode and
calls snd_pcm_wait() after EAGAIN. With no-period-wakeup set,
it has neither regular period notifications nor a timed check of
the hardware pointer. The wait times out even after a WoV trigger
has made audio available.

Leave period wakeups enabled so PCM readiness wakes the read loop.
On the DUT, wovdebug_111 captured all 4000 frames in S0 and
8000 frames after a six-second idle delay.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
@ujfalusi
ujfalusi requested a review from lgirdwood as a code owner October 9, 2026 07:21
@ujfalusi ujfalusi closed this Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.