Skip to content

audio: microwakeword: use HiFi4 nnlib kernels with xt-clang - #21

Closed
singalsu wants to merge 292 commits into
lgirdwood:mainfrom
singalsu:lgirdwood_wov-d0i3_v23
Closed

singalsu wants to merge 292 commits into
lgirdwood:mainfrom
singalsu:lgirdwood_wov-d0i3_v23

Conversation

@singalsu

Copy link
Copy Markdown

No description provided.

kv2019i and others added 30 commits August 17, 2026 09:47
Support to run DP scheduler when LL is running in user-space
is not yet complete, so disable this feature in the overlay
for now.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Port chain DMA to SOF's sof_dma_* syscall wrappers (as host and dai
already do) so its DMA operations can run unprivileged. Channel handles
are stored as integer indices instead of kernel-only struct
dma_chan_data pointers, matching the sof_dma_* API which takes indices.

Both channel indices are initialised to -EINVAL so an incomplete
initialisation is detected consistently for host and link.

No functional change for existing (privileged) builds.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
When the LL pipeline runs in user-space (CONFIG_SOF_USERSPACE_LL) the
component and its private data must reside on the user heap so the
unprivileged user LL thread can access them. Introduce chain_dev_alloc()/
chain_cd_alloc() and their free counterparts to keep the config-specific
allocation out of chain_task_create()/chain_task_free() instead of
sprinkling #ifdefs through the control flow.

The non-user-space path is unchanged (comp_alloc()/rzalloc()).

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
For CONFIG_SOF_USERSPACE_LL the DMA buffer must be allocated from the
user LL heap so it is reachable by the unprivileged user LL thread that
runs chain_task_run(). Pass the LL alloc context to buffer_alloc()
instead of NULL; for non-user-space builds alloc_ctx stays NULL and the
default heap is used as before.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Use the sof_dma_get_status() wrapper to get DMA status in
dai_common_position(). This allows the code to be used from user-space
context.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
The link_config computed here is handed to the UAOL driver as the stream
channel map and written to UAOLxPCMSyCM. On ACE 2.x and later that register
only defines the lchan, hchan and strm fields, its two upper bits are
reserved, and union hdalink_cfg keeps the direction in the topmost of them.

Setting the direction therefore wrote a reserved bit for every capture
stream, after which the link transferred no data at all and reported no
error. Playback was unaffected because its direction is zero.

The link does not need to be told the direction: transmit and receive
streams have separate indices, so the stream already implies it.

Fixes: ddd5c64 ("dai: uaol: add support for Intel UAOL")
Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
It is necessary cause p_dax is used in dax.c now.

Signed-off-by: Jun Lai <jun.lai@dolby.com>
While the tuning buffer is being updated, it may also
be being used simultaneously in the process thread.

Signed-off-by: Jun Lai <jun.lai@dolby.com>
Add NUM_DMICS to set SDW DMIC number. The value is 2 by default.

Signed-off-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Add 4 channel sdca mic function topologies support.

Signed-off-by: Bard Liao <yung-chuan.liao@linux.intel.com>
In CONFIG_SOF_USERSPACE_LL builds the LL scheduler thread runs
unprivileged, so every Zephyr kernel object it accesses must be
explicitly granted to it.

In commit ffa52df ("schedule: ll: dynamically allocate the semaphore"),
task semaphores were converted to dynamically allocated objects.
Only the bootstrap task's semaphore was granted to the LL thread (in
zephyr_ll_init_context()); tasks created later (e.g. chain_dma) were
not, so pausing/stopping such a task while it was running crashed the
DSP.

Fix the issue by grant the LL scheduling thread access to the task's
semaphore at allocation time, from the syscall implementation which runs
in privileged context.

Add zephyr_domain_thread_tid_for_core() to look up the LL thread for an
explicit core without relying on cpu_get_id(), and without dereferencing
user-accessible task struct.

Fixes: ffa52df ("schedule: ll: dynamically allocate the semaphore")
Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
The IDC layer only ever reads from msg->payload (it is memcpy'd into a
per-core scratch buffer before the target core runs, and any actual
output data is returned through a separately referenced coherent
buffer, e.g. get_attribute_remote_payload::value). Nothing writes
through this pointer, so mark it const void * to let senders pass
read-only data without casting away const.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
Make struct bind_info::ipc4_data a pointer to const struct
ipc4_module_bind_unbind, since bind/unbind handlers only ever read
this IPC4 payload. This required propagating const through the
second argument of struct module_interface::bind()/unbind(),
struct comp_ops::bind()/unbind(), comp_bind()/comp_unbind() and all
their implementations (copier, kpb, mixin/mixout, tone, tester,
userspace_proxy, dai_zephyr_unbind, DP scheduler IPC plumbing).

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
Currently both IPC3 and IPC4 use ipc_comp_connect() and
ipc_comp_disconnect() even though their second parameter has
different types. This makes no sense since both implementations and
calling sites are different. Rename IPC4 versions to drop type-casts.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
Remove export for mod_free_all(). It is not needed and unnecessary exports
have a cost when modules are loaded.

Fixes: a0939f4 ("module: generic: use a single static lock for module resources")
Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
sys/types.h is automatically included with glibc, not musl

Signed-off-by: eholzbach <ericholzbach@pm.me>
Signed-off-by: eholzbach <ericholzbach@pm.me>
Signed-off-by: Piotr Hoppe <piotr.hoppe@intel.com>
Signed-off-by: Piotr Hoppe <piotr.hoppe@intel.com>
Convert the DC blocking filter from the deprecated audio_stream /
process_audio_stream interface to the modern sof_source / sof_sink
processing API.

The module .process callback now acquires the source and sink circular
buffers once via source_get_data() / sink_get_buffer() and passes them
to the processing functions as struct cir_buf_source / struct cir_buf_sink
views, mirroring the volume component. The generic path uses the shared
cir_buf_samples_without_wrap_*() and cir_buf_wrap() helpers, while the
HiFi3/HiFi4 variants derive the circular buffer bounds from the same
views. Channel count is cached in comp_data during prepare().

Signed-off-by: Piotr Hoppe <piotr.hoppe@intel.com>
Enable CONFIG_DEBUG_COREDUMP with the AMD ACP panic dump backend and
minimal memory dump on the ACP 7.X ADSP board, so a DSP firmware crash
is captured into the ACP scratch exception buffer and reported to the
host SOF driver.

Signed-off-by: DineshKumar Kalva <DineshKumar.Kalva@amd.com>
Change LL task priorities and scheduler initialization interfaces from
uint16_t to int16_t. Lower numeric values continue to represent higher
priorities, so existing task ordering remains unchanged.

Pipelines can use priority 0. The signed range allows the IPC4 trigger
tasks introduced by the following commit to use priority -1 and run
before all pipeline copy tasks.

Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
Split IPC4 LL pipeline processing into two tasks:

* A one-shot trigger task that normally returns TASK_COMPLETED, causing
  the LL scheduler to remove it from its task list. It is not strictly
  one-shot, as it can be rescheduled to support delayed triggers.
* A copy task, with almost no changes to the existing implementation.

When a pipeline starts, both tasks are added to the LL scheduler.
Trigger tasks are appended with the highest priority, -1, regardless of
their pipeline priority. They are therefore added in IPC order and run
before any LL copy tasks. Copy tasks are added as before, respecting
their pipeline priorities.

Once a trigger task has completed, it returns TASK_COMPLETED and is
removed from the LL scheduler's task list. The copy task is prevented
from copying while its trigger task remains on the LL scheduler's list
to support delayed triggering.

When stopping a pipeline, only the trigger task needs to be added
because the copy task is already scheduled. Once the trigger task
changes the pipeline state to paused, both tasks return TASK_COMPLETED
and are removed from the scheduler's task list.

Separating trigger and copy processing preserves the pipeline trigger
order specified by a multi-pipeline-set-state IPC, regardless
of pipeline priorities, while allowing copy tasks to run according to
pipeline priorities.

This makes the blocking wait introduced by commit c5d638a
("ipc4: handler: maintain IPC set_pipeline_state order") unnecessary.

Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
This reverts commit 05bffd7.

Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
Validate the requested core ID before dispatching pipeline creation.
This prevents an out-of-bounds access found by IPC4 fuzzing.

Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
Use a type-cast instead of a memcpy() to obtain
struct ipc4_module_init_instance data.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
Join several needlessly broken lines.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
PAGE_SZ isn't used any more in module_adapter.c, remove the
definisions.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
The USB Audio Offload Link is an ACE2+ alt link. The firmware enumerates its
gateways in the ALH class family, but the ALH DAI class carries SoundWire
specific attributes, so UAOL is described by a DAI class of its own.

Allow "UAOL" as a dai type and as a copier type, and let the dai-copier take
the two UAOL node types which common_definitions.conf already defines.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
The sof_sdw machine driver creates a backend DAI link for the USB Audio
Offload Link when the platform reports one. Add the function topology which
is loaded next to the other function topologies of that card.

The playback path is a mixer based one, so a deep buffer PCM can share the
link with the normal playback PCM. The capture path is a straight one with a
high pass EQ IIR, matching what the SoundWire jack does, because the number
of captured channels follows the USB device while a mixer only handles a
fixed channel count.

What the link is run at is decided by the USB device that happens to be
plugged in, which is not known when the topology is built, so every widget
of both paths carries the whole set of formats a USB device may ask for:
S16_LE, S24_LE and S32_LE, 1 to 8 channels and the standard rates from 8 to
192 kHz, which is as far as UAOL goes. The kernel narrows the PCM down to
what the connected device actually supports. The matrix is spelled out with
CombineArrays in include/formats/uaol_*.conf, one file for the wire formats
and one for the 32 bit only modules in between. 7 channels is left out, it is
the one count in the range with no channel config and no channel map defined.

The deep buffer PCM stays at 48 kHz stereo, it comes from the shared
platform/intel/deep-buffer.conf, so it can only join the mixer while the
normal playback PCM runs at that format, or is not running at all.

The two directions are separate DAI links so that the format fixup of a link
is decided by the DAI copier of that direction alone. Their names are kept
short because alsatplg drops the widget tuples once the generated widget name
exceeds 36 characters.

The UAOL backend is the last one the machine driver creates, so its DAI link
ID depends on the composition of the board. Build the ID variants which the
SoundWire boards can produce.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
lrgirdwo and others added 28 commits October 9, 2026 13:48
When the volatile 'wovdebug' switch kcontrol is activated via ALSA mixer,
arm a 5000 ms timer (configurable via CONFIG_COMP_MWW_FAKE_WAKE_MS) instead
of firing keyword detection immediately.

The timer counts down during active audio processing in mww_process()
and fires synthetic detection and KPB history draining upon expiration,
resetting the ALSA control back to 0. Re-arm the timer upon D0i3 power state
transition or prepare if armed to ensure the full delay elapses.

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

Point the tflite-micro project at upstream tensorflow/tflite-micro
(e86d97b6237f88ab5925c0b41e3e3589a1560d86) instead of the thesofproject
fork, since west now natively manages all five TFLM dependencies via
submanifests/tflm.yml. scripts/tensorflow-clone.sh duplicated this same
REPOS/COMMIT_ID pinning outside of west and had drifted out of sync with
it; nothing in the tree invoked it, so remove it and repoint the one
stale comment referencing it.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No need for decimals in MWW probability value. The integer percent
is enough accurate for debug.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Add a built-in 100 Hz second-order highpass filter with +20 dB gain at
16 kHz to the ECNS component for the Pin 0 detection path to KPB.
The coefficients are initialized using a static coefficient blob and
run via iir_df1 on the downmixed mono signal before quantizing to 16-bit.

Filtering the single downmixed mono channel inside ECNS rather than
filtering all microphone channels upstream significantly reduces
computational overhead on the detection path.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Remove eqiir.119.1 from pipeline 119 and bind the dmic16k DAI copier
directly to ECNS input pin 0. Highpass filtering with +20 dB gain is
now handled internally inside ECNS on the downmixed mono signal for
the KPB detection path.

Drop the unused eqiir class include from dmic-wov-feature.conf and
update the pipeline routing.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
ECNS already depends on IPC_MAJOR_4 in Kconfig and relies on IPC4
structures for buffer formats, queues, and configuration handling.

Drop the unused non-IPC4 conditional blocks in ecns_params and
ecns_prepare to simplify the component implementation.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Refactor the oversized ecns_copy() function by splitting its top-level
processing paths into dedicated helper functions:
- ecns_process_kpb(): handles Pin 0 16 kHz stream downmixing, built-in
  highpass filtering, and KPB mono buffer output.
- ecns_process_host(): handles Pin 1 48 kHz (or fallback) channel
  mapping and host capture buffer output.
- ecns_consume_sources(): manages buffer consumption and idle draining
  for active and inactive source inputs.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Calling audio_stream_wrap() on every processed audio sample or frame
imposes a significant MCPS overhead in data processing copy loops.

Refactor the processing helper loops to compute the number of frames
until the next circular buffer boundary using
audio_stream_frames_without_wrap(). Process contiguous chunks in the
inner loop with plain pointer arithmetic and wrap the stream pointers
only when reaching buffer boundaries.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Add CONFIG_ECNS_TEST_SIGNAL to gate the test signal generator on the
ECNS clean mono output pin (Pin 0 to KPB), defaulting to 'n'.

When disabled, avoid compiling the test signal generator helpers and
runtime checks, and emit a warning trace in ecns_set_large_config() if
an attempt is made to enable the test signal via ALSA control.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Commit 32af3fe introduced CONFIG_ECNS_TEST_SIGNAL to gate the ECNS
test signal generator with 'default n'.

Explicitly select CONFIG_ECNS_TEST_SIGNAL=y in intel_adsp_ace30_ptl.conf
and intel_adsp_ace30_wcl.conf so hardware Wake-on-Voice test signal
generation on the ECNS mono output to KPB remains enabled for continuous
integration and automated validation suites.

Also select MATH_IIR alongside MATH_IIR_DF1 in COMP_ECNS to ensure its
dependency is met.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Multi-slot WoV pipeline uses dmic16k DAI (PDM1, 16 kHz) But
BIOS ACPI NHLT only has 48kHz dmic01 blob, so PCM-11[ECNS]
& PCM-12[WoV] FAIL at hw_params with "no matching blob for
sample rate: 16000".

Enable NHLT prepro plugin for sof-wcl-rt722-4ch-wov-multi
so topology keeps both 48kHz & 16 kHz DMIC blobs.

Test: WCL rt722 PCM-11 captures and PCM-12 opens cleanly

Signed-off-by: Naveen Manohar <naveen.m@intel.com>
When loading microWakeWord models from an ALSA bytes control blob,
the incoming payload sent via sof-ctl or ALSA ctl contains a 32-byte
SOF ABI header prefix with magic SOF_IPC4_ABI_MAGIC ('SOF4') or
SOF_ABI_MAGIC. Pass only the raw FlatBuffer payload to MWW_SetModel()
by detecting and stripping the ABI header.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
The MWW sink carries MFCC feature frames rather than linear PCM, so the
module adapter cannot derive a sensible DP period and falls back to
1 ms. Set the period explicitly to 10 ms to match the MFCC hop cadence,
as in the earlier working implementation.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
With the userspace and LLEXT build the MFCC and microWakeWord DP
modules run about four times slower than real time on PTL, so the
detector never catches up with audio and no keyword is ever detected.
Switch PTL and WCL to a static build without userspace, LLEXT and the
library manager, and build the previously loadable modules in. On PTL
average inference time drops from about 127 ms to 31 ms per 30 ms hop.

With LLEXT disabled vregion.c no longer gets the EXPORT_SYMBOL macro
indirectly, so include zephyr/llext/symbol.h explicitly.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
The low sensitivity microphones give a capture level on the ECNS host
output that is far below normal. Filter each channel of the Pin 1 host
stream with a second order 40 Hz high-pass IIR with +20 dB gain, so
the level appears more normal with minimal processing added to ECNS.
The high-pass also removes DC and low frequency noise that the gain
would otherwise amplify.

The 48 kHz or 16 kHz coefficient set is selected in prepare from the
Pin 1 rate. The filter is enabled by the new CONFIG_ECNS_HOST_HPF,
default y. When disabled the data is passed through with only the
s16/s32 format conversion.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
The 100 Hz high-pass filter with +20 dB gain on the ECNS mono KPB
output is always applied. Add CONFIG_ECNS_KPB_HPF, default y, so the
filter can be disabled in the same way as the host output filter, for
example to compare detection with unprocessed microphone audio.

When disabled the mono downmix is passed through with only the
s16/s32 format conversion, and the filter coefficients and state are
not built.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
ecns_prepare() forced the Pin 1 host output buffer to 4 channels
whenever the 48 kHz dmic01 input was connected. With a 2-channel DMIC
topology the host copier would then receive data labeled as 4-channel.
Take the Pin 1 channel count from the dmic01 input buffer instead, so
both 2 and 4 channel DMIC configurations work.

Also replace the magic channel map and channel config values with
named channel map macros and the IPC4 channel config enums.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
The dmic01 DAI copier formats and the ECNS Pin 1 input buffer size
were hardcoded for 4 channels. Use the WOVHOST channel definitions and
a new ECNS_PIN1_IN_IBS that follow DMIC_WOV_DAI_CHANNELS, so the
topology also builds correctly for a 2-channel DMIC.

The channel config and map in the DET16K and WOVHOST defines are now
literal values. The references to the CHANNEL_* defines resolved to 0,
so the DAI copiers got channel map 0 and remapped every output channel
from channel 0. This also fixes the dmic16k detection path, which had
been delivering channel 0 on all channels.

Add the sof-adl-max98357a-rt5682-2ch-wov-multi development target that
uses only the PDM0 stereo microphones on Google Brya.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Add 2-channel PCH DMIC variants of the PTL and WCL rt721 and rt722
multi-wov topologies for devices with only the PDM0 microphone pair.
All of them embed the NHLT so the dmic16k DAI finds its blob.

cavs-sdw.conf enables the PDM1 microphones by default, so the 2ch
targets set PDM1_MIC_A_ENABLE=0, PDM1_MIC_B_ENABLE=0 and
DMIC_WOV_DAI_CHANNELS=2 explicitly.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
The dmic16k DAI copier advertised both 32-bit and 16-bit input
formats. With the 16-bit format available the DMIC gateway on PTL
was set up so that the DMA write position advanced erratically and
the detector path received only about 87% of the expected 16 kHz
frame rate, starving the MFCC and MWW slots.

Advertise only the 32-bit format, as the other DMIC copiers do. The
dmic16k path now delivers exactly 16000 frames per second end to end
through ECNS and KPB.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Core 0 cannot run the DMIC, ECNS and KPB pipelines together with
three MFCC and MWW detector slots in real time. Add per-slot core
IDs and place the slots on cores 1, 2 and 3 on PTL, and on cores 1
and 2 on WCL with slot 2 staying on core 0. Other platforms keep all
slots on core 0.

The slot pipeline widgets now also set the core attribute. The
kernel takes the pipeline core from that attribute rather than from
core_id, and IPC4 requires the pipeline's scheduling component to be
on the pipeline core, so without it the slot pipelines were created
on core 0 while their modules were on the secondary cores.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
sof_init() runs at POST_KERNEL and calls idc_init() on core 0, which
starts the core 0 IDC p4wq worker thread. The p4wq static init runs
later at APPLICATION level by default, clears the queue and creates
the thread in delayed-start state, so the core 0 IDC worker is never
started. Secondary cores run idc_init() after power-up and are not
affected.

The problem shows up as soon as a secondary core notifies core 0,
e.g. MWW on core 1 notifying KPB on detection: "Out of worker
threads", the IDC times out and the next submit corrupts the p4wq
queue. Select CONFIG_P4WQ_INIT_STAGE_EARLY for PTL and WCL. Verified
on PTL with WoV detector slots on secondary cores.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
notifier_event() sends events to other cores as non-blocking IDC
messages. Each target core has a single notify_data slot and the IDC
work item is reused, so two events sent back to back to the same
core, such as the MWW detection followed by the KPB drain request,
can overwrite the first event's data or resubmit a work item that is
still queued.

Send remote notifications as blocking IDC so the next event to the
same core is not issued before the previous one has been handled.
This adds latency on the notifying core, as it now waits for the
remote callback to complete. Verified on PTL with WoV detection on
core 1 notifying the arbiter and KPB on core 0.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
When MWW runs on a different core than KPB, the keyword trigger passes
a kpb_event_data whose client_data member points to a kpb_client
written by the MWW core. notifier_event() only writes back the
kpb_event_data itself, so KPB read a stale client and started draining
with a zero length request, which never moved to host copy.

Write back the client data in MWW before sending the notification and
invalidate it in the KPB event handler before use. KPB now drains the
requested 2000 ms of history after a keyword trigger.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
The WoV arbiter consumes and discards everything arriving on its KPB
input while no slot is active. MWW notified KPB to begin draining
before notifying the arbiter of the detection, so the whole 2 s
history was drained into the arbiter and dropped before it switched
to the detecting slot. The captured stream then started with live
audio after the keyword.

Send the detection notification to the arbiter first and request KPB
draining after it, both for real detections and the fake wake test
path. The keyword is now present at the start of the captured stream.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
The MWW TFLM inference takes most of a core per detector slot and it
is not clear from the total alone which operators dominate. Add an
optional MicroProfilerInterface implementation that accumulates the
CCOUNT cycles of every operator in invoke order.

When CONFIG_COMP_MWW_PROFILE is enabled, the per-operator averages
and their total are logged with the stream shutdown summary. The
first inference after a reset also runs the CALL_ONCE init subgraph
and is excluded from the averages.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Profiling showed the MWW inference spending over 9M cycles per run in
the portable TFLM reference kernels, most of it in CONV_2D and
DEPTHWISE_CONV_2D, and in the QUANTIZE and DEQUANTIZE of the model's
float streaming state.

When building with xt-clang, compile these operators from the TFLM
Xtensa kernels backed by the Cadence HiFi4 nnlib and link a private
nnlib archive. nnlib exports a global gcd() that clashes with SOF's,
so it is renamed at build time, and the float quantize path needs
__fpclassifyf in the libc shim. Other toolchains keep the reference
kernels. The new CONFIG_COMP_MWW_HIFI_NNLIB allows opting out.

On PTL this reduces an inference from about 9.4M to 1.7M cycles.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
With the MWW inference on the HiFi4 nnlib kernels a detector slot
needs about 4 ms of every 30 ms, so core 0 can again run all three
MFCC and MWW slots together with the DMIC, ECNS and KPB pipelines.
Keeping everything on core 0 also avoids the intermittent slot stalls
seen on the secondary cores while the host is in D0i3.

Remove the PTL and WCL overrides that placed the slots on secondary
cores. The per-slot core IDs are kept so that a platform can still
move them. On PTL and WCL this requires the firmware to be built with
xt-clang, as the reference TFLM kernels used with other toolchains are
too slow to run three slots on one core.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
@singalsu singalsu closed this Oct 19, 2026
@singalsu
singalsu deleted the lgirdwood_wov-d0i3_v23 branch October 23, 2026 10:21
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.