From 002f765949c6eeaa805f3418fd0add374c5aeafa Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Sun, 6 Sep 2026 13:52:09 +0100 Subject: [PATCH 01/25] audio: kpb: add multi-client downstream WOV detector triggering and dynamic sample rates - Add downstream WOV detector client notification and buffer drainage - Support configurable history buffer depth - Add dynamic sample rate calculations supporting both 16kHz and 48kHz Signed-off-by: Liam Girdwood --- src/audio/Kconfig | 25 ++++ src/audio/kpb.c | 240 ++++++++++++++++++++++++++++++------ src/audio/kpb.toml | 2 +- src/include/ipc4/kpb.h | 2 + src/include/sof/audio/kpb.h | 13 +- 5 files changed, 237 insertions(+), 45 deletions(-) diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 8accb25738a2..7bb7446711be 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -102,6 +102,17 @@ config COMP_STUBS Select to force all 3P blocks to link against stubs rather than their libraries. This should only be used in testing environments like fuzzers or CI. +config COMP_WOV_ARBITER + bool "WOV arbiter component" + depends on COMP_KPB + depends on IPC_MAJOR_4 + help + Select to build the WOV (Wake-on-Voice) arbiter. The arbiter sits + between multiple KPB host-drain outputs and a single host PCM copier. + When a keyword is detected by one of the WOV detectors the arbiter + routes that KPB's drain stream to the host and instructs the remaining + detectors to pause. + config COMP_KPB bool "KPB component" default y @@ -109,6 +120,20 @@ config COMP_KPB Select for KPB component if COMP_KPB +config KPB_MAX_NO_OF_CLIENTS + int "Maximum number of KPB clients" + default 4 + help + Maximum number of simultaneous KPB drain clients (host + WOV detectors). + Increase if more than 4 clients need concurrent KPB access. + +config KPB_MAX_BUFF_TIME + int "KPB history buffer time (ms)" + default 6000 if XCHAL_HW_VERSION_MAJOR >= 300 + default 2100 + help + KPB history ring-buffer length in milliseconds. + config KPB_FORCE_COPY_TYPE_NORMAL bool "KPB force copy type normal" default y diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 2cec91393517..3812174a3feb 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -18,10 +18,14 @@ #include #include #include +#define SOF_MODULE_API_PRIVATE +#include +#include #include #include #include #include +#include #include #include #include @@ -100,6 +104,7 @@ struct comp_data { enum comp_copy_type force_copy_type; /**< should we force copy_type on kpb sink? */ #ifdef CONFIG_IPC_MAJOR_4 struct ipc4_kpb_module_cfg ipc4_cfg; + uint32_t buff_time_ms; /**< history depth in ms; 0 = CONFIG_KPB_MAX_BUFF_TIME */ #endif /* CONFIG_IPC_MAJOR_4 */ uint32_t num_of_sel_mic; uint32_t num_of_in_channels; @@ -232,6 +237,17 @@ static void kpb_lock_init(struct comp_data *kpb) #endif /* __ZEPHYR__ */ + +/* Return per-instance history depth (ms); fall back to Kconfig if not set via topology. */ +static inline uint32_t kpb_get_buff_time_ms(const struct comp_data *kpb) +{ +#if CONFIG_IPC_MAJOR_4 + return kpb->buff_time_ms ? kpb->buff_time_ms : CONFIG_KPB_MAX_BUFF_TIME; +#else + return CONFIG_KPB_MAX_BUFF_TIME; +#endif +} + #if CONFIG_IPC_MAJOR_4 /** * \brief Set and verify ipc params. @@ -265,8 +281,9 @@ static int kpb_set_verify_ipc_params(struct comp_dev *dev, return -EINVAL; } - if (kpb->config.sampling_freq != KPB_SAMPLNG_FREQUENCY) { - comp_err(dev, "requested sampling frequency not supported"); + if (kpb->config.sampling_freq != 16000 && kpb->config.sampling_freq != 48000) { + comp_err(dev, "requested sampling frequency %u not supported", + kpb->config.sampling_freq); return -EINVAL; } @@ -294,7 +311,7 @@ static void kpb_set_params(struct comp_dev *dev, params->sample_valid_bytes = kpb->ipc4_cfg.base_cfg.audio_fmt.valid_bit_depth / 8; params->buffer_fmt = kpb->ipc4_cfg.base_cfg.audio_fmt.interleaving_style; - params->buffer.size = kpb->ipc4_cfg.base_cfg.ibs * KPB_MAX_BUFF_TIME * params->channels; + params->buffer.size = kpb->ipc4_cfg.base_cfg.ibs * kpb_get_buff_time_ms(kpb) * params->channels; params->host_period_bytes = params->channels * params->sample_container_bytes * @@ -369,6 +386,9 @@ static int kpb_bind(struct comp_dev *dev, struct bind_info *bind_data) sink_buf_id = buf_get_id(sink); if (sink_buf_id == buf_id) { + struct comp_dev *sc = comp_buffer_get_sink_component(sink); + comp_dbg(dev, "kpb_bind: buf_id=%d sink_comp=0x%x -> %s", + buf_id, sc ? dev_comp_id(sc) : 0, sink_buf_id == 0 ? "sel_sink" : "host_sink"); if (sink_buf_id == 0) kpb->sel_sink = sink; else @@ -442,8 +462,9 @@ static int kpb_set_verify_ipc_params(struct comp_dev *dev, return -EINVAL; } - if (kpb->config.sampling_freq != KPB_SAMPLNG_FREQUENCY) { - comp_err(dev, "requested sampling frequency not supported"); + if (kpb->config.sampling_freq != 16000 && kpb->config.sampling_freq != 48000) { + comp_err(dev, "requested sampling frequency %u not supported", + kpb->config.sampling_freq); return -EINVAL; } @@ -802,7 +823,11 @@ static int kpb_prepare(struct comp_dev *dev) struct sof_ipc_stream_params params; int ret = 0; int i; - size_t hb_size_req = KPB_MAX_BUFFER_SIZE(kpb->config.sampling_width, kpb->config.channels); + /* Use topology-set depth if provided; otherwise Kconfig default. */ + uint32_t buff_ms = kpb_get_buff_time_ms(kpb); + size_t hb_size_req = (kpb->config.sampling_freq / 1000) * + (KPB_SAMPLE_CONTAINER_SIZE(kpb->config.sampling_width) / 8) * + buff_ms * kpb->config.channels; comp_dbg(dev, "entry"); @@ -887,6 +912,51 @@ static int kpb_prepare(struct comp_dev *dev) return -ENOMEM; } + struct comp_buffer *sink; + /* Output pin IDs: IPC4_COMP_ID(src_queue, dst_queue) = (dst_queue<<16)|src_queue. + * In a multi-KPB topology each KPB connects to a different arbiter input pin so + * dst_queue varies (0,1,2...) but src_queue is always 0 (sel) or 1 (host). + * Match on src_queue only (lower 16 bits of buf_id). */ + enum { KPB_PIN_SEL_SINK = 0, KPB_PIN_HOST_SINK_SRC = 1 }; + comp_dev_for_each_consumer(dev, sink) { + uint32_t src_q = buf_get_id(sink) & 0xFFFF; + + if (src_q == KPB_PIN_SEL_SINK) + kpb->sel_sink = sink; + else if (src_q == KPB_PIN_HOST_SINK_SRC && !kpb->host_sink) + kpb->host_sink = sink; + else + comp_warn(dev, "kpb_prepare: unexpected consumer pin, buf_id=0x%x", + buf_get_id(sink)); + } + comp_dbg(dev, "kpb_params: sel_sink=%p host_sink=%p", + kpb->sel_sink, kpb->host_sink); + + /* Cross-pipeline prepare: the WOV detector (detect_test) lives on a + * separate IPC4 pipeline and won't be prepared by the normal IPC4 walk, + * so KPB bootstraps it here during its own prepare phase. + */ + if (kpb->sel_sink) { + struct comp_dev *sink_comp = comp_buffer_get_sink_component(kpb->sel_sink); + if (sink_comp && sink_comp->state == COMP_STATE_INIT) { + struct sof_ipc_stream_params sink_params; + memset_s(&sink_params, sizeof(sink_params), 0, sizeof(sink_params)); + sink_params.channels = kpb->config.channels ? kpb->config.channels : 2; + sink_params.rate = kpb->config.sampling_freq ? kpb->config.sampling_freq : 16000; + sink_params.sample_container_bytes = 4; + sink_params.sample_valid_bytes = 4; + sink_params.frame_fmt = SOF_IPC_FRAME_S32_LE; + comp_params(sink_comp, &sink_params); + ret = comp_prepare(sink_comp); + if (ret < 0) { + comp_err(dev, "kpb_prepare: cross-pipeline prepare of wov detector failed: %d", ret); + return ret; + } + } + } + + kpb_change_state(kpb, KPB_STATE_RUN); + #ifndef CONFIG_IPC_MAJOR_4 /* Search for KPB related sinks. * NOTE! We assume here that channel selector component device @@ -936,10 +1006,42 @@ static int kpb_prepare(struct comp_dev *dev) } #endif /* CONFIG_IPC_MAJOR_4 */ + /* Fallback: iterate consumers to assign sel_sink and host_sink in order. + * The guards ensure each is set at most once — no overwrite on later iterations. */ + if (!kpb->sel_sink && !kpb->host_sink) { + struct comp_buffer *sink; + + comp_dev_for_each_consumer(dev, sink) { + if (!kpb->sel_sink) + kpb->sel_sink = sink; + else if (!kpb->host_sink) + kpb->host_sink = sink; + } + } + if (!kpb->sel_sink) { comp_err(dev, "could not find sink: sel_sink %p", kpb->sel_sink); ret = -EIO; + } else { + struct comp_dev *sink_comp = comp_buffer_get_sink_component(kpb->sel_sink); + if (sink_comp && sink_comp->state == COMP_STATE_INIT) { + struct sof_ipc_stream_params sink_params; + memset_s(&sink_params, sizeof(sink_params), 0, sizeof(sink_params)); + sink_params.channels = kpb->config.channels ? kpb->config.channels : 2; + sink_params.rate = kpb->config.sampling_freq ? kpb->config.sampling_freq : 16000; + sink_params.sample_container_bytes = 4; + sink_params.sample_valid_bytes = 4; + sink_params.frame_fmt = SOF_IPC_FRAME_S32_LE; + comp_params(sink_comp, &sink_params); + ret = comp_prepare(sink_comp); + comp_info(dev, "kpb_prepare: prepared downstream sink_comp %d in state %d", + dev_comp_id(sink_comp), sink_comp->state); + if (ret < 0) { + comp_err(dev, "kpb_prepare: cross-pipeline prepare failed: %d", ret); + return ret; + } + } } kpb->sync_draining_mode = true; @@ -981,11 +1083,33 @@ static int kpb_reset(struct comp_dev *dev) switch (kpb->state) { case KPB_STATE_BUFFERING: case KPB_STATE_DRAINING: - /* KPB is performing some task now, - * terminate it gently. + /* If a host drain is in progress, terminate gently and let + * kpb_copy complete the reset once scheduled. When there is + * no host_sink (WOV-only path) the scheduler has already + * stopped by the time RESET arrives, so reset immediately. */ - kpb_change_state(kpb, KPB_STATE_RESETTING); - ret = -EBUSY; + if (kpb->host_sink) { + kpb_change_state(kpb, KPB_STATE_RESETTING); + ret = -EBUSY; + break; + } + /* host_sink == NULL: immediate full reset (same as default) */ + kpb->hd.buffered = 0; + kpb->sel_sink = NULL; + kpb->host_sink = NULL; + kpb->host_buffer_size = 0; + kpb->host_period_size = 0; + for (i = 0; i < KPB_MAX_NO_OF_CLIENTS; i++) { + kpb->clients[i].state = KPB_CLIENT_UNREGISTERED; + kpb->clients[i].r_ptr = NULL; + } + if (kpb->hd.c_hb) + kpb_reset_history_buffer(kpb->hd.c_hb); + /* Must transition away from KPB_STATE_RUN before returning so that + * a subsequent kpb_copy() does not see a stale RUN state and + * immediately begin copying before the next prepare completes. */ + kpb_change_state(kpb, KPB_STATE_PREPARING); + ret = comp_set_state(dev, COMP_TRIGGER_RESET); break; case KPB_STATE_DISABLED: case KPB_STATE_CREATED: @@ -1234,19 +1358,16 @@ static int kpb_copy(struct comp_dev *dev) sink = kpb->sel_sink; ret = PPL_STATUS_PATH_STOP; + comp_dbg(dev, "kpb_copy: source_buf=%p sel_sink=%p avail=%u", + source, sink, audio_stream_get_avail_bytes(&source->stream)); + if (!sink) { - comp_err(dev, "no sink."); + comp_warn(dev, "no sink."); ret = -EINVAL; break; } - /* Discard data if sink is not active */ - if (comp_buffer_get_sink_component(sink)->state != COMP_STATE_ACTIVE) { - copy_bytes = audio_stream_get_avail_bytes(&source->stream); - comp_update_buffer_consume(source, copy_bytes); - comp_dbg(dev, "KD not active, dropping %zu bytes...", copy_bytes); - break; - } + /* Allow downstream WOV detector copy regardless of state */ /* Validate sink */ if (!audio_stream_get_wptr(&sink->stream)) { @@ -1257,7 +1378,7 @@ static int kpb_copy(struct comp_dev *dev) copy_bytes = audio_stream_get_copy_bytes(&source->stream, &sink->stream); if (!copy_bytes) { - comp_err(dev, "nothing to copy sink->free %u source->avail %u", + comp_warn(dev, "nothing to copy sink->free %u source->avail %u", audio_stream_get_free_bytes(&sink->stream), audio_stream_get_avail_bytes(&source->stream)); ret = PPL_STATUS_PATH_STOP; @@ -1278,7 +1399,7 @@ static int kpb_copy(struct comp_dev *dev) produced_bytes = copy_bytes * kpb->num_of_sel_mic / channels; produced_bytes = ROUND_DOWN(produced_bytes, total_bytes_per_sample); if (!copy_bytes) { - comp_err(dev, "nothing to copy sink->free %u source->avail %u", + comp_warn(dev, "nothing to copy sink->free %u source->avail %u", free, avail); ret = PPL_STATUS_PATH_STOP; @@ -1286,8 +1407,9 @@ static int kpb_copy(struct comp_dev *dev) } kpb_micselect_copy(dev, sink, source, produced_bytes, channels); } - /* Buffer source data internally in history buffer for future - * use by clients. + /* Buffer the FULL multi-channel source frame (copy_bytes, not produced_bytes) + * so all KPB clients get the complete channel-count history, regardless of + * which channels kpb_micselect_copy() forwarded to sel_sink. */ if (copy_bytes <= kpb->hd.buffer_size) { ret = kpb_buffer_data(dev, source, copy_bytes); @@ -1313,6 +1435,15 @@ static int kpb_copy(struct comp_dev *dev) else comp_update_buffer_produce(sink, produced_bytes); + struct comp_dev *wov_comp = sink ? comp_buffer_get_sink_component(sink) : NULL; + if (wov_comp) { + comp_dbg(dev, "kpb_copy: produced=%u bytes, triggering wov=0x%x", + copy_bytes, dev_comp_id(wov_comp)); + comp_copy(wov_comp); + } else { + comp_warn(dev, "kpb_copy: downstream sink_comp returned NULL!"); + } + comp_update_buffer_consume(source, copy_bytes); break; @@ -1607,6 +1738,28 @@ static int kpb_register_client(struct comp_data *kpb, struct kpb_client *cli) static void kpb_init_draining(struct comp_dev *dev, struct kpb_client *cli) { struct comp_data *kpb = comp_get_drvdata(dev); + + if (!kpb->host_sink) { + if (!kpb->sel_sink) { + comp_warn(dev, "kpb_init_draining: no drain path, skipping"); + return; + } + /* WOV-only path: no dedicated host PCM sink. Route history drain + * through sel_sink so wov passthrough delivers it to the arbiter. + * Set host_period_size to one real-time period so sync_draining_mode + * throttles the EDF drain task to match the LL pipeline rate. + */ + comp_warn(dev, "kpb_init_draining: no host_sink, draining via sel_sink"); + kpb->host_sink = kpb->sel_sink; + if (!kpb->host_period_size) { + size_t bpm = (size_t)(kpb->config.sampling_freq / 1000) * + (KPB_SAMPLE_CONTAINER_SIZE(kpb->config.sampling_width) / 8) * + kpb->config.channels; + kpb->host_period_size = bpm; + kpb->host_buffer_size = audio_stream_get_size(&kpb->sel_sink->stream); + } + } + bool is_sink_ready = (comp_buffer_get_sink_state(kpb->host_sink) == COMP_STATE_ACTIVE); size_t sample_width = kpb->config.sampling_width; size_t drain_req = (size_t)cli->drain_req * kpb->config.channels * @@ -1618,7 +1771,7 @@ static void kpb_init_draining(struct comp_dev *dev, struct kpb_client *cli) size_t local_buffered; size_t drain_interval; size_t host_period_size = kpb->host_period_size; - size_t bytes_per_ms = (size_t)KPB_SAMPLES_PER_MS * + size_t bytes_per_ms = (size_t)(kpb->config.sampling_freq / 1000) * (KPB_SAMPLE_CONTAINER_SIZE(sample_width) / 8) * kpb->config.channels; size_t period_bytes_limit; @@ -1633,14 +1786,16 @@ static void kpb_init_draining(struct comp_dev *dev, struct kpb_client *cli) /* TODO: check also if client is registered */ } else if (!is_sink_ready) { comp_err(dev, "sink not ready for draining"); - } else if (kpb->hd.buffered < drain_req || - cli->drain_req > KPB_MAX_DRAINING_REQ) { - comp_cl_err(&comp_kpb, "not enough data in history buffer"); + } else if (cli->drain_req > kpb_get_buff_time_ms(kpb)) { + comp_cl_err(&comp_kpb, "drain request exceeds max"); } else { - /* Draining accepted, find proper buffer to start reading - * At this point we are guaranteed that there is enough data - * in the history buffer. All we have to do now is to calculate - * read pointer from which we will start draining. + if (kpb->hd.buffered < drain_req) { + comp_cl_warn(&comp_kpb, "partial pre-roll: capping drain to buffered"); + drain_req = kpb->hd.buffered; + } + /* Draining accepted, find proper buffer to start reading. + * If less history than requested is buffered, drain_req is + * capped above so we drain whatever is available. */ kpb_lock(kpb); @@ -1750,8 +1905,11 @@ static void kpb_init_draining(struct comp_dev *dev, struct kpb_client *cli) comp_set_attribute(comp_buffer_get_sink_component(kpb->host_sink), COMP_ATTR_COPY_TYPE, &kpb->force_copy_type); - /* Pause selector copy. */ - comp_buffer_get_sink_component(kpb->sel_sink)->state = COMP_STATE_PAUSED; + /* Pause selector copy to stop detection on stale drain data. + * Skip when sel_sink IS the drain path (wov passthrough needed). + */ + if (kpb->host_sink != kpb->sel_sink) + comp_buffer_get_sink_component(kpb->sel_sink)->state = COMP_STATE_PAUSED; if (!pm_runtime_is_active(PM_RUNTIME_DSP, PLATFORM_PRIMARY_CORE_ID)) pm_runtime_disable(PM_RUNTIME_DSP, PLATFORM_PRIMARY_CORE_ID); @@ -1790,7 +1948,7 @@ static void adjust_drain_interval(struct comp_data *kpb, struct draining_data *d /* average drained bytes per second */ actual_pace = (size_t)k_ms_to_cyc_ceil64(1000) / elapsed * drained; - pipeline_period = (size_t)KPB_SAMPLES_PER_MS * + pipeline_period = (size_t)(kpb->config.sampling_freq / 1000) * (KPB_SAMPLE_CONTAINER_SIZE(dd->sample_width) / 8) * kpb->config.channels; /* desired draining pace in bytes per second */ optimal_pace = pipeline_period * KPB_DRAIN_NUM_OF_PPL_PERIODS_AT_ONCE * 1000; @@ -2368,9 +2526,11 @@ static void kpb_reset_history_buffer(struct history_buffer *buff) if (!buff) return; - kpb_clear_history_buffer(buff); + do { + /* Reset to start so no stale data from a prior drain session is + * re-played on the next KPB activation. */ buff->w_ptr = buff->start_addr; buff->r_ptr = buff->start_addr; buff->state = KPB_BUFFER_FREE; @@ -2396,7 +2556,7 @@ static inline bool validate_host_params(struct comp_dev *dev, */ struct comp_data *kpb = comp_get_drvdata(dev); size_t sample_width = kpb->config.sampling_width; - size_t bytes_per_ms = (size_t)KPB_SAMPLES_PER_MS * + size_t bytes_per_ms = (size_t)(kpb->config.sampling_freq / 1000) * (KPB_SAMPLE_CONTAINER_SIZE(sample_width) / 8) * kpb->config.channels; size_t pipeline_period_size = (dev->pipeline->period / 1000) @@ -2712,6 +2872,16 @@ static int kpb_set_large_config(struct comp_dev *dev, uint32_t param_id, #endif case KP_BUF_CLIENT_MIC_SELECT: return kpb_set_micselect(dev, data, data_offset); +#if CONFIG_IPC_MAJOR_4 + case KP_BUF_CFG_BUFF_TIME_MS: { + struct comp_data *kpb = comp_get_drvdata(dev); + if (data_offset < sizeof(uint32_t)) + return -EINVAL; + kpb->buff_time_ms = *(const uint32_t *)data; + comp_info(dev, "kpb: buff_time_ms set to %u ms", kpb->buff_time_ms); + return 0; + } +#endif default: return -EINVAL; } diff --git a/src/audio/kpb.toml b/src/audio/kpb.toml index e384632c1be8..2de8ef17aa80 100644 --- a/src/audio/kpb.toml +++ b/src/audio/kpb.toml @@ -2,7 +2,7 @@ name = "KPB" uuid = UUIDREG_STR_KPB4 affinity_mask = "0x1" - instance_count = "1" + instance_count = "4" domain_types = "0" load_type = "0" module_type = "0xB" diff --git a/src/include/ipc4/kpb.h b/src/include/ipc4/kpb.h index 0c56942313c3..aa33f0909994 100644 --- a/src/include/ipc4/kpb.h +++ b/src/include/ipc4/kpb.h @@ -24,6 +24,8 @@ struct ipc4_kpb_module_cfg { enum ipc4_kpb_module_config_params { /*! Configure the module ID's which would be part of the Fast mode tasks */ KP_BUF_CFG_FM_MODULE = 1, + /*! Set history ring-buffer duration; 0 = use CONFIG_KPB_MAX_BUFF_TIME */ + KP_BUF_CFG_BUFF_TIME_MS = 2, /* Mic selector for client - sets microphone id for real time sink mic selector * IPC4-compatible ID - please do not change the number */ diff --git a/src/include/sof/audio/kpb.h b/src/include/sof/audio/kpb.h index 14c7b33a38e9..bfbd4ab2a332 100644 --- a/src/include/sof/audio/kpb.h +++ b/src/include/sof/audio/kpb.h @@ -21,17 +21,12 @@ #endif struct comp_buffer; +struct comp_dev *get_wov_detector_comp(uint32_t ppl_id); /* KPB internal defines */ -#if CONFIG_TIGERLAKE -#define KPB_MAX_BUFF_TIME 3000 /**< time of buffering in miliseconds */ -#define HOST_WAKEUP_TIME 1000 /* aprox. time of host DMA wakup from suspend [ms] */ -#else -/** Due to memory constraints on non-TGL platforms, the buffers are smaller. */ -#define KPB_MAX_BUFF_TIME 2100 /**< time of buffering in miliseconds */ -#define HOST_WAKEUP_TIME 0 /* aprox. time of host DMA wakup from suspend [ms] */ -#endif +#define KPB_MAX_BUFF_TIME CONFIG_KPB_MAX_BUFF_TIME /**< time of buffering in miliseconds */ +#define HOST_WAKEUP_TIME 0 /* host DMA already live; see CONFIG_KPB_MAX_BUFF_TIME */ #define KPB_MAX_DRAINING_REQ (KPB_MAX_BUFF_TIME - HOST_WAKEUP_TIME) #define KPB_MAX_SUPPORTED_CHANNELS 6 /**< number of supported channels */ @@ -42,7 +37,7 @@ struct comp_buffer; #define KPB_MAX_BUFFER_SIZE(sw, channels_number) ((KPB_SAMPLNG_FREQUENCY / 1000) * \ (KPB_SAMPLE_CONTAINER_SIZE(sw) / 8) * KPB_MAX_BUFF_TIME * \ (channels_number)) -#define KPB_MAX_NO_OF_CLIENTS 4 +#define KPB_MAX_NO_OF_CLIENTS CONFIG_KPB_MAX_NO_OF_CLIENTS #define KPB_MAX_SINK_CNT (1 + KPB_MAX_NO_OF_CLIENTS) #define KPB_NO_OF_HISTORY_BUFFERS 2 /**< no of internal buffers */ #define KPB_ALLOCATION_STEP 0x100 From c549ccf7bb38a1fcfa248547b66d654a61dfb0ec Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Sun, 6 Sep 2026 13:52:11 +0100 Subject: [PATCH 02/25] audio: ams: add WOV detect and control message definitions Signed-off-by: Liam Girdwood --- src/include/sof/lib/ams_msg.h | 2 ++ src/include/sof/lib/notifier.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/include/sof/lib/ams_msg.h b/src/include/sof/lib/ams_msg.h index 9c1ad5ce6efb..1978fa06f30a 100644 --- a/src/include/sof/lib/ams_msg.h +++ b/src/include/sof/lib/ams_msg.h @@ -15,4 +15,6 @@ typedef uint8_t ams_uuid_t[16]; #define AMS_KPD_MSG_UUID { 0x80, 0xa1, 0x11, 0x22, 0xb3, 0x6c, 0x11, 0xed, \ 0xaf, 0xa1, 0x02, 0x42, 0xac, 0x12, 0x00, 0x02 } + + #endif /* __SOF_LIB_AMS_MSG_H__ */ diff --git a/src/include/sof/lib/notifier.h b/src/include/sof/lib/notifier.h index f022b805144c..8b81f559a38f 100644 --- a/src/include/sof/lib/notifier.h +++ b/src/include/sof/lib/notifier.h @@ -31,6 +31,8 @@ enum notify_id { NOTIFIER_ID_DMA_IRQ, /* struct dma_chan_data * */ NOTIFIER_ID_DAI_TRIGGER, /* struct dai_group * */ NOTIFIER_ID_MIC_PRIVACY_STATE_CHANGE, /* struct mic_privacy_settings * */ + NOTIFIER_ID_WOV_DETECT, /* struct wov_detect_notif *: keyword detected */ + NOTIFIER_ID_WOV_CTRL, /* struct wov_ctrl_notif *: pause/resume detectors */ NOTIFIER_ID_COUNT }; From 9a870f6e4c504ddf8c9b085cf387114d0ac25202 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Sun, 6 Sep 2026 13:52:13 +0100 Subject: [PATCH 03/25] audio: wov_arbiter: add multi-slot WOV arbiter component - Add multi-slot WOV arbiter component - Support mono-to-stereo channel expansion and sample width casting - Implement period-bounded zero-fill pacing during idle/startup - Add single-source default slot 0 routing Signed-off-by: Liam Girdwood --- src/audio/CMakeLists.txt | 3 + src/audio/wov_arbiter/CMakeLists.txt | 3 + src/audio/wov_arbiter/wov_arbiter.c | 528 +++++++++++++++++++++++++ src/audio/wov_arbiter/wov_arbiter.toml | 19 + src/include/sof/audio/wov_arbiter.h | 50 +++ tools/rimage/config/tgl.toml.h | 4 + uuid-registry.txt | 1 + 7 files changed, 608 insertions(+) create mode 100644 src/audio/wov_arbiter/CMakeLists.txt create mode 100644 src/audio/wov_arbiter/wov_arbiter.c create mode 100644 src/audio/wov_arbiter/wov_arbiter.toml create mode 100644 src/include/sof/audio/wov_arbiter.h diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 92002c8b7c1c..d7a2e60aab60 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -107,6 +107,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_VOLUME) add_subdirectory(volume) endif() + if(CONFIG_COMP_WOV_ARBITER) + add_subdirectory(wov_arbiter) + endif() if(CONFIG_DTS_CODEC) add_subdirectory(codec) endif() diff --git a/src/audio/wov_arbiter/CMakeLists.txt b/src/audio/wov_arbiter/CMakeLists.txt new file mode 100644 index 000000000000..fd15150321c6 --- /dev/null +++ b/src/audio/wov_arbiter/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: BSD-3-Clause + +add_local_sources(sof wov_arbiter.c) diff --git a/src/audio/wov_arbiter/wov_arbiter.c b/src/audio/wov_arbiter/wov_arbiter.c new file mode 100644 index 000000000000..d3a9561c5d6d --- /dev/null +++ b/src/audio/wov_arbiter/wov_arbiter.c @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation +// +// WOV Arbiter -- routes the drain output of one-of-N KPB host sinks to the +// single host PCM copier, while silencing the idle inputs and coordinating +// pause/resume of sibling WOV detectors via the SOF notifier system. +// +// Topology connectivity (per KPB slot i): +// KPB_i host_sink (output pin 1) --> wov_arbiter input pin i +// wov_arbiter output pin 0 --> host copier +// +// Notifier events: +// Subscribes to NOTIFIER_ID_WOV_DETECT (detector -> arbiter on keyword) +// Publishes NOTIFIER_ID_WOV_CTRL (arbiter -> detectors: pause/resume) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(wov_arbiter, CONFIG_SOF_LOG_LEVEL); + +SOF_DEFINE_REG_UUID(wov_arbiter); +DECLARE_TR_CTX(wov_arbiter_tr, SOF_UUID(wov_arbiter_uuid), LOG_LEVEL_INFO); + +/* Private runtime data. */ +struct wov_arb_data { + struct ipc4_base_module_cfg base_cfg; + /* + * Index of the currently active KPB slot (0..num_slots-1). + * WOV_ARB_NO_ACTIVE when no drain is in progress. + * Protected by the scheduler; no extra lock needed. + */ + uint8_t active_slot; + /* Number of input pins (= KPB slots); read from nb_input_pins at init. */ + uint8_t num_slots; +}; + +#if CONFIG_IPC_MAJOR_4 +static void notify_control_change(const struct comp_dev *dev, uint16_t control_id, uint32_t val) +{ + struct sof_ipc4_notify_module_data *msg_module_data; + struct sof_ipc4_control_msg_payload *msg_payload; + struct ipc_msg *msg; + uint32_t data_size = sizeof(struct sof_ipc4_notify_module_data) + + sizeof(struct sof_ipc4_control_msg_payload) + + sizeof(struct sof_ipc4_ctrl_value_chan); + struct ipc4_voice_cmd_notification notif; + + memset_s(¬if, sizeof(notif), 0, sizeof(notif)); + notif.primary.r.notif_type = SOF_IPC4_MODULE_NOTIFICATION; + notif.primary.r.type = SOF_IPC4_GLB_NOTIFICATION; + notif.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + notif.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + + msg = ipc_msg_w_ext_init(notif.primary.dat, 0, data_size); + if (!msg) + return; + + msg_module_data = (struct sof_ipc4_notify_module_data *)msg->tx_data; + msg_module_data->instance_id = IPC4_INST_ID(dev->ipc_config.id); + msg_module_data->module_id = IPC4_MOD_ID(dev->ipc_config.id); + msg_module_data->event_id = SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL | + SOF_IPC4_ENUM_CONTROL_PARAM_ID; + msg_module_data->event_data_size = sizeof(struct sof_ipc4_control_msg_payload) + + sizeof(struct sof_ipc4_ctrl_value_chan); + + msg_payload = (struct sof_ipc4_control_msg_payload *)msg_module_data->event_data; + msg_payload->id = control_id; + msg_payload->num_elems = 1; + msg_payload->chanv[0].channel = 0; + msg_payload->chanv[0].value = val; + + ipc_msg_send(msg, NULL, true); +} +#endif + +/* ------------------------------------------------------------------------- + * Notifier callbacks + * ---------------------------------------------------------------------- */ + +/* Notifier callback: a WOV detector has fired. Activates the winning slot + * and broadcasts PAUSE to all detectors via NOTIFIER_ID_WOV_CTRL. */ +static void arb_on_detect(void *arg, enum notify_id id, void *data) +{ + struct comp_dev *dev = arg; + struct wov_arb_data *cd = comp_get_drvdata(dev); + const struct wov_detect_notif *det = data; + + if (det->slot_id >= cd->num_slots) { + comp_err(dev, "wov_arb: bad slot_id %u num_slots=%u", + det->slot_id, cd->num_slots); + return; + } + + /* First-wins: ignore if another slot is already draining. */ + if (cd->active_slot != WOV_ARB_NO_ACTIVE) { + comp_warn(dev, "wov_arb: slot %u detected but slot %u active, ignoring", + det->slot_id, cd->active_slot); + return; + } + + comp_info(dev, "wov_arb: activating slot %u", det->slot_id); + cd->active_slot = det->slot_id; + +#if CONFIG_IPC_MAJOR_4 + /* Notify host ALSA enum control: 1..N corresponds to Slot 1..N (0 is Listening) */ + notify_control_change(dev, 0, (uint32_t)cd->active_slot + 1); +#endif + + /* Broadcast PAUSE to all detectors. The winning slot continues draining + * its KPB history buffer; all others suspend detection until RESUME. */ + struct wov_ctrl_notif ctrl = { .cmd = WOV_ARB_CMD_PAUSE, .slot_id = det->slot_id }; + notifier_event(dev, NOTIFIER_ID_WOV_CTRL, NOTIFIER_TARGET_CORE_ALL_MASK, + &ctrl, sizeof(ctrl)); +} + +/* ------------------------------------------------------------------------- + * Component lifecycle + * ---------------------------------------------------------------------- */ + +static struct comp_dev *wov_arb_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ + struct comp_dev *dev; + struct wov_arb_data *cd; + + comp_cl_info(&drv->tctx, "wov_arb_new"); + + dev = comp_alloc(drv, sizeof(*dev)); + if (!dev) + return NULL; + dev->ipc_config = *config; + + cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd)); + if (!cd) { + comp_free_device(dev); + return NULL; + } + + const struct ipc4_base_module_cfg *base_cfg = spec; + memcpy_s(&cd->base_cfg, sizeof(cd->base_cfg), base_cfg, sizeof(*base_cfg)); + + /* Detection notifications arrive via notifier bus across pipelines, so support up to MAX_SLOTS */ + cd->num_slots = WOV_ARB_MAX_SLOTS; + + + /* Start with no active slot; first WOV_DETECT notifier will activate one. */ + cd->active_slot = WOV_ARB_NO_ACTIVE; + + comp_set_drvdata(dev, cd); + /* Arbiter produces a capture stream that feeds the host PCM copier. */ + dev->direction = SOF_IPC_STREAM_CAPTURE; + dev->direction_set = true; + dev->state = COMP_STATE_READY; + + comp_info(dev, "wov_arb_new: num_slots=%u", cd->num_slots); + + return dev; +} + +static void wov_arb_free(struct comp_dev *dev) +{ + comp_info(dev, "wov_arb_free"); + + notifier_unregister(dev, NULL, NOTIFIER_ID_WOV_DETECT); + + struct wov_arb_data *cd = comp_get_drvdata(dev); + rfree(cd); + comp_free_device(dev); +} + +static int wov_arb_prepare(struct comp_dev *dev) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + comp_info(dev, "wov_arb_prepare"); + + cd->active_slot = WOV_ARB_NO_ACTIVE; + + /* Subscribe to keyword-detected events from any WOV detector. */ + notifier_register(dev, NULL, NOTIFIER_ID_WOV_DETECT, arb_on_detect, 0); + + /* Broadcast RESUME so all WOV detector slots start unpaused. */ + struct wov_ctrl_notif ctrl = { .cmd = WOV_ARB_CMD_RESUME }; + notifier_event(dev, NOTIFIER_ID_WOV_CTRL, NOTIFIER_TARGET_CORE_ALL_MASK, + &ctrl, sizeof(ctrl)); + + return comp_set_state(dev, COMP_TRIGGER_PREPARE); +} + +static int wov_arb_reset(struct comp_dev *dev) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + comp_info(dev, "wov_arb_reset"); + + cd->active_slot = WOV_ARB_NO_ACTIVE; + +#if CONFIG_IPC_MAJOR_4 + /* Reset enum control to 0 (Listening) */ + notify_control_change(dev, 0, 0); +#endif + + notifier_unregister(dev, NULL, NOTIFIER_ID_WOV_DETECT); + + return comp_set_state(dev, COMP_TRIGGER_RESET); +} + +static int wov_arb_trigger(struct comp_dev *dev, int cmd) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + int ret; + + comp_info(dev, "wov_arb_trigger cmd %d", cmd); + + ret = comp_set_state(dev, cmd); + if (ret) + return ret; + + /* + * Stream stopped or paused: deactivate the active slot and resume + * all detectors so they return to listening mode. + */ + if (cmd == COMP_TRIGGER_STOP || cmd == COMP_TRIGGER_PAUSE) { + if (cd->active_slot != WOV_ARB_NO_ACTIVE) { + comp_info(dev, "wov_arb: stream stopped, resuming all slots"); + cd->active_slot = WOV_ARB_NO_ACTIVE; +#if CONFIG_IPC_MAJOR_4 + notify_control_change(dev, 0, 0); +#endif + struct wov_ctrl_notif c = { .cmd = WOV_ARB_CMD_RESUME }; + notifier_event(dev, NOTIFIER_ID_WOV_CTRL, + NOTIFIER_TARGET_CORE_ALL_MASK, + &c, sizeof(c)); + } + } + + return 0; +} + +static int wov_arb_params(struct comp_dev *dev, + struct sof_ipc_stream_params *params) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + /* Translate IPC4 base_cfg audio_fmt to IPC3-style stream params. */ + memset(params, 0, sizeof(*params)); + params->channels = cd->base_cfg.audio_fmt.channels_count; + params->rate = cd->base_cfg.audio_fmt.sampling_frequency; + params->sample_container_bytes = cd->base_cfg.audio_fmt.depth / 8; + params->sample_valid_bytes = + cd->base_cfg.audio_fmt.valid_bit_depth / 8; + params->buffer_fmt = cd->base_cfg.audio_fmt.interleaving_style; + component_set_nearest_period_frames(dev, params->rate); + + return 0; +} + +/* ------------------------------------------------------------------------- + * IPC4 large-config: allow host to force-select a slot (debug/test use). + * ---------------------------------------------------------------------- */ + +static int wov_arb_set_large_config(struct comp_dev *dev, + uint32_t param_id, + bool first_block, + bool last_block, + uint32_t data_offset, + const char *data) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + switch (param_id) { + case SOF_IPC4_ENUM_CONTROL_PARAM_ID: + case SOF_IPC4_BYTES_CONTROL_PARAM_ID: + case IPC4_WOV_ARB_GET_ACTIVE_SLOT: + return 0; + case IPC4_WOV_ARB_SET_ACTIVE_SLOT: + cd->active_slot = *(const uint8_t *)data; + comp_info(dev, "wov_arb: force active_slot=%u", cd->active_slot); + return 0; + default: + return -EINVAL; + } +} + +static int wov_arb_get_attribute(struct comp_dev *dev, + uint32_t type, void *value) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + if (type == COMP_ATTR_BASE_CONFIG) { + *(struct ipc4_base_module_cfg *)value = cd->base_cfg; + return 0; + } + return -EINVAL; +} + +/* ------------------------------------------------------------------------- + * copy() -- main audio processing + * + * For the active input pin: forward frames to the output. + * For all other input pins: consume and discard to prevent buffer stalls. + * + * Input buffers are ordered by connection order in bsource_list. + * Slot 0 = first connected source, etc. + * ---------------------------------------------------------------------- */ +static int wov_arb_copy(struct comp_dev *dev) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + struct comp_buffer *sink; + struct comp_buffer *source; + struct list_item *src_item; + uint32_t slot; + uint32_t sink_free; + uint32_t active_avail = 0; + uint32_t copy_bytes; + + comp_dbg(dev, "wov_arb_copy active=%u", cd->active_slot); + + sink = comp_dev_get_first_data_consumer(dev); + if (!sink) + return 0; + + sink_free = audio_stream_get_free_bytes(&sink->stream); + + uint32_t num_sources = 0; + list_for_item(src_item, &dev->bsource_list) { + num_sources++; + } + + uint32_t eff_active_slot = cd->active_slot; + if (eff_active_slot == WOV_ARB_NO_ACTIVE && num_sources <= 1) + eff_active_slot = 0; + + /* First pass: find how many bytes the active source has available. */ + slot = 0; + list_for_item(src_item, &dev->bsource_list) { + source = list_item(src_item, struct comp_buffer, sink_list); + if (slot == eff_active_slot) { + active_avail = audio_stream_get_avail_bytes(&source->stream); + break; + } + if (++slot >= cd->num_slots) + break; + } + + copy_bytes = MIN(active_avail, sink_free); + + uint32_t copied_dst_bytes = 0; + + /* Second pass: copy active slot, silently drain idle slots. */ + slot = 0; + list_for_item(src_item, &dev->bsource_list) { + source = list_item(src_item, struct comp_buffer, sink_list); + + if (slot == eff_active_slot && copy_bytes > 0) { + uint32_t src_frame_bytes = audio_stream_frame_bytes(&source->stream); + uint32_t dst_frame_bytes = audio_stream_frame_bytes(&sink->stream); + uint32_t src_frames = copy_bytes / src_frame_bytes; + uint32_t dst_frames = sink_free / dst_frame_bytes; + uint32_t frames = MIN(src_frames, dst_frames); + + if (frames > 0) { + uint32_t src_bytes = frames * src_frame_bytes; + uint32_t dst_bytes = frames * dst_frame_bytes; + uint32_t src_ch = audio_stream_get_channels(&source->stream); + uint32_t dst_ch = audio_stream_get_channels(&sink->stream); + + buffer_stream_invalidate(source, src_bytes); + + if (src_ch == 1 && dst_ch == 2) { + if (audio_stream_sample_bytes(&source->stream) == sizeof(int16_t)) { + for (uint32_t i = 0; i < frames; i++) { + int16_t s = *(int16_t *)audio_stream_read_frag_s16(&source->stream, i); + *(int16_t *)audio_stream_write_frag_s16(&sink->stream, 2 * i) = s; + *(int16_t *)audio_stream_write_frag_s16(&sink->stream, 2 * i + 1) = s; + } + } else { + for (uint32_t i = 0; i < frames; i++) { + int32_t s = *(int32_t *)audio_stream_read_frag_s32(&source->stream, i); + *(int32_t *)audio_stream_write_frag_s32(&sink->stream, 2 * i) = s; + *(int32_t *)audio_stream_write_frag_s32(&sink->stream, 2 * i + 1) = s; + } + } + } else { + audio_stream_copy(&source->stream, 0, + &sink->stream, 0, + src_bytes / audio_stream_sample_bytes(&source->stream)); + } + + comp_update_buffer_consume(source, src_bytes); + buffer_stream_writeback(sink, dst_bytes); + comp_update_buffer_produce(sink, dst_bytes); + copied_dst_bytes = dst_bytes; + } + } else { + uint32_t avail = audio_stream_get_avail_bytes(&source->stream); + + if (avail > 0) + comp_update_buffer_consume(source, avail); + } + + if (++slot >= cd->num_slots) + break; + } + + if (copied_dst_bytes == 0 && sink_free > 0) { + /* No active audio copied: push period-sized silence so the host copier stays fed. */ + uint32_t dst_frame_bytes = audio_stream_frame_bytes(&sink->stream); + uint32_t period_dst_bytes = dev->frames * dst_frame_bytes; + uint32_t fill_bytes = MIN(sink_free, period_dst_bytes ? period_dst_bytes : 640); + + if (fill_bytes > 0) { + audio_stream_set_zero(&sink->stream, fill_bytes); + buffer_stream_writeback(sink, fill_bytes); + comp_update_buffer_produce(sink, fill_bytes); + } + } + + return 0; +} + +/* ------------------------------------------------------------------------- + * IPC4 large-config (get): expose active slot to userspace as a volatile + * RO enum kcontrol; the host reads this via GET_MODULE_LARGE_CONFIG. + * ---------------------------------------------------------------------- */ + +static int wov_arb_get_large_config(struct comp_dev *dev, + uint32_t param_id, + bool first_block, + bool last_block, + uint32_t *data_offset, + char *data) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + switch (param_id) { + case SOF_IPC4_ENUM_CONTROL_PARAM_ID: { + struct sof_ipc4_control_msg_payload *cp = + (struct sof_ipc4_control_msg_payload *)data; + uint16_t ctl_id = cp->id; + uint32_t resp_size = sizeof(struct sof_ipc4_control_msg_payload) + + sizeof(struct sof_ipc4_ctrl_value_chan); + + if (resp_size > *data_offset) { + comp_err(dev, "wrong enum control response size %u vs %u", + resp_size, *data_offset); + return -EINVAL; + } + + *data_offset = resp_size; + memset_s(cp, resp_size, 0, resp_size); + cp->id = ctl_id; + cp->num_elems = 1; + cp->chanv[0].channel = 0; + /* 0 for Listening, 1..N for triggered slots */ + if (cd->active_slot == WOV_ARB_NO_ACTIVE) + cp->chanv[0].value = 0; + else + cp->chanv[0].value = (uint32_t)cd->active_slot + 1; + return 0; + } + case IPC4_WOV_ARB_GET_ACTIVE_SLOT: + *(uint32_t *)data = (uint32_t)cd->active_slot; + *data_offset = sizeof(uint32_t); + return 0; + default: + return -EINVAL; + } +} + +/* ------------------------------------------------------------------------- + * Component driver registration + * ---------------------------------------------------------------------- */ + +static const struct comp_driver wov_arbiter_drv = { + .type = SOF_COMP_KEYWORD_DETECT, + .uid = SOF_RT_UUID(wov_arbiter_uuid), + .tctx = &wov_arbiter_tr, + .ops = { + .create = wov_arb_new, + .free = wov_arb_free, + .params = wov_arb_params, + .trigger = wov_arb_trigger, + .copy = wov_arb_copy, + .prepare = wov_arb_prepare, + .reset = wov_arb_reset, + .set_large_config = wov_arb_set_large_config, + .get_large_config = wov_arb_get_large_config, + .get_attribute = wov_arb_get_attribute, + }, +}; + +static SHARED_DATA struct comp_driver_info wov_arbiter_info = { + .drv = &wov_arbiter_drv, +}; + +UT_STATIC void sys_comp_wov_arbiter_init(void) +{ + comp_register(&wov_arbiter_info); +} + +DECLARE_MODULE(sys_comp_wov_arbiter_init); +SOF_MODULE_INIT(wov_arbiter, sys_comp_wov_arbiter_init); diff --git a/src/audio/wov_arbiter/wov_arbiter.toml b/src/audio/wov_arbiter/wov_arbiter.toml new file mode 100644 index 000000000000..9a7251627ab8 --- /dev/null +++ b/src/audio/wov_arbiter/wov_arbiter.toml @@ -0,0 +1,19 @@ + [[module.entry]] + name = "WOVARB" + uuid = UUIDREG_STR_WOV_ARBITER + affinity_mask = "0x1" + instance_count = "3" + domain_types = "0" + load_type = "0" + module_type = "0xB" + auto_start = "0" + sched_caps = [1, 0x00008000] + + REM # pin = [dir, type, sample rate, size, container, channel-cfg] + pin = [0, 0, 0xfeef, 0xf, 0xf, 0x45ff, + 1, 0, 0xfeef, 0xf, 0xa, 0x45ff] + + REM # mod_cfg [PAR_0 PAR_1 PAR_2 PAR_3 IS_BYTES CPS IBS OBS MOD_FLAGS CPC OBLS] + mod_cfg = [0, 0, 0, 0, 14400, 1114000, 32, 32, 0, 0, 0] + + index = __COUNTER__ diff --git a/src/include/sof/audio/wov_arbiter.h b/src/include/sof/audio/wov_arbiter.h new file mode 100644 index 000000000000..a1db30a1a3c8 --- /dev/null +++ b/src/include/sof/audio/wov_arbiter.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2024 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_AUDIO_WOV_ARBITER_H__ +#define __SOF_AUDIO_WOV_ARBITER_H__ + +/* Maximum number of WOV detector slots (= KPB host-sink input pins). */ +#define WOV_ARB_MAX_SLOTS 8 + +/* Sentinel: no slot is currently draining. */ +#define WOV_ARB_NO_ACTIVE 0xff + +/* + * IPC4 SET_LARGE_CONFIG param ID used to set the arbiter's active input + * explicitly from the host (testing / override). + */ +#define IPC4_WOV_ARB_SET_ACTIVE_SLOT 1 + +/* + * IPC4 GET_LARGE_CONFIG param ID: read the currently active slot. + * Host uses this for the volatile RO kcontrol; returns WOV_ARB_NO_ACTIVE + * when no drain is in progress. + */ +#define IPC4_WOV_ARB_GET_ACTIVE_SLOT 2 + +/* Command codes for WOV_CTRL notifier payload (arbiter → detectors). */ +#define WOV_ARB_CMD_PAUSE 0 +#define WOV_ARB_CMD_RESUME 1 + +/* WOV detect notifier payload (detector → arbiter). */ +struct wov_detect_notif { + uint8_t slot_id; /* 0-based detector slot that fired */ +}; + +/* WOV ctrl notifier payload (arbiter → detectors). */ +struct wov_ctrl_notif { + uint8_t cmd; /* WOV_ARB_CMD_PAUSE or WOV_ARB_CMD_RESUME */ + uint8_t slot_id; /* winning slot; receivers skip if this is their own slot */ +}; + +/* Sentinel value matching WOV_SLOT_INVALID in ams_msg.h */ +#define WOV_SLOT_INVALID 0xff + +#ifdef UNIT_TEST +void sys_comp_wov_arbiter_init(void); +#endif + +#endif /* __SOF_AUDIO_WOV_ARBITER_H__ */ diff --git a/tools/rimage/config/tgl.toml.h b/tools/rimage/config/tgl.toml.h index 2ca246880727..f231ea6e158f 100644 --- a/tools/rimage/config/tgl.toml.h +++ b/tools/rimage/config/tgl.toml.h @@ -68,6 +68,10 @@ #include