From 34e00a85f09c613e851c8425347f81862bb0a729 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Fri, 29 May 2026 16:12:08 +0200 Subject: [PATCH 001/303] fuzz: Enable heap protection option IPC fuzzer is built with Zephyr sys_heap. Because we have a custom allocator, the compiler sanitizers alone are not able to detect all errors related to memory allocation. Enabling heap hardening aims to increase the number of potentially detectable errors in fuzz builds. CONFIG_SYS_HEAP_HARDENING_EXTREME: adds per-chunk canary trailers (catching buffer overflows that spill even a single byte into the next chunk), double-free detection, free-list pointer validation, and a full heap structure walk after every alloc/free operation. This last check catches external corruption (e.g. a wild write from an unrelated component damaging heap metadata) before the allocator acts on it, rather than letting the damage propagate silently to the next allocation that happens to touch the corrupted region. The measured cost is ~7% throughput (288k -> 280k execs/30s), negligible given the class of bugs it surfaces. Signed-off-by: Tomasz Leman --- app/boards/native_sim_libfuzzer.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/app/boards/native_sim_libfuzzer.conf b/app/boards/native_sim_libfuzzer.conf index ece1e35845c9..8f03377b0ed2 100644 --- a/app/boards/native_sim_libfuzzer.conf +++ b/app/boards/native_sim_libfuzzer.conf @@ -5,6 +5,7 @@ CONFIG_ASSERT=y CONFIG_EXCEPTION_DEBUG=y CONFIG_ARCH_POSIX_TRAP_ON_FATAL=y CONFIG_SYS_HEAP_BIG_ONLY=y +CONFIG_SYS_HEAP_HARDENING_EXTREME=y CONFIG_ZEPHYR_NATIVE_DRIVERS=y CONFIG_ARCH_POSIX_LIBFUZZER=y CONFIG_ZEPHYR_POSIX_FUZZ_TICKS=100 From 2b8fc70c3fd5f41db0d8bb551cec9040f343ae23 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Mon, 1 Jun 2026 15:42:12 +0200 Subject: [PATCH 002/303] fuzz: Enable stack sentinel for fuzz builds Enable Zephyr stack sentinel checks for the native libFuzzer build. This complements the heap hardening option by catching Zephyr thread stack overflows closer to the corrupting input. CONFIG_STACK_SENTINEL: stores a magic value at the lowest addresses of each thread stack and checks it on context switch, interrupt return, k_yield(), and thread exit. When the sentinel is corrupted the system traps immediately, giving the fuzzer a clear crash signal instead of allowing silent corruption that manifests later in an unrelated path. This is particularly useful in UBSan-only fuzz runs where ASan stack redzones are not available. Signed-off-by: Tomasz Leman --- app/boards/native_sim_libfuzzer.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/app/boards/native_sim_libfuzzer.conf b/app/boards/native_sim_libfuzzer.conf index 8f03377b0ed2..ba17224424cf 100644 --- a/app/boards/native_sim_libfuzzer.conf +++ b/app/boards/native_sim_libfuzzer.conf @@ -6,6 +6,7 @@ CONFIG_EXCEPTION_DEBUG=y CONFIG_ARCH_POSIX_TRAP_ON_FATAL=y CONFIG_SYS_HEAP_BIG_ONLY=y CONFIG_SYS_HEAP_HARDENING_EXTREME=y +CONFIG_STACK_SENTINEL=y CONFIG_ZEPHYR_NATIVE_DRIVERS=y CONFIG_ARCH_POSIX_LIBFUZZER=y CONFIG_ZEPHYR_POSIX_FUZZ_TICKS=100 From efbc17bafc46a27ca3c72095510723ef17810e53 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 26 May 2026 17:39:39 +0200 Subject: [PATCH 003/303] pipeline: reject double-connect of already-attached buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pipeline_connect() had no guard against being called twice for the same buffer-component pair. Calling list_item_prepend() on a node that is already in a doubly-linked list corrupts the list by creating a self-loop where node->next points back to itself instead of to the list head. The corruption was discovered through IPC3 fuzzing in persistent mode. Without per-testcase topology teardown, components and buffers created by testcase N survive into testcase N+1. When N+1 sends a TPLG_COMP_CONNECT for IDs that N already connected, ipc_comp_connect() finds the surviving objects and calls pipeline_connect() a second time. The same sequence can also be triggered within a single testcase by two CONNECT messages for the same pair. The self-loop causes ipc_comp_free() to hang indefinitely. The function walks bsource_list / bsink_list with comp_dev_for_each_producer_safe() whose termination condition checks node->next == &comp->bsource_list. With the self-loop that check is always false. The walk runs inside irq_local_disable(), so the native_sim timer cannot preempt the thread and nsi_exec_for() never returns, making libFuzzer's max_total_time limit unreachable. A second failure mode arises when ipc_buffer_free() calls pipeline_disconnect() on the corrupted buffer. list_item_del() updates comp->bsource_list.next to node->next, which due to the self-loop is the node itself — leaving the component's list head pointing into the freed buffer memory. When that memory is reused by a later allocation and overwritten, the next walk of bsource_list dereferences an invalid pointer, crashing with a null dereference or a corrupt-pointer access. Move the list-consistency check down into buffer_attach() itself, where it belongs alongside the list_item_prepend() call it protects. The function is changed to return int: it inspects the buffer's list node for the given direction via buffer_comp_list() and returns -EALREADY when list_is_empty() reports the node is already linked (node->next != node), otherwise prepends and returns 0. pipeline_connect() propagates that error and emits the component-context log message; the existing single caller is the only one to update. Verified with -s address on the full IPC3 corpus (~95K runs, 41 s): zero crashes, zero hangs, ~2300 exec/s. The unfixed build stalled indefinitely on the same run. Signed-off-by: Tomasz Leman --- src/audio/buffers/comp_buffer.c | 10 +++++++++- src/audio/pipeline/pipeline-graph.c | 9 ++++++++- src/include/sof/audio/buffer.h | 5 ++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/audio/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index 502103693689..8c64ff3d7d02 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -604,12 +604,20 @@ static inline struct list_item *buffer_comp_list(struct comp_buffer *buffer, * from racing attach / detach calls, but the scheduler can interrupt the IPC * thread and begin using the buffer for streaming. FIXME: this is still a * problem with different cores. + * + * Returns -EALREADY if the buffer is already attached in this direction. + * Attaching the same buffer twice would corrupt the list (list_item_prepend + * on an already-linked node creates a self-loop), so callers must propagate + * the error. */ -void buffer_attach(struct comp_buffer *buffer, struct list_item *head, int dir) +int buffer_attach(struct comp_buffer *buffer, struct list_item *head, int dir) { struct list_item *list = buffer_comp_list(buffer, dir); CORE_CHECK_STRUCT(&buffer->audio_buffer); + if (!list_is_empty(list)) + return -EALREADY; list_item_prepend(list, head); + return 0; } /* diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 47d5d0127fd0..2a679c2b57ef 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -198,6 +198,7 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, int dir) { struct list_item *comp_list; + int ret; PPL_LOCK_DECLARE; if (dir == PPL_CONN_DIR_COMP_TO_BUFFER) @@ -208,7 +209,13 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, PPL_LOCK(); comp_list = comp_buffer_list(comp, dir); - buffer_attach(buffer, comp_list, dir); + ret = buffer_attach(buffer, comp_list, dir); + if (ret < 0) { + comp_err(comp, "buffer %d already connected dir %d", + buf_get_id(buffer), dir); + PPL_UNLOCK(); + return ret; + } buffer_set_comp(buffer, comp, dir); PPL_UNLOCK(); diff --git a/src/include/sof/audio/buffer.h b/src/include/sof/audio/buffer.h index 6e6b8a9caef8..517f229e5420 100644 --- a/src/include/sof/audio/buffer.h +++ b/src/include/sof/audio/buffer.h @@ -289,8 +289,11 @@ static inline void buffer_stream_writeback(struct comp_buffer *buffer, uint32_t * really be the head of the list, not a list head within another buffer. We * don't synchronise its cache, so it must not be embedded in an object, using * the coherent API. The caller takes care to protect list heads. + * + * Returns -EINVAL if the buffer is already linked in this direction + * (re-attaching would create a self-loop and corrupt the list). */ -void buffer_attach(struct comp_buffer *buffer, struct list_item *head, int dir); +int buffer_attach(struct comp_buffer *buffer, struct list_item *head, int dir); /* * Detach a buffer from anywhere in the list. "head" is again the head of the From 8f4e140a26be492211eac4196cdce181f4e91f76 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 27 Feb 2026 16:59:02 +0200 Subject: [PATCH 004/303] schedule: add scheduler_init_context() Add an optional method that allow the schedule.h user to get access to the thread context that will be used for scheduling. This is critical when the callbacks are run in user-space context and schedule.h client needs to grant access to objects like locks to the callback thread. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/schedule.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/include/sof/schedule/schedule.h b/src/include/sof/schedule/schedule.h index bbdcbbecf3b4..9418064beb02 100644 --- a/src/include/sof/schedule/schedule.h +++ b/src/include/sof/schedule/schedule.h @@ -158,6 +158,16 @@ struct scheduler_ops { * This operation is optional. */ int (*scheduler_restore)(void *data); + + /** + * Initializes context + * @param data Private data of selected scheduler. + * @param task task that needs to be scheduled + * @return thread that will be used to run the scheduled task + * + * This operation is optional. + */ + struct k_thread *(*scheduler_init_context)(void *data, struct task *task); }; /** \brief Holds information about scheduler. */ @@ -379,6 +389,24 @@ static inline int schedulers_restore(void) return 0; } +/** See scheduler_ops::scheduler_init_context */ +static inline struct k_thread *scheduler_init_context(struct task *task) +{ + struct schedulers *schedulers = *arch_schedulers_get(); + struct schedule_data *sch; + struct list_item *slist; + + assert(schedulers); + + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (task->type == sch->type && sch->ops->scheduler_init_context) + return sch->ops->scheduler_init_context(sch->data, task); + } + + return NULL; +} + /** * Initializes scheduling task. * @param task Task to be initialized. From d0b18c19574b5c756384e63384cffe50e77fb16f Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 4 Mar 2026 19:25:43 +0200 Subject: [PATCH 005/303] schedule: ll_schedule_domain: add domain_thread_init/free ops Add new domain ops that are called from privileged context and are used to set up resources like threads and initialize other kernel objects. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/ll_schedule_domain.h | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index 451ad7739f8f..4a10fcefd464 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -47,6 +47,20 @@ struct ll_schedule_domain_ops { void (*handler)(void *arg), void *arg); int (*domain_unregister)(struct ll_schedule_domain *domain, struct task *task, uint32_t num_tasks); +#if CONFIG_SOF_USERSPACE_LL + /* + * Initialize the scheduling thread and perform all privileged setup + * (thread creation, timer init, access grants). Called once from + * kernel context before any user-space domain_register() calls. + */ + int (*domain_thread_init)(struct ll_schedule_domain *domain, + struct task *task); + /* Free resources acquired by domain_thread_init(). Called from + * kernel context when the scheduling context is being torn down. + */ + void (*domain_thread_free)(struct ll_schedule_domain *domain, + uint32_t num_tasks); +#endif void (*domain_enable)(struct ll_schedule_domain *domain, int core); void (*domain_disable)(struct ll_schedule_domain *domain, int core); #if CONFIG_CROSS_CORE_STREAM @@ -181,6 +195,31 @@ static inline void domain_task_cancel(struct ll_schedule_domain *domain, domain->ops->domain_task_cancel(domain, task); } +#if CONFIG_SOF_USERSPACE_LL +/* + * Initialize the scheduling thread and do all privileged setup. + * Must be called from kernel context before user-space tasks register. + */ +static inline int domain_thread_init(struct ll_schedule_domain *domain, + struct task *task) +{ + assert(domain->ops->domain_thread_init); + + return domain->ops->domain_thread_init(domain, task); +} + +/* + * Free resources acquired by domain_thread_init(). + * Must be called from kernel context. + */ +static inline void domain_thread_free(struct ll_schedule_domain *domain, + uint32_t num_tasks) +{ + if (domain->ops->domain_thread_free) + domain->ops->domain_thread_free(domain, num_tasks); +} +#endif + static inline int domain_register(struct ll_schedule_domain *domain, struct task *task, void (*handler)(void *arg), void *arg) From a450b9279d54218a27beace178bf3303a928705c Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 25 May 2026 13:52:46 +0300 Subject: [PATCH 006/303] schedule: zephyr_ll: implement user/kernel split with init_context() Use the new scheduler_init_context() and domain_thread_init/free() interfaces to separate LL scheduler logic into privileged and unprivileged parts. The latter can be used by the audio pipeline to add, remove and schedule tasks from the user-space audio thread. The privileged interfaces are used to set up the scheduler during firmware boot. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_domain.c | 172 ++++++++++++++++++++++++----------- src/schedule/zephyr_ll.c | 48 +++++++--- 2 files changed, 158 insertions(+), 62 deletions(-) diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index ebf5aea8d4d2..daace8bac708 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -126,7 +126,8 @@ static void zephyr_domain_thread_fn(void *p1, void *p2, void *p3) } #endif - dt->handler(dt->arg); + if (dt->handler) + dt->handler(dt->arg); #ifdef CONFIG_SCHEDULE_LL_STATS_LOG cycles1 = k_cycle_get_32(); @@ -289,63 +290,65 @@ static int zephyr_domain_unregister(struct ll_schedule_domain *domain, #else /* CONFIG_SOF_USERSPACE_LL */ -/* User-space implementation for register/unregister */ - -static int zephyr_domain_register_user(struct ll_schedule_domain *domain, - struct task *task, - void (*handler)(void *arg), void *arg) +/* + * Privileged thread initialization for userspace LL scheduling. + * Creates the scheduling thread, sets up timer, grants access to kernel + * objects. Must be called from kernel context before any user-space + * domain_register() calls. + */ +static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, + struct task *task) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); - int core = cpu_get_id(); - struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + struct zephyr_domain_thread *dt; char thread_name[] = "ll_thread0"; k_tid_t thread; + int core = task->core; - tr_dbg(&ll_tr, "entry"); + tr_dbg(&ll_tr, "thread_init entry"); - /* domain work only needs registered once on each core */ - if (dt->handler) - return 0; + if (core >= CONFIG_CORE_COUNT) + return -EINVAL; - __ASSERT_NO_MSG(task->core == core); + dt = zephyr_domain->domain_thread + core; - dt->handler = handler; - dt->arg = arg; + /* thread only needs to be created once per core */ + if (dt->ll_thread) + return 0; + + dt->handler = NULL; + dt->arg = NULL; /* 10 is rather random, we better not accumulate 10 missed timer interrupts */ k_sem_init(dt->sem, 0, 10); thread_name[sizeof(thread_name) - 2] = '0' + core; + /* Allocate thread structure dynamically */ + dt->ll_thread = k_object_alloc(K_OBJ_THREAD); if (!dt->ll_thread) { - /* Allocate thread structure dynamically */ - dt->ll_thread = k_object_alloc(K_OBJ_THREAD); - if (!dt->ll_thread) { - tr_err(&ll_tr, "Failed to allocate thread object for core %d", core); - dt->handler = NULL; - dt->arg = NULL; - return -ENOMEM; - } + tr_err(&ll_tr, "Failed to allocate thread object for core %d", core); + return -ENOMEM; + } - thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE, - zephyr_domain_thread_fn, zephyr_domain, - INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY, - K_USER, K_FOREVER); + thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE, + zephyr_domain_thread_fn, zephyr_domain, + INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY, + K_USER, K_FOREVER); #ifdef CONFIG_SCHED_CPU_MASK - k_thread_cpu_mask_clear(thread); - k_thread_cpu_mask_enable(thread, core); + k_thread_cpu_mask_clear(thread); + k_thread_cpu_mask_enable(thread, core); #endif - k_thread_name_set(thread, thread_name); + k_thread_name_set(thread, thread_name); - k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread); - k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); - user_grant_dai_access_all(thread); - user_grant_dma_access_all(thread); - tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core); + k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread); + k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); + user_grant_dai_access_all(thread); + user_grant_dma_access_all(thread); + tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core); - k_thread_start(thread); - } + k_thread_start(thread); k_mutex_lock(domain->lock, K_FOREVER); if (!k_timer_user_data_get(zephyr_domain->timer)) { @@ -368,6 +371,43 @@ static int zephyr_domain_register_user(struct ll_schedule_domain *domain, return 0; } +/* + * User-space register: bookkeeping only. The privileged thread setup has + * already been done by domain_thread_init() called from kernel context. + */ +static int zephyr_domain_register_user(struct ll_schedule_domain *domain, + struct task *task, + void (*handler)(void *arg), void *arg) +{ + struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); + struct zephyr_domain_thread *dt; + int core; + + tr_dbg(&ll_tr, "register_user entry"); + + if (task->core >= CONFIG_CORE_COUNT) + return -EINVAL; + + core = task->core; + dt = zephyr_domain->domain_thread + core; + + if (!dt->ll_thread) { + tr_err(&ll_tr, "domain_thread_init() not called for core %d", core); + return -EINVAL; + } + + __ASSERT_NO_MSG(!dt->handler || dt->handler == handler); + if (dt->handler) + return 0; + + dt->handler = handler; + dt->arg = arg; + + tr_info(&ll_tr, "task registered on core %d", core); + + return 0; +} + static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, struct task *task, uint32_t num_tasks) { @@ -382,30 +422,58 @@ static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, k_mutex_lock(domain->lock, K_FOREVER); - if (!atomic_read(&domain->total_num_tasks)) { - /* Disable the watchdog */ + zephyr_domain->domain_thread[core].handler = NULL; + + k_mutex_unlock(domain->lock); + + /* + * In this user thread implementation, the timer is left + * running until privileged domain_thread_free() is called + * to clean up resources. + */ + + tr_dbg(&ll_tr, "exit"); + + return 0; +} + +/* + * Free resources acquired by zephyr_domain_thread_init(). + * Stops the timer, aborts the scheduling thread and frees the thread object. + * Must be called from kernel context. + */ +static void zephyr_domain_thread_free(struct ll_schedule_domain *domain, + uint32_t num_tasks) +{ + struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); + int core = cpu_get_id(); + struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + + tr_dbg(&ll_tr, "thread_free entry, core %d, num_tasks %u", core, num_tasks); + + /* Still tasks on other cores, only clean up this core's thread */ + k_mutex_lock(domain->lock, K_FOREVER); + + if (!num_tasks && !atomic_read(&domain->total_num_tasks)) { + /* Last task globally: stop the timer and watchdog */ watchdog_disable(core); k_timer_stop(zephyr_domain->timer); k_timer_user_data_set(zephyr_domain->timer, NULL); } - zephyr_domain->domain_thread[core].handler = NULL; + dt->handler = NULL; + dt->arg = NULL; k_mutex_unlock(domain->lock); - tr_info(&ll_tr, "domain->type %d domain->clk %d", - domain->type, domain->clk); - - /* Thread not removed here, only the timer is stopped. - * Thread object cleanup would require k_thread_abort() which cannot - * be safely called from this context. The thread remains allocated - * but dormant until next registration or system shutdown. - */ - - tr_dbg(&ll_tr, "exit"); + if (dt->ll_thread) { + k_thread_abort(dt->ll_thread); + k_object_free(dt->ll_thread); + dt->ll_thread = NULL; + } - return 0; + tr_info(&ll_tr, "thread_free done, core %d", core); } struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain) @@ -450,6 +518,8 @@ APP_TASK_DATA static const struct ll_schedule_domain_ops zephyr_domain_ops = { #ifdef CONFIG_SOF_USERSPACE_LL .domain_register = zephyr_domain_register_user, .domain_unregister = zephyr_domain_unregister_user, + .domain_thread_init = zephyr_domain_thread_init, + .domain_thread_free = zephyr_domain_thread_free, #else .domain_register = zephyr_domain_register, .domain_unregister = zephyr_domain_unregister, diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 575a82d91dda..1c1717d30ac6 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -360,17 +360,7 @@ static int zephyr_ll_task_schedule_common(struct zephyr_ll *sch, struct task *ta ret = domain_register(sch->ll_domain, task, &schedule_ll_callback, sch); if (ret < 0) - tr_err(&ll_tr, "cannot register domain %d", - ret); - -#if CONFIG_SOF_USERSPACE_LL - k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock); - - tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock, - zephyr_domain_thread_tid(sch->ll_domain)); - tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock, - zephyr_domain_thread_tid(sch->ll_domain)); -#endif + tr_err(&ll_tr, "cannot register domain %d", ret); return 0; } @@ -509,8 +499,41 @@ static void zephyr_ll_scheduler_free(void *data, uint32_t flags) if (sch->n_tasks) tr_err(&ll_tr, "%u tasks are still active!", sch->n_tasks); + +#if CONFIG_SOF_USERSPACE_LL + domain_thread_free(sch->ll_domain, sch->n_tasks); +#endif } +#if CONFIG_SOF_USERSPACE_LL +struct k_thread *zephyr_ll_init_context(void *data, struct task *task) +{ + struct zephyr_ll *sch = data; + int ret; + + /* + * Use domain_thread_init() for privileged setup (thread creation, + * timer, access grants). domain_register() is now bookkeeping only + * and will be called later from user context when scheduling tasks. + */ + ret = domain_thread_init(sch->ll_domain, task); + if (ret < 0) { + tr_err(&ll_tr, "cannot init_context %d", ret); + return NULL; + } + + assert(!k_is_user_context()); + k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock); + + tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock, + zephyr_domain_thread_tid(sch->ll_domain)); + tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock, + zephyr_domain_thread_tid(sch->ll_domain)); + + return zephyr_domain_thread_tid(sch->ll_domain); +} +#endif + static const struct scheduler_ops zephyr_ll_ops = { .schedule_task = zephyr_ll_task_schedule, .schedule_task_before = zephyr_ll_task_schedule_before, @@ -518,6 +541,9 @@ static const struct scheduler_ops zephyr_ll_ops = { .schedule_task_free = zephyr_ll_task_free, .schedule_task_cancel = zephyr_ll_task_cancel, .scheduler_free = zephyr_ll_scheduler_free, +#if CONFIG_SOF_USERSPACE_LL + .scheduler_init_context = zephyr_ll_init_context, +#endif }; #if CONFIG_SOF_USERSPACE_LL From 2684660ffc30dfec9902cc31dcdffe52d13b208d Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Feb 2026 17:44:14 +0200 Subject: [PATCH 007/303] schedule: zephyr_ll_user: make the heap accessible from user-space Rework the heap allocation. Instead of reusing module_driver_heap_init(), allocate the heap in zephyr_ll_user_resources_init(). A copy of heap pointer is stored into user-space accessible memory and zephyr_ll_user_heap_verify() is added to allow kernel code verify the heap object. CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE is added to Kconfig to control size of the LL user heap. This will initially cover all user-space audio pipeline allocations. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/ll_schedule_domain.h | 1 + src/schedule/zephyr_ll_user.c | 83 ++++++++++++++++--- zephyr/Kconfig | 11 +++ 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index 4a10fcefd464..1367c408899f 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -118,6 +118,7 @@ static inline struct ll_schedule_domain *dma_domain_get(void) #ifdef CONFIG_SOF_USERSPACE_LL struct task *zephyr_ll_task_alloc(void); struct k_heap *zephyr_ll_user_heap(void); +bool zephyr_ll_user_heap_verify(struct k_heap *heap); void zephyr_ll_user_resources_init(void); #endif /* CONFIG_SOF_USERSPACE_LL */ diff --git a/src/schedule/zephyr_ll_user.c b/src/schedule/zephyr_ll_user.c index aa33807b4aa3..b6b00691d7c9 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -17,25 +17,51 @@ LOG_MODULE_DECLARE(ll_schedule, CONFIG_SOF_LOG_LEVEL); * * This structure encapsulates the memory management resources required for the * low-latency (LL) scheduler in userspace mode. It provides memory isolation - * and heap management for LL scheduler threads. + * and heap management for LL scheduler threads. Only kernel accessible. */ struct zephyr_ll_mem_resources { struct k_mem_domain mem_domain; /**< Memory domain for LL thread isolation */ - struct k_heap *heap; /**< Heap allocator for LL scheduler memory */ + struct k_heap heap; /**< Heap allocator for LL scheduler memory */ }; static struct zephyr_ll_mem_resources ll_mem_resources; -static struct k_heap *zephyr_ll_heap_init(void) +/** + * Heap allocator for LL scheduler memory (user accessible pointer) + * + * Note: this is also user-writable, so kernel must not rely on this to + * be correct and must always validate it separately. + */ +APP_SYSUSER_DATA static struct k_heap *zephyr_ll_heap; + +static struct k_heap *ll_heap_alloc(void) { - struct k_heap *heap = module_driver_heap_init(); - struct k_mem_partition mem_partition; - int ret; + const size_t alloc_size = CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE; + + BUILD_ASSERT(CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE % CONFIG_MM_DRV_PAGE_SIZE == 0); + + void *mem = rballoc_align(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, alloc_size, + CONFIG_MM_DRV_PAGE_SIZE); + if (!mem) + return NULL; + + k_heap_init(&ll_mem_resources.heap, mem, alloc_size); /* - * TODO: the size of LL heap should be independently configurable and - * not tied to CONFIG_SOF_ZEPHYR_USERSPACE_MODULE_HEAP_SIZE + * k_heap_init() does not set these, so set the values + * manually here */ + ll_mem_resources.heap.heap.init_mem = mem; + ll_mem_resources.heap.heap.init_bytes = alloc_size; + + return &ll_mem_resources.heap; +} + +static void ll_heap_init(void) +{ + struct k_heap *heap = ll_heap_alloc(); + struct k_mem_partition mem_partition; + int ret; if (!heap) { tr_err(&ll_tr, "heap alloc fail"); @@ -60,25 +86,58 @@ static struct k_heap *zephyr_ll_heap_init(void) (void *)mem_partition.start, heap->heap.init_bytes, ret); if (ret) k_panic(); - - return heap; } void zephyr_ll_user_resources_init(void) { + int ret; + k_mem_domain_init(&ll_mem_resources.mem_domain, 0, NULL); - ll_mem_resources.heap = zephyr_ll_heap_init(); + ll_heap_init(); + + /* store a user-accessible pointer */ + zephyr_ll_heap = &ll_mem_resources.heap; /* attach common partition to LL domain */ user_memory_attach_common_partition(zephyr_ll_mem_domain()); + + ret = user_memory_attach_system_user_partition(zephyr_ll_mem_domain()); + if (ret) + k_panic(); +} + +/** + * Check if 'heap' is a valid heap pointer. + * + * Available only in kernel mode. + * + * @return true if valid + */ +bool zephyr_ll_user_heap_verify(struct k_heap *heap) +{ + return heap == &ll_mem_resources.heap; } +/** + * Returns heap object to use in user-space LL code. + * + * Can be called from user-space. + * + * @return heap pointer that can be passed to sof_heap_alloc() + */ struct k_heap *zephyr_ll_user_heap(void) { - return ll_mem_resources.heap; + return zephyr_ll_heap; } +/** + * Returns pointer to LL user-space memory domain. + * + * Available only in kernel mode. + * + * @return pointer to memory domain + */ struct k_mem_domain *zephyr_ll_mem_domain(void) { return &ll_mem_resources.mem_domain; diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 998abae8a969..f1c28f4753d7 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -116,6 +116,17 @@ config SOF_ZEPHYR_USERSPACE_MODULE_HEAP_SIZE module has its own independent heap to which only it has access. This heap is shared between instances of the same module. +config SOF_ZEPHYR_SYS_USER_HEAP_SIZE + hex "Size of the shared LL user-space heap" + default 0x20000 + depends on SOF_USERSPACE_LL + help + The size of the shared heap used by the low-latency (LL) scheduler + user-space thread. This heap is shared across all pipelines running + on the LL thread and must be large enough to hold all host DMA + buffers, chain DMA data, SG elements, and module adapter buffers + for all simultaneously active pipelines. + config SOF_USERSPACE_PROXY bool "Use userspace proxy to support userspace modules" select SOF_USERSPACE_USE_DRIVER_HEAP From 3437211146a8aefdfa076011626becbcbb87c05a Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 27 May 2026 13:23:20 +0300 Subject: [PATCH 008/303] schedule: zephyr_ll_user: make double-mapping conditional Only double-map the LL resources if CONFIG_CACHE_HAS_MIRRORED_MEMORY_REGIONS is set. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll_user.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/schedule/zephyr_ll_user.c b/src/schedule/zephyr_ll_user.c index b6b00691d7c9..bad2f6c9cbb6 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -79,6 +79,7 @@ static void ll_heap_init(void) if (ret) k_panic(); +#ifdef CONFIG_CACHE_HAS_MIRRORED_MEMORY_REGIONS mem_partition.start = (uintptr_t)sys_cache_uncached_ptr_get(heap->heap.init_mem); mem_partition.attr = K_MEM_PARTITION_P_RW_U_RW; ret = k_mem_domain_add_partition(&ll_mem_resources.mem_domain, &mem_partition); @@ -86,6 +87,7 @@ static void ll_heap_init(void) (void *)mem_partition.start, heap->heap.init_bytes, ret); if (ret) k_panic(); +#endif } void zephyr_ll_user_resources_init(void) From 4999413cc94115da631ccc0e02a546a3c08200f5 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 8 May 2026 16:46:17 +0300 Subject: [PATCH 009/303] zephyr: lib: make sof_heap_alloc/free system calls Add a built option to make sof_heap_alloc/free available as system calls to user-space. Add a test case for the functions that runs in a user-space thread. Signed-off-by: Kai Vehmanen --- zephyr/CMakeLists.txt | 2 + zephyr/Kconfig | 7 +++ zephyr/include/rtos/alloc.h | 12 +++++ zephyr/lib/alloc.c | 28 ++++++++-- zephyr/syscall/alloc.c | 39 ++++++++++++++ zephyr/test/CMakeLists.txt | 3 ++ zephyr/test/userspace/test_heap_alloc.c | 72 +++++++++++++++++++++++++ 7 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 zephyr/syscall/alloc.c create mode 100644 zephyr/test/userspace/test_heap_alloc.c diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 16575d5e4eec..9bdaa6453ad4 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -620,6 +620,8 @@ zephyr_library_sources_ifdef(CONFIG_SHELL zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/audio/module_adapter/module/generic.h) zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/fast-get.h) +zephyr_syscall_header(include/rtos/alloc.h) +zephyr_library_sources_ifdef(CONFIG_SOF_USERSPACE_INTERFACE_ALLOC syscall/alloc.c) zephyr_library_link_libraries(SOF) target_link_libraries(SOF INTERFACE zephyr_interface) diff --git a/zephyr/Kconfig b/zephyr/Kconfig index f1c28f4753d7..4e223ac82aa4 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -29,6 +29,13 @@ config SOF_USERSPACE_INTERFACE_DMA help Allow user-space threads to use the SOF DMA interface. +config SOF_USERSPACE_INTERFACE_ALLOC + bool "Enable SOF heap alloc interface to userspace threads" + depends on USERSPACE + help + Allow user-space threads to use sof_heap_alloc/sof_heap_free + as Zephyr system calls. + config SOF_USERSPACE_LL bool "Run Low-Latency pipelines in userspace threads" depends on USERSPACE diff --git a/zephyr/include/rtos/alloc.h b/zephyr/include/rtos/alloc.h index e21e498f0471..b11f62cbb74e 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -99,9 +99,21 @@ void rfree(void *ptr); */ void l3_heap_save(void); +/* + * This is ugly to define the signatures twice, but this + * is required to support userspace builds that do not export alloc. + */ +#ifdef CONFIG_SOF_USERSPACE_INTERFACE_ALLOC +__syscall void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment); +__syscall void sof_heap_free(struct k_heap *heap, void *addr); +#include +#else void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, size_t alignment); void sof_heap_free(struct k_heap *heap, void *addr); +#endif + #if CONFIG_SOF_FULL_ZEPHYR_APPLICATION struct k_heap *sof_sys_heap_get(void); #else diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index 8192c2caff0f..6fcae7505f96 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -628,8 +628,8 @@ EXPORT_SYMBOL(rfree); * To match the fall-back SOF main heap all private heaps should also be in the * uncached address range. */ -void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, - size_t alignment) +void *z_impl_sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment) { if (flags & (SOF_MEM_FLAG_LARGE_BUFFER | SOF_MEM_FLAG_USER_SHARED_BUFFER)) return rballoc_align(flags, bytes, alignment); @@ -643,7 +643,7 @@ void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, return (__sparse_force void *)heap_alloc_aligned_cached(heap, alignment, bytes); } -void sof_heap_free(struct k_heap *heap, void *addr) +void z_impl_sof_heap_free(struct k_heap *heap, void *addr) { if (heap && addr && is_heap_pointer(heap, addr)) heap_free(heap, addr); @@ -651,6 +651,28 @@ void sof_heap_free(struct k_heap *heap, void *addr) rfree(addr); } +#ifndef CONFIG_SOF_USERSPACE_INTERFACE_ALLOC + +/* + * Putting these as inlines in alloc.h breaks Zephyr native + * ztests like sof/test/ztest/unit/fast-get that include rtos/alloc.h + * but do not link lib/alloc.c . To to keep the tests happy, + * implement the functions here. + */ + +void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment) +{ + return z_impl_sof_heap_alloc(heap, flags, bytes, alignment); +} + +void sof_heap_free(struct k_heap *heap, void *addr) +{ + return z_impl_sof_heap_free(heap, addr); +} + +#endif /* CONFIG_SOF_USERSPACE_INTERFACE_ALLOC */ + static int heap_init(void) { sys_heap_init(&sof_heap.heap, heapmem, HEAPMEM_SIZE - SHARED_BUFFER_HEAP_MEM_SIZE); diff --git a/zephyr/syscall/alloc.c b/zephyr/syscall/alloc.c new file mode 100644 index 000000000000..3fc7001fa3aa --- /dev/null +++ b/zephyr/syscall/alloc.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include + +static inline void *z_vrfy_sof_heap_alloc(struct k_heap *heap, uint32_t flags, + size_t bytes, size_t alignment) +{ + /* only allow flags that are safe for user-space heap isolation */ + static const uint32_t allowed_flags = + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA; + + K_OOPS(flags & ~allowed_flags); + + /* user-space use of sof_heap_alloc() limited to this single heap */ + K_OOPS(!zephyr_ll_user_heap_verify(heap)); + + return z_impl_sof_heap_alloc(heap, flags, bytes, alignment); +} +#include + +static inline void z_vrfy_sof_heap_free(struct k_heap *heap, void *addr) +{ + /* user-space use of sof_heap_alloc() limited to this single heap */ + K_OOPS(!zephyr_ll_user_heap_verify(heap)); + + if (addr) { + uintptr_t start = (uintptr_t)heap->heap.init_mem; + uintptr_t addr_uc = (uintptr_t)sys_cache_uncached_ptr_get(addr); + K_OOPS(addr_uc < start || addr_uc >= start + heap->heap.init_bytes); + K_OOPS(K_SYSCALL_MEMORY_WRITE(addr, 1)); + } + z_impl_sof_heap_free(heap, addr); +} +#include diff --git a/zephyr/test/CMakeLists.txt b/zephyr/test/CMakeLists.txt index 6edf90ec840a..397e9e31b938 100644 --- a/zephyr/test/CMakeLists.txt +++ b/zephyr/test/CMakeLists.txt @@ -11,6 +11,9 @@ if(CONFIG_SOF_BOOT_TEST) zephyr_library_sources_ifdef(CONFIG_USERSPACE userspace/ksem.c ) + if(CONFIG_USERSPACE AND CONFIG_SOF_USERSPACE_INTERFACE_ALLOC) + zephyr_library_sources(userspace/test_heap_alloc.c) + endif() endif() if(CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_SOF_USERSPACE_INTERFACE_DMA) diff --git a/zephyr/test/userspace/test_heap_alloc.c b/zephyr/test/userspace/test_heap_alloc.c new file mode 100644 index 000000000000..8018e4b27b9d --- /dev/null +++ b/zephyr/test/userspace/test_heap_alloc.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright(c) 2026 Intel Corporation. + */ + +/* + * Test case for sof_heap_alloc() / sof_heap_free() use from a Zephyr + * user-space thread. + */ + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); + +#define USER_STACKSIZE 2048 + +static struct k_thread user_thread; +static K_THREAD_STACK_DEFINE(user_stack, USER_STACKSIZE); + +static void user_function(void *p1, void *p2, void *p3) +{ + struct k_heap *heap = (struct k_heap *)p1; + void *ptr; + + __ASSERT(k_is_user_context(), "isn't user"); + + LOG_INF("SOF thread %s (%s)", + k_is_user_context() ? "UserSpace!" : "privileged mode.", + CONFIG_BOARD_TARGET); + + /* allocate a block from the user heap */ + ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); + zassert_not_null(ptr, "sof_heap_alloc returned NULL"); + + LOG_INF("sof_heap_alloc returned %p", ptr); + + /* free the block */ + sof_heap_free(heap, ptr); + + LOG_INF("sof_heap_free done"); +} + +static void test_user_thread_heap_alloc(void) +{ + struct k_heap *heap; + + heap = zephyr_ll_user_heap(); + zassert_not_null(heap, "user heap not found"); + + k_thread_create(&user_thread, user_stack, USER_STACKSIZE, + user_function, heap, NULL, NULL, + -1, K_USER, K_FOREVER); + + /* Add thread to LL memory domain so it can access the user heap */ + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &user_thread); + + k_thread_start(&user_thread); + k_thread_join(&user_thread, K_FOREVER); +} + +ZTEST(sof_boot, user_space_heap_alloc) +{ + test_user_thread_heap_alloc(); + + ztest_test_pass(); +} From dfaf4d99d76d22b83cb2bf1cba33067d2e36a9e1 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 27 May 2026 12:07:50 +0300 Subject: [PATCH 010/303] zephyr: boot_test: add capability to handle negative test Add infrastructure to handle boot tests that cause a Zephyr fatal error. sof_boot_test_set_fault_valid() can be used by tests to inform the SOF fault handler that a fault is expected and SOF execution should not be stopped. Signed-off-by: Kai Vehmanen --- src/include/sof/boot_test.h | 16 ++++++++++++++++ zephyr/wrapper.c | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/include/sof/boot_test.h b/src/include/sof/boot_test.h index 1af6e7f2d8e0..dfa8671ee55f 100644 --- a/src/include/sof/boot_test.h +++ b/src/include/sof/boot_test.h @@ -13,6 +13,8 @@ #endif #include +struct k_thread; + #if CONFIG_SOF_BOOT_TEST #define TEST_RUN_ONCE(fn, ...) do { \ static bool once; \ @@ -36,4 +38,18 @@ void sof_run_boot_tests(void); +/** + * Mark a boot-test thread as expected to trigger a fatal error. + * + * @param thread Thread that is allowed to fault once, or NULL to clear. + */ +#if CONFIG_SOF_BOOT_TEST +void sof_boot_test_set_fault_valid(struct k_thread *thread); +#else +static inline void sof_boot_test_set_fault_valid(struct k_thread *thread) +{ + (void)thread; +} +#endif + #endif diff --git a/zephyr/wrapper.c b/zephyr/wrapper.c index c0c167b930fe..f43d2ed19a6b 100644 --- a/zephyr/wrapper.c +++ b/zephyr/wrapper.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -325,11 +326,36 @@ volatile int *_sof_fatal_null = NULL; struct arch_esf; +#if CONFIG_SOF_BOOT_TEST +static struct k_thread *sof_boot_test_fault_thread; + +void sof_boot_test_set_fault_valid(struct k_thread *thread) +{ + sof_boot_test_fault_thread = thread; +} + +static bool sof_boot_test_fault_expected(void) +{ + if (sof_boot_test_fault_thread != k_current_get()) + return false; + + sof_boot_test_fault_thread = NULL; + return true; +} +#endif + void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *esf) { ARG_UNUSED(esf); +#if CONFIG_SOF_BOOT_TEST + if (sof_boot_test_fault_expected()) { + LOG_ERR("Expected fatal error as part of boot test"); + return; + } +#endif + /* flush and switch to immediate mode */ LOG_PANIC(); From 5480437868256dd728decdeeae8e18f4ab571053 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 27 May 2026 12:09:49 +0300 Subject: [PATCH 011/303] zephyr: test: userspace: add negative tests for sof_heap_alloc() Add negative tests to cover system call validation for heap allocation. Signed-off-by: Kai Vehmanen --- zephyr/test/userspace/test_heap_alloc.c | 124 ++++++++++++++++++++---- 1 file changed, 104 insertions(+), 20 deletions(-) diff --git a/zephyr/test/userspace/test_heap_alloc.c b/zephyr/test/userspace/test_heap_alloc.c index 8018e4b27b9d..e4dcb567b616 100644 --- a/zephyr/test/userspace/test_heap_alloc.c +++ b/zephyr/test/userspace/test_heap_alloc.c @@ -4,7 +4,7 @@ */ /* - * Test case for sof_heap_alloc() / sof_heap_free() use from a Zephyr + * Test cases for sof_heap_alloc() / sof_heap_free() use from a Zephyr * user-space thread. */ @@ -15,6 +15,7 @@ #include #include #include +#include LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); @@ -23,9 +24,22 @@ LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); static struct k_thread user_thread; static K_THREAD_STACK_DEFINE(user_stack, USER_STACKSIZE); +K_APPMEM_PARTITION_DEFINE(heap_alloc_test_part); +K_APP_BMEM(heap_alloc_test_part) static uint8_t non_heap_byte; + +enum heap_alloc_test_case { + HEAP_ALLOC_VALID, + HEAP_ALLOC_NULL_HEAP, + HEAP_ALLOC_FORBIDDEN_FLAGS, + HEAP_FREE_NON_HEAP_POINTER, + HEAP_FREE_INACCESSIBLE_POINTER, +}; + static void user_function(void *p1, void *p2, void *p3) { - struct k_heap *heap = (struct k_heap *)p1; + struct k_heap *heap = p1; + enum heap_alloc_test_case test_case = (enum heap_alloc_test_case)(uintptr_t)p2; + void *free_ptr = p3; void *ptr; __ASSERT(k_is_user_context(), "isn't user"); @@ -34,39 +48,109 @@ static void user_function(void *p1, void *p2, void *p3) k_is_user_context() ? "UserSpace!" : "privileged mode.", CONFIG_BOARD_TARGET); - /* allocate a block from the user heap */ - ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); - zassert_not_null(ptr, "sof_heap_alloc returned NULL"); + switch (test_case) { + case HEAP_ALLOC_VALID: + ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); + zassert_not_null(ptr, "sof_heap_alloc returned NULL"); + sof_heap_free(heap, ptr); + return; + case HEAP_ALLOC_NULL_HEAP: + (void)sof_heap_alloc(NULL, SOF_MEM_FLAG_USER, 128, 0); + break; + case HEAP_ALLOC_FORBIDDEN_FLAGS: + (void)sof_heap_alloc(heap, SOF_MEM_FLAG_LARGE_BUFFER, 128, 0); + break; + case HEAP_FREE_NON_HEAP_POINTER: + sof_heap_free(heap, &non_heap_byte); + break; + case HEAP_FREE_INACCESSIBLE_POINTER: + sof_heap_free(heap, free_ptr); + break; + default: + zassert_unreachable("unknown heap allocation test case"); + } + + zassert_unreachable("syscall security check did not fault"); +} - LOG_INF("sof_heap_alloc returned %p", ptr); +static void run_user_heap_alloc_case(enum heap_alloc_test_case test_case, struct k_heap *heap, + void *ptr, bool grant_ll_domain, bool expect_fault) +{ + int ret; - /* free the block */ - sof_heap_free(heap, ptr); + k_thread_create(&user_thread, user_stack, USER_STACKSIZE, + user_function, heap, (void *)(uintptr_t)test_case, ptr, + -1, K_USER, K_FOREVER); - LOG_INF("sof_heap_free done"); + if (grant_ll_domain) + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &user_thread); + + if (expect_fault) + sof_boot_test_set_fault_valid(&user_thread); + + k_thread_start(&user_thread); + ret = k_thread_join(&user_thread, K_FOREVER); + zassert_equal(ret, 0, "user thread join failed: %d", ret); } -static void test_user_thread_heap_alloc(void) +static void test_user_thread_heap_alloc(enum heap_alloc_test_case test_case) { + bool grant_ll_domain = true; + bool expect_fault = false; + void *ptr = NULL; struct k_heap *heap; heap = zephyr_ll_user_heap(); zassert_not_null(heap, "user heap not found"); - k_thread_create(&user_thread, user_stack, USER_STACKSIZE, - user_function, heap, NULL, NULL, - -1, K_USER, K_FOREVER); - - /* Add thread to LL memory domain so it can access the user heap */ - k_mem_domain_add_thread(zephyr_ll_mem_domain(), &user_thread); + switch (test_case) { + case HEAP_ALLOC_VALID: + break; + case HEAP_ALLOC_NULL_HEAP: + case HEAP_ALLOC_FORBIDDEN_FLAGS: + case HEAP_FREE_NON_HEAP_POINTER: + expect_fault = true; + break; + case HEAP_FREE_INACCESSIBLE_POINTER: + ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); + zassert_not_null(ptr, "kernel heap allocation failed"); + grant_ll_domain = false; + expect_fault = true; + break; + default: + zassert_unreachable("unknown heap allocation test case"); + } + + run_user_heap_alloc_case(test_case, heap, ptr, grant_ll_domain, expect_fault); - k_thread_start(&user_thread); - k_thread_join(&user_thread, K_FOREVER); + sof_heap_free(heap, ptr); } ZTEST(sof_boot, user_space_heap_alloc) { - test_user_thread_heap_alloc(); + test_user_thread_heap_alloc(HEAP_ALLOC_VALID); +} + +ZTEST(sof_boot, user_space_heap_alloc_rejects_null_heap) +{ + test_user_thread_heap_alloc(HEAP_ALLOC_NULL_HEAP); +} + +ZTEST(sof_boot, user_space_heap_alloc_rejects_forbidden_flags) +{ + test_user_thread_heap_alloc(HEAP_ALLOC_FORBIDDEN_FLAGS); +} + +ZTEST(sof_boot, user_space_heap_free_rejects_non_heap_pointer) +{ + k_mem_domain_add_partition(zephyr_ll_mem_domain(), &heap_alloc_test_part); + + test_user_thread_heap_alloc(HEAP_FREE_NON_HEAP_POINTER); - ztest_test_pass(); + k_mem_domain_remove_partition(zephyr_ll_mem_domain(), &heap_alloc_test_part); +} + +ZTEST(sof_boot, user_space_heap_free_rejects_inaccessible_pointer) +{ + test_user_thread_heap_alloc(HEAP_FREE_INACCESSIBLE_POINTER); } From accf902215e0b813e878945ff3f3218da063ef95 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 26 May 2026 19:35:35 +0300 Subject: [PATCH 012/303] zephyr: set SOF_USERSPACE_INTERFACE_ALLOC for LL user builds Enable CONFIG_SOF_USERSPACE_INTERFACE_ALLOC to allow user-space pipeline code to allocate memory with sof_heap_alloc(). Signed-off-by: Kai Vehmanen --- zephyr/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 4e223ac82aa4..27f37d829b4e 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -39,6 +39,7 @@ config SOF_USERSPACE_INTERFACE_ALLOC config SOF_USERSPACE_LL bool "Run Low-Latency pipelines in userspace threads" depends on USERSPACE + select SOF_USERSPACE_INTERFACE_ALLOC select SOF_USERSPACE_INTERFACE_DMA help Run Low-Latency (LL) pipelines in userspace threads. This adds From 4ab694778151bd10dc60fbcd1192a03f0a5d346e Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 2 Jun 2026 17:19:12 +0300 Subject: [PATCH 013/303] schedule: zephyr_domain: remove unnecessary tr_dbg function prefix Remove redundant function name from log message in zephyr_domain_thread_init(). Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_domain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index daace8bac708..ef649f975be8 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -305,7 +305,7 @@ static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, k_tid_t thread; int core = task->core; - tr_dbg(&ll_tr, "thread_init entry"); + tr_dbg(&ll_tr, "entry"); if (core >= CONFIG_CORE_COUNT) return -EINVAL; From eff4772ffdc33c63b02a2bb13af29a85ad3290b6 Mon Sep 17 00:00:00 2001 From: Wojciech Jablonski Date: Tue, 2 Jun 2026 10:45:46 +0200 Subject: [PATCH 014/303] boards: nvl: add Google RTC Audio Processing to NVL configs Enable the Google RTC Audio Processing component as a loadable module for NVL and NVL-S boards. This change adds CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=m and CONFIG_GOOGLE_RTC_AUDIO_PROCESSING_MOCK=y to both board config files. Signed-off-by: Wojciech Jablonski --- app/boards/intel_adsp_ace40_nvl.conf | 6 ++++++ app/boards/intel_adsp_ace40_nvls.conf | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/boards/intel_adsp_ace40_nvl.conf b/app/boards/intel_adsp_ace40_nvl.conf index becef1a65f10..331630a9e3bd 100644 --- a/app/boards/intel_adsp_ace40_nvl.conf +++ b/app/boards/intel_adsp_ace40_nvl.conf @@ -12,6 +12,12 @@ CONFIG_COMP_SRC_IPC4_FULL_MATRIX=y CONFIG_COMP_TESTER=y CONFIG_FORMAT_CONVERT_HIFI3=n +# SOF / audio modules / mocks +# This mock is part of official sof-bin releases because the CI that +# tests it can't use extra CONFIGs. See #9410, #8722 and #9386 +CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=m +CONFIG_GOOGLE_RTC_AUDIO_PROCESSING_MOCK=y + # SOF / infrastructure CONFIG_PROBE=y CONFIG_PROBE_DMA_MAX=2 diff --git a/app/boards/intel_adsp_ace40_nvls.conf b/app/boards/intel_adsp_ace40_nvls.conf index becef1a65f10..331630a9e3bd 100644 --- a/app/boards/intel_adsp_ace40_nvls.conf +++ b/app/boards/intel_adsp_ace40_nvls.conf @@ -12,6 +12,12 @@ CONFIG_COMP_SRC_IPC4_FULL_MATRIX=y CONFIG_COMP_TESTER=y CONFIG_FORMAT_CONVERT_HIFI3=n +# SOF / audio modules / mocks +# This mock is part of official sof-bin releases because the CI that +# tests it can't use extra CONFIGs. See #9410, #8722 and #9386 +CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=m +CONFIG_GOOGLE_RTC_AUDIO_PROCESSING_MOCK=y + # SOF / infrastructure CONFIG_PROBE=y CONFIG_PROBE_DMA_MAX=2 From 34a0916239bde64ec27e174ee8cfef5130f51e95 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 14 Apr 2026 13:16:15 +0300 Subject: [PATCH 015/303] app: overlays: ptl: add ll_usespace_overlay.conf Add an overlay for Intel 'ptl' target that allows to build SOF with all audio pipeline code running in Zephyr user-space. Some features are disabled in the overlay as the feature is not working in user-space configuration. This list will be adjusted as more features are made compatible with user-space. Signed-off-by: Kai Vehmanen --- app/overlays/ptl/ll_userspace_overlay.conf | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 app/overlays/ptl/ll_userspace_overlay.conf diff --git a/app/overlays/ptl/ll_userspace_overlay.conf b/app/overlays/ptl/ll_userspace_overlay.conf new file mode 100644 index 000000000000..8888a66f4c24 --- /dev/null +++ b/app/overlays/ptl/ll_userspace_overlay.conf @@ -0,0 +1,38 @@ +# This is overlay configuration file to enable user-space LL audio +# pipeline support. This overlay is intended for development +# purposes. If user-space LL is enabled for some build target by +# default, the settings should be made in the SOF board file directly. + +# Enable user-space LL support in SOF +# ----------------------------------- + +CONFIG_SOF_USERSPACE_LL=y + +# make the drivers work in user-space +CONFIG_SOF_USERSPACE_INTERFACE_DMA=y +CONFIG_DAI_USERSPACE=y + +# Temporary settings that are needed currently to enable user-space LL +# -------------------------------------------------------------------- + +# problem with DSP panics due to illegal instruction hit in user-space if cold +# store execution is enabled. Disable it for now until rootcause is found. +CONFIG_COLD_STORE_EXECUTE_DRAM=n +CONFIG_COLD_STORE_EXECUTE_DEBUG=n + +# telemetry not yet user-space compatible +CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS=n +CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS=n + +# disable loadable modules (hits privilege issues in user-space now) +CONFIG_LLEXT_STORAGE_WRITABLE=n +CONFIG_LLEXT_EXPERIMENTAL=n +CONFIG_MODULES=n + +# some of current boot tests interfere with user-space setup +CONFIG_SOF_BOOT_TEST_ALLOWED=n + +# misc features not yet compatible with user-space +CONFIG_CROSS_CORE_STREAM=n +CONFIG_INTEL_ADSP_MIC_PRIVACY=n +CONFIG_XRUN_NOTIFICATIONS_ENABLE=n From 9526d7d5da2db9840dff8ea8e3ce2d199b19f1d6 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 26 Feb 2026 18:00:10 +0200 Subject: [PATCH 016/303] zephyr: wrapper: modify platform_dai_timestamp/wallclock() for user-space Don't use sof_cycle_get_64() if SOF built for user-space LL. Signed-off-by: Kai Vehmanen --- zephyr/wrapper.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/zephyr/wrapper.c b/zephyr/wrapper.c index f43d2ed19a6b..e929135ce851 100644 --- a/zephyr/wrapper.c +++ b/zephyr/wrapper.c @@ -258,15 +258,24 @@ void platform_dai_timestamp(struct comp_dev *dai, posn->flags |= SOF_TIME_DAI_VALID; /* get SSP wallclock - DAI sets this to stream start value */ +#ifndef CONFIG_SOF_USERSPACE_LL posn->wallclock = sof_cycle_get_64() - posn->wallclock; posn->wallclock_hz = sys_cycle_get_64_rate(); +#else + posn->wallclock = k_uptime_ticks() - posn->wallclock; + posn->wallclock_hz = CONFIG_SYS_CLOCK_TICKS_PER_SEC; +#endif posn->flags |= SOF_TIME_WALL_VALID; } /* get current wallclock for componnent */ void platform_dai_wallclock(struct comp_dev *dai, uint64_t *wallclock) { +#ifndef CONFIG_SOF_USERSPACE_LL *wallclock = sof_cycle_get_64(); +#else + *wallclock = k_uptime_ticks(); +#endif } /* From 3dd7289f3a16267c9494d2caed431f21c373a6f7 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 31 Mar 2026 18:29:20 +0300 Subject: [PATCH 017/303] schedule: zephyr_ll: ISR check is not needed when LL in user-space No need to check whether we are running in ISR as this can never happen when LL scheduler is run in user-space. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 1c1717d30ac6..0a6fd3f14221 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -402,10 +402,12 @@ static int zephyr_ll_task_free(void *data, struct task *task) zephyr_ll_assert_core(sch); +#ifndef CONFIG_SOF_USERSPACE_LL if (k_is_in_isr()) { tr_err(&ll_tr, "cannot free tasks from interrupt context!"); return -EDEADLK; } +#endif zephyr_ll_lock(sch, &flags); From 3ad5e6d9acc579a947516f823a3ef80702829292 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 3 Jun 2026 17:56:45 +0300 Subject: [PATCH 018/303] vregion: remove need to include rtos/alloc.h in !CONFIG_SOF_VREGIONS build Remove need to include rtos/alloc.h in !CONFIG_SOF_VREGIONS build. We do not need the dummy vregion objects for anything, so get rid of them. From now on creating a vregion object when CONFIG_SOF_VREGIONS is not defined will fail. Also make sure the vregions is not tried to be used if its not enabled. Even before this change the failure would have happened couple of lines later. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 4 ++-- src/include/sof/lib/vregion.h | 11 +---------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 01126446860b..a68af6b65eee 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -92,8 +92,8 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT : SOF_MEM_FLAG_USER; size_t heap_size; - if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && IS_ENABLED(CONFIG_USERSPACE) && - !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) { + if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && IS_ENABLED(CONFIG_SOF_VREGIONS) && + IS_ENABLED(CONFIG_USERSPACE) && !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) { mod_vreg = module_adapter_dp_heap_new(config, &heap_size); if (!mod_vreg) { comp_cl_err(drv, "Failed to allocate DP module heap / vregion"); diff --git a/src/include/sof/lib/vregion.h b/src/include/sof/lib/vregion.h index 612443f5bc48..d64249ed64c8 100644 --- a/src/include/sof/lib/vregion.h +++ b/src/include/sof/lib/vregion.h @@ -125,29 +125,20 @@ void vregion_mem_info(struct vregion *vr, size_t *size, uintptr_t *start); #else /* CONFIG_SOF_VREGIONS */ -#include - struct vregion { unsigned int use_count; }; static inline struct vregion *vregion_create(size_t lifetime_size, size_t interim_size) { - struct vregion *vr = rmalloc(0, sizeof(*vr)); - - vr->use_count = 1; - return vr; + return NULL; } static inline struct vregion *vregion_get(struct vregion *vr) { - if (vr) - vr->use_count++; return vr; } static inline struct vregion *vregion_put(struct vregion *vr) { - if (vr && !--vr->use_count) - rfree(vr); return vr; } static inline void *vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size) From 216e69c1ef005eaf91a7083818cf90cb064ff8e3 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 1 Jun 2026 23:19:28 +0300 Subject: [PATCH 019/303] zephyr: lib: Remove ctx_alloc.h and put the contents into rtos/alloc.h Remove the ctx_alloc.h header and move the implementation into rtos/alloc.h. Signed-off-by: Jyri Sarha --- src/audio/buffers/comp_buffer.c | 1 - src/audio/buffers/ring_buffer.c | 1 - src/audio/module_adapter/module/generic.c | 1 - src/include/sof/audio/component.h | 1 - src/include/sof/ctx_alloc.h | 81 ----------------------- src/include/sof/lib/dai-zephyr.h | 1 - zephyr/include/rtos/alloc.h | 68 +++++++++++++++++++ 7 files changed, 68 insertions(+), 86 deletions(-) delete mode 100644 src/include/sof/ctx_alloc.h diff --git a/src/audio/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index 8c64ff3d7d02..9aa5c0f0ab10 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/src/audio/buffers/ring_buffer.c b/src/audio/buffers/ring_buffer.c index 62ac121acda4..51245e91ca40 100644 --- a/src/audio/buffers/ring_buffer.c +++ b/src/audio/buffers/ring_buffer.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index f9531033dadb..b62ae38f415f 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #if CONFIG_IPC_MAJOR_4 #include diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index ebb88c8f79a1..13f2524ac909 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include diff --git a/src/include/sof/ctx_alloc.h b/src/include/sof/ctx_alloc.h deleted file mode 100644 index 389c16a27507..000000000000 --- a/src/include/sof/ctx_alloc.h +++ /dev/null @@ -1,81 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * - * Copyright(c) 2026 Intel Corporation. All rights reserved. - */ - -#ifndef __SOF_CTX_ALLOC_H__ -#define __SOF_CTX_ALLOC_H__ - -#include -#include -#include -#include -#include - -struct mod_alloc_ctx { - struct k_heap *heap; - struct vregion *vreg; -}; - -/** - * Allocate memory from a mod_alloc_ctx context. - * - * When the context has a vregion, allocates from the vregion interim - * partition. Coherent memory is used when SOF_MEM_FLAG_COHERENT is set - * in flags. Falls back to sof_heap_alloc() otherwise. - * - * @param ctx Allocation context (heap + optional vregion). - * @param flags Allocation flags (SOF_MEM_FLAG_*). - * @param size Size in bytes. - * @param alignment Required alignment in bytes. - * @return Pointer to allocated memory or NULL on failure. - */ -static inline void *sof_ctx_alloc(struct mod_alloc_ctx *ctx, uint32_t flags, - size_t size, size_t alignment) -{ - if (!ctx || !ctx->vreg) - return sof_heap_alloc(ctx ? ctx->heap : NULL, flags, size, alignment); - - if (flags & SOF_MEM_FLAG_COHERENT) - return vregion_alloc_coherent_align(ctx->vreg, VREGION_MEM_TYPE_INTERIM, - size, alignment); - - return vregion_alloc_align(ctx->vreg, VREGION_MEM_TYPE_INTERIM, size, alignment); -} - -/** - * Allocate zero-initialized memory from a mod_alloc_ctx context. - * @param ctx Allocation context. - * @param flags Allocation flags (SOF_MEM_FLAG_*). - * @param size Size in bytes. - * @param alignment Required alignment in bytes. - * @return Pointer to allocated memory or NULL on failure. - */ -static inline void *sof_ctx_zalloc(struct mod_alloc_ctx *ctx, uint32_t flags, - size_t size, size_t alignment) -{ - void *ptr = sof_ctx_alloc(ctx, flags, size, alignment); - - if (ptr) - memset(ptr, 0, size); - - return ptr; -} - -/** - * Free memory allocated from a mod_alloc_ctx context. - * @param ctx Allocation context. - * @param ptr Pointer to free. - */ -static inline void sof_ctx_free(struct mod_alloc_ctx *ctx, void *ptr) -{ - if (!ptr) - return; - - if (ctx && ctx->vreg) - vregion_free(ctx->vreg, ptr); - else - sof_heap_free(ctx ? ctx->heap : NULL, ptr); -} - -#endif /* __SOF_CTX_ALLOC_H__ */ diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 595d11de9b47..3e40c6e682af 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/zephyr/include/rtos/alloc.h b/zephyr/include/rtos/alloc.h index b11f62cbb74e..98deb489f91d 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -162,4 +162,72 @@ size_t get_shared_buffer_heap_size(void); #endif +#include + +struct mod_alloc_ctx { + struct k_heap *heap; + struct vregion *vreg; +}; + +/** + * Allocate memory from a mod_alloc_ctx context. + * + * When the context has a vregion, allocates from the vregion interim + * partition. Coherent memory is used when SOF_MEM_FLAG_COHERENT is set + * in flags. Falls back to sof_heap_alloc() otherwise. + * + * @param ctx Allocation context (heap + optional vregion). + * @param flags Allocation flags (SOF_MEM_FLAG_*). + * @param size Size in bytes. + * @param alignment Required alignment in bytes. + * @return Pointer to allocated memory or NULL on failure. + */ +static inline void *sof_ctx_alloc(struct mod_alloc_ctx *ctx, uint32_t flags, + size_t size, size_t alignment) +{ + if (!ctx || !ctx->vreg) + return sof_heap_alloc(ctx ? ctx->heap : NULL, flags, size, alignment); + + if (flags & SOF_MEM_FLAG_COHERENT) + return vregion_alloc_coherent_align(ctx->vreg, VREGION_MEM_TYPE_INTERIM, + size, alignment); + + return vregion_alloc_align(ctx->vreg, VREGION_MEM_TYPE_INTERIM, size, alignment); +} + +/** + * Allocate zero-initialized memory from a mod_alloc_ctx context. + * @param ctx Allocation context. + * @param flags Allocation flags (SOF_MEM_FLAG_*). + * @param size Size in bytes. + * @param alignment Required alignment in bytes. + * @return Pointer to allocated memory or NULL on failure. + */ +static inline void *sof_ctx_zalloc(struct mod_alloc_ctx *ctx, uint32_t flags, + size_t size, size_t alignment) +{ + void *ptr = sof_ctx_alloc(ctx, flags, size, alignment); + + if (ptr) + memset(ptr, 0, size); + + return ptr; +} + +/** + * Free memory allocated from a mod_alloc_ctx context. + * @param ctx Allocation context. + * @param ptr Pointer to free. + */ +static inline void sof_ctx_free(struct mod_alloc_ctx *ctx, void *ptr) +{ + if (!ptr) + return; + + if (ctx && ctx->vreg) + vregion_free(ctx->vreg, ptr); + else + sof_heap_free(ctx ? ctx->heap : NULL, ptr); +} + #endif /* __ZEPHYR_RTOS_ALLOC_H__ */ From e3246b8008517aa28432eead9624fa7d88ffe349 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 3 Jun 2026 18:57:12 +0300 Subject: [PATCH 020/303] posix: alloc.h: Add dummy mod_alloc_ctx and sof_ctx_alloc() support Signed-off-by: Jyri Sarha --- posix/include/rtos/alloc.h | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/posix/include/rtos/alloc.h b/posix/include/rtos/alloc.h index 84751ef9f1f4..c599afe08d82 100644 --- a/posix/include/rtos/alloc.h +++ b/posix/include/rtos/alloc.h @@ -120,6 +120,56 @@ void sof_heap_free(struct k_heap *heap, void *addr); struct k_heap *sof_sys_heap_get(void); struct k_heap *sof_sys_user_heap_get(void); +/* Posix version of struct mod_alloc_ctx without vregion support */ +struct vregion; + +struct mod_alloc_ctx { + struct k_heap *heap; + struct vregion *vreg; +}; + +/** + * Allocate memory from a mod_alloc_ctx context. + * Dummy version, only heap allocation is supported + */ +static inline void *sof_ctx_alloc(struct mod_alloc_ctx *ctx, uint32_t flags, + size_t size, size_t alignment) +{ + return sof_heap_alloc(ctx ? ctx->heap : NULL, flags, size, alignment); +} + +/** + * Allocate zero-initialized memory from a mod_alloc_ctx context. + * @param ctx Allocation context. + * @param flags Allocation flags (SOF_MEM_FLAG_*). + * @param size Size in bytes. + * @param alignment Required alignment in bytes. + * @return Pointer to allocated memory or NULL on failure. + */ +static inline void *sof_ctx_zalloc(struct mod_alloc_ctx *ctx, uint32_t flags, + size_t size, size_t alignment) +{ + void *ptr = sof_ctx_alloc(ctx, flags, size, alignment); + + if (ptr) + memset(ptr, 0, size); + + return ptr; +} + +/** + * Free memory allocated from a mod_alloc_ctx context. + * @param ctx Allocation context. + * @param ptr Pointer to free. + */ +static inline void sof_ctx_free(struct mod_alloc_ctx *ctx, void *ptr) +{ + if (!ptr) + return; + + sof_heap_free(ctx ? ctx->heap : NULL, ptr); +} + /** * Calculates length of the null-terminated string. * @param s String. From d0334e59cbd2e83232ee58ade9be3e036b48517a Mon Sep 17 00:00:00 2001 From: Adrian Warecki Date: Fri, 22 May 2026 17:27:23 +0200 Subject: [PATCH 021/303] module: tflm: rework module to use sink/source api Rework the tflm module to only use the sink/source api to prepare sof for the full transition to pipeline 2.0. Signed-off-by: Adrian Warecki --- src/audio/tensorflow/tflm-classify.c | 33 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/audio/tensorflow/tflm-classify.c b/src/audio/tensorflow/tflm-classify.c index 827f6711bc41..9c93329bce1c 100644 --- a/src/audio/tensorflow/tflm-classify.c +++ b/src/audio/tensorflow/tflm-classify.c @@ -3,8 +3,9 @@ // Copyright(c) 2025 Intel Corporation. All rights reserved. #include -#include #include +#include +#include #include #include #include @@ -174,16 +175,15 @@ int8_t expected_feature_yes[TFLM_FEATURE_SIZE] = { */ static int tflm_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, - int num_input_buffers, - struct output_stream_buffer *output_buffers, - int num_output_buffers) + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { struct tflm_comp_data *cd = module_get_private_data(mod); struct comp_dev *dev = mod->dev; - struct audio_stream *source = input_buffers[0].data; - struct audio_stream *sink = output_buffers[0].data; - int features = input_buffers[0].size; + size_t frame_bytes = source_get_frame_bytes(sources[0]); + int features = source_get_data_frames_available(sources[0]); + const void *data_ptr, *buf_start; + size_t buf_size; int ret; comp_dbg(dev, "entry"); @@ -192,12 +192,18 @@ static int tflm_process(struct processing_module *mod, * by TFLM_FEATURE_SIZE until buffer empty. */ while (features >= TFLM_FEATURE_ELEM_COUNT) { - cd->tfc.audio_features = source->r_ptr; + ret = source_get_data(sources[0], TFLM_FEATURE_ELEM_COUNT * frame_bytes, + &data_ptr, &buf_start, &buf_size); + if (ret) + return ret; + + cd->tfc.audio_features = data_ptr; cd->tfc.audio_data_size = TFLM_FEATURE_ELEM_COUNT; ret = TF_ProcessClassify(&cd->tfc); if (!ret) { comp_err(dev, "classify failed %s.", cd->tfc.error); + source_release_data(sources[0], 0); return ret; } @@ -207,10 +213,9 @@ static int tflm_process(struct processing_module *mod, cd->tfc.predictions[i], prediction[i]); } - /* calc new free and available after moving onto next feature */ - module_update_buffer_position(&input_buffers[0], - &output_buffers[0], TFLM_FEATURE_SIZE); - features = input_buffers[0].size; + /* advance by one stride */ + source_release_data(sources[0], TFLM_FEATURE_SIZE * frame_bytes); + features = source_get_data_frames_available(sources[0]); } return ret; @@ -226,7 +231,7 @@ static int tflm_reset(struct processing_module *mod) static const struct module_interface tflmcly_interface = { .init = tflm_init, // .prepare = tflm_prepare, - .process_audio_stream = tflm_process, + .process = tflm_process, .set_configuration = tflm_set_config, // .get_configuration = tflm_get_config, .reset = tflm_reset, From c063496b1ece607762c9c02b20bddb2679e66968 Mon Sep 17 00:00:00 2001 From: Adrian Warecki Date: Mon, 18 May 2026 17:00:34 +0200 Subject: [PATCH 022/303] module: waves: rework module to use sink/source api Rework the waves module to only use the sink/source api to prepare sof for the full transition to pipeline 2.0. Signed-off-by: Adrian Warecki --- src/audio/module_adapter/module/waves/waves.c | 64 ++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/src/audio/module_adapter/module/waves/waves.c b/src/audio/module_adapter/module/waves/waves.c index 6616527d2308..d328d9e5167f 100644 --- a/src/audio/module_adapter/module/waves/waves.c +++ b/src/audio/module_adapter/module/waves/waves.c @@ -753,18 +753,25 @@ static int waves_codec_init_process(struct processing_module *mod) return 0; } -static int -waves_codec_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, int num_input_buffers, - struct output_stream_buffer *output_buffers, int num_output_buffers) +static int waves_codec_process(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { int ret; struct comp_dev *dev = mod->dev; struct module_data *codec = &mod->priv; struct waves_codec_data *waves_codec = codec->private; + const void *src_ptr; + const void *src_buf_start; + size_t src_buf_size; + void *snk_ptr; + void *snk_buf_start; + size_t snk_buf_size; + size_t n_bytes = codec->mpd.in_buff_size; + size_t size_to_wrap; /* Proceed only if we have enough data to fill the module buffer completely */ - if (input_buffers[0].size < codec->mpd.in_buff_size) { + if (source_get_data_available(sources[0]) < n_bytes) { comp_dbg(dev, "not enough data to process"); return -ENODATA; } @@ -772,9 +779,20 @@ waves_codec_process(struct processing_module *mod, if (!codec->mpd.init_done) waves_codec_init_process(mod); - memcpy_s(codec->mpd.in_buff, codec->mpd.in_buff_size, - input_buffers[0].data, codec->mpd.in_buff_size); - codec->mpd.avail = codec->mpd.in_buff_size; + ret = source_get_data(sources[0], n_bytes, &src_ptr, &src_buf_start, &src_buf_size); + if (ret) + return ret; + + /* src_buf_size is the total ring buffer size; handle wrap when copying to in_buff */ + size_to_wrap = (const uint8_t *)src_buf_start + src_buf_size - (const uint8_t *)src_ptr; + if (n_bytes <= size_to_wrap) { + memcpy_s(codec->mpd.in_buff, n_bytes, src_ptr, n_bytes); + } else { + memcpy_s(codec->mpd.in_buff, n_bytes, src_ptr, size_to_wrap); + memcpy_s((uint8_t *)codec->mpd.in_buff + size_to_wrap, n_bytes - size_to_wrap, + src_buf_start, n_bytes - size_to_wrap); + } + codec->mpd.avail = n_bytes; comp_dbg(dev, "start"); @@ -806,18 +824,34 @@ waves_codec_process(struct processing_module *mod, status = MaxxEffect_Process(waves_codec->effect, i_streams, o_streams); if (status) { + source_release_data(sources[0], 0); comp_err(dev, "MaxxEffect_Process returned %d", status); ret = -EINVAL; } else { codec->mpd.produced = waves_codec->o_stream.numAvailableSamples * waves_codec->o_format.numChannels * waves_codec->sample_size_in_bytes; codec->mpd.consumed = codec->mpd.produced; - input_buffers[0].consumed = codec->mpd.consumed; - ret = 0; - /* copy the produced samples into the output buffer */ - memcpy_s(output_buffers[0].data, codec->mpd.produced, codec->mpd.out_buff, - codec->mpd.produced); - output_buffers[0].size = codec->mpd.produced; + + source_release_data(sources[0], codec->mpd.consumed); + + ret = sink_get_buffer(sinks[0], codec->mpd.produced, + &snk_ptr, &snk_buf_start, &snk_buf_size); + if (!ret) { + /* snk_buf_size is the total ring buffer size; handle wrap */ + size_to_wrap = (uint8_t *)snk_buf_start + snk_buf_size - + (uint8_t *)snk_ptr; + if (codec->mpd.produced <= size_to_wrap) { + memcpy_s(snk_ptr, codec->mpd.produced, + codec->mpd.out_buff, codec->mpd.produced); + } else { + memcpy_s(snk_ptr, codec->mpd.produced, + codec->mpd.out_buff, size_to_wrap); + memcpy_s(snk_buf_start, codec->mpd.produced - size_to_wrap, + (const uint8_t *)codec->mpd.out_buff + size_to_wrap, + codec->mpd.produced - size_to_wrap); + } + sink_commit_buffer(sinks[0], codec->mpd.produced); + } } if (ret) @@ -899,7 +933,7 @@ waves_codec_set_configuration(struct processing_module *mod, uint32_t config_id, static const struct module_interface waves_interface = { .init = waves_codec_init, .prepare = waves_codec_prepare, - .process_raw_data = waves_codec_process, + .process = waves_codec_process, .set_configuration = waves_codec_set_configuration, .reset = waves_codec_reset, .free = waves_codec_free From 0b94fe7431adbd6ec55d0b39d37211b9c0defe76 Mon Sep 17 00:00:00 2001 From: Adrian Warecki Date: Tue, 19 May 2026 17:43:59 +0200 Subject: [PATCH 023/303] module: cadence: rework module to use sink/source api Rework the cadence ipc3 module variant to only use the sink/source api to prepare sof for the full transition to pipeline 2.0. Make cadence_copy_data_from_buffer() shared across both ipc3 and ipc4 variants. Add a common cadence_copy_data_to_buffer() function. Remove the unnecessary const modifier from sink_buffer_start and eliminate redundant type casts. Signed-off-by: Adrian Warecki --- src/audio/module_adapter/module/cadence.c | 39 +++++++++- .../module_adapter/module/cadence_ipc3.c | 78 ++++++++++++------- .../module_adapter/module/cadence_ipc4.c | 54 +++---------- .../sof/audio/module_adapter/module/cadence.h | 7 +- 4 files changed, 103 insertions(+), 75 deletions(-) diff --git a/src/audio/module_adapter/module/cadence.c b/src/audio/module_adapter/module/cadence.c index 78a9c68b8afa..77b8b0d7fc78 100644 --- a/src/audio/module_adapter/module/cadence.c +++ b/src/audio/module_adapter/module/cadence.c @@ -221,7 +221,7 @@ size_t cadence_api_table_size(void) return ARRAY_SIZE(cadence_api_table); } -int cadence_codec_get_samples(struct processing_module *mod) +unsigned int cadence_codec_get_samples(struct processing_module *mod) { struct cadence_codec_data *cd = module_get_private_data(mod); struct comp_dev *dev = mod->dev; @@ -573,3 +573,40 @@ int cadence_codec_process_data(struct processing_module *mod, return 0; } + +void cadence_copy_data_from_buffer(void *dest, const void *buffer_ptr, size_t bytes_to_copy, + size_t buffer_size, void const *buffer_start) +{ + size_t bytes_to_end = (size_t)((const uint8_t *)buffer_start + + buffer_size - (const uint8_t *)buffer_ptr); + + if (bytes_to_end >= bytes_to_copy) { + /* No wrap, copy directly */ + memcpy_s(dest, bytes_to_copy, buffer_ptr, bytes_to_copy); + return; + } + + /* Wrap occurs, copy in two parts */ + memcpy_s(dest, bytes_to_end, buffer_ptr, bytes_to_end); + memcpy_s((uint8_t *)dest + bytes_to_end, bytes_to_copy - bytes_to_end, + buffer_start, bytes_to_copy - bytes_to_end); +} + +void cadence_copy_data_to_buffer(void *buffer_ptr, size_t bytes_to_copy, + size_t buffer_size, void *buffer_start, + const void *src) +{ + size_t bytes_to_end = (size_t)((uint8_t *)buffer_start + + buffer_size - (uint8_t *)buffer_ptr); + + if (bytes_to_end >= bytes_to_copy) { + /* No wrap, copy directly */ + memcpy_s(buffer_ptr, bytes_to_copy, src, bytes_to_copy); + return; + } + + /* Wrap occurs, copy in two parts */ + memcpy_s(buffer_ptr, bytes_to_end, src, bytes_to_end); + memcpy_s(buffer_start, bytes_to_copy - bytes_to_end, + (const uint8_t *)src + bytes_to_end, bytes_to_copy - bytes_to_end); +} diff --git a/src/audio/module_adapter/module/cadence_ipc3.c b/src/audio/module_adapter/module/cadence_ipc3.c index d1711e79f800..c9a6f7705062 100644 --- a/src/audio/module_adapter/module/cadence_ipc3.c +++ b/src/audio/module_adapter/module/cadence_ipc3.c @@ -211,71 +211,93 @@ static int cadence_codec_prepare(struct processing_module *mod, return ret; } -static int -cadence_codec_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, int num_input_buffers, - struct output_stream_buffer *output_buffers, int num_output_buffers) +static int cadence_codec_process(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { - struct comp_buffer *local_buff; struct comp_dev *dev = mod->dev; struct module_data *codec = &mod->priv; - int free_bytes, output_bytes = cadence_codec_get_samples(mod) * + size_t output_bytes = cadence_codec_get_samples(mod) * mod->stream_params->sample_container_bytes * mod->stream_params->channels; - uint32_t remaining = input_buffers[0].size; + size_t remaining = source_get_data_available(sources[0]); + const void *source_buffer_start, *src_ptr; + void *sink_ptr, *sink_buffer_start; + size_t src_bytes, sink_bytes; int ret; if (!cadence_codec_deep_buff_allowed(mod)) mod->deep_buff_bytes = 0; /* Proceed only if we have enough data to fill the module buffer completely */ - if (input_buffers[0].size < codec->mpd.in_buff_size) { + if (remaining < codec->mpd.in_buff_size) { comp_dbg(dev, "not enough data to process"); return -ENODATA; } if (!codec->mpd.init_done) { - memcpy_s(codec->mpd.in_buff, codec->mpd.in_buff_size, input_buffers[0].data, - codec->mpd.in_buff_size); + ret = source_get_data(sources[0], codec->mpd.in_buff_size, &src_ptr, + &source_buffer_start, &src_bytes); + if (ret) { + comp_err(dev, "cannot get data from source buffer"); + return ret; + } + + cadence_copy_data_from_buffer(codec->mpd.in_buff, src_ptr, + codec->mpd.in_buff_size, src_bytes, + source_buffer_start); codec->mpd.avail = codec->mpd.in_buff_size; ret = cadence_codec_init_process(mod); - if (ret) + if (ret) { + source_release_data(sources[0], 0); return ret; + } remaining -= codec->mpd.consumed; - input_buffers[0].consumed = codec->mpd.consumed; + source_release_data(sources[0], codec->mpd.consumed); } - /* do not proceed with processing if not enough free space left in the local buffer */ - local_buff = list_first_item(&mod->raw_data_buffers_list, struct comp_buffer, buffers_list); - free_bytes = audio_stream_get_free(&local_buff->stream); - if (free_bytes < output_bytes) + /* do not proceed with processing if not enough free space left in the sink buffer */ + if (sink_get_free_size(sinks[0]) < output_bytes) return -ENOSPC; /* Proceed only if we have enough data to fill the module buffer completely */ if (remaining < codec->mpd.in_buff_size) return -ENODATA; - memcpy_s(codec->mpd.in_buff, codec->mpd.in_buff_size, - (uint8_t *)input_buffers[0].data + input_buffers[0].consumed, - codec->mpd.in_buff_size); + ret = source_get_data(sources[0], codec->mpd.in_buff_size, &src_ptr, &source_buffer_start, + &src_bytes); + if (ret) + return ret; + + cadence_copy_data_from_buffer(codec->mpd.in_buff, src_ptr, + codec->mpd.in_buff_size, src_bytes, + source_buffer_start); codec->mpd.avail = codec->mpd.in_buff_size; comp_dbg(dev, "cadence_codec_process() start"); ret = cadence_codec_process_data(mod, NULL); - if (ret) + if (ret) { + source_release_data(sources[0], 0); return ret; + } + + source_release_data(sources[0], codec->mpd.consumed); + + ret = sink_get_buffer(sinks[0], codec->mpd.produced, &sink_ptr, &sink_buffer_start, + &sink_bytes); + if (ret) { + comp_err(dev, "cannot get sink buffer"); + return ret; + } - /* update consumed with the number of samples consumed during init */ - input_buffers[0].consumed += codec->mpd.consumed; - codec->mpd.consumed = input_buffers[0].consumed; + /* Copy the produced samples into the output buffer */ + cadence_copy_data_to_buffer(sink_ptr, codec->mpd.produced, sink_bytes, + sink_buffer_start, codec->mpd.out_buff); - /* copy the produced samples into the output buffer */ - memcpy_s(output_buffers[0].data, codec->mpd.produced, codec->mpd.out_buff, - codec->mpd.produced); - output_buffers[0].size = codec->mpd.produced; + sink_commit_buffer(sinks[0], codec->mpd.produced); comp_dbg(dev, "cadence_codec_process() done"); @@ -307,7 +329,7 @@ static int cadence_codec_reset(struct processing_module *mod) static const struct module_interface cadence_codec_interface = { .init = cadence_codec_init, .prepare = cadence_codec_prepare, - .process_raw_data = cadence_codec_process, + .process = cadence_codec_process, .set_configuration = cadence_codec_set_configuration, .reset = cadence_codec_reset, .free = cadence_codec_free diff --git a/src/audio/module_adapter/module/cadence_ipc4.c b/src/audio/module_adapter/module/cadence_ipc4.c index 9621d8ab0f1d..17069b97113a 100644 --- a/src/audio/module_adapter/module/cadence_ipc4.c +++ b/src/audio/module_adapter/module/cadence_ipc4.c @@ -395,24 +395,6 @@ static int cadence_codec_prepare(struct processing_module *mod, return 0; } -static void cadence_copy_data_from_buffer(void *dest, const void *buffer_ptr, size_t bytes_to_copy, - size_t buffer_size, uint8_t const *buffer_start) -{ - size_t bytes_to_end = (size_t)((uint8_t *)buffer_start + - buffer_size - (uint8_t *)buffer_ptr); - - if (bytes_to_end >= bytes_to_copy) { - /* No wrap, copy directly */ - memcpy_s(dest, bytes_to_copy, buffer_ptr, bytes_to_copy); - return; - } - - /* Wrap occurs, copy in two parts */ - memcpy_s(dest, bytes_to_end, buffer_ptr, bytes_to_end); - memcpy_s((uint8_t *)dest + bytes_to_end, bytes_to_copy - bytes_to_end, - buffer_start, bytes_to_copy - bytes_to_end); -} - static int cadence_codec_process(struct processing_module *mod, struct sof_source **sources, int num_of_sources, struct sof_sink **sinks, int num_of_sinks) { @@ -420,11 +402,10 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc struct module_data *codec = &mod->priv; size_t in_size = source_get_data_available(sources[0]); size_t out_space = sink_get_free_size(sinks[0]); - uint8_t const *source_buffer_start; - int consumed_during_init = 0; + const void *source_buffer_start, *src_ptr; + void *sink_buffer_start, *sink_ptr; + size_t src_bytes, sink_bytes; uint32_t remaining = in_size; - const void *src_ptr; - size_t src_bytes; int ret; if (!codec->mpd.init_done) { @@ -446,7 +427,6 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc remaining -= codec->mpd.consumed; source_release_data(sources[0], codec->mpd.consumed); - consumed_during_init = codec->mpd.consumed; } codec->mpd.consumed = 0; @@ -456,8 +436,8 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc return -ENODATA; /* Acquire data from the source buffer */ - ret = source_get_data(sources[0], codec->mpd.in_buff_size, &src_ptr, - (const void **)&source_buffer_start, &src_bytes); + ret = source_get_data(sources[0], codec->mpd.in_buff_size, &src_ptr, &source_buffer_start, + &src_bytes); cadence_copy_data_from_buffer(codec->mpd.in_buff, src_ptr, codec->mpd.in_buff_size, src_bytes, source_buffer_start); @@ -507,32 +487,16 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc return -ENOSPC; } - void *sink_ptr; - size_t sink_bytes; - uint8_t const *sink_buffer_start; - - ret = sink_get_buffer(sinks[0], codec->mpd.produced, &sink_ptr, - (void **)&sink_buffer_start, &sink_bytes); + ret = sink_get_buffer(sinks[0], codec->mpd.produced, &sink_ptr, &sink_buffer_start, + &sink_bytes); if (ret) { comp_err(dev, "cannot get sink buffer"); return ret; } /* Copy the produced samples into the output buffer */ - size_t bytes_to_end = (size_t)((uint8_t *)sink_buffer_start + - sink_bytes - (uint8_t *)sink_ptr); - - if (bytes_to_end >= codec->mpd.produced) { - /* No wrap, copy directly */ - memcpy_s(sink_ptr, codec->mpd.produced, codec->mpd.out_buff, - codec->mpd.produced); - } else { - /* Wrap occurs, copy in two parts */ - memcpy_s(sink_ptr, bytes_to_end, codec->mpd.out_buff, bytes_to_end); - memcpy_s((uint8_t *)sink_buffer_start, codec->mpd.produced - bytes_to_end, - (uint8_t *)codec->mpd.out_buff + bytes_to_end, - codec->mpd.produced - bytes_to_end); - } + cadence_copy_data_to_buffer(sink_ptr, codec->mpd.produced, sink_bytes, + sink_buffer_start, codec->mpd.out_buff); source_release_data(sources[0], codec->mpd.consumed); sink_commit_buffer(sinks[0], codec->mpd.produced); diff --git a/src/include/sof/audio/module_adapter/module/cadence.h b/src/include/sof/audio/module_adapter/module/cadence.h index 8ba658749c05..ba3c38a75b49 100644 --- a/src/include/sof/audio/module_adapter/module/cadence.h +++ b/src/include/sof/audio/module_adapter/module/cadence.h @@ -101,11 +101,16 @@ int cadence_codec_process_data(struct processing_module *mod, int cadence_codec_apply_config(struct processing_module *mod); void cadence_codec_free_memory_tables(struct processing_module *mod); int cadence_codec_init_memory_tables(struct processing_module *mod); -int cadence_codec_get_samples(struct processing_module *mod); +unsigned int cadence_codec_get_samples(struct processing_module *mod); int cadence_codec_init_process(struct processing_module *mod); int cadence_init_codec_object(struct processing_module *mod); int cadence_codec_resolve_api(struct processing_module *mod); int cadence_codec_free(struct processing_module *mod); size_t cadence_api_table_size(void); +void cadence_copy_data_from_buffer(void *dest, const void *buffer_ptr, size_t bytes_to_copy, + size_t buffer_size, void const *buffer_start); +void cadence_copy_data_to_buffer(void *buffer_ptr, size_t bytes_to_copy, + size_t buffer_size, void *buffer_start, + const void *src); #endif /* __SOF_AUDIO_CADENCE_CODEC__ */ From c1aced490eabdb9df01faeb835027800702e6261 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 2 Jun 2026 14:04:32 +0300 Subject: [PATCH 024/303] schedule: remove scheduler_restore() The last user of scheduler_restore() was removed in commit 4f2e3810f648 ("build: remove more XTOS left-overs"), so we can proceed it to remove it from schedule.h interface. This will pave way for further simplications of the schedule.h interface now as the role of this interface is reduce to audio task scheduling, while low-level task scheduling is done in Zephyr. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/schedule.h | 27 --------------------------- src/schedule/ll_schedule_xtos.c | 1 - 2 files changed, 28 deletions(-) diff --git a/src/include/sof/schedule/schedule.h b/src/include/sof/schedule/schedule.h index 9418064beb02..2b222d7448ad 100644 --- a/src/include/sof/schedule/schedule.h +++ b/src/include/sof/schedule/schedule.h @@ -150,15 +150,6 @@ struct scheduler_ops { */ void (*scheduler_free)(void *data, uint32_t flags); - /** - * Restores scheduler's resources. - * @param data Private data of selected scheduler. - * @return 0 if succeeded, error code otherwise. - * - * This operation is optional. - */ - int (*scheduler_restore)(void *data); - /** * Initializes context * @param data Private data of selected scheduler. @@ -371,24 +362,6 @@ static inline void schedule_free(uint32_t flags) } } -/** See scheduler_ops::scheduler_restore */ -static inline int schedulers_restore(void) -{ - struct schedulers *schedulers = *arch_schedulers_get(); - struct schedule_data *sch; - struct list_item *slist; - - assert(schedulers); - - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (sch->ops->scheduler_restore) - return sch->ops->scheduler_restore(sch->data); - } - - return 0; -} - /** See scheduler_ops::scheduler_init_context */ static inline struct k_thread *scheduler_init_context(struct task *task) { diff --git a/src/schedule/ll_schedule_xtos.c b/src/schedule/ll_schedule_xtos.c index 10615045d4d2..d0460b57a563 100644 --- a/src/schedule/ll_schedule_xtos.c +++ b/src/schedule/ll_schedule_xtos.c @@ -808,6 +808,5 @@ static const struct scheduler_ops schedule_ll_ops = { .schedule_task_cancel = schedule_ll_task_cancel, .reschedule_task = reschedule_ll_task, .scheduler_free = scheduler_free_ll, - .scheduler_restore = NULL, .schedule_task_running = NULL, }; From 0f9fa75e13405532964f0706a9c4e18c1560aa1b Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 8 May 2026 00:05:29 +0300 Subject: [PATCH 025/303] schedule: dp: Set thread name with component ID Name DP threads using k_thread_name_set() with the component ID in hex. This makes it easier to identify DP threads in debug tools and Zephyr shell thread listings. Signed-off-by: Jyri Sarha --- src/schedule/zephyr_dp_schedule_application.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/schedule/zephyr_dp_schedule_application.c b/src/schedule/zephyr_dp_schedule_application.c index 918f13188671..ac03bb59fe5d 100644 --- a/src/schedule/zephyr_dp_schedule_application.c +++ b/src/schedule/zephyr_dp_schedule_application.c @@ -22,6 +22,7 @@ #include #include +#include #include "zephyr_dp_schedule.h" @@ -410,6 +411,15 @@ void scheduler_dp_internal_free(struct task *task) mod_free(pdata->mod, container_of(task, struct scheduler_dp_task_memory, task)); } +static void scheduler_dp_thread_name_set(k_tid_t thread_id, struct processing_module *mod) +{ + char name[CONFIG_THREAD_MAX_NAME_LEN]; + + snprintf(name, sizeof(name), "DP:%#x", mod->dev->ipc_config.id); + + k_thread_name_set(thread_id, name); +} + /* Called only in IPC context */ int scheduler_dp_task_init(struct task **task, const struct sof_uuid_entry *uid, const struct task_ops *ops, struct processing_module *mod, @@ -493,6 +503,7 @@ int scheduler_dp_task_init(struct task **task, const struct sof_uuid_entry *uid, pdata->thread_id = k_thread_create(pdata->thread, p_stack, stack_size, dp_thread_fn, ptask, NULL, NULL, CONFIG_DP_THREAD_PRIORITY, ptask->flags, K_FOREVER); + scheduler_dp_thread_name_set(pdata->thread_id, mod); unsigned int pidx; size_t size; From d94306c070e05219d68dcbdbb9b8d7666d279d73 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Fri, 29 May 2026 23:38:27 +0200 Subject: [PATCH 026/303] ipc4: handler: reject get_large_config vendor payload larger than hostbox ipc4_get_large_config_module_instance() reads config.extension.r.data_off_size straight from the host message and, for the VENDOR_CONFIG_PARAM case, uses it without an upper bound: * dcache_invalidate_region() is asked to invalidate that many bytes starting at MAILBOX_HOSTBOX_BASE, and * ipc4_get_vendor_config_module_instance() computes tl_count = data_off_size / sizeof(struct sof_tl) and walks that many TL records straight out of the hostbox. data_off_size is a 20-bit field, so it can claim up to ~1 MB while the hostbox is only MAILBOX_HOSTBOX_SIZE (SOF_IPC_MSG_MAX_SIZE) bytes. The symmetric set path already rejects this in ipc4_set_vendor_config_module_instance() ("data_off_size ... > MAILBOX_HOSTBOX_SIZE"), the get path was missing the same guard, leaving a cache-maintenance over-range on real hardware and an out-of-bounds hostbox read for any vendor param that returns success with a zero-length value (which keeps produced_data from advancing the DSPBOX cap that otherwise terminates the loop early). Found by audit while reviewing the IPC4 hostbox readers surfaced by the fuzz harness, not by a live crash: on native_sim CONFIG_CACHE is unset so the invalidate is a no-op, and the common TL walk is bounded in practice by the DSPBOX size check. Convert the missing bound into a hard rejection before the region is touched, mirroring the set path. Signed-off-by: Tomasz Leman --- src/ipc/ipc4/handler-user.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index df243fa6ff58..5a5f3d6d6f32 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -1040,6 +1040,14 @@ __cold static int ipc4_get_large_config_module_instance(struct ipc4_message_requ /* check for vendor param first */ if (config.extension.r.large_param_id == VENDOR_CONFIG_PARAM) { + /* data_off_size is a 20-bit host-controlled field, so it can + * claim far more than the hostbox can physically hold. + */ + if (data_offset > MAILBOX_HOSTBOX_SIZE) { + ipc_cmd_err(&ipc_tr, "data_off_size %u exceeds mailbox bound", + data_offset); + return IPC4_INVALID_CONFIG_DATA_STRUCT; + } /* For now only vendor_config case uses payload from hostbox */ dcache_invalidate_region((__sparse_force void __sparse_cache *)MAILBOX_HOSTBOX_BASE, config.extension.r.data_off_size); From bbef32bc428fc735b1ab6bde665623a5de6ccaba Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 2 Jun 2026 14:29:53 +0300 Subject: [PATCH 027/303] schedule: bind scheduler to a task at task init time Change the operation model of schedule.h such that a task is bound to a scheduler instance at task init time. In the past, the scheduler instance has been looked up at each task scheduling invocation. The old model assume all schedule.h callers have access to all scheduler objects in the system. This complicates implementation of user-space support in SOF. Store a pointer to the matching schedule_data in the task struct at schedule_task_init() time. This allows all schedule API functions (schedule_task, schedule_task_before, schedule_task_after, reschedule_task, schedule_task_cancel, schedule_task_free, and schedule_task_running) to use the cached scheduler reference directly from task->sch, instead of iterating the global scheduler list on every call. This reduces runtime overhead and simplifies the inline functions in schedule.h, removing the repeated list traversal and type matching logic. The scheduler binding is validated at init time, so subsequent operations can assume a valid task->sch pointer. Note: in Zephyr builds, arch_schedulers_get() is sensitive on which core it is executed on, so add an assert to ensure schedule_task_init() is called on the target core. Signed-off-by: Kai Vehmanen --- posix/include/rtos/task.h | 2 + src/include/sof/schedule/schedule.h | 115 ++++++------------ src/platform/library/schedule/schedule.c | 20 +++ src/schedule/schedule.c | 21 ++++ src/schedule/zephyr_ll.c | 6 + .../pipeline/pipeline_connection_mocks.c | 3 + zephyr/include/rtos/task.h | 2 + 7 files changed, 90 insertions(+), 79 deletions(-) diff --git a/posix/include/rtos/task.h b/posix/include/rtos/task.h index 5aca574a5787..bb09ac63bac4 100644 --- a/posix/include/rtos/task.h +++ b/posix/include/rtos/task.h @@ -18,6 +18,7 @@ struct comp_dev; struct sof; +struct schedule_data; /** \brief Predefined LL task priorities. */ #define SOF_TASK_PRI_HIGH 0 /* priority level 0 - high */ @@ -60,6 +61,7 @@ struct task { uint16_t priority; /**< priority of the task (used by LL) */ uint16_t core; /**< execution core */ uint16_t flags; /**< custom flags */ + struct schedule_data *sch; /**< scheduler bound to task */ enum task_state state; /**< current state */ void *data; /**< custom data passed to all ops */ struct list_item list; /**< used by schedulers to hold tasks */ diff --git a/src/include/sof/schedule/schedule.h b/src/include/sof/schedule/schedule.h index 2b222d7448ad..9b16a3eff6b1 100644 --- a/src/include/sof/schedule/schedule.h +++ b/src/include/sof/schedule/schedule.h @@ -203,149 +203,106 @@ static inline void *scheduler_get_data(uint16_t type) /** See scheduler_ops::schedule_task_running */ static inline int schedule_task_running(struct task *task) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) { - /* optional operation */ - if (!sch->ops->schedule_task_running) - return 0; + assert(task); + sch = task->sch; - return sch->ops->schedule_task_running(sch->data, task); - } - } + if (!sch->ops->schedule_task_running) + return 0; - return -ENODEV; + return sch->ops->schedule_task_running(sch->data, task); } /** See scheduler_ops::schedule_task */ static inline int schedule_task(struct task *task, uint64_t start, uint64_t period) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; if (!task) return -EINVAL; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) - return sch->ops->schedule_task(sch->data, task, start, - period); - } + sch = task->sch; - return -ENODEV; + return sch->ops->schedule_task(sch->data, task, start, period); } /** See scheduler_ops::schedule_task_before */ static inline int schedule_task_before(struct task *task, uint64_t start, uint64_t period, struct task *before) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; if (!task || !before) return -EINVAL; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) { - if (sch->ops->schedule_task_before) - return sch->ops->schedule_task_before(sch->data, task, start, - period, before); - - return sch->ops->schedule_task(sch->data, task, start, - period); - } - } + sch = task->sch; + + if (sch->ops->schedule_task_before) + return sch->ops->schedule_task_before(sch->data, task, start, + period, before); - return -ENODEV; + return sch->ops->schedule_task(sch->data, task, start, period); } /** See scheduler_ops::schedule_task_after */ static inline int schedule_task_after(struct task *task, uint64_t start, uint64_t period, struct task *after) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; if (!task || !after) return -EINVAL; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) { - if (sch->ops->schedule_task_after) - return sch->ops->schedule_task_after(sch->data, task, start, - period, after); - - return sch->ops->schedule_task(sch->data, task, start, - period); - } - } + sch = task->sch; - return -ENODEV; + if (sch->ops->schedule_task_after) + return sch->ops->schedule_task_after(sch->data, task, start, + period, after); + + return sch->ops->schedule_task(sch->data, task, start, period); } /** See scheduler_ops::reschedule_task */ static inline int reschedule_task(struct task *task, uint64_t start) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) { - /* optional operation */ - if (!sch->ops->reschedule_task) - return 0; - - return sch->ops->reschedule_task(sch->data, task, - start); - } - } + assert(task); + sch = task->sch; - return -ENODEV; + /* optional operation */ + if (!sch->ops->reschedule_task) + return 0; + + return sch->ops->reschedule_task(sch->data, task, start); } /** See scheduler_ops::schedule_task_cancel */ static inline int schedule_task_cancel(struct task *task) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) - return sch->ops->schedule_task_cancel(sch->data, task); - } + if (!task || !task->sch) + return -EINVAL; + + sch = task->sch; - return -ENODEV; + return sch->ops->schedule_task_cancel(sch->data, task); } /** See scheduler_ops::schedule_task_free */ static inline int schedule_task_free(struct task *task) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type) - return sch->ops->schedule_task_free(sch->data, task); - } + if (!task || !task->sch) + return -EINVAL; + + sch = task->sch; - return -ENODEV; + return sch->ops->schedule_task_free(sch->data, task); } /** See scheduler_ops::scheduler_free */ diff --git a/src/platform/library/schedule/schedule.c b/src/platform/library/schedule/schedule.c index b1dfcbb19445..f110711afe35 100644 --- a/src/platform/library/schedule/schedule.c +++ b/src/platform/library/schedule/schedule.c @@ -25,9 +25,29 @@ int schedule_task_init(struct task *task, uint16_t priority, enum task_state (*run)(void *data), void *data, uint16_t core, uint32_t flags) { + struct schedulers *schedulers = *arch_schedulers_get(); + struct schedule_data *sch = NULL; + struct list_item *slist; + if (type >= SOF_SCHEDULE_COUNT) return -EINVAL; + if (!schedulers) + return -ENODEV; + + task->sch = NULL; + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (type == sch->type) { + task->sch = sch; + break; + } + } + + if (!task->sch) { + return -ENODEV; + } + task->uid = uid; task->type = type; task->priority = priority; diff --git a/src/schedule/schedule.c b/src/schedule/schedule.c index 5e56b6c490e0..1848b603145e 100644 --- a/src/schedule/schedule.c +++ b/src/schedule/schedule.c @@ -27,11 +27,32 @@ int schedule_task_init(struct task *task, uint16_t priority, enum task_state (*run)(void *data), void *data, uint16_t core, uint32_t flags) { + struct schedulers *schedulers = *arch_schedulers_get(); + struct schedule_data *sch = NULL; + struct list_item *slist; + if (type >= SOF_SCHEDULE_COUNT) { tr_err(&sch_tr, "invalid task type"); return -EINVAL; } + if (!schedulers) + return -ENODEV; + + task->sch = NULL; + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (type == sch->type) { + task->sch = sch; + break; + } + } + + if (!task->sch) { + tr_err(&sch_tr, "unable to bind scheduler to task %p (type %d)", task, type); + return -ENODEV; + } + task->uid = uid; task->type = type; task->priority = priority; diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 0a6fd3f14221..bfb3b5ed66c0 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -571,6 +571,12 @@ int zephyr_ll_task_init(struct task *task, if (task->priv_data) return -EEXIST; + /* + * schedule_task_init() must be run on target core, see + * sof/zephyr/schedule.c:arch_schedulers_get() + */ + assert(cpu_get_id() == core); + ret = schedule_task_init(task, uid, type, priority, run, data, core, flags); if (ret < 0) diff --git a/test/cmocka/src/audio/pipeline/pipeline_connection_mocks.c b/test/cmocka/src/audio/pipeline/pipeline_connection_mocks.c index de05f8e3b284..7315ce387562 100644 --- a/test/cmocka/src/audio/pipeline/pipeline_connection_mocks.c +++ b/test/cmocka/src/audio/pipeline/pipeline_connection_mocks.c @@ -49,6 +49,9 @@ struct pipeline_connect_data *get_standard_connect_objects(void) sch->ops = &schedule_mock_ops; list_item_append(&sch->list, &schedulers->list); + /* Bind the scheduler to the task, as schedule_task_init() would */ + pipe->pipe_task->sch = sch; + struct comp_dev *first = calloc(sizeof(struct comp_dev), 1); struct comp_ipc_config *first_comp = &first->ipc_config; diff --git a/zephyr/include/rtos/task.h b/zephyr/include/rtos/task.h index 9fcc202c1e2e..cfcba9130cef 100644 --- a/zephyr/include/rtos/task.h +++ b/zephyr/include/rtos/task.h @@ -17,6 +17,7 @@ struct comp_dev; struct sof; +struct schedule_data; /** \brief Predefined LL task priorities. */ #define SOF_TASK_PRI_HIGH 0 /* priority level 0 - high */ @@ -59,6 +60,7 @@ struct task { uint16_t priority; /**< priority of the task (used by LL) */ uint16_t core; /**< execution core */ uint16_t flags; /**< custom flags */ + struct schedule_data *sch; /**< scheduler bound to task */ enum task_state state; /**< current state */ void *data; /**< custom data passed to all ops */ struct list_item list; /**< used by schedulers to hold tasks */ From 60283dcabbc65bd550eb305e70659ff792b144cd Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 3 Jun 2026 13:33:15 +0300 Subject: [PATCH 028/303] ipc4: Replace interim_heap_bytes and lifetime_heap_bytes with heap_bytes Simplify the IPC4 memory data structs by replacing separate interim_heap_bytes, lifetime_heap_bytes, and shared_bytes fields with a single heap_bytes field, matching the Linux driver side changes. Affects both ipc4_module_init_ext_obj_dp_data and ipc4_pipeline_ext_obj_mem_data structs, and their corresponding log messages. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter_ipc4.c | 5 ++--- src/include/ipc4/module.h | 4 +--- src/include/ipc4/pipeline.h | 4 +--- src/ipc/ipc4/helper.c | 5 ++--- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/audio/module_adapter/module_adapter_ipc4.c b/src/audio/module_adapter/module_adapter_ipc4.c index 74d39559a004..a639755a31e2 100644 --- a/src/audio/module_adapter/module_adapter_ipc4.c +++ b/src/audio/module_adapter/module_adapter_ipc4.c @@ -81,10 +81,9 @@ int module_ext_init_decode(const struct comp_driver *drv, struct module_ext_init } ext_data->dp_data = dp_data; comp_cl_info(drv, - "init_ext_obj_dp_data domain %u stack %u interim %u lifetime %u shared %u", + "init_ext_obj_dp_data domain %u stack %u heap %u", dp_data->domain_id, dp_data->stack_bytes, - dp_data->interim_heap_bytes, dp_data->lifetime_heap_bytes, - dp_data->shared_bytes); + dp_data->heap_bytes); break; } case IPC4_MOD_INIT_DATA_ID_MODULE_DATA: diff --git a/src/include/ipc4/module.h b/src/include/ipc4/module.h index dfe0f02e628e..4e60ebc9bc13 100644 --- a/src/include/ipc4/module.h +++ b/src/include/ipc4/module.h @@ -93,9 +93,7 @@ struct ipc4_module_init_ext_object { struct ipc4_module_init_ext_obj_dp_data { uint32_t domain_id; /* userspace domain ID */ uint32_t stack_bytes; /* required stack size in bytes */ - uint32_t interim_heap_bytes; /* required interim heap size in bytes */ - uint32_t lifetime_heap_bytes; /* required lifetime heap size in bytes */ - uint32_t shared_bytes; /* required shared memory size in bytes */ + uint32_t heap_bytes; /* required heap size in bytes */ } __attribute__((packed, aligned(4))); /* diff --git a/src/include/ipc4/pipeline.h b/src/include/ipc4/pipeline.h index 198918cf8577..1e85072fd15c 100644 --- a/src/include/ipc4/pipeline.h +++ b/src/include/ipc4/pipeline.h @@ -88,9 +88,7 @@ struct ipc4_pipeline_ext_object { struct ipc4_pipeline_ext_obj_mem_data { uint32_t domain_id; /* userspace domain ID */ uint32_t stack_bytes; /* required stack size in bytes */ - uint32_t interim_heap_bytes; /* required interim heap size in bytes */ - uint32_t lifetime_heap_bytes; /* required lifetime heap size in bytes */ - uint32_t shared_bytes; /* required shared memory in bytes */ + uint32_t heap_bytes; /* required heap size in bytes */ } __packed __aligned(4); /* diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 8e3073ab7797..88b181450521 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -317,10 +317,9 @@ __cold static int ipc4_create_pipeline_payload_decode(char *data, } pparams->mem_data = mem_data; tr_info(&ipc_tr, - "init_ext_obj_mem_data domain %u stack %u interim %u lifetime %u shared %u", + "init_ext_obj_mem_data domain %u stack %u heap %u", mem_data->domain_id, mem_data->stack_bytes, - mem_data->interim_heap_bytes, mem_data->lifetime_heap_bytes, - mem_data->shared_bytes); + mem_data->heap_bytes); break; } default: From fa33d154d8f8a776c0e793de34c2737aca576862 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 8 Jun 2026 15:39:17 +0300 Subject: [PATCH 029/303] test: cmocka: common_mocks: add sof_sys_user_heap_get() Add a mock implementation for sof_sys_user_heap_get() so this function can be used in modules covered by cmocka tests. Signed-off-by: Kai Vehmanen --- test/cmocka/src/common_mocks.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/cmocka/src/common_mocks.c b/test/cmocka/src/common_mocks.c index 16fee8f2ff48..2c2867346293 100644 --- a/test/cmocka/src/common_mocks.c +++ b/test/cmocka/src/common_mocks.c @@ -119,6 +119,11 @@ struct k_heap * WEAK sof_sys_heap_get(void) return NULL; } +struct k_heap * WEAK sof_sys_user_heap_get(void) +{ + return NULL; +} + void WEAK *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, size_t alignment) { From 51cb0b8e77b1b74641e4ed5ed127a170e7d3e172 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 5 May 2026 12:41:59 +0300 Subject: [PATCH 030/303] audio: module_adapter: use system user heap for allocs When SOF is built with LL pipes in user-space, module adapter should allocate all resources from the user system heap. Add support for this by modifying allocs to use sof_heap_alloc() and initialize the heap with sof_sys_user_heap_get(). This will return a user-space heap in case SOF is built with CONFIG_SOF_USERSPACE_LL. Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module_adapter.c | 46 +++++++++++++++-------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index a68af6b65eee..d9518de7e13e 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -101,7 +101,12 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv } mod_heap = NULL; } else { +#ifdef CONFIG_SOF_USERSPACE_LL + mod_heap = sof_sys_user_heap_get(); + comp_cl_dbg(drv, "using ll user heap for module"); +#else mod_heap = drv->user_heap; +#endif heap_size = 0; mod_vreg = NULL; } @@ -118,7 +123,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv goto emod; } - struct mod_alloc_ctx *alloc = rmalloc(flags, sizeof(*alloc)); + struct mod_alloc_ctx *alloc = sof_heap_alloc(mod_heap, flags, sizeof(*alloc), 0); if (!alloc) goto ealloc; @@ -154,7 +159,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv return mod; edev: - rfree(alloc); + sof_heap_free(mod_heap, alloc); ealloc: if (mod_vreg) vregion_free(mod_vreg, mod); @@ -184,11 +189,11 @@ static void module_adapter_mem_free(struct processing_module *mod) vregion_free(mod_vreg, mod->dev); vregion_free(mod_vreg, mod); if (!vregion_put(mod_vreg)) - rfree(alloc); + sof_heap_free(alloc->heap, alloc); } else { sof_heap_free(mod_heap, mod->dev); sof_heap_free(mod_heap, mod); - rfree(alloc); + sof_heap_free(mod_heap, alloc); } } @@ -527,11 +532,13 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for input buffers */ if (mod->max_sources) { mod->input_buffers = - rzalloc(memory_flags, sizeof(*mod->input_buffers) * mod->max_sources); + sof_heap_alloc(sof_sys_user_heap_get(), memory_flags, + sizeof(*mod->input_buffers) * mod->max_sources, 0); if (!mod->input_buffers) { comp_err(dev, "failed to allocate input buffers"); return -ENOMEM; } + memset(mod->input_buffers, 0, sizeof(*mod->input_buffers) * mod->max_sources); } else { mod->input_buffers = NULL; } @@ -539,12 +546,14 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for output buffers */ if (mod->max_sinks) { mod->output_buffers = - rzalloc(memory_flags, sizeof(*mod->output_buffers) * mod->max_sinks); + sof_heap_alloc(sof_sys_user_heap_get(), memory_flags, + sizeof(*mod->output_buffers) * mod->max_sinks, 0); if (!mod->output_buffers) { comp_err(dev, "failed to allocate output buffers"); ret = -ENOMEM; goto in_out_free; } + memset(mod->output_buffers, 0, sizeof(*mod->output_buffers) * mod->max_sinks); } else { mod->output_buffers = NULL; } @@ -605,7 +614,8 @@ int module_adapter_prepare(struct comp_dev *dev) size_t size = MAX(mod->deep_buff_bytes, mod->period_bytes); list_for_item(blist, &dev->bsource_list) { - mod->input_buffers[i].data = rballoc(memory_flags, size); + mod->input_buffers[i].data = sof_heap_alloc(sof_sys_user_heap_get(), + memory_flags, size, 0); if (!mod->input_buffers[i].data) { comp_err(mod->dev, "Failed to alloc input buffer data"); ret = -ENOMEM; @@ -617,7 +627,9 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for output buffer data */ i = 0; list_for_item(blist, &dev->bsink_list) { - mod->output_buffers[i].data = rballoc(memory_flags, md->mpd.out_buff_size); + mod->output_buffers[i].data = sof_heap_alloc(sof_sys_user_heap_get(), + memory_flags, + md->mpd.out_buff_size, 0); if (!mod->output_buffers[i].data) { comp_err(mod->dev, "Failed to alloc output buffer data"); ret = -ENOMEM; @@ -686,16 +698,16 @@ int module_adapter_prepare(struct comp_dev *dev) out_data_free: for (i = 0; i < mod->num_of_sinks; i++) - rfree(mod->output_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers[i].data); in_data_free: for (i = 0; i < mod->num_of_sources; i++) - rfree(mod->input_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers[i].data); in_out_free: - rfree(mod->output_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers); mod->output_buffers = NULL; - rfree(mod->input_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers); mod->input_buffers = NULL; return ret; } @@ -1407,14 +1419,16 @@ int module_adapter_reset(struct comp_dev *dev) if (IS_PROCESSING_MODE_RAW_DATA(mod)) { for (i = 0; i < mod->num_of_sinks; i++) - rfree((__sparse_force void *)mod->output_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), + (__sparse_force void *)mod->output_buffers[i].data); for (i = 0; i < mod->num_of_sources; i++) - rfree((__sparse_force void *)mod->input_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), + (__sparse_force void *)mod->input_buffers[i].data); } if (IS_PROCESSING_MODE_RAW_DATA(mod) || IS_PROCESSING_MODE_AUDIO_STREAM(mod)) { - rfree(mod->output_buffers); - rfree(mod->input_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers); mod->num_of_sources = 0; mod->num_of_sinks = 0; From 0049d73c4efca8ea339eaa6327d8a9235155e69b Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 19 Mar 2026 21:39:40 +0200 Subject: [PATCH 031/303] ipc: ipc4: use correct API to get DMA status Use sof_dma_get_status() call to allow the audio pipeline to be run in user-space. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/dai.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 58c08d9ec04b..1a0135cb5d5d 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -450,7 +450,7 @@ void dai_dma_position_update(struct dai_data *dd, struct comp_dev *dev) if (!dd->slot_info.node_id) return; - ret = dma_get_status(dd->dma->z_dev, dd->chan_index, &status); + ret = sof_dma_get_status(dd->dma, dd->chan_index, &status); if (ret < 0) return; From 1839efa6f4aeb4d1630f39887a3fd80a53c9e2f7 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 20 May 2026 10:04:10 +0300 Subject: [PATCH 032/303] ipc: ipc4: dai: fix direct use of DMA driver calls Fix a few remaining uses of direct DMA driver calls. Use the sof_dma.h wrapper instead, allowing the code to be used also from user-space. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/dai.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 1a0135cb5d5d..1540d95cf0f0 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -236,9 +236,9 @@ void dai_dma_release(struct dai_data *dd, struct comp_dev *dev) * TODO: refine power management when stream is paused */ /* if reset is after pause dma has already been stopped */ - dma_stop(dd->dma->z_dev, dd->chan_index); + sof_dma_stop(dd->dma, dd->chan_index); - dma_release_channel(dd->dma->z_dev, dd->chan_index); + sof_dma_release_channel(dd->dma, dd->chan_index); dd->chan_index = -EINVAL; } } From 3f7738d5d81324e526c0035ad4b6a0e7c1801760 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Mon, 8 Jun 2026 20:57:54 +0200 Subject: [PATCH 033/303] audio: init align to avoid uninitialized read in error log sof_dma_get_attribute() can return an error without writing its output parameter, so logging align on that path read an uninitialized value. Initialize align to 0 in the host/dai zephyr and legacy params paths. Signed-off-by: Adrian Bonislawski --- src/audio/dai-legacy.c | 2 +- src/audio/dai-zephyr.c | 2 +- src/audio/host-legacy.c | 2 +- src/audio/host-zephyr.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/audio/dai-legacy.c b/src/audio/dai-legacy.c index 655fa94afb86..fb095a16a626 100644 --- a/src/audio/dai-legacy.c +++ b/src/audio/dai-legacy.c @@ -467,7 +467,7 @@ int dai_common_params(struct dai_data *dd, struct comp_dev *dev, uint32_t period_bytes; uint32_t buffer_size; uint32_t addr_align; - uint32_t align; + uint32_t align = 0; int err; comp_dbg(dev, "dai_params()"); diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index e49d3e0f0122..fc2a065741cd 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -977,7 +977,7 @@ static int dai_set_dma_buffer(struct dai_data *dd, struct comp_dev *dev, uint32_t buffer_size; uint32_t buffer_size_preferred; uint32_t addr_align; - uint32_t align; + uint32_t align = 0; int err; comp_dbg(dev, "entry"); diff --git a/src/audio/host-legacy.c b/src/audio/host-legacy.c index 3d62e271f518..033c4cf2a09e 100644 --- a/src/audio/host-legacy.c +++ b/src/audio/host-legacy.c @@ -686,7 +686,7 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, uint32_t period_bytes; uint32_t buffer_size; uint32_t addr_align; - uint32_t align; + uint32_t align = 0; int err; /* host params always installed by pipeline IPC */ diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 90b0821caf5a..e3f79419b389 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -887,7 +887,7 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, uint32_t buffer_size; uint32_t buffer_size_preferred; uint32_t addr_align; - uint32_t align; + uint32_t align = 0; int i, err; bool is_scheduling_source = dev == dev->pipeline->sched_comp; uint32_t round_up_size; From 1cbc3a1757719ceef18a64d9a1df0a2dd5e49bad Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Mon, 8 Jun 2026 15:23:11 +0200 Subject: [PATCH 034/303] audio: kpb: cast operands to size_t before width-widening multiplies Six multiplications in the key-phrase buffer compute their product at 32-bit width and only then assign it to a wider size_t result. If the operands are large the overflow has already occurred before widening. The KPB sizing math is partly driven by externally-influenced values (cli->drain_req, the configured channel count, sampling frequency and container width), so this is a real overflow surface rather than a purely theoretical one. Cast the leading operand to size_t in each expression so the whole product is evaluated at the destination width: - kpb_micselect_copy16/32(): loop bound samples_per_chan * in_channels - kpb_init_draining(): drain_req and bytes_per_ms - adjust_drain_interval(): pipeline_period - validate_host_params(): bytes_per_ms No functional change on in-range inputs; only the intermediate arithmetic width changes. Found-by: CodeQL 2.24.2 (codeql/cpp-queries cpp-security-extended), rule cpp/integer-multiplication-cast-to-long. Run with database build-mode=none over sof/src (host clang cannot target the Xtensa production build), 867 files / 98 queries. Findings at kpb.c:1117,1148,1610,1619,1791,2397. AI-triaged: findings manually cross-referenced against clang-tidy bugprone-implicit-widening-of-multiplication-result and semgrep raptor-integer-truncation on the same surface, and confirmed the operand types (uint32_t / macro constants) against struct sof_kpb_config and struct kpb_client before fixing. Signed-off-by: Tomasz Leman --- src/audio/kpb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 9dca05630da1..1591c21885b5 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -1107,7 +1107,7 @@ static void kpb_micselect_copy16(struct comp_buffer *sink, const int16_t *in_data; int16_t *out_data; - const uint32_t samples_per_chan = size / (sizeof(uint16_t) * micsel_channels); + const size_t samples_per_chan = size / (sizeof(uint16_t) * micsel_channels); for (ch = 0; ch < micsel_channels; ch++) { out_samples = 0; @@ -1138,7 +1138,7 @@ static void kpb_micselect_copy32(struct comp_buffer *sink, uint16_t ch; const int32_t *in_data; int32_t *out_data; - const uint32_t samples_per_chan = size / (sizeof(uint32_t) * micsel_channels); + const size_t samples_per_chan = size / (sizeof(uint32_t) * micsel_channels); for (ch = 0; ch < micsel_channels; ch++) { out_samples = 0; @@ -1607,7 +1607,7 @@ static void kpb_init_draining(struct comp_dev *dev, struct kpb_client *cli) struct comp_data *kpb = comp_get_drvdata(dev); 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 = cli->drain_req * kpb->config.channels * + size_t drain_req = (size_t)cli->drain_req * kpb->config.channels * (kpb->config.sampling_freq / 1000) * (KPB_SAMPLE_CONTAINER_SIZE(sample_width) / 8); struct history_buffer *buff = kpb->hd.c_hb; @@ -1616,7 +1616,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 = KPB_SAMPLES_PER_MS * + size_t bytes_per_ms = (size_t)KPB_SAMPLES_PER_MS * (KPB_SAMPLE_CONTAINER_SIZE(sample_width) / 8) * kpb->config.channels; size_t period_bytes_limit; @@ -1788,7 +1788,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 = KPB_SAMPLES_PER_MS * + pipeline_period = (size_t)KPB_SAMPLES_PER_MS * (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; @@ -2394,7 +2394,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 = KPB_SAMPLES_PER_MS * + size_t bytes_per_ms = (size_t)KPB_SAMPLES_PER_MS * (KPB_SAMPLE_CONTAINER_SIZE(sample_width) / 8) * kpb->config.channels; size_t pipeline_period_size = (dev->pipeline->period / 1000) From 73908d13c986c71c9a44807664f003b2f870e463 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Mon, 8 Jun 2026 15:31:54 +0200 Subject: [PATCH 035/303] audio: kpb: widen channel loop counter to match micsel_channels The four kpb_micselect_copy16/32() variants declare the channel loop counter "ch" as uint16_t while iterating "for (ch = 0; ch < ... micsel_channels; ch++)", where micsel_channels is uint32_t. The loop condition promotes ch to a wider type for the comparison, so if micsel_channels ever exceeds UINT16_MAX the counter wraps at 65536 and the loop fails to terminate. The same narrow counter would also truncate channel indexing. Declare ch as uint32_t so its width matches the bound it is compared against and the channel count it indexes. KPB_MAX_MICSEL_CHANNELS keeps the real value small, so this is hardening rather than an observed runaway, but the type mismatch is removed at the source. CodeQL flagged the two non-HiFi variants (kpb.c:1112,1143); the two KPB_HIFI3 variants (kpb.c:1050,1084) carry the identical pattern and are fixed in the same change for consistency. Found-by: CodeQL 2.24.2 (codeql/cpp-queries cpp-security-extended), rule cpp/comparison-with-wider-type. Run with database build-mode=none over sof/src, 867 files / 98 queries. AI-triaged: traced ch and micsel_channels declarations across all four micselect copy functions and confirmed the bound is uint32_t before widening the counter; extended the fix to the HiFi3 variants the tool did not reach in this build configuration. Signed-off-by: Tomasz Leman --- src/audio/kpb.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 1591c21885b5..696ee0e1042d 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -1033,7 +1033,7 @@ static void kpb_micselect_copy16(struct comp_buffer *sink, { struct audio_stream *istream = &source->stream; struct audio_stream *ostream = &sink->stream; - uint16_t ch; + uint32_t ch; size_t i; AE_SETCBEGIN0(audio_stream_get_addr(ostream)); @@ -1066,7 +1066,7 @@ static void kpb_micselect_copy32(struct comp_buffer *sink, { struct audio_stream *istream = &source->stream; struct audio_stream *ostream = &sink->stream; - uint16_t ch; + uint32_t ch; size_t i; AE_SETCBEGIN0(audio_stream_get_addr(ostream)); @@ -1103,7 +1103,7 @@ static void kpb_micselect_copy16(struct comp_buffer *sink, buffer_stream_invalidate(source, size); size_t out_samples; - uint16_t ch; + uint32_t ch; const int16_t *in_data; int16_t *out_data; @@ -1135,7 +1135,7 @@ static void kpb_micselect_copy32(struct comp_buffer *sink, buffer_stream_invalidate(source, size); size_t out_samples; - uint16_t ch; + uint32_t ch; const int32_t *in_data; int32_t *out_data; const size_t samples_per_chan = size / (sizeof(uint32_t) * micsel_channels); From 00b9f75b6e7428fb356b4e3a63154804e87879bd Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 9 Jun 2026 17:08:52 +0200 Subject: [PATCH 036/303] audio: kpb: fix buffer_stream_invalidate size in micselect copy helpers In all four kpb_micselect_copy16/32 variants (HiFi3 and generic), the call to buffer_stream_invalidate(source, size) used the output byte count (produced_bytes) as the invalidation size. When in_channels > micsel_channels, the actual input span read by the copy loop is: samples_per_chan * in_channels * sample_size which is larger than the output span (size). Under-invalidating the source buffer on CONFIG_INCOHERENT platforms can cause stale cache reads. Fix: compute samples_per_chan before the invalidate call and pass the correct input span to buffer_stream_invalidate() in all four helpers. Signed-off-by: Tomasz Leman --- src/audio/kpb.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 696ee0e1042d..71fdca29e73e 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -1039,12 +1039,13 @@ static void kpb_micselect_copy16(struct comp_buffer *sink, AE_SETCBEGIN0(audio_stream_get_addr(ostream)); AE_SETCEND0(audio_stream_get_end_addr(ostream)); - buffer_stream_invalidate(source, size); + const size_t samples_per_chan = size / (sizeof(ae_int16) * micsel_channels); + + buffer_stream_invalidate(source, samples_per_chan * in_channels * sizeof(ae_int16)); const ae_int16 *in_ptr = audio_stream_get_rptr(istream); ae_int16x4 d16 = AE_ZERO16(); const size_t in_offset = in_channels * sizeof(ae_int16); const size_t out_offset = micsel_channels * sizeof(ae_int16); - const size_t samples_per_chan = size / (sizeof(uint16_t) * micsel_channels); ae_int16 *out_ptr; for (ch = 0; ch < micsel_channels; ch++) { @@ -1072,13 +1073,14 @@ static void kpb_micselect_copy32(struct comp_buffer *sink, AE_SETCBEGIN0(audio_stream_get_addr(ostream)); AE_SETCEND0(audio_stream_get_end_addr(ostream)); - buffer_stream_invalidate(source, size); + const size_t samples_per_chan = size / (sizeof(ae_int32) * micsel_channels); + + buffer_stream_invalidate(source, samples_per_chan * in_channels * sizeof(ae_int32)); const ae_int32 *in_ptr = audio_stream_get_rptr(istream); ae_int32x2 d32 = AE_ZERO32(); const size_t in_offset = in_channels * sizeof(ae_int32); const size_t out_offset = micsel_channels * sizeof(ae_int32); - const size_t samples_per_chan = size / (sizeof(uint32_t) * micsel_channels); ae_int32 *out_ptr; for (ch = 0; ch < micsel_channels; ch++) { @@ -1100,8 +1102,6 @@ static void kpb_micselect_copy16(struct comp_buffer *sink, { struct audio_stream *istream = &source->stream; struct audio_stream *ostream = &sink->stream; - - buffer_stream_invalidate(source, size); size_t out_samples; uint32_t ch; @@ -1109,6 +1109,8 @@ static void kpb_micselect_copy16(struct comp_buffer *sink, int16_t *out_data; const size_t samples_per_chan = size / (sizeof(uint16_t) * micsel_channels); + buffer_stream_invalidate(source, samples_per_chan * in_channels * sizeof(uint16_t)); + for (ch = 0; ch < micsel_channels; ch++) { out_samples = 0; in_data = audio_stream_get_rptr(istream); @@ -1132,14 +1134,14 @@ static void kpb_micselect_copy32(struct comp_buffer *sink, { struct audio_stream *istream = &source->stream; struct audio_stream *ostream = &sink->stream; - - buffer_stream_invalidate(source, size); size_t out_samples; uint32_t ch; const int32_t *in_data; int32_t *out_data; const size_t samples_per_chan = size / (sizeof(uint32_t) * micsel_channels); + buffer_stream_invalidate(source, samples_per_chan * in_channels * sizeof(uint32_t)); + for (ch = 0; ch < micsel_channels; ch++) { out_samples = 0; in_data = audio_stream_get_rptr(istream); From 382ea2fe25592e6998aeefa78b4a6ae3b2aab5d2 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Wed, 3 Jun 2026 18:38:22 +0200 Subject: [PATCH 037/303] audio: mux: Fix incorrect iterator in demux_trigger demux_trigger() checks for cross-pipeline sinks to set the overrun_permitted flag. Commit 96b4fdb4f ("buf: use API for iteration bsource_list in components") incorrectly converted the bsink_list iteration to comp_dev_for_each_producer instead of comp_dev_for_each_consumer. Since demux has 1 input and multiple outputs, the loop must iterate over consumers (sinks), not producers (sources). The incorrect iterator caused the pipeline comparison to never match, making audio_stream_set_overrun() unreachable. Fix by using comp_dev_for_each_consumer with comp_buffer_get_sink_component and add a defensive NULL check on the returned component pointer. Issue found using semgrep with custom rules. Signed-off-by: Tomasz Leman --- src/audio/mux/mux.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/audio/mux/mux.c b/src/audio/mux/mux.c index 965faaf8037e..1476779e7324 100644 --- a/src/audio/mux/mux.c +++ b/src/audio/mux/mux.c @@ -290,9 +290,11 @@ static int demux_trigger(struct processing_module *mod, int cmd) */ if (cmd == COMP_TRIGGER_PRE_START) { struct comp_buffer *b; + struct comp_dev *sink_comp; - comp_dev_for_each_producer(mod->dev, b) { - if (comp_buffer_get_sink_component(b)->pipeline != mod->dev->pipeline) + comp_dev_for_each_consumer(mod->dev, b) { + sink_comp = comp_buffer_get_sink_component(b); + if (sink_comp && sink_comp->pipeline != mod->dev->pipeline) audio_stream_set_overrun(&b->stream, true); } } From 8aeb4451b601bd165d307dba1e7056efe608785c Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Tue, 9 Jun 2026 13:45:42 +0200 Subject: [PATCH 038/303] ipc4: helper: validate host device_count before indexing channel map dma_cfg->channel_map.device_count comes from a host supplied gateway config blob and was used directly to bound the loop indexing the fixed channel_map.map[GTW_DMA_DEVICE_MAX_COUNT] array, allowing an out-of-bounds read. Reject counts above the ABI maximum with IPC4_INVALID_REQUEST. Signed-off-by: Adrian Bonislawski --- src/ipc/ipc4/helper.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 88b181450521..c13804c3e46e 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -1312,12 +1312,20 @@ int ipc4_find_dma_config_multiple(struct ipc_config_dai *dai, uint8_t *data_buff if (!dma_cfg) continue; + uint32_t device_count = dma_cfg->channel_map.device_count; + + if (device_count > GTW_DMA_DEVICE_MAX_COUNT) { + tr_err(&ipc_tr, "device_count %u exceeds max %u", + device_count, GTW_DMA_DEVICE_MAX_COUNT); + return IPC4_INVALID_REQUEST; + } + /* To be able to retrieve proper DMA config we need to check if * device_id value (which is alh_id) is equal to device_address. * They both contain SNDW master id and PDI. If they match then * proper config is found. */ - for (uint32_t i = 0; i < dma_cfg->channel_map.device_count; i++) { + for (uint32_t i = 0; i < device_count; i++) { if (dma_cfg->channel_map.map[i].device_address == device_id) { dai->host_dma_config[dma_cfg_idx] = dma_cfg; return IPC4_SUCCESS; From 6349fdfc14f291d9eefb04839ff9fafb57184059 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Mon, 8 Jun 2026 21:13:58 +0200 Subject: [PATCH 039/303] audio: mixin_mixout: check sink_get_buffer() return value sink_get_buffer() can return -EBUSY or -ENODATA without writing its output parameters, so using buf_size right after the call read an uninitialized value and built an invalid buffer end pointer. Check the return value in both acquire sites. In mixout_process() bail out on error. In mixin_process() skip only the failed mixout and keep mixing the rest; the source is consumed once after the loop so it is never reprocessed. Signed-off-by: Adrian Bonislawski --- src/audio/mixin_mixout/mixin_mixout.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/audio/mixin_mixout/mixin_mixout.c b/src/audio/mixin_mixout/mixin_mixout.c index 81dfb20e143e..932c216ad4f1 100644 --- a/src/audio/mixin_mixout/mixin_mixout.c +++ b/src/audio/mixin_mixout/mixin_mixout.c @@ -458,8 +458,13 @@ static int mixin_process(struct processing_module *mod, size_t free_bytes = sink_get_free_size(sink); size_t buf_size; - sink_get_buffer(sink, free_bytes, &mixout_data->acquired_buf.ptr, - &mixout_data->acquired_buf.buf_start, &buf_size); + ret = sink_get_buffer(sink, free_bytes, &mixout_data->acquired_buf.ptr, + &mixout_data->acquired_buf.buf_start, &buf_size); + /* on failed acquire, skip this mixout and keep mixing the rest; + * source is consumed once after the loop so it is not reprocessed + */ + if (ret < 0) + continue; mixout_data->acquired_buf.buf_end = (uint8_t *)mixout_data->acquired_buf.buf_start + buf_size; mixout_data->acquired_buf_free_frames = @@ -513,7 +518,7 @@ static int mixout_process(struct processing_module *mod, uint32_t frames_to_produce = INT32_MAX; uint32_t bytes_to_produce; struct pending_frames *pending_frames; - int i; + int i, ret; comp_dbg(dev, "entry"); @@ -574,8 +579,11 @@ static int mixout_process(struct processing_module *mod, if (!md->acquired_buf.ptr) { size_t buf_size; - sink_get_buffer(sinks[0], bytes_to_produce, &md->acquired_buf.ptr, - &md->acquired_buf.buf_start, &buf_size); + ret = sink_get_buffer(sinks[0], bytes_to_produce, &md->acquired_buf.ptr, + &md->acquired_buf.buf_start, &buf_size); + if (ret < 0) + return ret; + md->acquired_buf.buf_end = (uint8_t *)md->acquired_buf.buf_start + buf_size; } From 9c415201a0b08deb23117ca5ce35cfb2f3740cc0 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Wed, 10 Jun 2026 16:28:42 +0200 Subject: [PATCH 040/303] audio: mixin_mixout: check source_get_data() return source_get_data() can return -EBUSY/-ENODATA without writing the buf_size out-parameter, leaving it (and source_ptr) uninitialized. Return the error instead of deriving buf_end from stack garbage, mirroring the sink_get_buffer() check. Signed-off-by: Adrian Bonislawski --- src/audio/mixin_mixout/mixin_mixout.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/audio/mixin_mixout/mixin_mixout.c b/src/audio/mixin_mixout/mixin_mixout.c index 932c216ad4f1..6e30eaee72d3 100644 --- a/src/audio/mixin_mixout/mixin_mixout.c +++ b/src/audio/mixin_mixout/mixin_mixout.c @@ -412,8 +412,10 @@ static int mixin_process(struct processing_module *mod, frames_to_copy = MIN(source_avail_frames, sinks_free_frames); bytes_to_consume = frames_to_copy * source_get_frame_bytes(sources[0]); - source_get_data(sources[0], bytes_to_consume, (const void **)&source_ptr.ptr, - (const void **)&source_ptr.buf_start, &buf_size); + ret = source_get_data(sources[0], bytes_to_consume, (const void **)&source_ptr.ptr, + (const void **)&source_ptr.buf_start, &buf_size); + if (ret < 0) + return ret; source_ptr.buf_end = (uint8_t *)source_ptr.buf_start + buf_size; } else { /* if source does not produce any data -- do NOT block mixing but generate From 9cd055b85c0c5ead5e5421170e6d38ad8aee5f65 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 26 May 2026 19:29:29 +0300 Subject: [PATCH 041/303] dai: turn dai_get_device() into a syscall Make dai_get_device() a syscall, so it can be called from user-space threads. Signed-off-by: Kai Vehmanen --- src/include/sof/lib/dai-zephyr.h | 4 +++- src/lib/dai.c | 2 +- zephyr/CMakeLists.txt | 2 ++ zephyr/syscall/dai.c | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 zephyr/syscall/dai.c diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 3e40c6e682af..a0c42bff351e 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -309,7 +309,7 @@ void dai_release_llp_slot(struct dai_data *dd); /** * \brief Retrieve a pointer to the Zephyr device structure for a DAI of a given type and index. */ -const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); +__syscall const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); /** * \brief Retrieve the list of all DAI devices. @@ -319,4 +319,6 @@ const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); const struct device **dai_get_device_list(size_t *count); /** @}*/ +#include + #endif /* __SOF_LIB_DAI_ZEPHYR_H__ */ diff --git a/src/lib/dai.c b/src/lib/dai.c index 7a8e44087a65..d51266aa345e 100644 --- a/src/lib/dai.c +++ b/src/lib/dai.c @@ -243,7 +243,7 @@ static int sof_dai_type_to_zephyr(uint32_t type) } } -const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index) +const struct device *z_impl_dai_get_device(enum sof_ipc_dai_type type, uint32_t index) { struct dai_config cfg; int z_type; diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 9bdaa6453ad4..a3c4b643bcda 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -622,6 +622,8 @@ zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/audio/module_adapter/module/ge zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/fast-get.h) zephyr_syscall_header(include/rtos/alloc.h) zephyr_library_sources_ifdef(CONFIG_SOF_USERSPACE_INTERFACE_ALLOC syscall/alloc.c) +zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/dai-zephyr.h) +zephyr_library_sources_ifdef(CONFIG_USERSPACE syscall/dai.c) zephyr_library_link_libraries(SOF) target_link_libraries(SOF INTERFACE zephyr_interface) diff --git a/zephyr/syscall/dai.c b/zephyr/syscall/dai.c new file mode 100644 index 000000000000..2e09187e146b --- /dev/null +++ b/zephyr/syscall/dai.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include + +static inline const struct device *z_vrfy_dai_get_device(enum sof_ipc_dai_type type, + uint32_t index) +{ + const struct device *dev = z_impl_dai_get_device(type, index); + + if (dev && !k_object_is_valid(dev, K_OBJ_DRIVER_DAI)) + return NULL; + + return dev; +} +#include From bd296e4a76519cfac9994bb3a2c18b2ef7efd579 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Tue, 9 Jun 2026 10:06:32 +0200 Subject: [PATCH 042/303] audio: copier: avoid serializing uninitialized stream position to host copier_get_configuration() returns LLP/position data to the host over IPC4 for IPC4_COPIER_MODULE_CFG_PARAM_LLP_READING and _EXTENDED. It declared the source 'posn' on the stack without initialization and called comp_position() ignoring its return value. On Zephyr-native DAI builds dai_common_position() writes posn.comp_posn only after a successful dma_get_status(); on a DMA-status error it returns early, leaving comp_posn uninitialized. The unchecked return then let convert_u64_to_u32s() serialize uninitialized stack bytes into the host reply (information disclosure) and report a fabricated stream position. Signed-off-by: Adrian Bonislawski --- src/audio/copier/copier.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index 93e018408bf3..9d2e2938a210 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -360,7 +360,7 @@ static int copier_comp_trigger(struct comp_dev *dev, int cmd) { struct processing_module *mod = comp_mod(dev); struct copier_data *cd = module_get_private_data(mod); - struct sof_ipc_stream_posn posn; + struct sof_ipc_stream_posn posn = { 0 }; struct comp_dev *dai_copier; struct comp_buffer *buffer; uint32_t latency; @@ -930,8 +930,9 @@ __cold static int copier_get_configuration(struct processing_module *mod, struct copier_data *cd = module_get_private_data(mod); struct ipc4_llp_reading_extended llp_ext; struct comp_dev *dev = mod->dev; - struct sof_ipc_stream_posn posn; + struct sof_ipc_stream_posn posn = { 0 }; struct ipc4_llp_reading llp; + int ret; assert_can_be_cold(); @@ -961,7 +962,9 @@ __cold static int copier_get_configuration(struct processing_module *mod, } /* get llp from dai */ - comp_position(dev, &posn); + ret = comp_position(dev, &posn); + if (ret < 0) + return ret; convert_u64_to_u32s(posn.comp_posn, &llp.llp_l, &llp.llp_u); convert_u64_to_u32s(posn.wallclock, &llp.wclk_l, &llp.wclk_u); @@ -991,7 +994,9 @@ __cold static int copier_get_configuration(struct processing_module *mod, } /* get llp from dai */ - comp_position(dev, &posn); + ret = comp_position(dev, &posn); + if (ret < 0) + return ret; convert_u64_to_u32s(posn.comp_posn, &llp_ext.llp_reading.llp_l, &llp_ext.llp_reading.llp_u); From 3cf71d1956d53025c19f5be881065092bd67f2c7 Mon Sep 17 00:00:00 2001 From: Wojciech Jablonski Date: Thu, 11 Jun 2026 11:30:38 +0200 Subject: [PATCH 043/303] lib_manager: align auth result checks with newer API With newer authentication API of ROM_EXT, the authentication result is provided only after the last phase. This change aligns SOF FW to this behavior. The change is backward compatible with older API versions. Signed-off-by: Wojciech Jablonski --- src/library_manager/lib_manager.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/library_manager/lib_manager.c b/src/library_manager/lib_manager.c index e090a321a056..56cd616cd9f5 100644 --- a/src/library_manager/lib_manager.c +++ b/src/library_manager/lib_manager.c @@ -110,16 +110,17 @@ static int lib_manager_auth_proc(const void *buffer_data, size_t buffer_size, while (auth_api_busy(auth_ctx)) ; + if (phase != AUTH_PHASE_LAST) + return 0; + ret = auth_api_result(auth_ctx); + auth_api_cleanup(auth_ctx); if (ret != AUTH_IMAGE_TRUSTED) { tr_err(&lib_manager_tr, "Untrusted library!"); return -EACCES; } - if (phase == AUTH_PHASE_LAST) - auth_api_cleanup(auth_ctx); - return 0; } #endif /* CONFIG_LIBRARY_AUTH_SUPPORT */ From 29b4ae35b33c3225da0a29c7521e383f37fdf502 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Wed, 10 Jun 2026 12:18:12 +0200 Subject: [PATCH 044/303] intel: ace4: Disable custom PM policy There is no need to use a custom Power Management policy on platforms from the ACE family. Signed-off-by: Tomasz Leman --- app/boards/intel_adsp_ace40_nvl.conf | 1 - app/boards/intel_adsp_ace40_nvls.conf | 1 - 2 files changed, 2 deletions(-) diff --git a/app/boards/intel_adsp_ace40_nvl.conf b/app/boards/intel_adsp_ace40_nvl.conf index 331630a9e3bd..0472a7c00de1 100644 --- a/app/boards/intel_adsp_ace40_nvl.conf +++ b/app/boards/intel_adsp_ace40_nvl.conf @@ -53,7 +53,6 @@ CONFIG_SYS_CLOCK_TICKS_PER_SEC=12000 # Zephyr / power settings CONFIG_ADSP_IMR_CONTEXT_SAVE=y -CONFIG_PM_POLICY_CUSTOM=y CONFIG_PM_PREWAKEUP_CONV_MODE_CEIL=y CONFIG_SRAM_RETENTION_MODE=n CONFIG_PM_DEVICE_RUNTIME_ASYNC=n diff --git a/app/boards/intel_adsp_ace40_nvls.conf b/app/boards/intel_adsp_ace40_nvls.conf index 331630a9e3bd..0472a7c00de1 100644 --- a/app/boards/intel_adsp_ace40_nvls.conf +++ b/app/boards/intel_adsp_ace40_nvls.conf @@ -53,7 +53,6 @@ CONFIG_SYS_CLOCK_TICKS_PER_SEC=12000 # Zephyr / power settings CONFIG_ADSP_IMR_CONTEXT_SAVE=y -CONFIG_PM_POLICY_CUSTOM=y CONFIG_PM_PREWAKEUP_CONV_MODE_CEIL=y CONFIG_SRAM_RETENTION_MODE=n CONFIG_PM_DEVICE_RUNTIME_ASYNC=n From 7920b655c79846976e12257805c80983655553e0 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Tue, 9 Jun 2026 12:56:04 +0200 Subject: [PATCH 045/303] zephyr: pm_runtime: use the correct type for CPU power state count pm_state_cpu_get_all() returns uint8_t, so num_cpu_states must be uint8_t too. The explicit (int) cast on the loop init is only cosmetics, matching the Zephyr reference pm policy. Signed-off-by: Adrian Bonislawski --- zephyr/lib/pm_runtime.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zephyr/lib/pm_runtime.c b/zephyr/lib/pm_runtime.c index fcaa03cb7275..21b3e3ae3df5 100644 --- a/zephyr/lib/pm_runtime.c +++ b/zephyr/lib/pm_runtime.c @@ -21,12 +21,12 @@ DECLARE_TR_CTX(power_tr, SOF_UUID(power_uuid), LOG_LEVEL_INFO); #if defined(CONFIG_PM_POLICY_CUSTOM) const struct pm_state_info *pm_policy_next_state(uint8_t cpu, int32_t ticks) { - unsigned int num_cpu_states; + uint8_t num_cpu_states; const struct pm_state_info *cpu_states; num_cpu_states = pm_state_cpu_get_all(cpu, &cpu_states); - for (int i = num_cpu_states - 1; i >= 0; i--) { + for (int i = (int)num_cpu_states - 1; i >= 0; i--) { const struct pm_state_info *state = &cpu_states[i]; uint32_t min_residency, exit_latency; From 569e54faf781d5609ae3695acd2c8e187704ed67 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Thu, 11 Jun 2026 12:17:24 +0300 Subject: [PATCH 046/303] audio: tdfb: Improve robustness for invalid configuration Add validity checks against malformed configuration blobs and IPC control payloads: - Bound the blob length reported by comp_get_data_blob() to [sizeof(*cd->config), SOF_TDFB_MAX_SIZE] in both tdfb_prepare and the runtime new-blob path; store it in cd->config_size and require config->size to match in tdfb_init_coef. - Bump SOF_TDFB_MAX_SIZE 4096 -> 16384 to match the topology budget. - Tie SOF_TDFB_MAX_MICROPHONES to PLATFORM_MAX_CHANNELS so num_mic_locations cannot exceed the per-channel direction state arrays in tdfb_comp.h. - Reject num_angles == 0, angle_enum_mult == 0, negative or out-of-range filter_index, and negative input_channel_select[]. - Check cdata->num_elems vs. SOF_IPC_MAX_CHANNELS in the IPC3 GET handler. Signed-off-by: Seppo Ingalsuo --- src/audio/tdfb/tdfb.c | 51 +++++++++++++++++++++++---------- src/audio/tdfb/tdfb.h | 5 ++-- src/audio/tdfb/tdfb_comp.h | 1 + src/audio/tdfb/tdfb_direction.c | 2 +- src/audio/tdfb/tdfb_ipc3.c | 3 ++ 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/audio/tdfb/tdfb.c b/src/audio/tdfb/tdfb.c index 5df4563c3558..1cda385cae84 100644 --- a/src/audio/tdfb/tdfb.c +++ b/src/audio/tdfb/tdfb.c @@ -323,6 +323,11 @@ static int tdfb_init_coef(struct processing_module *mod, int source_nch, int i; /* Sanity checks */ + if (config->size != cd->config_size) { + comp_err(dev, "Incorrect configuration blob size"); + return -EINVAL; + } + if (config->num_output_channels > PLATFORM_MAX_CHANNELS || !config->num_output_channels) { comp_err(dev, "invalid num_output_channels %d", @@ -342,12 +347,21 @@ static int tdfb_init_coef(struct processing_module *mod, int source_nch, return -EINVAL; } - if (config->num_angles > SOF_TDFB_MAX_ANGLES) { + /* In SOF v1.6 - 1.8 based beamformer topologies the multiple angles, mic locations, + * and beam on/off switch were not defined. A most basic supported blob has num_angles + * equal to 1. Mic locations data is optional. + */ + if (config->num_angles == 0 || config->num_angles > SOF_TDFB_MAX_ANGLES) { comp_err(dev, "invalid num_angles %d", config->num_angles); return -EINVAL; } + if (!config->angle_enum_mult) { + comp_err(dev, "invalid angle_enum_mult"); + return -EINVAL; + } + if (config->beam_off_defined > 1) { comp_err(dev, "invalid beam_off_defined %d", config->beam_off_defined); @@ -360,15 +374,6 @@ static int tdfb_init_coef(struct processing_module *mod, int source_nch, return -EINVAL; } - /* In SOF v1.6 - 1.8 based beamformer topologies the multiple angles, mic locations, - * and beam on/off switch were not defined. Return error if such configuration is seen. - * A most basic blob has num_angles equals 1. Mic locations data is optional. - */ - if (config->num_angles == 0 && config->num_mic_locations == 0) { - comp_err(dev, "ABI version less than 3.19.1 is not supported."); - return -EINVAL; - } - /* Skip filter coefficients */ num_filters = config->num_filters * (config->num_angles + config->beam_off_defined); coefp = tdfb_filter_seek(config, num_filters); @@ -423,6 +428,12 @@ static int tdfb_init_coef(struct processing_module *mod, int source_nch, cd->filter_angles[min_delta_idx].azimuth, idx); } + if (idx < 0 || idx + (int)config->num_filters > num_filters) { + comp_err(dev, "invalid filter_index %d for angle %d", + idx, cd->filter_angles[min_delta_idx].azimuth); + return -EINVAL; + } + /* Seek to proper filter for requested angle or beam off configuration */ coefp = tdfb_filter_seek(config, idx); @@ -451,6 +462,11 @@ static int tdfb_init_coef(struct processing_module *mod, int source_nch, for (i = 0; i < config->num_filters; i++) { if (cd->input_channel_select[i] > max_ch) max_ch = cd->input_channel_select[i]; + + if (cd->input_channel_select[i] < 0) { + comp_err(dev, "invalid channel select for filter %d", i); + return -EINVAL; + } } /* The stream must contain at least the number of channels that is @@ -641,7 +657,12 @@ static int tdfb_process(struct processing_module *mod, /* Check for changed configuration */ if (comp_is_new_data_blob_available(cd->model_handler)) { - cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (!cd->config || cd->config_size < sizeof(*cd->config) || + cd->config_size > SOF_TDFB_MAX_SIZE) { + comp_err(dev, "invalid configuration blob, size %zu", cd->config_size); + return -EINVAL; + } ret = tdfb_setup(mod, audio_stream_get_channels(source), audio_stream_get_channels(sink), audio_stream_get_frm_fmt(source)); @@ -705,7 +726,6 @@ static int tdfb_prepare(struct processing_module *mod, struct comp_buffer *sourceb, *sinkb; struct comp_dev *dev = mod->dev; enum sof_ipc_frame frame_fmt; - size_t data_size; int source_channels; int sink_channels; int rate; @@ -735,9 +755,10 @@ static int tdfb_prepare(struct processing_module *mod, rate = audio_stream_get_rate(&sourceb->stream); /* Initialize filter */ - cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (!cd->config || !data_size) { - comp_err(dev, "Missing a configuration blob."); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (!cd->config || cd->config_size < sizeof(*cd->config) || + cd->config_size > SOF_TDFB_MAX_SIZE) { + comp_err(dev, "invalid configuration blob, size %zu", cd->config_size); ret = -EINVAL; goto out; } diff --git a/src/audio/tdfb/tdfb.h b/src/audio/tdfb/tdfb.h index d5a09776301f..c6fd1ff0f97d 100644 --- a/src/audio/tdfb/tdfb.h +++ b/src/audio/tdfb/tdfb.h @@ -8,16 +8,17 @@ #ifndef __USER_TDFB_H__ #define __USER_TDFB_H__ +#include #include #define SOF_TDFB_NUM_INPUT_PINS 1 /* One source */ #define SOF_TDFB_NUM_OUTPUT_PINS 1 /* One sink */ -#define SOF_TDFB_MAX_SIZE 4096 /* Max size for coef data in bytes */ +#define SOF_TDFB_MAX_SIZE 16384 /* Max size for coef data in bytes */ #define SOF_TDFB_FIR_MAX_LENGTH 256 /* Max length for individual filter */ #define SOF_TDFB_FIR_MAX_COUNT 16 /* A blob can define max 16 FIR EQs */ #define SOF_TDFB_MAX_STREAMS 8 /* Support 1..8 sinks */ #define SOF_TDFB_MAX_ANGLES 360 /* Up to 1 degree precision for 360 degrees coverage */ -#define SOF_TDFB_MAX_MICROPHONES 16 /* Up to 16 microphone locations */ +#define SOF_TDFB_MAX_MICROPHONES PLATFORM_MAX_CHANNELS /* Bounded by direction state arrays */ /* The driver assigns running numbers for control index. If there's single control of * type switch, enum, binary they all have index 0. diff --git a/src/audio/tdfb/tdfb_comp.h b/src/audio/tdfb/tdfb_comp.h index ef33f28fa612..9e0727474f4a 100644 --- a/src/audio/tdfb/tdfb_comp.h +++ b/src/audio/tdfb/tdfb_comp.h @@ -88,6 +88,7 @@ struct tdfb_comp_data { int16_t *output_stream_mix; /**< for each FIR define stream */ int16_t az_value; /**< beam steer azimuth as in control enum */ int16_t az_value_estimate; /**< beam steer azimuth as in control enum */ + size_t config_size; /**< size of the configuration blob */ size_t fir_delay_size; /**< allocated size */ unsigned int max_frames; /**< max frames to process */ bool direction_updates:1; /**< set true if direction angle control is updated */ diff --git a/src/audio/tdfb/tdfb_direction.c b/src/audio/tdfb/tdfb_direction.c index 71b544003ee7..bd435fa2ebd3 100644 --- a/src/audio/tdfb/tdfb_direction.c +++ b/src/audio/tdfb/tdfb_direction.c @@ -386,7 +386,7 @@ static int16_t distance_from_source(struct tdfb_comp_data *cd, int mic_n, static void theoretical_time_differences(struct tdfb_comp_data *cd, int16_t az) { - int16_t d[PLATFORM_MAX_CHANNELS]; + int16_t d[SOF_TDFB_MAX_MICROPHONES]; int16_t src_x; int16_t src_y; int16_t sin_az; diff --git a/src/audio/tdfb/tdfb_ipc3.c b/src/audio/tdfb/tdfb_ipc3.c index 544acbe9a41f..1ebdb9641ccf 100644 --- a/src/audio/tdfb/tdfb_ipc3.c +++ b/src/audio/tdfb/tdfb_ipc3.c @@ -112,6 +112,9 @@ static int tdfb_cmd_get_value(struct processing_module *mod, struct sof_ipc_ctrl { struct tdfb_comp_data *cd = module_get_private_data(mod); + if (cdata->num_elems == 0 || cdata->num_elems > SOF_IPC_MAX_CHANNELS) + return -EINVAL; + switch (cdata->cmd) { case SOF_CTRL_CMD_ENUM: comp_dbg(mod->dev, "SOF_CTRL_CMD_ENUM index=%d", From 2575428730817d79753aa9aab3cd9cdb40ae012b Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 11 Jun 2026 17:54:11 +0200 Subject: [PATCH 047/303] audio: smart_amp: validate DSM parameter id before db write maxim_dsm_set_param() masks the host-supplied parameter id with DSM_CH_MASK() and uses the result directly as an index into the model database db[], which is sized for hspk->param.max_param parameters. A larger id leads to an out-of-bounds write. Reject id values that are not below max_param before indexing db[]. Signed-off-by: Tomasz Leman --- src/audio/smart_amp/smart_amp_maxim_dsm.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/audio/smart_amp/smart_amp_maxim_dsm.c b/src/audio/smart_amp/smart_amp_maxim_dsm.c index b9289861aa8d..c83a971d8175 100644 --- a/src/audio/smart_amp/smart_amp_maxim_dsm.c +++ b/src/audio/smart_amp/smart_amp_maxim_dsm.c @@ -385,6 +385,13 @@ static int maxim_dsm_set_param(struct smart_amp_mod_struct_t *hspk, id = DSM_CH_MASK(param->param.id); ch = (param->param.id & DSM_CH1_BITMASK) ? 0 : 1; + /* id indexes the model database; reject values past its size */ + if (id >= hspk->param.max_param) { + comp_err(dev, "[DSM] invalid param id:%x max:%d", + id, hspk->param.max_param); + return -EINVAL; + } + /* 2nd channel has (hspk->param.max_param * DSM_PARAM_MAX) sized offset */ db[(id + ch * hspk->param.max_param) * DSM_PARAM_MAX + DSM_PARAM_VALUE] = param->param.value; From bed995fbbde03477f1bbfa43c454e0b65edc823c Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 11 Jun 2026 17:56:34 +0200 Subject: [PATCH 048/303] audio: base_fw: validate dma control payload length before subtract basefw_dma_control() computes data_size = data_offset - sizeof(struct ipc4_dma_control) where data_offset is the host-supplied payload length. When data_offset is smaller than the header the unsigned subtraction wraps to a huge value that passes the length check and is forwarded as the payload size, leading to an out-of-bounds read. Reject data_offset values smaller than the fixed header before the subtraction. Signed-off-by: Tomasz Leman --- src/audio/base_fw.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/audio/base_fw.c b/src/audio/base_fw.c index b86db469765a..db2acea19125 100644 --- a/src/audio/base_fw.c +++ b/src/audio/base_fw.c @@ -770,6 +770,13 @@ __cold static int basefw_dma_control(bool first_block, bool last_block, uint32_t } dma_control = (struct ipc4_dma_control *)data; + + /* data_offset must cover the fixed header before computing the payload size */ + if (data_offset < sizeof(struct ipc4_dma_control)) { + tr_err(&ipc_tr, "DMA Control message too short: %u", data_offset); + return IPC4_ERROR_INVALID_PARAM; + } + data_size = data_offset - sizeof(struct ipc4_dma_control); if (data_size < (dma_control->config_length * sizeof(uint32_t))) { From 34a8d7c223de35175ca5cd4178101fd6c8366947 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Fri, 12 Jun 2026 08:09:14 +0200 Subject: [PATCH 049/303] up_down_mixer: reject unsupported channel configurations init_mix() left mix_routine NULL for combos unhandled by select_mix_out_*() but accepted by set_downmix_coefficients() add NULL-check before calling set_downmix_coefficients() Signed-off-by: Adrian Bonislawski --- src/audio/up_down_mixer/up_down_mixer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/audio/up_down_mixer/up_down_mixer.c b/src/audio/up_down_mixer/up_down_mixer.c index a5843026710a..4a9a644434e7 100644 --- a/src/audio/up_down_mixer/up_down_mixer.c +++ b/src/audio/up_down_mixer/up_down_mixer.c @@ -307,6 +307,13 @@ static int init_mix(struct processing_module *mod, return -EINVAL; } + /* select_mix_out_*() return NULL for unsupported in/out combos */ + if (!cd->mix_routine) { + comp_err(dev, "unsupported channel configuration (in=%d out=%d)", + format->ch_cfg, out_channel_config); + return -EINVAL; + } + /* Update audio format. */ cd->out_fmt[0].valid_bit_depth = IPC4_DEPTH_24BIT; cd->out_fmt[0].depth = IPC4_DEPTH_32BIT; From 930bd109f34beab131728e6c9e6e0997033676c4 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Wed, 10 Jun 2026 12:20:17 +0200 Subject: [PATCH 050/303] audio: copier: reject ALH multi-gateway config with zero count copier_alh_assign_dai_index() rejected only count > MAX and count < 0, letting count == 0 pass. A zero count creates a SOF_COMP_DAI copier with endpoint_num == 0 (the dai-creation loop never runs), which later makes copier_position() return -EINVAL before writing posn. Reject count <= 0. Signed-off-by: Adrian Bonislawski --- src/audio/copier/copier_dai.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/copier/copier_dai.c b/src/audio/copier/copier_dai.c index dfd2590c7108..bbd387b88ab8 100644 --- a/src/audio/copier/copier_dai.c +++ b/src/audio/copier/copier_dai.c @@ -108,7 +108,7 @@ static int copier_alh_assign_dai_index(struct comp_dev *dev, } dai_num = alh_blob->alh_cfg.count; - if (dai_num > IPC4_ALH_MAX_NUMBER_OF_GTW || dai_num < 0) { + if (dai_num > IPC4_ALH_MAX_NUMBER_OF_GTW || dai_num <= 0) { comp_err(mod->dev, "Invalid dai_count: %d", dai_num); return -EINVAL; } @@ -138,7 +138,7 @@ static int copier_alh_assign_dai_index(struct comp_dev *dev, } dai_num = alh_blob->alh_cfg.count; - if (dai_num > IPC4_ALH_MAX_NUMBER_OF_GTW || dai_num < 0) { + if (dai_num > IPC4_ALH_MAX_NUMBER_OF_GTW || dai_num <= 0) { comp_err(mod->dev, "Invalid dai_count: %d", dai_num); return -EINVAL; } From 98e2fc67da1aa69ca56e4f4758aed089f6184ce6 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 11 Jun 2026 12:30:29 +0300 Subject: [PATCH 051/303] zephyr: syscall: sof_dma: handle overflow in deep_copy_dma_blk_cfg_list() deep_copy_dma_blk_cfg_list() is used to verify the syscall arguments. Fix an issue with possible overflow when calculating the alloc size for DMA blocks. Signed-off-by: Kai Vehmanen --- zephyr/syscall/sof_dma.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index f12a29aa1efb..ef8ec635479a 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef CONFIG_SOF_USERSPACE_INTERFACE_DMA @@ -111,12 +112,22 @@ static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_con { struct dma_block_config *kern_cfg; struct dma_block_config *kern_prev = NULL, *kern_next, *user_next; + size_t alloc_size; int i = 0; if (!cfg->block_count) return NULL; - kern_cfg = rmalloc(0, sizeof(*kern_cfg) * cfg->block_count); + /* + * block_count is user-controlled, so compute the allocation size + * with an overflow check. Without it, a large block_count would + * wrap the product on 32-bit size_t, yield an undersized buffer, + * and let the copy loop below overflow the kernel heap. + */ + if (size_mul_overflow(sizeof(*kern_cfg), cfg->block_count, &alloc_size)) + return NULL; + + kern_cfg = rmalloc(0, alloc_size); if (!kern_cfg) return NULL; From 88732a1e4ea25451a28fb378f0e2b3656ccb6b4e Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 11 Jun 2026 13:45:22 +0300 Subject: [PATCH 052/303] zephyr: syscall: sof_dma: initialize all fields of dma_config z_vrfy_sof_dma_config() only passes allowed fields of dma_config struct to kernel. Add initialization of the stack dma_config object kern_cfg to ensure the skipped fields are set to a known state. Signed-off-by: Kai Vehmanen --- zephyr/syscall/sof_dma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index ef8ec635479a..9c5d635cb394 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -190,7 +190,8 @@ static inline int z_vrfy_sof_dma_config(struct sof_dma *dma, uint32_t channel, struct dma_config *config) { struct dma_block_config *blk_cfgs; - struct dma_config kern_cfg, user_cfg; + struct dma_config kern_cfg = { 0 }; + struct dma_config user_cfg; int ret; K_OOPS(!sof_dma_is_valid(dma)); From c72905ef0dbaa87c95b66c208a86e0de3b677bd4 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 11 Jun 2026 14:06:22 +0300 Subject: [PATCH 053/303] zephyr: syscall: sof_dma: fix block count validation in dma blk copy deep_copy_dma_blk_cfg_list() does not check that number of entries in the linked list of DMA blocks matches cfg->block_count. This could be used to make kernel read from unvalidated user memory. Fix the issue by limiting list traversal to cfg->block_count. Signed-off-by: Kai Vehmanen --- zephyr/syscall/sof_dma.c | 52 +++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index 9c5d635cb394..05d6df377c95 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -111,9 +111,9 @@ static inline void z_vrfy_sof_dma_release_channel(struct sof_dma *dma, static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_config *cfg) { struct dma_block_config *kern_cfg; - struct dma_block_config *kern_prev = NULL, *kern_next, *user_next; + struct dma_block_config *kern_next, *user_next; size_t alloc_size; - int i = 0; + uint32_t i; if (!cfg->block_count) return NULL; @@ -131,17 +131,16 @@ static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_con if (!kern_cfg) return NULL; - for (user_next = cfg->head_block, kern_next = kern_cfg; - user_next; - user_next = user_next->next_block, kern_next++, i++) { - if (i == cfg->block_count) { - /* last block can point to first one */ - if (user_next != cfg->head_block) - goto err; - - kern_prev->next_block = kern_cfg; - break; - } + user_next = cfg->head_block; + for (i = 0, kern_next = kern_cfg; i < cfg->block_count; i++, kern_next++) { + /* + * The user list must contain exactly block_count entries. + * A list that terminates early (NULL before the count is + * reached) is rejected so the kernel copy and block_count + * stay consistent and the driver never walks past the copy. + */ + if (!user_next) + goto err; if (k_usermode_from_copy(kern_next, user_next, sizeof(*kern_next))) goto err; @@ -169,10 +168,29 @@ static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_con goto err; } - if (kern_prev) - kern_prev->next_block = kern_next; - - kern_prev = kern_next; + /* + * kern_next->next_block now holds the untrusted user-space + * pointer copied above. Never let the kernel walk it: relink + * every block to the kernel copy so the list can never point + * outside the kern_cfg array. + */ + user_next = kern_next->next_block; + + if (i + 1 < cfg->block_count) { + /* link to the next kernel block */ + kern_next->next_block = kern_next + 1; + } else { + /* + * Last block: the user list must end here, either + * NULL-terminated or cyclic back to the first block. + */ + if (user_next == cfg->head_block) + kern_next->next_block = kern_cfg; + else if (!user_next) + kern_next->next_block = NULL; + else + goto err; + } } /* set transfer list to point to first kernel transfer config object */ From 0d9b5ec31f4f4b7fb5a23f3a9f3d1ec96b1dd214 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 11 Jun 2026 14:36:00 +0300 Subject: [PATCH 054/303] ipc: ipc4: harden ipc_comp_disconnect() against invalid sink Check the provided sink actually is connected to the buffer, before proceeding to free the buffer. This protects against an invalid IPC sent by the host. Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/helper.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index c13804c3e46e..8065e24e70f6 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -920,6 +920,18 @@ __cold int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) if (!buffer) return IPC4_INVALID_RESOURCE_ID; + /* + * The buffer was located on the source's consumer list, but the sink was + * resolved solely from the host-supplied dst_module_id/dst_instance_id. + * Make sure the buffer is actually connected to that sink, otherwise an + * incorrect dst would leave the real sink bound to a buffer we are about + * to free, while unbinding an unrelated component instead. + */ + if (comp_buffer_get_sink_component(buffer) != sink) { + tr_err(&ipc_tr, "buffer %#x is not connected to sink %#x", buffer_id, sink_id); + return IPC4_INVALID_RESOURCE_ID; + } + /* * Disconnect and unbind buffer from source/sink components and continue to free the buffer * even in case of errors. Block LL processing during disconnect and unbinding to prevent From ceab6e41ef9d300483e612a01fc60dc9f30cd249 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 11 Jun 2026 17:55:15 +0200 Subject: [PATCH 055/303] audio: aria: reject zero sample group size in init aria_init() computes sgs = (depth >> 3) * channels_count and then divides ibs by sgs. A host-supplied base config with channels_count of 0 or depth below 8 makes sgs zero, causing a divide-by-zero. Validate channels_count and depth before the division. Signed-off-by: Tomasz Leman --- src/audio/aria/aria.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/audio/aria/aria.c b/src/audio/aria/aria.c index dc265cfac240..ef7285d2f564 100644 --- a/src/audio/aria/aria.c +++ b/src/audio/aria/aria.c @@ -123,6 +123,13 @@ static int aria_init(struct processing_module *mod) list_init(&dev->bsource_list); list_init(&dev->bsink_list); + /* sample group size is used as a divisor below, reject configs that make it zero */ + if (!base_cfg->audio_fmt.channels_count || base_cfg->audio_fmt.depth < 8) { + comp_err(dev, "invalid channels:%u depth:%d", + base_cfg->audio_fmt.channels_count, base_cfg->audio_fmt.depth); + return -EINVAL; + } + cd = mod_zalloc(mod, sizeof(*cd)); if (!cd) { return -ENOMEM; From 462cfb586912ea8b2079baed7e98d8854a9b5201 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Fri, 12 Jun 2026 11:55:59 +0300 Subject: [PATCH 056/303] audio: eq_fir: Improve robustness for invalid configuration Harden the EQ FIR setup path against malformed IPC configuration blobs. The blob length returned by comp_get_data_blob() is now stored and checked against the expected range every time a new blob is taken, and the blob's self-declared size is cross-checked against it before use. The per-response walk that previously trusted the FIR length field from the blob now bounds the header and coefficient data against the blob, and rejects lengths that are non-positive, exceed SOF_FIR_MAX_LENGTH or are not a multiple of four. The IPC validator applies the same blob length bounds before calling into eq_fir_init_coef(), so an oversized or truncated blob is rejected up front rather than at prepare or process time. Also a blob that has an odd channels_in_config is rejected to ensure the coefficients are aligned per current ASSUME_ALIGNED() constraint. Signed-off-by: Seppo Ingalsuo --- src/audio/eq_fir/eq_fir.c | 84 ++++++++++++++++++++++++++++++++++----- src/audio/eq_fir/eq_fir.h | 1 + 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/audio/eq_fir/eq_fir.c b/src/audio/eq_fir/eq_fir.c index f08b25315616..6c4c3d630562 100644 --- a/src/audio/eq_fir/eq_fir.c +++ b/src/audio/eq_fir/eq_fir.c @@ -72,12 +72,13 @@ static void eq_fir_free_delaylines(struct processing_module *mod) } static int eq_fir_init_coef(struct comp_dev *dev, struct sof_eq_fir_config *config, - struct fir_state_32x16 *fir, int nch) + size_t config_size, struct fir_state_32x16 *fir, int nch) { struct sof_fir_coef_data *lookup[SOF_EQ_FIR_MAX_RESPONSES]; struct sof_fir_coef_data *eq; int16_t *assign_response; int16_t *coef_data; + size_t coef_words_max; size_t size_sum = 0; int resp = 0; int i; @@ -101,27 +102,70 @@ static int eq_fir_init_coef(struct comp_dev *dev, struct sof_eq_fir_config *conf config->number_of_responses, config->channels_in_config, nch); /* Sanity checks */ + if (config->size != config_size) { + comp_err(dev, "Incorrect configuration blob size"); + return -EINVAL; + } + if (nch > PLATFORM_MAX_CHANNELS || config->channels_in_config > PLATFORM_MAX_CHANNELS || !config->channels_in_config) { comp_err(dev, "invalid channels count"); return -EINVAL; } + /* channels_in_config indexes into a int16_t array. An odd count would + * leave the coefficient area at a 2-byte alignment, breaking the + * 4-byte aligned int32_t loads in the optimized FIR kernels. + */ + if (config->channels_in_config & 0x1) { + comp_err(dev, "channels_in_config %u must be even", + config->channels_in_config); + return -EINVAL; + } if (config->number_of_responses > SOF_EQ_FIR_MAX_RESPONSES) { comp_err(dev, "# of resp exceeds max"); return -EINVAL; } + /* Compute the size of the coefficient area in int16_t words from the + * blob's self-declared size. The blob layout is: + * sizeof(*config) header bytes + * channels_in_config int16_t assign_response[] + * coefficient data[] + */ + if (config->size < sizeof(*config) || + config->size - sizeof(*config) < + (size_t)config->channels_in_config * sizeof(int16_t)) { + comp_err(dev, "config size %u too small", config->size); + return -EINVAL; + } + coef_words_max = (config->size - sizeof(*config)) / sizeof(int16_t) - + config->channels_in_config; + /* Collect index of response start positions in all_coefficients[] */ j = 0; assign_response = ASSUME_ALIGNED(&config->data[0], 4); - coef_data = ASSUME_ALIGNED(&config->data[config->channels_in_config], - 4); + coef_data = ASSUME_ALIGNED(&config->data[config->channels_in_config], 4); for (i = 0; i < SOF_EQ_FIR_MAX_RESPONSES; i++) { if (i < config->number_of_responses) { + /* Header must fit before reading length */ + if (j + SOF_FIR_COEF_NHEADER > coef_words_max) { + comp_err(dev, "response %d header out of bounds", i); + return -EINVAL; + } eq = (struct sof_fir_coef_data *)&coef_data[j]; + /* Bound length so it is valid and the coefficient data + * stays within the blob. + */ + if (eq->length <= 0 || eq->length > SOF_FIR_MAX_LENGTH || + (eq->length & 0x3) || + j + SOF_FIR_COEF_NHEADER + eq->length > coef_words_max) { + comp_err(dev, "response %d length %d out of bounds", + i, eq->length); + return -EINVAL; + } lookup[i] = eq; - j += SOF_FIR_COEF_NHEADER + coef_data[j]; + j += SOF_FIR_COEF_NHEADER + eq->length; } else { lookup[i] = NULL; } @@ -209,7 +253,7 @@ static int eq_fir_setup(struct processing_module *mod, int nch) cd->nch = nch; /* Set coefficients for each channel EQ from coefficient blob */ - delay_size = eq_fir_init_coef(dev, cd->config, cd->fir, nch); + delay_size = eq_fir_init_coef(dev, cd->config, cd->config_size, cd->fir, nch); if (delay_size < 0) return delay_size; /* Contains error code */ @@ -234,9 +278,25 @@ static int eq_fir_setup(struct processing_module *mod, int nch) return 0; } +static int eq_fir_check_blob_size(struct comp_dev *dev, size_t size) +{ + if (size < sizeof(struct sof_eq_fir_config) || size > SOF_EQ_FIR_MAX_SIZE) { + comp_err(dev, "invalid configuration blob, size %zu", size); + return -EINVAL; + } + + return 0; +} + static int eq_fir_validator(struct comp_dev *dev, void *new_data, uint32_t new_data_size) { - return eq_fir_init_coef(dev, new_data, NULL, -1); + int ret; + + ret = eq_fir_check_blob_size(dev, new_data_size); + if (ret < 0) + return ret; + + return eq_fir_init_coef(dev, new_data, new_data_size, NULL, -1); } /* @@ -332,7 +392,9 @@ static int eq_fir_process(struct processing_module *mod, /* Check for changed configuration */ if (comp_is_new_data_blob_available(cd->model_handler)) { - cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (!cd->config || eq_fir_check_blob_size(mod->dev, cd->config_size) < 0) + return -EINVAL; ret = eq_fir_setup(mod, audio_stream_get_channels(source)); if (ret < 0) { comp_err(mod->dev, "failed FIR setup"); @@ -384,7 +446,6 @@ static int eq_fir_prepare(struct processing_module *mod, int channels; enum sof_ipc_frame frame_fmt; int ret = 0; - size_t data_size; comp_dbg(dev, "entry"); @@ -407,8 +468,11 @@ static int eq_fir_prepare(struct processing_module *mod, frame_fmt = audio_stream_get_frm_fmt(&sourceb->stream); cd->eq_fir_func = eq_fir_passthrough; - cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (cd->config && data_size > 0) { + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (cd->config) { + if (eq_fir_check_blob_size(dev, cd->config_size) < 0) + return -EINVAL; + ret = eq_fir_setup(mod, channels); if (ret < 0) comp_err(dev, "eq_fir_setup failed."); diff --git a/src/audio/eq_fir/eq_fir.h b/src/audio/eq_fir/eq_fir.h index 921d39939d69..5af74dc5674a 100644 --- a/src/audio/eq_fir/eq_fir.h +++ b/src/audio/eq_fir/eq_fir.h @@ -36,6 +36,7 @@ struct comp_data { struct comp_data_blob_handler *model_handler; struct sof_eq_fir_config *config; int32_t *fir_delay; /**< pointer to allocated RAM */ + size_t config_size; /**< configuration size */ size_t fir_delay_size; /**< allocated size */ void (*eq_fir_func)(struct fir_state_32x16 fir[], struct input_stream_buffer *bsource, From 7b5b5646fd0d85b619a4acf7341d03c3d8b4033b Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Wed, 10 Jun 2026 16:02:00 +0200 Subject: [PATCH 057/303] ipc4: harden IPC4 pipeline triggering logic Using IPC4_MOD_ID() is not the best way to check if IPC4 is enabled. For module ID == 0, IPC4_MOD_ID() returns 0 for both IPC3 and IPC4. Module ID 0 is a valid IPC4 BASEFW ID. Since BASEFW is never added to a pipeline, this change doesn't fix any real problem. However, it's just more appropriate and safer to use IS_ENABLED(CONFIG_IPC_MAJOR_4): if module ID data becomes corrupted (zeroed) at runtime, this shouldn't make debugging even harder by causing unexpected pipeline behavior. Signed-off-by: Serhiy Katsyuba --- src/audio/pipeline/pipeline-graph.c | 2 +- src/audio/pipeline/pipeline-params.c | 2 +- src/audio/pipeline/pipeline-schedule.c | 2 +- src/audio/pipeline/pipeline-stream.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 2a679c2b57ef..8494f17998bd 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -369,7 +369,7 @@ static int pipeline_comp_reset(struct comp_dev *current, * scheduled together, except for IPC4, where each pipeline receives * commands from the host separately */ - if (!is_single_ppl && IPC4_MOD_ID(current->ipc_config.id)) + if (!is_single_ppl && IS_ENABLED(CONFIG_IPC_MAJOR_4)) return 0; /* Propagate reset across pipelines only in the same direction diff --git a/src/audio/pipeline/pipeline-params.c b/src/audio/pipeline/pipeline-params.c index 16273e0897d4..ed32fc9e3b02 100644 --- a/src/audio/pipeline/pipeline-params.c +++ b/src/audio/pipeline/pipeline-params.c @@ -267,7 +267,7 @@ static int pipeline_comp_prepare(struct comp_dev *current, if (!comp_is_single_pipeline(current, ppl_data->start)) { /* ipc4 module is only prepared in its parent pipeline */ - if (IPC4_MOD_ID(current->ipc_config.id)) + if (IS_ENABLED(CONFIG_IPC_MAJOR_4)) return 0; /* Propagate prepare only to pipelines in the same direction */ diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index 45fd1eed639c..4d0566aeff7e 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -140,7 +140,7 @@ static enum task_state pipeline_task_cmd(struct pipeline *p, err = SOF_TASK_STATE_RESCHEDULE; } else if (p->status == COMP_STATE_PAUSED) { /* reset the pipeline components for IPC4 after the STOP trigger */ - if (cmd == COMP_TRIGGER_STOP && IPC4_MOD_ID(host->ipc_config.id)) { + if (cmd == COMP_TRIGGER_STOP && IS_ENABLED(CONFIG_IPC_MAJOR_4)) { err = pipeline_reset(host->pipeline, host); if (err < 0) reply->error = err; diff --git a/src/audio/pipeline/pipeline-stream.c b/src/audio/pipeline/pipeline-stream.c index 8bfdfc182912..e4a166554fa2 100644 --- a/src/audio/pipeline/pipeline-stream.c +++ b/src/audio/pipeline/pipeline-stream.c @@ -229,7 +229,7 @@ static int pipeline_comp_list(struct comp_dev *current, * component and we aren't using IPC4. With IPC4 each pipeline receives * commands separately so we don't need to trigger them together */ - if (!is_single_ppl && (!is_same_sched || IPC4_MOD_ID(current->ipc_config.id))) { + if (!is_single_ppl && (!is_same_sched || IS_ENABLED(CONFIG_IPC_MAJOR_4))) { pipe_dbg(current->pipeline, "current is from another pipeline"); return 0; From 202c7f603d4be3d113671e88f7152d6606ec3e03 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Wed, 10 Jun 2026 16:44:32 +0200 Subject: [PATCH 058/303] ipc4: assert IPC4 pipeline triggering logic With IPC4, each pipeline is triggered separately. Exactly 1 pipeline is expected in the pipelines list in pipeline_schedule_triggered(). Unfortunately, we still have considerable complex IPC3 pipeline triggering code being used with IPC4. This assertion ensures that the code works correctly for the IPC4 case. Signed-off-by: Serhiy Katsyuba --- src/audio/pipeline/pipeline-schedule.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index 4d0566aeff7e..cb4ec8fd3c62 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -284,6 +284,16 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, struct pipeline *p; uint32_t flags; +#ifdef CONFIG_IPC_MAJOR_4 + /* + * With IPC4, each pipeline is triggered separately. Exactly 1 pipeline + * is expected in the pipelines list (it's unclear whether an empty list + * should be tolerated). + */ + assert(list_is_empty(&ctx->pipelines) || + list_item_is_last(ctx->pipelines.next, &ctx->pipelines)); +#endif + /* * Interrupts have to be disabled while adding tasks to or removing them * from the scheduler list. Without that scheduling can begin From 3e72906549c3cb2c6c48623ce436de51bd9a2d73 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:38:26 +0100 Subject: [PATCH 059/303] drc: hifi4: 8-byte align detector average input buffer Force 8-byte alignment on abs_input_array in drc_update_detector_average(). The HiFi4 64-bit AE load/store intrinsics require 8-byte alignment; without it the stack buffer could be 4-byte aligned and trigger misaligned accesses. Signed-off-by: Liam Girdwood --- src/audio/drc/drc_hifi4.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/drc/drc_hifi4.c b/src/audio/drc/drc_hifi4.c index b2252058e885..a4c32c0deb15 100644 --- a/src/audio/drc/drc_hifi4.c +++ b/src/audio/drc/drc_hifi4.c @@ -116,7 +116,7 @@ void drc_update_detector_average(struct drc_state *state, int nch) { ae_f32 detector_average = state->detector_average; /* Q2.30 */ - ae_int32 abs_input_array[DRC_DIVISION_FRAMES]; /* Q1.31 */ + ae_int32 abs_input_array[DRC_DIVISION_FRAMES] __attribute__((aligned(8))); /* Q1.31 */ ae_int32 *abs_input_array_p; int div_start, i, ch; ae_int16 *sample16_p; /* for s16 format case */ From c9be8cb7f3c35fa312dc2ed777167d2df74cf9d1 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Sat, 13 Jun 2026 14:24:07 +0100 Subject: [PATCH 060/303] up_down_mixer: hifi3: 8-byte align downmix coefficient array downmix16bit_4ch_mono() loads its coeffs[] array with AE_L16X4_X, an aligned 64-bit HiFi load that requires 8-byte alignment. The uint16_t stack array is only 2-byte aligned, so the compiler may place it on a 4-byte boundary and trigger a misaligned access. Force 8-byte alignment to match the intrinsic's requirement. Signed-off-by: Liam Girdwood --- src/audio/up_down_mixer/up_down_mixer_hifi3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/up_down_mixer/up_down_mixer_hifi3.c b/src/audio/up_down_mixer/up_down_mixer_hifi3.c index 259cf5128b9f..87540d02e8e3 100644 --- a/src/audio/up_down_mixer/up_down_mixer_hifi3.c +++ b/src/audio/up_down_mixer/up_down_mixer_hifi3.c @@ -1247,7 +1247,7 @@ void downmix16bit_4ch_mono(struct up_down_mixer_data *cd, const uint8_t * const uint32_t idx3 = get_channel_index(cd->in_channel_map, 2); uint32_t idx4 = get_channel_index(cd->in_channel_map, 3); - uint16_t coeffs[4] = {cd->downmix_coefficients[idx1], + uint16_t coeffs[4] __attribute__((aligned(8))) = {cd->downmix_coefficients[idx1], cd->downmix_coefficients[idx2], cd->downmix_coefficients[idx3], cd->downmix_coefficients[idx4] From 3e76c5e8cb07eca6c29cd8d3c7366ab5874ed6dc Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Mon, 1 Jun 2026 14:13:14 +0200 Subject: [PATCH 061/303] ipc3: helper: clear component pipeline pointers before freeing pipeline When ipc_pipeline_free() frees a pipeline, component devices that were connected to it retain stale cd->pipeline pointers. If an IPC (e.g. stream position request) later dereferences that pointer, it triggers a use-after-free. Fix this by iterating all components in the IPC comp_list and setting cd->pipeline = NULL for any component whose pipeline matches the one being freed. This makes the existing NULL checks in handler.c effective and prevents the dangling pointer dereference. Found by fuzzing with AddressSanitizer enabled. Signed-off-by: Tomasz Leman --- src/ipc/ipc3/helper.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ipc/ipc3/helper.c b/src/ipc/ipc3/helper.c index 4d87f042dd1d..149ff6008a1a 100644 --- a/src/ipc/ipc3/helper.c +++ b/src/ipc/ipc3/helper.c @@ -439,6 +439,8 @@ int ipc_pipeline_new(struct ipc *ipc, ipc_pipe_new *_pipe_desc) int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) { struct ipc_comp_dev *ipc_pipe; + struct ipc_comp_dev *icd; + struct list_item *clist; int ret; /* check whether pipeline exists */ @@ -457,6 +459,19 @@ int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) if (!cpu_is_me(ipc_pipe->core)) return ipc_process_on_core(ipc_pipe->core, false); + /* Clear stale cd->pipeline pointers on all components and buffers + * that still belong to this pipeline. A well-behaved host driver + * frees components before freeing the pipeline, but if it does not + * (or in fuzz/error paths) the dangling pointer would be a + * use-after-free on any subsequent IPC referencing that component. + */ + list_for_item(clist, &ipc->comp_list) { + icd = container_of(clist, struct ipc_comp_dev, list); + if (icd->type == COMP_TYPE_COMPONENT && + icd->cd && icd->cd->pipeline == ipc_pipe->pipeline) + icd->cd->pipeline = NULL; + } + /* free buffer and remove from list */ ret = pipeline_free(ipc_pipe->pipeline); if (ret < 0) { From 391dd77d8e17b6695264be503c9f9b87180f79b9 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 11 Jun 2026 15:20:28 +0300 Subject: [PATCH 062/303] ipc4: large_config: fix data_off_size underflow on init-only block ipc4_set_vendor_config_module_instance() only validated data_off_size in the bursted-config path (init_block && final_block). The else path with init_block == 1 && final_block == 0 unconditionally executed: data += sizeof(struct sof_tlv); data_off_size -= sizeof(struct sof_tlv); data_off_size is a host-controlled 20-bit field taken straight from the IPC message. When it is smaller than sizeof(struct sof_tlv) (8) the subtraction underflows and wraps to a value close to 0xFFFFFFFF, which is then forwarded as the length to the module's set_large_config() handler. The actual backing buffer is only the MAILBOX_HOSTBOX_SIZE mailbox, so a compromised host could trigger out-of-bounds reads of DSP SRAM (and possible corruption depending on the target module) by sending MOD_LARGE_CONFIG_SET with init_block=1, final_block=0 and data_off_size < 8. Hoist the existing "data_off_size < sizeof(struct sof_tlv) || data_off_size > MAILBOX_HOSTBOX_SIZE" bounds check to the top of the function so it runs for every entry, before any pointer or size arithmetic. The duplicate check in the bursted-config branch is removed as it is now covered by the hoisted one. Signed-off-by: Jyri Sarha --- src/ipc/ipc4/handler-user.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index 5a5f3d6d6f32..16becf73d532 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -1109,15 +1109,15 @@ __cold static int ipc4_set_vendor_config_module_instance(struct comp_dev *dev, assert_can_be_cold(); + /* Validate host-controlled payload size before any use or arithmetic. */ + if (data_off_size > MAILBOX_HOSTBOX_SIZE) + return IPC4_INVALID_CONFIG_DATA_STRUCT; + if (init_block && data_off_size < sizeof(struct sof_tlv)) + return IPC4_INVALID_CONFIG_DATA_STRUCT; + /* Old FW comment: bursted configs */ if (init_block && final_block) { const struct sof_tlv *tlv = (struct sof_tlv *)data; - /* if there is no payload in this large config set - * (4 bytes type | 4 bytes length=0 | no value) - * we do not handle such case - */ - if (data_off_size < sizeof(struct sof_tlv) || data_off_size > MAILBOX_HOSTBOX_SIZE) - return IPC4_INVALID_CONFIG_DATA_STRUCT; /* ===Iterate over payload=== * Payload can have multiple sof_tlv structures inside, From 779b5491906e5512c671456eeb74376eed1ba477 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 11 Jun 2026 14:49:15 +0300 Subject: [PATCH 063/303] ipc4: chain_dma: fix use-after-free on chain DMA deallocate ipc4_process_chain_dma() called ipc4_chain_dma_state() and then, on the deallocate path (allocate == 0 && enable == 0), unconditionally executed list_item_del(&cdma_comp->list). However, on that same deallocate path ipc4_chain_dma_state() already unlinks the matching ipc_comp_dev from ipc->comp_list and frees it with rfree(): list_item_del(&icd->list); rfree(icd); Since icd is the same object as cdma_comp, the subsequent list_item_del(&cdma_comp->list) in the caller dereferenced and wrote to already-freed memory (prev->next / next->prev), a use-after-free. With heap grooming a host sending GLB_CHAIN_DMA with allocate=0/enable=0 on an existing chain could turn this into controlled heap corruption. The unlink-before-free is already handled correctly by ipc4_chain_dma_state(), so the duplicate list_item_del() in the caller is both redundant and unsafe. Remove it. Signed-off-by: Jyri Sarha --- src/ipc/ipc4/handler-user.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index 16becf73d532..e98baa5121af 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -611,9 +611,6 @@ __cold static int ipc4_process_chain_dma(struct ipc4_message_request *ipc4) if (ret < 0) return IPC4_INVALID_CHAIN_STATE_TRANSITION; - if (!cdma.primary.r.allocate && !cdma.primary.r.enable) - list_item_del(&cdma_comp->list); - return IPC4_SUCCESS; #else return IPC4_UNAVAILABLE; From 9ed5804ee7f30724c43f9eaf5296383729307ff4 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 28 May 2026 17:59:52 +0300 Subject: [PATCH 064/303] debug_stream: Export ds_msg() to allow using it in loadable modules Export ds_msg() to allow using it in loadable modules. Signed-off-by: Jyri Sarha --- src/debug/debug_stream/debug_stream_text_msg.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/debug/debug_stream/debug_stream_text_msg.c b/src/debug/debug_stream/debug_stream_text_msg.c index 97db0fd29330..f4b67d4d307a 100644 --- a/src/debug/debug_stream/debug_stream_text_msg.c +++ b/src/debug/debug_stream/debug_stream_text_msg.c @@ -43,6 +43,7 @@ void ds_msg(const char *format, ...) ds_vamsg(format, args); va_end(args); } +EXPORT_SYMBOL(ds_msg); #if defined(CONFIG_EXCEPTION_DUMP_HOOK) /* The debug stream debug window slot is 4k, and when it is split From a94f45c9e31f1428734392517ee9b61ca3083e32 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 5 Jun 2026 14:57:10 +0300 Subject: [PATCH 065/303] debug_stream: make debug_stream_slot_send_record() a syscall Make debug_stream_slot_send_record() a Zephyr syscall. This allows user-space threads to send debug stream records directly. Rename the implementation to z_impl_debug_stream_slot_send_record() and add z_vrfy_debug_stream_slot_send_record() with K_SYSCALL_MEMORY_READ validation of the record buffer. Register the syscall header in CMakeLists.txt. Signed-off-by: Jyri Sarha --- src/debug/debug_stream/debug_stream_slot.c | 16 +++++++++++++++- src/include/user/debug_stream_slot.h | 4 +++- zephyr/CMakeLists.txt | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/debug/debug_stream/debug_stream_slot.c b/src/debug/debug_stream/debug_stream_slot.c index 82816a6788f3..db27490a98a4 100644 --- a/src/debug/debug_stream/debug_stream_slot.c +++ b/src/debug/debug_stream/debug_stream_slot.c @@ -12,6 +12,10 @@ #include #include +#ifdef CONFIG_USERSPACE +#include +#endif + LOG_MODULE_REGISTER(debug_stream_slot); struct cpu_mutex { @@ -66,7 +70,7 @@ debug_stream_get_circular_buffer(struct debug_stream_section_descriptor *desc, u return (struct debug_stream_circular_buf *) (((uint8_t *)hdr) + desc->offset); } -int debug_stream_slot_send_record(struct debug_stream_record *rec) +int z_impl_debug_stream_slot_send_record(struct debug_stream_record *rec) { struct debug_stream_section_descriptor desc = { 0 }; struct debug_stream_circular_buf *buf = @@ -119,6 +123,16 @@ int debug_stream_slot_send_record(struct debug_stream_record *rec) return 0; } +#ifdef CONFIG_USERSPACE +static inline int z_vrfy_debug_stream_slot_send_record(struct debug_stream_record *rec) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(rec, sizeof(*rec))); + K_OOPS(K_SYSCALL_MEMORY_READ(rec, rec->size_words * sizeof(uint32_t))); + return z_impl_debug_stream_slot_send_record(rec); +} +#include +#endif + static int debug_stream_slot_init(void) { struct debug_stream_slot_hdr *hdr = debug_stream_get_slot(); diff --git a/src/include/user/debug_stream_slot.h b/src/include/user/debug_stream_slot.h index 4464c9ed56b8..926b7f9634c8 100644 --- a/src/include/user/debug_stream_slot.h +++ b/src/include/user/debug_stream_slot.h @@ -127,6 +127,8 @@ struct debug_stream_record; * -ENODEV if debug stream slot is not configured * -ENOMEM if the record is too big */ -int debug_stream_slot_send_record(struct debug_stream_record *rec); +__syscall int debug_stream_slot_send_record(struct debug_stream_record *rec); + +#include #endif /* __SOC_DEBUG_WINDOW_SLOT_H__ */ diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index a3c4b643bcda..5b02dddcbb14 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -625,6 +625,8 @@ zephyr_library_sources_ifdef(CONFIG_SOF_USERSPACE_INTERFACE_ALLOC syscall/alloc. zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/dai-zephyr.h) zephyr_library_sources_ifdef(CONFIG_USERSPACE syscall/dai.c) +zephyr_syscall_header(${SOF_SRC_PATH}/include/user/debug_stream_slot.h) + zephyr_library_link_libraries(SOF) target_link_libraries(SOF INTERFACE zephyr_interface) From e3f4cf4cbbfd00bfd4cf0872bd0b5c82cb1da46f Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 11 Jun 2026 17:59:33 +0200 Subject: [PATCH 066/303] audio: kpb: validate channel count in kpb_set_micselect kpb_set_micselect() computed mic_cnt = channels - KPB_REFERENCE_SUPPORT_CHANNELS without checking the lower bound. With a host-configured channel count below 2 the unsigned subtraction wraps, producing a huge loop bound and out-of-bounds writes to the fixed offsets[] array. Reject payloads smaller than the config struct and channel counts outside the supported range before computing mic_cnt, and bound the offsets[] index inside the loop. Signed-off-by: Tomasz Leman --- src/audio/kpb.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 71fdca29e73e..94cd13b945a6 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -2459,12 +2459,27 @@ static int kpb_set_micselect(struct comp_dev *dev, const void *data, { const struct kpb_micselector_config *mic_sel = data; struct comp_data *kpb = comp_get_drvdata(dev); - const size_t mic_cnt = kpb->config.channels - KPB_REFERENCE_SUPPORT_CHANNELS; - const uint8_t valid_mask = KPB_COUNT_TO_BITMASK(mic_cnt); + size_t mic_cnt; + uint8_t valid_mask; size_t i; + if (max_data_size < (int)sizeof(*mic_sel)) { + comp_err(dev, "micselector payload too small: got %d, need %d", + max_data_size, (int)sizeof(*mic_sel)); + return -EINVAL; + } + + if (kpb->config.channels < KPB_REFERENCE_SUPPORT_CHANNELS || + kpb->config.channels > KPB_MAX_SUPPORTED_CHANNELS) { + comp_err(dev, "unsupported channel count %u", kpb->config.channels); + return -EINVAL; + } + + mic_cnt = kpb->config.channels - KPB_REFERENCE_SUPPORT_CHANNELS; + valid_mask = KPB_COUNT_TO_BITMASK(mic_cnt); + if ((valid_mask & mic_sel->mask) == 0) { - comp_err(dev, "error: invalid micselector bit mask"); + comp_err(dev, "invalid micselector bit mask"); return -EINVAL; } /* selected mics counter */ @@ -2472,6 +2487,10 @@ static int kpb_set_micselect(struct comp_dev *dev, const void *data, for (i = 0; i < mic_cnt; i++) { if (KPB_IS_BIT_SET(mic_sel->mask, i)) { + if (num_of_sel_mic >= ARRAY_SIZE(kpb->offsets)) { + comp_err(dev, "too many selected mics"); + return -EINVAL; + } kpb->offsets[num_of_sel_mic] = i; num_of_sel_mic++; } From ed894d61694d6e5ecc09f095f5bcccd6072805eb Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 11 Jun 2026 18:00:41 +0200 Subject: [PATCH 067/303] audio: kpb: validate FMT module list against payload size The KP_BUF_CFG_FM_MODULE large-config path cast the host payload to struct kpb_task_params and iterated dev_ids[] for number_of_modules entries without checking it against the declared payload length, so a number_of_modules larger than the payload caused out-of-bounds reads. Verify the payload covers the header and all declared dev_ids[] entries before processing the list. Signed-off-by: Tomasz Leman --- src/audio/kpb.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 94cd13b945a6..67313982ed61 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -2688,6 +2688,15 @@ static int kpb_set_large_config(struct comp_dev *dev, uint32_t param_id, const struct kpb_task_params *cfg = (struct kpb_task_params *)data; uint32_t outpin_id = extended_param_id.part.parameter_instance; + /* payload must cover the header and all declared dev_ids[] entries */ + if (!cfg || data_offset < offsetof(struct kpb_task_params, dev_ids)) + return -EINVAL; + + if (cfg->number_of_modules > + (data_offset - offsetof(struct kpb_task_params, dev_ids)) / + sizeof(cfg->dev_ids[0])) + return -EINVAL; + return configure_fast_mode_task(dev, cfg, outpin_id); } #endif From 901c0ced2e66055ba24eb81dcb92161a9060ae22 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Fri, 12 Jun 2026 17:19:06 +0200 Subject: [PATCH 068/303] audio: kpb: clean up FMT module list on prepare failure prepare_fmt_modules_list() populates kpb_list_item[], device_list[] and modules_list_item[] entries as it walks the module list. On any mid-loop failure it returned without undoing those entries, while the caller had already cleared the previous list, leaving a half-configured Fast Mode Task list with stale component references. Roll back the touched entries via clear_fmt_modules_list() on the error path, and add a defensive bound check on outpin_idx. Signed-off-by: Tomasz Leman --- src/audio/kpb.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 67313982ed61..7dc874bf58d2 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -2561,32 +2561,42 @@ static int prepare_fmt_modules_list(struct comp_dev *kpb_dev, struct kpb_fmt_dev_list *fmt_device_list = &((struct comp_data *)comp_get_drvdata(kpb_dev))->fmt_device_list; + if (outpin_idx >= KPB_MAX_SINK_CNT) + return -EINVAL; + fmt_device_list->kpb_list_item[outpin_idx] = kpb_dev; ret = devicelist_push(&fmt_device_list->device_list[outpin_idx], &fmt_device_list->kpb_list_item[outpin_idx]); if (ret < 0) - return ret; + goto err; for (size_t mod_idx = 0; mod_idx < modules_to_prepare->number_of_modules; ++mod_idx) { uint32_t comp_id = IPC4_COMP_ID(modules_to_prepare->dev_ids[mod_idx].module_id, modules_to_prepare->dev_ids[mod_idx].instance_id); dev = ipc4_get_comp_dev(comp_id); - if (!dev) - return -EINVAL; + if (!dev) { + ret = -EINVAL; + goto err; + } struct comp_dev **new_list_item_ptr; ret = alloc_fmt_module_list_item(fmt_device_list, dev, &new_list_item_ptr); if (ret < 0) - return ret; + goto err; *new_list_item_ptr = dev; ret = devicelist_push(&fmt_device_list->device_list[outpin_idx], new_list_item_ptr); if (ret < 0) - return ret; + goto err; } return 0; + +err: + /* drop any entries pushed so far to avoid leaving a half-configured list */ + clear_fmt_modules_list(fmt_device_list, outpin_idx); + return ret; } static int clear_fmt_modules_list(struct kpb_fmt_dev_list *fmt_device_list, From 26917d927201aec24aa88a869d3fbbf56e295dc9 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Mon, 18 May 2026 13:22:03 +0200 Subject: [PATCH 069/303] boards: disable LLEXT EDK generation for all platforms SOF does not use the Zephyr LLEXT Extension Development Kit. The EDK target is automatically enabled by Kconfig (default y if LLEXT) but serves no purpose for SOF builds and adds unnecessary build steps. Additionally, the EDK build infrastructure has a known bug on Windows with ccache enabled (zephyrproject-rtos/zephyr#109146) that causes CI failures due to path quoting issues in misc/llext_edk/CMakeLists.txt. Explicitly disable CONFIG_LLEXT_EDK on all LLEXT-enabled boards to avoid the broken code path and reduce build time. Signed-off-by: Tomasz Leman --- app/boards/intel_adsp_ace15_mtpm.conf | 1 + app/boards/intel_adsp_ace20_lnl.conf | 1 + app/boards/intel_adsp_ace30_ptl.conf | 1 + app/boards/intel_adsp_ace30_wcl.conf | 1 + app/boards/intel_adsp_ace40_nvl.conf | 1 + app/boards/intel_adsp_ace40_nvls.conf | 1 + 6 files changed, 6 insertions(+) diff --git a/app/boards/intel_adsp_ace15_mtpm.conf b/app/boards/intel_adsp_ace15_mtpm.conf index 4f124eee5285..9bbf01b9c61b 100644 --- a/app/boards/intel_adsp_ace15_mtpm.conf +++ b/app/boards/intel_adsp_ace15_mtpm.conf @@ -55,6 +55,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y CONFIG_TIMING_FUNCTIONS=y CONFIG_WATCHDOG=y diff --git a/app/boards/intel_adsp_ace20_lnl.conf b/app/boards/intel_adsp_ace20_lnl.conf index 2aef09fb9a4a..695be421be20 100644 --- a/app/boards/intel_adsp_ace20_lnl.conf +++ b/app/boards/intel_adsp_ace20_lnl.conf @@ -38,6 +38,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y CONFIG_TIMING_FUNCTIONS=y diff --git a/app/boards/intel_adsp_ace30_ptl.conf b/app/boards/intel_adsp_ace30_ptl.conf index 20e866ade585..b6ac41938398 100644 --- a/app/boards/intel_adsp_ace30_ptl.conf +++ b/app/boards/intel_adsp_ace30_ptl.conf @@ -42,6 +42,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y # Zephyr / device drivers diff --git a/app/boards/intel_adsp_ace30_wcl.conf b/app/boards/intel_adsp_ace30_wcl.conf index 999a3c309d41..a46b4dfaa08f 100644 --- a/app/boards/intel_adsp_ace30_wcl.conf +++ b/app/boards/intel_adsp_ace30_wcl.conf @@ -40,6 +40,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y # Zephyr / device drivers diff --git a/app/boards/intel_adsp_ace40_nvl.conf b/app/boards/intel_adsp_ace40_nvl.conf index 0472a7c00de1..bb39cf026ada 100644 --- a/app/boards/intel_adsp_ace40_nvl.conf +++ b/app/boards/intel_adsp_ace40_nvl.conf @@ -39,6 +39,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y # Zephyr / device drivers diff --git a/app/boards/intel_adsp_ace40_nvls.conf b/app/boards/intel_adsp_ace40_nvls.conf index 0472a7c00de1..bb39cf026ada 100644 --- a/app/boards/intel_adsp_ace40_nvls.conf +++ b/app/boards/intel_adsp_ace40_nvls.conf @@ -39,6 +39,7 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_LLEXT=y CONFIG_LLEXT_STORAGE_WRITABLE=y CONFIG_LLEXT_EXPERIMENTAL=y +CONFIG_LLEXT_EDK=n CONFIG_MODULES=y # Zephyr / device drivers From 75730d7278c75dbf60623ce9016f9c1ab6eb2980 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Mon, 11 May 2026 15:55:57 +0200 Subject: [PATCH 070/303] west.yml: update zephyr to ef5cfd369f4a Total of 3581 commits. Changes include: b6c7c8fff5a5 xtensa: add dedicated NMI handler 94507fc8c0fe soc: imx95: m7: Apply clang-format formatting c375832244b5 soc: imx95: m7: add SAI clock setup for all enabled SAI instances e5a8c2188555 xtensa: exc: give magic custom exccause names 65a542bd12db arch: xtensa: initialize secondary cpu interrupt stacks on smp 53f001200f30 soc: nxp: imx95: Refine condition to enable MCUX_LPTMR_TIMER ff30cf8a3bd8 drivers: audio: add native_sim DMIC driver 2826b086d135 drivers: dai: add AMD ACP 7.X SoundWire DAI support bcfa5e42c4d5 xtensa: mpu: consolidate map if not enough free slots 064bde92a5b1 xtensa: mpu: consolidate_entries() to return if success 679d3fbc5b83 xtensa: mpu: fix adding to empty MPU map 8eb69ba1c6cb xtensa: mpu: optimize arch_mem_domain_thread_add() a bit 1ed96c9b490f xtensa: mpu: restore boot perms when removing thread from domain 7433d1f286b0 xtensa: mpu: limit MPU entries sorting to enabled ones b92d7325d9ca xtensa: mpu: introduce memory type table 58a5ec11ceca xtensa: mpu: remove xtensa_soc_mpu_ranges[] acadac7f3e78 xtensa: mpu: extract the MPU region table to its own file 4167d79b71fb xtensa: move MPU code into its own directory 7491f2c62d2a drivers: audio: add dummy codec stub driver 03200bf45774 soc: intel_adsp: ace: remove including manifest.h in sram.c f329a610ed36 soc: intel_adsp: correct dependency on cached region kconfigs f4fe6aa62b7c soc: intel_adsp/ace: correct CONFIG_XTENSA_CPU_HAS_HIFI* 52a570f308c0 xtensa: add CONFIG_XTENSA_HIFI5 f2b07e6fd28a soc: intel_adsp: use generated linker snippet for vectors d0b389da9eae kernel: remove redundant kernel_structs.h includes 4dadeb5069aa arch/xtensa: Add CPU load support for Xtensa e716cf058d2a logging: log_output_custom: drop fatal assert on unset callback 39582c1c2fc9 soc: nxp: migrate PM hooks to idle-owned IRQ restore 16d69ce7d082 xtensa: unsupported unsigned load / store emulation ec8e9a54504b dts: amd: acp_7_x: add devicetree and bindings cabf34d7dd9b drivers: intc: add AMD ACP 7.X interrupt controller support b44b52e9c13c soc: amd: acp_7_x: add SoC and board support 4612efcb6ec2 drivers: clock_control: clock update for LPIT instances on iMX93 Mcore 45bdb70e1c05 dma: amd: add TDM DMA driver for AMD ACP 7.0 74a32bdff3ca modules: hal_nxp: make MCUX SDK section placement configurable 15af50d20252 pm: keep irq restore ownership in idle 0b0b41ce9dad kernel: Initialize timeout.dticks on k_timer_init() c3f2a6a07ac9 kernel: timeslicing: rearm with slice_size-1 when slicer just fired 2ec65238d68a kernel: timeout: make in-announce check CPU-aware 491583951036 kernel: add kobj NULL check in k_thread_name_copy() 3b1bdaf54821 xtensa: mpu: fix arch_buffer_validate() if overflow fdc42fa256b8 kernel: userspace: fix SMP use-after-free a13fd968e132 soc: intel: Add support for dts RAM configuration 1d935da70029 kernel: Add support for dts RAM configuration 96d1142210e3 arch: Add support for dts RAM configuration 8509270a0e34 kernel: poll timeout: Fix race condition 1b8c7a3038d9 kernel: thread timeout: Fix race condition 667c3184d6e8 logging: log_output: Fix formatting of function prefix e1434e141ecf logging: fix immediate clean output locking on SMP 1296dc85e332 kernel: timer: make k_timer_start duration match documented semantics 2e2202af616c kernel: timeout: make z_add_timeout round-up conditional on announce d157b3da193d kernel: timeout: keep announce_remaining stable across same-tick group 827ce93035bc soc: kernel: use configdefault for CONFIG_SMP 603fa4818e8f drivers: timer: assert sys_clock lock held where required 1dc47344d0a2 kernel: use arch_cpu_irqs_are_enabled() for IRQ-state probes 01b3821fd933 kernel: spinlock: provide z_spin_is_locked() for UP builds c91e5e1390f3 kernel: move atomic_c.c to lib/os cc6136628341 kernel: move errno from kernel to lib/libc/common 81ccaca788db kernel: ensure kheap.c is linked for static heap initialization 39b23fa582d8 kernel: bypass k_heap_free() in k_free() to avoid scheduler locking db7a5e80a47b kernel: move bootargs out of kernel into lib/os. 9fe4cc20a2b5 kernel: move boot banner into lib/os f7c6f1d92110 soc: intel_adsp: ace30: extend hwreg0 MMU range 814cfb41191a soc: intel_adsp: ace40: extend hwreg0 MMU range 1bc5f0543c4d drivers: counter: introduce counter capture api e81266be2bfe arch: add arch_cpu_irqs_are_enabled() primitive Zephyr commit 58e859c5dd37b88 ("boards: nxp: imx95_evk: enable EDMA1 for M7 core") enables EDMA1 node which in turn enables dma_mcux_edma.c driver. dma_mcux_edma.c requires some attention w.r.t cache configuration options. SOF doesnt use EDMA1 nodes but inherits edma1 node which selects DMA_MCUX_EDMA and causes the following compilation errors: dma_mcux_edma.c:1225:2: error: #error Unexpected or disallowed cache situation for dma descriptors So disable edma1 node as we don't use it. Co-authored-by: Daniel Baluta Signed-off-by: Tomasz Leman Signed-off-by: Daniel Baluta --- app/boards/imx95_evk_mimx9596_m7_ddr.overlay | 4 ++++ west.yml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/boards/imx95_evk_mimx9596_m7_ddr.overlay b/app/boards/imx95_evk_mimx9596_m7_ddr.overlay index f1d2cce00781..4c9ce43c9452 100644 --- a/app/boards/imx95_evk_mimx9596_m7_ddr.overlay +++ b/app/boards/imx95_evk_mimx9596_m7_ddr.overlay @@ -26,6 +26,10 @@ status = "okay"; }; +&edma1 { + status = "disabled"; +}; + &edma2 { compatible = "nxp,edma"; reg = <0x42000000 (DT_SIZE_K(64) * 33)>; diff --git a/west.yml b/west.yml index faf374b914fb..abb3dcaa9736 100644 --- a/west.yml +++ b/west.yml @@ -43,7 +43,7 @@ manifest: - name: zephyr repo-path: zephyr - revision: c162980efd9ad8616d0e2fe886ca917d8d8d240a + revision: ef5cfd369f4a0ab3601db9a23c1da8c406cc1a72 remote: zephyrproject # Import some projects listed in zephyr/west.yml@revision From cdb7f911bb15e91d34c254892916864293eb0c09 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Fri, 12 Jun 2026 15:44:41 +0200 Subject: [PATCH 071/303] audio: module_adapter: bound host channels_count at ipc4 init Reject 0 or > PLATFORM_MAX_CHANNELS before modules index channel-sized arrays. Signed-off-by: Adrian Bonislawski --- src/audio/module_adapter/module_adapter_ipc4.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/audio/module_adapter/module_adapter_ipc4.c b/src/audio/module_adapter/module_adapter_ipc4.c index a639755a31e2..07b7039c3a40 100644 --- a/src/audio/module_adapter/module_adapter_ipc4.c +++ b/src/audio/module_adapter/module_adapter_ipc4.c @@ -155,6 +155,14 @@ int module_adapter_init_data(struct comp_dev *dev, dst->base_cfg = cfg->base_cfg; dst->size = cfgsz; + /* Host-supplied channel count indexes PLATFORM_MAX_CHANNELS arrays. */ + if (dst->base_cfg.audio_fmt.channels_count == 0 || + dst->base_cfg.audio_fmt.channels_count > PLATFORM_MAX_CHANNELS) { + comp_err(dev, "invalid channels count %u", + dst->base_cfg.audio_fmt.channels_count); + return -EINVAL; + } + if (cfgsz >= sizeof(*cfg)) { int n_in = cfg->base_cfg_ext.nb_input_pins; int n_out = cfg->base_cfg_ext.nb_output_pins; From f6d5bf5f3b119e1bf8cba72f3466f62cf4232273 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Fri, 12 Jun 2026 15:44:41 +0200 Subject: [PATCH 072/303] audio: cadence: validate TLV param size before applying config Bound each host-supplied module_param against the bytes remaining to avoid an OOB read or a stalled loop. Signed-off-by: Adrian Bonislawski --- src/audio/module_adapter/module/cadence.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/audio/module_adapter/module/cadence.c b/src/audio/module_adapter/module/cadence.c index 77b8b0d7fc78..1da75081e2f8 100644 --- a/src/audio/module_adapter/module/cadence.c +++ b/src/audio/module_adapter/module/cadence.c @@ -358,6 +358,19 @@ int cadence_codec_apply_params(struct processing_module *mod, int size, void *da */ while (size > 0) { param = data; + + if (size < (int)sizeof(*param)) { + comp_err(dev, "param header truncated, %d bytes left", size); + return -EINVAL; + } + + /* param->size covers the whole record and must fit */ + if (param->size <= sizeof(*param) || param->size > (uint32_t)size) { + comp_err(dev, "invalid param size %u, %d bytes left", + param->size, size); + return -EINVAL; + } + comp_dbg(dev, "cadence_codec_apply_config() applying param %d value %d", param->id, param->data[0]); From 830e14fea9053b2a0922247b37e743d6918cf0d4 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 16 Jun 2026 14:52:20 +0200 Subject: [PATCH 073/303] lib-manager: fix a NULL dereference lib_manager_get_module_manifest() can return NULL, if it is then passed to module_is_llext() as an argument, it will lead to an exception. Check for NULL before calling module_is_llext(). Signed-off-by: Guennadi Liakhovetski --- src/library_manager/lib_manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library_manager/lib_manager.c b/src/library_manager/lib_manager.c index 56cd616cd9f5..ec26968694fd 100644 --- a/src/library_manager/lib_manager.c +++ b/src/library_manager/lib_manager.c @@ -1165,7 +1165,7 @@ int lib_manager_load_library(uint32_t dma_id, uint32_t lib_id, uint32_t type) uint32_t module_id = lib_id << LIB_MANAGER_LIB_ID_SHIFT; const struct sof_man_module *mod = lib_manager_get_module_manifest(module_id); - if (!ret && module_is_llext(mod)) + if (!ret && mod && module_is_llext(mod)) /* Auxiliary LLEXT libraries need to be linked upon loading */ ret = llext_manager_add_library(module_id); From f40de6545391341aad6379342e6c618cfeed7323 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 12 Jun 2026 14:13:12 +0300 Subject: [PATCH 074/303] tools: tplg_parser: add bounds checking to topology object reader macros Add tplg_check_bounds() macro that validates ctx->tplg_offset + advance does not exceed ctx->tplg_size before advancing the offset. Apply this check in all topology object reader macros: tplg_get_hdr, tplg_skip_hdr_payload, tplg_get_object, tplg_get_object_priv, tplg_get_widget, tplg_get_graph, and tplg_get_pcm. Without these checks, a crafted .tplg file with malicious payload_size or priv.size values can drive the offset past the end of the mapped topology data, causing out-of-bounds reads in all subsequent object parsing. Signed-off-by: Jyri Sarha --- .../include/tplg_parser/topology.h | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tools/tplg_parser/include/tplg_parser/topology.h b/tools/tplg_parser/include/tplg_parser/topology.h index 04988d688082..d593284eb158 100644 --- a/tools/tplg_parser/include/tplg_parser/topology.h +++ b/tools/tplg_parser/include/tplg_parser/topology.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -194,8 +196,20 @@ struct tplg_context { #define tplg_get(ctx) ((void *)(ctx->tplg_base + ctx->tplg_offset)) +#define tplg_check_bounds(ctx, advance) \ + do { \ + if ((long)(advance) < 0 || \ + ctx->tplg_offset + (long)(advance) > (long)ctx->tplg_size) { \ + printf("%s %d topology offset %ld + %ld > size %zu\n", \ + __func__, __LINE__, (ctx)->tplg_offset, \ + (long)(advance), (ctx)->tplg_size); \ + exit(1); \ + } \ + } while (0) + #define tplg_get_hdr(ctx) \ ({struct snd_soc_tplg_hdr *ptr; \ + tplg_check_bounds(ctx, sizeof(*ptr)); \ ptr = (struct snd_soc_tplg_hdr *)(ctx->tplg_base + ctx->tplg_offset); \ if (ptr->size != sizeof(*ptr)) { \ printf("%s %d hdr size mismatch 0x%x:0x%zx at offset %ld\n", \ @@ -206,30 +220,40 @@ struct tplg_context { #define tplg_skip_hdr_payload(ctx) \ ({struct snd_soc_tplg_hdr *ptr; \ + tplg_check_bounds(ctx, hdr->payload_size); \ ptr = (struct snd_soc_tplg_hdr *)(ctx->tplg_base + ctx->tplg_offset); \ ctx->tplg_offset += hdr->payload_size; (void *)ptr; }) #define tplg_get_object(ctx, obj) \ - ({void *ptr; ptr = ctx->tplg_base + ctx->tplg_offset; \ + ({void *ptr; \ + tplg_check_bounds(ctx, sizeof(*(obj))); \ + ptr = ctx->tplg_base + ctx->tplg_offset; \ ctx->tplg_offset += sizeof(*(obj)); ptr; }) #define tplg_get_object_priv(ctx, obj, priv_size) \ - ({void *ptr; ptr = ctx->tplg_base + ctx->tplg_offset; \ + ({void *ptr; \ + tplg_check_bounds(ctx, sizeof(*(obj)) + (priv_size)); \ + ptr = ctx->tplg_base + ctx->tplg_offset; \ ctx->tplg_offset += sizeof(*(obj)) + priv_size; ptr; }) #define tplg_get_widget(ctx) \ ({struct snd_soc_tplg_dapm_widget *w; \ + tplg_check_bounds(ctx, sizeof(*w)); \ w = (struct snd_soc_tplg_dapm_widget *)(ctx->tplg_base + ctx->tplg_offset); \ + tplg_check_bounds(ctx, sizeof(*w) + w->priv.size); \ ctx->tplg_offset += sizeof(*w) + w->priv.size; w; }) #define tplg_get_graph(ctx) \ ({struct snd_soc_tplg_dapm_graph_elem *w; \ + tplg_check_bounds(ctx, sizeof(*w)); \ w = (struct snd_soc_tplg_dapm_graph_elem *)(ctx->tplg_base + ctx->tplg_offset); \ ctx->tplg_offset += sizeof(*w); w; }) #define tplg_get_pcm(ctx) \ ({struct snd_soc_tplg_pcm *pcm; \ + tplg_check_bounds(ctx, sizeof(*pcm)); \ pcm = (struct snd_soc_tplg_pcm *)(ctx->tplg_base + ctx->tplg_offset); \ + tplg_check_bounds(ctx, sizeof(*pcm) + pcm->priv.size); \ ctx->tplg_offset += sizeof(*pcm) + pcm->priv.size; pcm; }) static inline int tplg_valid_widget(struct snd_soc_tplg_dapm_widget *widget) From 5e82c8fb05843b114f395f6405db889f52f6e6a1 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 15 Jun 2026 18:40:17 +0300 Subject: [PATCH 075/303] tools: tplg_parser: Add () around all macro argument instances Add () around all macro argument instances. Signed-off-by: Jyri Sarha --- .../include/tplg_parser/topology.h | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tools/tplg_parser/include/tplg_parser/topology.h b/tools/tplg_parser/include/tplg_parser/topology.h index d593284eb158..c929d23f5c45 100644 --- a/tools/tplg_parser/include/tplg_parser/topology.h +++ b/tools/tplg_parser/include/tplg_parser/topology.h @@ -199,7 +199,7 @@ struct tplg_context { #define tplg_check_bounds(ctx, advance) \ do { \ if ((long)(advance) < 0 || \ - ctx->tplg_offset + (long)(advance) > (long)ctx->tplg_size) { \ + (ctx)->tplg_offset + (long)(advance) > (long)(ctx)->tplg_size) { \ printf("%s %d topology offset %ld + %ld > size %zu\n", \ __func__, __LINE__, (ctx)->tplg_offset, \ (long)(advance), (ctx)->tplg_size); \ @@ -210,51 +210,51 @@ struct tplg_context { #define tplg_get_hdr(ctx) \ ({struct snd_soc_tplg_hdr *ptr; \ tplg_check_bounds(ctx, sizeof(*ptr)); \ - ptr = (struct snd_soc_tplg_hdr *)(ctx->tplg_base + ctx->tplg_offset); \ + ptr = (struct snd_soc_tplg_hdr *)((ctx)->tplg_base + (ctx)->tplg_offset); \ if (ptr->size != sizeof(*ptr)) { \ printf("%s %d hdr size mismatch 0x%x:0x%zx at offset %ld\n", \ __func__, __LINE__, ptr->size, sizeof(*ptr), \ - ctx->tplg_offset); assert(0); \ + (ctx)->tplg_offset); assert(0); \ } \ ctx->tplg_offset += sizeof(*ptr); (void *)ptr; }) #define tplg_skip_hdr_payload(ctx) \ ({struct snd_soc_tplg_hdr *ptr; \ tplg_check_bounds(ctx, hdr->payload_size); \ - ptr = (struct snd_soc_tplg_hdr *)(ctx->tplg_base + ctx->tplg_offset); \ - ctx->tplg_offset += hdr->payload_size; (void *)ptr; }) + ptr = (struct snd_soc_tplg_hdr *)((ctx)->tplg_base + (ctx)->tplg_offset); \ + (ctx)->tplg_offset += hdr->payload_size; (void *)ptr; }) #define tplg_get_object(ctx, obj) \ ({void *ptr; \ tplg_check_bounds(ctx, sizeof(*(obj))); \ - ptr = ctx->tplg_base + ctx->tplg_offset; \ - ctx->tplg_offset += sizeof(*(obj)); ptr; }) + ptr = (ctx)->tplg_base + (ctx)->tplg_offset; \ + (ctx)->tplg_offset += sizeof(*(obj)); ptr; }) #define tplg_get_object_priv(ctx, obj, priv_size) \ ({void *ptr; \ tplg_check_bounds(ctx, sizeof(*(obj)) + (priv_size)); \ - ptr = ctx->tplg_base + ctx->tplg_offset; \ - ctx->tplg_offset += sizeof(*(obj)) + priv_size; ptr; }) + ptr = (ctx)->tplg_base + (ctx)->tplg_offset; \ + (ctx)->tplg_offset += sizeof(*(obj)) + (priv_size); ptr; }) #define tplg_get_widget(ctx) \ ({struct snd_soc_tplg_dapm_widget *w; \ tplg_check_bounds(ctx, sizeof(*w)); \ - w = (struct snd_soc_tplg_dapm_widget *)(ctx->tplg_base + ctx->tplg_offset); \ + w = (struct snd_soc_tplg_dapm_widget *)((ctx)->tplg_base + (ctx)->tplg_offset); \ tplg_check_bounds(ctx, sizeof(*w) + w->priv.size); \ - ctx->tplg_offset += sizeof(*w) + w->priv.size; w; }) + (ctx)->tplg_offset += sizeof(*w) + w->priv.size; w; }) #define tplg_get_graph(ctx) \ ({struct snd_soc_tplg_dapm_graph_elem *w; \ tplg_check_bounds(ctx, sizeof(*w)); \ - w = (struct snd_soc_tplg_dapm_graph_elem *)(ctx->tplg_base + ctx->tplg_offset); \ - ctx->tplg_offset += sizeof(*w); w; }) + w = (struct snd_soc_tplg_dapm_graph_elem *)((ctx)->tplg_base + (ctx)->tplg_offset); \ + (ctx)->tplg_offset += sizeof(*w); w; }) #define tplg_get_pcm(ctx) \ ({struct snd_soc_tplg_pcm *pcm; \ tplg_check_bounds(ctx, sizeof(*pcm)); \ - pcm = (struct snd_soc_tplg_pcm *)(ctx->tplg_base + ctx->tplg_offset); \ + pcm = (struct snd_soc_tplg_pcm *)((ctx)->tplg_base + (ctx)->tplg_offset); \ tplg_check_bounds(ctx, sizeof(*pcm) + pcm->priv.size); \ - ctx->tplg_offset += sizeof(*pcm) + pcm->priv.size; pcm; }) + (ctx)->tplg_offset += sizeof(*pcm) + pcm->priv.size; pcm; }) static inline int tplg_valid_widget(struct snd_soc_tplg_dapm_widget *widget) { From 40aa3baa2d75900ea5434af247163c6f4e797789 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 12 Jun 2026 14:13:23 +0300 Subject: [PATCH 076/303] tools: tplg_parser: fix stack buffer overflow in tplg_create_graph() Replace unbounded strcat() calls with snprintf() that tracks remaining buffer capacity. Add a pipeline_string_size parameter to tplg_create_graph() so the function knows the buffer limit. Without this fix, a crafted topology file with many or long graph element names can overflow the caller's fixed 256-byte pipeline_string buffer via repeated strcat(), corrupting the host stack. Signed-off-by: Jyri Sarha --- tools/testbench/topology_ipc3.c | 3 ++- tools/tplg_parser/graph.c | 23 +++++++++++++------ .../include/tplg_parser/topology.h | 1 + 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/tools/testbench/topology_ipc3.c b/tools/testbench/topology_ipc3.c index b95dc9859e68..331ab2ff1103 100644 --- a/tools/testbench/topology_ipc3.c +++ b/tools/testbench/topology_ipc3.c @@ -98,7 +98,8 @@ static int tb_register_graph(struct tplg_context *ctx, struct tplg_comp_info *te for (i = 0; i < num_connections; i++) { ret = tplg_create_graph(ctx, num_comps, pipeline_id, temp_comp_list, - pipeline_string, &connection, i); + pipeline_string, sizeof(pipeline_string), + &connection, i); if (ret < 0) return ret; diff --git a/tools/tplg_parser/graph.c b/tools/tplg_parser/graph.c index 7bc9f33449bd..f92b316893a9 100644 --- a/tools/tplg_parser/graph.c +++ b/tools/tplg_parser/graph.c @@ -23,6 +23,7 @@ /* load pipeline graph DAPM widget*/ int tplg_create_graph(struct tplg_context *ctx, int count, int pipeline_id, struct tplg_comp_info *temp_comp_list, char *pipeline_string, + size_t pipeline_string_size, struct sof_ipc_pipe_comp_connect *connection, int route_num) { @@ -64,13 +65,21 @@ int tplg_create_graph(struct tplg_context *ctx, int count, int pipeline_id, printf("loading route %s -> %s\n", source, sink); - strcat(pipeline_string, graph_elem->source); - strcat(pipeline_string, "->"); - - if (route_num == (count - 1)) { - strcat(pipeline_string, graph_elem->sink); - strcat(pipeline_string, "\n"); - } + size_t cur_len = strnlen(pipeline_string, pipeline_string_size); + size_t remaining = pipeline_string_size > cur_len ? + pipeline_string_size - cur_len : 0; + int written; + + if (route_num == (count - 1)) + written = snprintf(pipeline_string + cur_len, remaining, + "%s->%s\n", graph_elem->source, + graph_elem->sink); + else + written = snprintf(pipeline_string + cur_len, remaining, + "%s->", graph_elem->source); + + if (written < 0 || (size_t)written >= remaining) + fprintf(stderr, "warning: pipeline string truncated\n"); return 0; } diff --git a/tools/tplg_parser/include/tplg_parser/topology.h b/tools/tplg_parser/include/tplg_parser/topology.h index c929d23f5c45..b37d14444d14 100644 --- a/tools/tplg_parser/include/tplg_parser/topology.h +++ b/tools/tplg_parser/include/tplg_parser/topology.h @@ -338,6 +338,7 @@ int tplg_new_process(struct tplg_context *ctx, void *process, size_t process_siz int tplg_create_graph(struct tplg_context *ctx, int count, int pipeline_id, struct tplg_comp_info *temp_comp_list, char *pipeline_string, + size_t pipeline_string_size, struct sof_ipc_pipe_comp_connect *connection, int route_num); From ee06d02b9ed5fe24a2f9f5758bc9558c0d719b0c Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 12 Jun 2026 14:13:33 +0300 Subject: [PATCH 077/303] tools: probes: reject oversized data_size_bytes to prevent integer overflow Add a sanity check in process_sync() to reject packets with data_size_bytes exceeding 16 MiB before performing the data_size_bytes + sizeof(uint64_t) addition used for realloc sizing. Without this check, a crafted probe dump with data_size_bytes near UINT32_MAX wraps the realloc size to a small value, then the subsequent data copy writes data_size_bytes into the undersized buffer. Signed-off-by: Jyri Sarha --- tools/probes/probes_demux.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/probes/probes_demux.c b/tools/probes/probes_demux.c index a411eef117b9..d220bb3fcd6f 100644 --- a/tools/probes/probes_demux.c +++ b/tools/probes/probes_demux.c @@ -23,6 +23,7 @@ #define APP_NAME "sof-probes" #define PACKET_MAX_SIZE 4096 /**< Size limit for probe data packet */ +#define PACKET_DATA_SIZE_MAX (16u * 1024u * 1024u) /**< Sanity limit for packet data size */ #define DATA_READ_LIMIT 4096 /**< Data limit for file read */ #define FILES_LIMIT 32 /**< Maximum num of probe output files */ #define FILE_PATH_LIMIT 128 /**< Path limit for probe output files */ @@ -194,6 +195,12 @@ int process_sync(struct dma_frame_parser *p) { struct probe_data_packet *temp_packet; + if (p->packet->data_size_bytes > PACKET_DATA_SIZE_MAX) { + fprintf(stderr, "error: packet data size %u exceeds maximum %u\n", + p->packet->data_size_bytes, PACKET_DATA_SIZE_MAX); + return -EINVAL; + } + /* request to copy data_size from probe packet and 64-bit checksum */ p->total_data_to_copy = p->packet->data_size_bytes + sizeof(uint64_t); From f20e9554bc0796427dbea529ba243f218ebb1ade Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:07:54 +0100 Subject: [PATCH 078/303] copier: bound gateway config length to init payload size The gateway configuration length from the init payload was multiplied and used as a copy length from the mailbox without checking it against the actual payload size. Reject a configuration that would read past the init payload. Signed-off-by: Liam Girdwood --- src/audio/copier/copier.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index 9d2e2938a210..819ad0341ab1 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -147,6 +147,18 @@ __cold static int copier_init(struct processing_module *mod) cfg_total_size += gtw_cfg_var_size; } + /* + * gtw_cfg.config_length is host-controlled; make sure the resulting + * copy length does not read past the init payload in the mailbox. + * cfg_total_size is at least sizeof(*copier), so this also rejects an + * empty (md->cfg.size == 0) or otherwise too-small init payload. + */ + if (cfg_total_size > md->cfg.size) { + comp_err(dev, "copier_init(): cfg size %zu exceeds init payload %zu", + cfg_total_size, md->cfg.size); + return -EINVAL; + } + cd = mod_zalloc(mod, sizeof(*cd) + gtw_cfg_var_size); if (!cd) return -ENOMEM; From f159d0ba2227d0d82dbc0318bcbac71b378de50f Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:07:54 +0100 Subject: [PATCH 079/303] copier: validate gateway count before computing config size The multi-gateway count from the gateway config was used to compute sizes and walk the mapping array before being bounded. Reject a count above the supported maximum before any arithmetic. Signed-off-by: Liam Girdwood --- src/audio/copier/copier_dai.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/audio/copier/copier_dai.c b/src/audio/copier/copier_dai.c index bbd387b88ab8..13ceae32e4b5 100644 --- a/src/audio/copier/copier_dai.c +++ b/src/audio/copier/copier_dai.c @@ -88,6 +88,16 @@ static int copier_alh_assign_dai_index(struct comp_dev *dev, switch (dai->type) { case SOF_DAI_INTEL_HDA: /* We use DAI_INTEL_HDA for ACE 2.0 platforms */ + /* + * alh_cfg.count is host-controlled and scales the config size + * and mapping[] walk below; bound it before any arithmetic so a + * crafted blob cannot read past the gateway config. + */ + if (alh_blob->alh_cfg.count > IPC4_ALH_MAX_NUMBER_OF_GTW) { + comp_err(mod->dev, "Invalid ALH count: %u", + alh_blob->alh_cfg.count); + return -EINVAL; + } alh_cfg_size = get_alh_config_size(alh_blob); dma_config = (uint8_t *)gtw_cfg_data + alh_cfg_size; dma_config_length = (cd->config.gtw_cfg.config_length << 2) - alh_cfg_size; From 249cf37798d1ef5a91fbbd0e6dd0c474af8fb458 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:07:54 +0100 Subject: [PATCH 080/303] copier: bound output format index by sink queue id The sink queue id was used directly to index the output format array when updating parameters, unlike the other call sites which bound it. Skip a sink whose queue id exceeds the output pin count. Signed-off-by: Liam Girdwood --- src/audio/copier/copier_generic.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/audio/copier/copier_generic.c b/src/audio/copier/copier_generic.c index 89805f25af0c..bb5918819e5b 100644 --- a/src/audio/copier/copier_generic.c +++ b/src/audio/copier/copier_generic.c @@ -330,6 +330,12 @@ void copier_update_params(struct copier_data *cd, struct comp_dev *dev, int j; j = IPC4_SRC_QUEUE_ID(buf_get_id(sink)); + /* src_queue id is host-controlled; bound it before indexing out_fmt[] */ + if (j >= IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT) { + comp_err(dev, "invalid src_queue id %d", j); + continue; + } + ipc4_update_buffer_format(sink, &cd->out_fmt[j]); } From 4764a4fac4937b2760b5bf3d824ea01d8d82f140 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:13 +0100 Subject: [PATCH 081/303] copier: require a full word for the attenuation control The attenuation setter only rejected oversized payloads, then dereferenced the data as a 32-bit value; a payload shorter than that read past the mailbox. Require exactly a 32-bit payload. Signed-off-by: Liam Girdwood --- src/audio/copier/copier.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index 819ad0341ab1..6e7b379a1f33 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -797,9 +797,11 @@ __cold static int set_attenuation(struct comp_dev *dev, uint32_t data_offset, co assert_can_be_cold(); - /* only support attenuation in format of 32bit */ - if (data_offset > sizeof(uint32_t)) { - comp_err(dev, "attenuation data size %d is incorrect", data_offset); + /* only support attenuation in format of 32bit; the payload is + * dereferenced as a uint32_t below so it must be exactly that size + */ + if (data_offset != sizeof(uint32_t)) { + comp_err(dev, "attenuation data size %u is incorrect", data_offset); return -EINVAL; } From e2796800058a4d9f021710655873d522a8751384 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:13 +0100 Subject: [PATCH 082/303] copier: validate ipc gateway config length covers the blob The IPC gateway path read a config blob from the gateway data without checking the declared config length covered it, over-reading the mailbox tail. Reject a config length too small for the gateway config header and blob. Signed-off-by: Liam Girdwood --- src/audio/copier/copier_ipcgtw.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/audio/copier/copier_ipcgtw.c b/src/audio/copier/copier_ipcgtw.c index ed365c1e50d8..115c9a4a7e2d 100644 --- a/src/audio/copier/copier_ipcgtw.c +++ b/src/audio/copier/copier_ipcgtw.c @@ -223,6 +223,20 @@ __cold int copier_ipcgtw_create(struct processing_module *mod, return -EINVAL; } + /* config_length is in dwords; require enough dwords to cover the + * gateway config header and the blob read below. Compare dword counts + * (rather than scaling config_length by 4) so a large host-supplied + * value cannot overflow the multiplication on 32-bit size_t. + */ + if (gtw_cfg->config_length < + SOF_DIV_ROUND_UP(sizeof(struct ipc4_gateway_config_data) + + sizeof(struct ipc4_ipc_gateway_config_blob), + sizeof(uint32_t))) { + comp_err(dev, "ipc4_gateway_config_data too small: %u", + gtw_cfg->config_length); + return -EINVAL; + } + cd->ipc_gtw = true; /* The IPC gateway is treated as a host gateway */ From 5b329f3454c0eb565dd838c98f14aeec70b77e38 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:37 +0100 Subject: [PATCH 083/303] cadence: validate init payload covers the direction field The init path read the direction word at a fixed offset past the codec params without checking the payload was large enough, reading past the mailbox. Require the payload to cover the field. Signed-off-by: Liam Girdwood --- src/audio/module_adapter/module/cadence_ipc4.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/audio/module_adapter/module/cadence_ipc4.c b/src/audio/module_adapter/module/cadence_ipc4.c index 17069b97113a..107a6895b212 100644 --- a/src/audio/module_adapter/module/cadence_ipc4.c +++ b/src/audio/module_adapter/module/cadence_ipc4.c @@ -245,6 +245,16 @@ static int cadence_codec_init(struct processing_module *mod) if (codec->state == MODULE_DISABLED && ext_data->module_data_size > 0) { int size = ext_data->module_data_size; uint8_t *init_bytes; + uint32_t direction; + + /* the init payload holds the codec params followed by the + * direction word; validate the size up front before using it + */ + if (size < (int)(sizeof(struct snd_codec) + sizeof(uint32_t))) { + comp_err(dev, "setup config too small: %d", size); + ret = -EINVAL; + goto free_cd; + } setup_cfg = &cd->setup_cfg; @@ -265,9 +275,13 @@ static int cadence_codec_init(struct processing_module *mod) setup_cfg->avail = true; codec->cfg.avail = false; - /* direction follows the codec params in init data */ + /* direction follows the codec params; copy it out rather than + * dereferencing a possibly unaligned uint32_t pointer + */ init_bytes = (uint8_t *)ext_data->module_data; - cd->direction = *(uint32_t *)(init_bytes + sizeof(struct snd_codec)); + memcpy(&direction, init_bytes + sizeof(struct snd_codec), + sizeof(direction)); + cd->direction = direction; comp_info(dev, "codec direction set to %u", cd->direction); } From 9774754a6bef706ad0504ffb80451649b876e4f5 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:37 +0100 Subject: [PATCH 084/303] waves: check remaining size before reading a tlv header The config parse loop read a parameter header before confirming a whole header remained in the blob, reading past it near the end. Bound the read by the remaining size. Signed-off-by: Liam Girdwood --- src/audio/module_adapter/module/waves/waves.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/audio/module_adapter/module/waves/waves.c b/src/audio/module_adapter/module/waves/waves.c index d328d9e5167f..aa800b32f992 100644 --- a/src/audio/module_adapter/module/waves/waves.c +++ b/src/audio/module_adapter/module/waves/waves.c @@ -612,6 +612,15 @@ static int waves_effect_apply_config(struct processing_module *mod) for (index = 0; index < cfg->size && (!ret); param_number++) { uint32_t param_data_size; + /* make sure a whole param header remains before reading + * param->size / param->id below + */ + if (index + header_size > cfg->size) { + comp_err(dev, "module_param header at index %u (header %u) exceeds cfg size %zu", + index, header_size, cfg->size); + return -EINVAL; + } + param = (struct module_param *)((char *)cfg->data + index); param_data_size = param->size - sizeof(param->size) - sizeof(param->id); From 9668b1d9e9b8cf87c82db76722ed9e012c1e6b1f Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:58 +0100 Subject: [PATCH 085/303] module_adapter: make fragmented-transfer size per-instance The reassembly size for fragmented runtime-params transfers was a file-scope static shared by every component, so interleaved transfers to different components corrupted each other's state. Move it into per-instance module state. Signed-off-by: Liam Girdwood --- src/audio/module_adapter/module_adapter_ipc3.c | 14 +++++++++----- src/include/module/module/base.h | 7 +++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/audio/module_adapter/module_adapter_ipc3.c b/src/audio/module_adapter/module_adapter_ipc3.c index 74c8f02afa2f..e6098dec0799 100644 --- a/src/audio/module_adapter/module_adapter_ipc3.c +++ b/src/audio/module_adapter/module_adapter_ipc3.c @@ -184,21 +184,25 @@ static int module_adapter_get_set_params(struct comp_dev *dev, struct sof_ipc_ct const struct module_interface *const interface = mod->dev->drv->adapter_ops; enum module_cfg_fragment_position pos; uint32_t data_offset_size; - static uint32_t size; comp_dbg(dev, "num_of_elem %d, elem remain %d msg_index %u", cdata->num_elems, cdata->elems_remaining, cdata->msg_index); - /* set the fragment position, data offset and config data size */ + /* + * The total fragmented-transfer size is kept per instance in + * mod->runtime_params_size; a file-scope static would be shared across + * all module_adapter components and corrupted by interleaved transfers. + */ if (!cdata->msg_index) { - size = cdata->num_elems + cdata->elems_remaining; - data_offset_size = size; + mod->runtime_params_size = cdata->num_elems + cdata->elems_remaining; + data_offset_size = mod->runtime_params_size; if (cdata->elems_remaining) pos = MODULE_CFG_FRAGMENT_FIRST; else pos = MODULE_CFG_FRAGMENT_SINGLE; } else { - data_offset_size = size - (cdata->num_elems + cdata->elems_remaining); + data_offset_size = mod->runtime_params_size - + (cdata->num_elems + cdata->elems_remaining); if (cdata->elems_remaining) pos = MODULE_CFG_FRAGMENT_MIDDLE; else diff --git a/src/include/module/module/base.h b/src/include/module/module/base.h index 657673099d60..8818ffb9f212 100644 --- a/src/include/module/module/base.h +++ b/src/include/module/module/base.h @@ -203,6 +203,13 @@ struct processing_module { struct userspace_context *user_ctx; struct k_mem_domain *mdom; #endif /* CONFIG_USERSPACE */ + + /* total size of a fragmented runtime-params (get/set) transfer, kept + * per instance so concurrent transfers to different components do not + * corrupt each other's reassembly state. Appended here to avoid shifting + * the offsets of the fields above. + */ + uint32_t runtime_params_size; #endif /* SOF_MODULE_PRIVATE */ }; From f810c82d250015efe1294d88d06e7a90b521d940 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 16 Jun 2026 15:00:39 +0200 Subject: [PATCH 086/303] lib-manager: check dma_request_channel() success dma_request_channel() can return a negative error code. Check for it before using the returned value as a channel number. Signed-off-by: Guennadi Liakhovetski --- src/library_manager/lib_manager.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/library_manager/lib_manager.c b/src/library_manager/lib_manager.c index ec26968694fd..b243d59b9f12 100644 --- a/src/library_manager/lib_manager.c +++ b/src/library_manager/lib_manager.c @@ -844,6 +844,9 @@ static int lib_manager_dma_init(struct lib_manager_dma_ext *dma_ext, uint32_t dm } chan_index = dma_request_channel(dma_ext->dma->z_dev, &dma_id); + if (chan_index < 0) + return chan_index; + dma_ext->chan = &dma_ext->dma->chan[chan_index]; if (!dma_ext->chan) return -EINVAL; From 01326c50e568171dfe17fef8da1aa3aa035c89e2 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:08:12 +0100 Subject: [PATCH 087/303] pcm_converter: reject out-of-range channel map at runtime The remap routines validated the source channel nibble only with an assert, which is compiled out in release builds. Fold the bound into the existing mute path so an out-of-range nibble mutes the output instead of indexing past the source frame. Signed-off-by: Liam Girdwood --- src/audio/pcm_converter/pcm_remap.c | 35 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/audio/pcm_converter/pcm_remap.c b/src/audio/pcm_converter/pcm_remap.c index 9204b21ee8ab..f0ad6912a5f8 100644 --- a/src/audio/pcm_converter/pcm_remap.c +++ b/src/audio/pcm_converter/pcm_remap.c @@ -68,13 +68,14 @@ static int remap_c16(const struct audio_stream *source, uint32_t dummy1, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c16(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int16_t *)audio_stream_get_rptr(source) + src_channel; dst = (int16_t *)audio_stream_get_wptr(sink) + sink_channel; @@ -126,13 +127,14 @@ static inline int remap_c32_left_shift(const struct audio_stream *source, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c32(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int32_t *)audio_stream_get_rptr(source) + src_channel; dst = (int32_t *)audio_stream_get_wptr(sink) + sink_channel; @@ -184,13 +186,14 @@ static inline int remap_c32_right_shift(const struct audio_stream *source, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c32(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int32_t *)audio_stream_get_rptr(source) + src_channel; dst = (int32_t *)audio_stream_get_wptr(sink) + sink_channel; @@ -243,13 +246,14 @@ static inline int remap_c16_to_c32(const struct audio_stream *source, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c32(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int16_t *)audio_stream_get_rptr(source) + src_channel; dst = (int32_t *)audio_stream_get_wptr(sink) + sink_channel; @@ -302,13 +306,14 @@ static inline int remap_c32_to_c16(const struct audio_stream *source, src_channel = chmap & 0xf; chmap >>= 4; - if (src_channel == 0xf) { + /* 0xf means "mute"; also mute any out-of-range source channel so + * a crafted chmap nibble cannot index past the source frame. + */ + if (src_channel == 0xf || src_channel >= num_src_channels) { mute_channel_c16(sink, sink_channel, frames); continue; } - assert(src_channel < num_src_channels); - src = (int32_t *)audio_stream_get_rptr(source) + src_channel; dst = (int16_t *)audio_stream_get_wptr(sink) + sink_channel; From 96951d8df3d89eab6ef97ed08c33879eac339e0f Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Mon, 15 Jun 2026 14:29:30 +0300 Subject: [PATCH 088/303] math: auditory: guard mod_psy_get_mel_filterbank() against zero divisors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mel-bin loop in mod_psy_get_mel_filterbank() divides by delta_cl and delta_rc on every FFT bin, and by (right_hz - left_hz) when slaney normalization is enabled. With unusual configurations these denominators can become zero or negative — for example when start_freq >= end_freq, when the requested mel_bins is large enough that integer division truncates the mel step to zero, or at extreme inputs where psy_mel_to_hz saturates and right_hz no longer exceeds left_hz. Reject such configurations explicitly. After computing mel_step, return -EINVAL if it is not positive, which also covers both delta values since they equal mel_step. In the slaney_normalize branch, also verify right_hz > left_hz before computing the scale factor. Signed-off-by: Seppo Ingalsuo --- src/include/sof/math/auditory.h | 1 + src/math/auditory/auditory.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/include/sof/math/auditory.h b/src/include/sof/math/auditory.h index bd707dc5079a..a68f0468d591 100644 --- a/src/include/sof/math/auditory.h +++ b/src/include/sof/math/auditory.h @@ -14,6 +14,7 @@ #include #include +#define AUDITORY_MAX_MEL_BANDS 256 #define AUDITORY_EPS_Q31 1 /* Smallest nonzero Q1.31 value */ #define AUDITORY_LOG2_2P25_Q16 Q_CONVERT_FLOAT(25.0, 16) /* log2(2^25) */ diff --git a/src/math/auditory/auditory.c b/src/math/auditory/auditory.c index 57641c46aa5a..0abd10de9308 100644 --- a/src/math/auditory/auditory.c +++ b/src/math/auditory/auditory.c @@ -12,10 +12,13 @@ #include #include #include +#include #include #include #include +LOG_MODULE_REGISTER(math_auditory, CONFIG_SOF_LOG_LEVEL); + #define ONE_Q16 Q_CONVERT_FLOAT(1, 16) #define ONE_Q20 Q_CONVERT_FLOAT(1, 20) #define ONE_Q30 Q_CONVERT_FLOAT(1, 30) @@ -117,6 +120,16 @@ int mod_psy_get_mel_filterbank(struct processing_module *mod, struct psy_mel_fil if (!fb->scratch_data1 || !fb->scratch_data2) return -ENOMEM; + /* fb->mel_bins is used both as the loop bound and as part of the + * (mel_bins + 1) divisor below. Reject non-positive values up front to + * avoid a divide by zero (mel_bins == -1) and to keep mel_step + * meaningful. + */ + if (fb->mel_bins <= 0 || fb->mel_bins > AUDITORY_MAX_MEL_BANDS) { + comp_cl_err(mod->dev, "Invalid mel_bins %d", fb->mel_bins); + return -EINVAL; + } + /* Log power can be log, or log10 or dB, get multiply coef to convert * log to desired format. */ @@ -149,6 +162,17 @@ int mod_psy_get_mel_filterbank(struct processing_module *mod, struct psy_mel_fil mel_start = psy_hz_to_mel(fb->start_freq); mel_end = psy_hz_to_mel(fb->end_freq); mel_step = (mel_end - mel_start) / (fb->mel_bins + 1); + /* delta_cl / delta_rc below are both equal to mel_step; guard against + * a non-positive step (start_freq >= end_freq, or so many bins that + * the integer division truncates to zero) before using them as + * divisors. + */ + if (mel_step <= 0) { + comp_cl_err(mod->dev, "Invalid mel_step %d (start_freq=%d end_freq=%d mel_bins=%d)", + mel_step, fb->start_freq, fb->end_freq, fb->mel_bins); + return -EINVAL; + } + for (i = 0; i < fb->mel_bins; i++) { left_mel = mel_start + i * mel_step; center_mel = mel_start + (i + 1) * mel_step; @@ -160,6 +184,11 @@ int mod_psy_get_mel_filterbank(struct processing_module *mod, struct psy_mel_fil if (fb->slaney_normalize) { left_hz = psy_mel_to_hz(left_mel); right_hz = psy_mel_to_hz(right_mel); + if (right_hz <= left_hz) { + comp_cl_err(mod->dev, "Invalid Hz range left=%d right=%d at mel bin %d", + left_hz, right_hz, i); + return -EINVAL; + } scale = Q_SHIFT_RND(TWO_Q29 / (right_hz - left_hz), 29, 16); /* Q16.16*/ if (i == 0) { scale_inv = Q_SHIFT_LEFT(ONE_Q30 / scale, 14, 16); From e62284bf1399673d40fb7ffad00f658c07830f96 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 16 Jun 2026 15:09:50 +0200 Subject: [PATCH 089/303] lib-manager: check for invalid module instance count Module instance_max_count should never be 0, assume 1 if such a module is loaded. Signed-off-by: Guennadi Liakhovetski --- src/library_manager/lib_manager.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/library_manager/lib_manager.c b/src/library_manager/lib_manager.c index b243d59b9f12..6019aa41b8c1 100644 --- a/src/library_manager/lib_manager.c +++ b/src/library_manager/lib_manager.c @@ -281,7 +281,10 @@ void lib_manager_get_instance_bss_address(uint32_t instance_id, const struct sof_man_module *mod, void __sparse_cache **va_addr, size_t *size) { - *size = mod->segment[SOF_MAN_SEGMENT_BSS].flags.r.length / mod->instance_max_count * + /* mod->instance_max_count should never be 0, assume 1 if that happens */ + unsigned int inst_cnt = mod->instance_max_count ? : 1; + + *size = mod->segment[SOF_MAN_SEGMENT_BSS].flags.r.length / inst_cnt * PAGE_SZ; size_t inst_offset = *size * instance_id; *va_addr = (void __sparse_cache *)(mod->segment[SOF_MAN_SEGMENT_BSS].v_base_addr + From 689eb09a8ff33ad14c412d89dcd0a231efa96004 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:13 +0100 Subject: [PATCH 090/303] base_fw: reject undersized system time payload The system-time handler dereferenced the payload as a fixed struct without checking the supplied size, reading past the mailbox for a short payload. Reject a payload smaller than the struct. Signed-off-by: Liam Girdwood --- src/audio/base_fw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/audio/base_fw.c b/src/audio/base_fw.c index db2acea19125..113186cd2995 100644 --- a/src/audio/base_fw.c +++ b/src/audio/base_fw.c @@ -277,6 +277,9 @@ __cold static uint32_t basefw_set_system_time(uint32_t param_id, bool first_bloc if (!(first_block && last_block)) return IPC4_INVALID_REQUEST; + if (data_offset < sizeof(struct ipc4_system_time)) + return IPC4_ERROR_INVALID_PARAM; + global_system_time_info.host_time.val_l = ((const struct ipc4_system_time *)data)->val_l; global_system_time_info.host_time.val_u = ((const struct ipc4_system_time *)data)->val_u; From eb6a85fb09df34ff306b47f2b7d79f8b30ea6200 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Fri, 12 Jun 2026 13:33:24 +0200 Subject: [PATCH 091/303] audio: base_fw: validate host core_id in KCPS allocation request basefw_kcps_allocation_request() passed request->core_id from the host IPC payload straight into core_kcps_adjust(), which uses it to index kcps_consumption[CONFIG_CORE_COUNT]. An out-of-range core_id turns the add-assign into an arbitrary relative write into DSP .bss. Reject core_id >= CONFIG_CORE_COUNT at the IPC boundary, mirroring the existing check in schedulers_info_get(). Signed-off-by: Adrian Bonislawski --- src/audio/base_fw.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/audio/base_fw.c b/src/audio/base_fw.c index 113186cd2995..309392ea3e9c 100644 --- a/src/audio/base_fw.c +++ b/src/audio/base_fw.c @@ -344,6 +344,12 @@ __cold static int basefw_kcps_allocation_request(struct ipc4_resource_kcps *requ assert_can_be_cold(); #if CONFIG_KCPS_DYNAMIC_CLOCK_CONTROL + if (request->core_id >= CONFIG_CORE_COUNT) { + tr_err(&ipc_tr, "invalid core_id %u", + request->core_id); + return IPC4_ERROR_INVALID_PARAM; + } + if (core_kcps_adjust(request->core_id, request->kcps)) return IPC4_ERROR_INVALID_PARAM; #endif From edd1f9325dc099b606fe248913316b90e2b1cfee Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Thu, 11 Jun 2026 12:56:13 +0200 Subject: [PATCH 092/303] ipc4: chain_dma: free stale ipc_comp_dev on error path When ipc4_chain_dma_state() fails for a freshly created chain DMA unlink and free the ipc_comp_dev as well, not just the comp_dev Signed-off-by: Adrian Bonislawski --- src/ipc/ipc4/handler-user.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index e98baa5121af..face1799193b 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -601,6 +601,8 @@ __cold static int ipc4_process_chain_dma(struct ipc4_message_request *ipc4) ret = ipc4_chain_dma_state(cdma_comp->cd, &cdma); if (ret < 0) { comp_free(cdma_comp->cd); + list_item_del(&cdma_comp->list); + rfree(cdma_comp); return IPC4_FAILURE; } From f15dc5b416ae9ca406a708db9566aaaaf4865225 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Thu, 11 Jun 2026 13:15:52 +0200 Subject: [PATCH 093/303] ipc: clear dangling pipeline comp refs on instance free ipc_comp_free() freed a component that a completed pipeline still referenced via source_comp/sink_comp/sched_comp, so a later SET_PIPELINE_STATE dereferenced the freed comp_dev. Clear those references on free and bail out of pipeline_get_host_dev() when an endpoint is gone. Signed-off-by: Adrian Bonislawski --- src/ipc/ipc-helper.c | 19 +++++++++++++++++++ src/ipc/ipc4/handler-user.c | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index ad7b3771a16b..b7ef418e0339 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -291,6 +291,7 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) struct ipc_comp_dev *icd; struct comp_buffer *buffer; struct comp_buffer *safe; + struct list_item *clist; uint32_t flags; assert_can_be_cold(); @@ -348,6 +349,24 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) irq_local_enable(flags); + /* + * A completed pipeline stores raw comp_dev pointers in its + * source_comp/sink_comp/sched_comp fields. + */ + list_for_item(clist, &ipc->comp_list) { + struct ipc_comp_dev *ppl_icd = container_of(clist, struct ipc_comp_dev, list); + + if (ppl_icd->type != COMP_TYPE_PIPELINE) + continue; + + if (ppl_icd->pipeline->source_comp == icd->cd) + ppl_icd->pipeline->source_comp = NULL; + if (ppl_icd->pipeline->sink_comp == icd->cd) + ppl_icd->pipeline->sink_comp = NULL; + if (ppl_icd->pipeline->sched_comp == icd->cd) + ppl_icd->pipeline->sched_comp = NULL; + } + /* free component and remove from list */ comp_free(icd->cd); diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index face1799193b..fa7f06edb33d 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -149,6 +149,11 @@ static struct ipc_comp_dev *pipeline_get_host_dev(struct ipc_comp_dev *ppl_icd) struct ipc *ipc = ipc_get(); int host_id; + if (!ppl_icd->pipeline->source_comp || !ppl_icd->pipeline->sink_comp) { + ipc_cmd_err(&ipc_tr, "pipeline %d: source/sink comp freed", ppl_icd->id); + return NULL; + } + /* If the source component's direction is not set but the sink's direction is, * this block will copy the direction from the sink to the source component and * mark the source's direction as set. From 5dcab1d4fc3a2a6c57473052e3696ab62f7fbd9f Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Fri, 12 Jun 2026 12:51:07 +0200 Subject: [PATCH 094/303] audio: copier: validate channels_count in copier_set_gain channels is host-controlled (8-bit, 0-255). Without validation it drives the memcpy length (channels * sizeof(uint16_t)) against a MAX_GAIN_COEFFS_CNT-sized stack buffer and, for channels == 0, causes divide-by-zero in the coefficient replication loop. Reject values outside [1, MAX_GAIN_COEFFS_CNT]. Signed-off-by: Adrian Bonislawski --- src/audio/copier/copier_gain.c | 9 +++++++-- src/audio/copier/copier_gain.h | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/audio/copier/copier_gain.c b/src/audio/copier/copier_gain.c index fc9f6664add8..fbf614fa3dde 100644 --- a/src/audio/copier/copier_gain.c +++ b/src/audio/copier/copier_gain.c @@ -150,7 +150,7 @@ int copier_gain_dma_control(union ipc4_connector_node_id node, const char *confi } struct ipc4_copier_module_cfg *copier_cfg = cd->dd[0]->dai_spec_config; - const int channels = copier_cfg->base.audio_fmt.channels_count; + const uint32_t channels = copier_cfg->base.audio_fmt.channels_count; ret = copier_set_gain(dev, cd->dd[0]->gain_data, gain_data, channels); if (ret) @@ -162,7 +162,7 @@ int copier_gain_dma_control(union ipc4_connector_node_id node, const char *confi } int copier_set_gain(struct comp_dev *dev, struct copier_gain_params *gain_params, - struct gain_dma_control_data *gain_data, int channels) + struct gain_dma_control_data *gain_data, uint32_t channels) { uint16_t static_gain[MAX_GAIN_COEFFS_CNT]; int ret; @@ -172,6 +172,11 @@ int copier_set_gain(struct comp_dev *dev, struct copier_gain_params *gain_params return -EINVAL; } + if (channels == 0 || channels > MAX_GAIN_COEFFS_CNT) { + comp_err(dev, "invalid channels count %u", channels); + return -EINVAL; + } + /* Set gain coefficients */ comp_info(dev, "Update gain coefficients from DMA_CONTROL ipc"); diff --git a/src/audio/copier/copier_gain.h b/src/audio/copier/copier_gain.h index ec55ff999206..bd7cc5719f5b 100644 --- a/src/audio/copier/copier_gain.h +++ b/src/audio/copier/copier_gain.h @@ -8,6 +8,7 @@ #ifndef __SOF_COPIER_GAIN_H__ #define __SOF_COPIER_GAIN_H__ +#include #include #include #include @@ -219,7 +220,7 @@ enum copier_gain_state copier_gain_eval_state(struct copier_gain_params *gain_pa * @return 0 on success, otherwise a negative error code. */ int copier_set_gain(struct comp_dev *dev, struct copier_gain_params *gain_params, - struct gain_dma_control_data *gain_data, int channels); + struct gain_dma_control_data *gain_data, uint32_t channels); /** * Checks for unity gain mode. From 79662c5154be480aa8bed1188b7ac51e3d938399 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Fri, 12 Jun 2026 12:51:25 +0200 Subject: [PATCH 095/303] audio: copier: fix memcpy_s dest_size in copier_set_gain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first memcpy_s call passed gain_coef_size (= channels * sizeof(uint16_t), host-controlled) as the dest_size argument, defeating the bounds check. Pass sizeof(static_gain) — the true buffer capacity so memcpy_s can enforce the limit. Signed-off-by: Adrian Bonislawski --- src/audio/copier/copier_gain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/copier/copier_gain.c b/src/audio/copier/copier_gain.c index fbf614fa3dde..842f92997108 100644 --- a/src/audio/copier/copier_gain.c +++ b/src/audio/copier/copier_gain.c @@ -182,7 +182,7 @@ int copier_set_gain(struct comp_dev *dev, struct copier_gain_params *gain_params size_t gain_coef_size = channels * sizeof(uint16_t); - ret = memcpy_s(static_gain, gain_coef_size, gain_data->gain_coeffs, + ret = memcpy_s(static_gain, sizeof(static_gain), gain_data->gain_coeffs, gain_coef_size); if (ret) { comp_err(dev, "memcpy_s failed with error %d", ret); From a70eabdc9248bbd3f35fb7f547d844ee99b704e8 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 12 Jun 2026 14:18:43 +0200 Subject: [PATCH 096/303] lib-manager: validate ELF sizes and offsets The library manager loads ELF files from the host file-system. Those files have to be only root-writable, so they are relatively trust- worthy - if you're root, it's anyway "game over." Still it's good to verify for corrupt or maniputalte invalid ELF images. Signed-off-by: Guennadi Liakhovetski --- src/library_manager/lib_manager.c | 47 +++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/library_manager/lib_manager.c b/src/library_manager/lib_manager.c index 6019aa41b8c1..a1bafdc025df 100644 --- a/src/library_manager/lib_manager.c +++ b/src/library_manager/lib_manager.c @@ -37,6 +37,7 @@ #if CONFIG_LLEXT #include #endif +#include #if CONFIG_LIBRARY_AUTH_SUPPORT #include @@ -129,7 +130,7 @@ static int lib_manager_auth_proc(const void *buffer_data, size_t buffer_size, #define PAGE_SZ CONFIG_MM_DRV_PAGE_SIZE -static int lib_manager_load_data_from_storage(void __sparse_cache *vma, void *s_addr, uint32_t size, +static int lib_manager_load_data_from_storage(void __sparse_cache *vma, void *s_addr, size_t size, uint32_t flags) { /* Region must be first mapped as writable in order to initialize its contents. */ @@ -147,16 +148,26 @@ static int lib_manager_load_data_from_storage(void __sparse_cache *vma, void *s_ return sys_mm_drv_update_region_flags((__sparse_force void *)vma, size, flags); } -static int lib_manager_load_module(const uint32_t module_id, const struct sof_man_module *const mod) +static int lib_manager_load_module(const struct sof_man_fw_desc *const desc, + const uint32_t module_id, const struct sof_man_module *const mod) { struct lib_manager_mod_ctx *ctx = lib_manager_get_mod_ctx(module_id); const uintptr_t load_offset = POINTER_TO_UINT(ctx->base_addr); + size_t lib_size; + size_t file_offset; void *src; void __sparse_cache *va_base; size_t size; uint32_t flags; int ret, idx; + /* + * ELF file segment offsets and sizes come from library files, so they + * have to be validated. + */ + if (size_mul_overflow(desc->header.preload_page_count, PAGE_SZ, &lib_size)) + return -EOVERFLOW; + for (idx = 0; idx < ARRAY_SIZE(mod->segment); ++idx) { if (!mod->segment[idx].flags.r.load) continue; @@ -168,9 +179,23 @@ static int lib_manager_load_module(const uint32_t module_id, const struct sof_ma else if (!mod->segment[idx].flags.r.readonly) flags = SYS_MM_MEM_PERM_RW; - src = UINT_TO_POINTER(mod->segment[idx].file_offset + load_offset); + file_offset = mod->segment[idx].file_offset; + if (size_mul_overflow(mod->segment[idx].flags.r.length, PAGE_SZ, &size)) { + ret = -EOVERFLOW; + goto err; + } + + /* Reject segments that would read outside the loaded library image. */ + if (file_offset > lib_size || size > lib_size - file_offset) { + tr_err(&lib_manager_tr, + "segment %d out of bounds: file_offset %#zx, size %#zx, total %#zx", + idx, file_offset, size, lib_size); + ret = -ENOSPC; + goto err; + } + + src = UINT_TO_POINTER(file_offset + load_offset); va_base = (void __sparse_cache *)UINT_TO_POINTER(mod->segment[idx].v_base_addr); - size = mod->segment[idx].flags.r.length * PAGE_SZ; ret = lib_manager_load_data_from_storage(va_base, src, size, flags); if (ret < 0) goto err; @@ -230,7 +255,8 @@ static int lib_manager_load_libcode_modules(const uint32_t module_id) for (idx = 0; idx < desc->header.num_module_entries; ++idx, ++module_entry) { if (module_entry->type.lib_code) { - ret = lib_manager_load_module(lib_id << LIB_MANAGER_LIB_ID_SHIFT | idx, + ret = lib_manager_load_module(desc, + lib_id << LIB_MANAGER_LIB_ID_SHIFT | idx, module_entry); if (ret < 0) goto err; @@ -341,7 +367,8 @@ static int lib_manager_free_module_instance(uint32_t instance_id, const struct s * * Function is responsible to allocate module in available free memory and assigning proper address. */ -static uintptr_t lib_manager_allocate_module(const struct sof_man_module *mod, +static uintptr_t lib_manager_allocate_module(const struct sof_man_fw_desc *const desc, + const struct sof_man_module *mod, const struct comp_ipc_config *ipc_config, const void *ipc_specific_config) { @@ -354,7 +381,7 @@ static uintptr_t lib_manager_allocate_module(const struct sof_man_module *mod, if (module_is_llext(mod)) return llext_manager_allocate_module(ipc_config, ipc_specific_config); - ret = lib_manager_load_module(module_id, mod); + ret = lib_manager_load_module(desc, module_id, mod); if (ret < 0) return 0; @@ -427,7 +454,8 @@ static int lib_manager_free_module(const uint32_t component_id) #define PAGE_SZ 4096 /* equals to MAN_PAGE_SIZE used by rimage */ -static uintptr_t lib_manager_allocate_module(const struct comp_ipc_config *ipc_config, +static uintptr_t lib_manager_allocate_module(const struct sof_man_fw_desc *const desc, + const struct comp_ipc_config *ipc_config, const void *ipc_specific_config, const void **buildinfo) { tr_err(&lib_manager_tr, "Dynamic module allocation is not supported"); @@ -647,7 +675,8 @@ static struct comp_dev *lib_manager_module_create(const struct comp_driver *drv, mod = (const struct sof_man_module *) ((const uint8_t *)desc + SOF_MAN_MODULE_OFFSET(entry_index)); - const uintptr_t module_entry_point = lib_manager_allocate_module(mod, config, args->data); + const uintptr_t module_entry_point = lib_manager_allocate_module(desc, mod, config, + args->data); if (!module_entry_point) { tr_err(&lib_manager_tr, "lib_manager_allocate_module() failed!"); From e11e51bc380063e68b9a4737ab2dc785fe467333 Mon Sep 17 00:00:00 2001 From: jmestwa-coder Date: Tue, 9 Jun 2026 10:28:34 +0530 Subject: [PATCH 097/303] logger: fix off-by-one sscanf width in filter_parse_component_name filter_parse_component_name() builds the sscanf format string with field width UUID_NAME_MAX_LEN, but a %N[...] conversion writes up to N characters plus a NUL terminator. comp_name is only UUID_NAME_MAX_LEN bytes, so a component name of exactly that length overflows the stack buffer by one byte. Cap the scan width at UUID_NAME_MAX_LEN - 1 so the terminator always fits in comp_name. Signed-off-by: jmestwa-coder --- tools/logger/filter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/logger/filter.c b/tools/logger/filter.c index 0943f68d16b2..7a3d53c37246 100644 --- a/tools/logger/filter.c +++ b/tools/logger/filter.c @@ -104,7 +104,7 @@ static char *filter_parse_component_name(char *input_str, struct filter_element */ if (strlen(scan_format_string) == 0) { ret = snprintf(scan_format_string, sizeof(scan_format_string), - "%%%d[^0-9* ]s", UUID_NAME_MAX_LEN); + "%%%d[^0-9* ]s", UUID_NAME_MAX_LEN - 1); if (ret <= 0) return NULL; } From 1a14fccd90b8e391769cab66e6f7cfc42556f961 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Thu, 11 Jun 2026 16:59:35 +0200 Subject: [PATCH 098/303] mux: ipc4: validate channels_count before building mask build_config() looped over host-supplied channels_count writing streams[].mask[] (size PLATFORM_MAX_CHANNELS), overflowing it. Reject counts above the max. Signed-off-by: Adrian Bonislawski --- src/audio/mux/mux_ipc4.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/audio/mux/mux_ipc4.c b/src/audio/mux/mux_ipc4.c index dc6877059f57..ed7195fc0e6b 100644 --- a/src/audio/mux/mux_ipc4.c +++ b/src/audio/mux/mux_ipc4.c @@ -36,9 +36,21 @@ static int build_config(struct processing_module *mod, struct mux_data *cfg) { struct comp_dev *dev = mod->dev; struct comp_data *cd = module_get_private_data(mod); + uint32_t base_ch = cfg->base_cfg.audio_fmt.channels_count; + uint32_t ref_ch = cfg->reference_format.channels_count; + uint32_t out_ch = cfg->output_format.channels_count; int mask = 1; int i; + /* base+reference map 1:1 to contiguous output channels: sink width + * must equal their sum and fit the 8-bit output mask + */ + if (base_ch + ref_ch > PLATFORM_MAX_CHANNELS || out_ch != base_ch + ref_ch) { + comp_err(dev, "invalid channel count: base %u + reference %u -> output %u (max %d)", + base_ch, ref_ch, out_ch, PLATFORM_MAX_CHANNELS); + return -EINVAL; + } + cd->config.num_streams = MUX_MAX_STREAMS; /* clear masks */ @@ -46,12 +58,12 @@ static int build_config(struct processing_module *mod, struct mux_data *cfg) memset(cd->config.streams[i].mask, 0, sizeof(cd->config.streams[i].mask)); /* Setting masks for streams */ - for (i = 0; i < cfg->base_cfg.audio_fmt.channels_count; i++) { + for (i = 0; i < base_ch; i++) { cd->config.streams[0].mask[i] = mask; mask <<= 1; } - for (i = 0; i < cfg->reference_format.channels_count; i++) { + for (i = 0; i < ref_ch; i++) { cd->config.streams[1].mask[i] = mask; mask <<= 1; } From f558e10c8a1d67806dd3cfafbb81ade0561bcc5d Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Thu, 11 Jun 2026 17:00:01 +0200 Subject: [PATCH 099/303] mux: ipc4: reject out-of-range source queue ID set_mux_params() indexed streams[] with the host-supplied bind queue ID without bounds, allowing an OOB write. Validate against MUX_MAX_STREAMS and propagate the error. Signed-off-by: Adrian Bonislawski --- src/audio/mux/mux_ipc4.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/audio/mux/mux_ipc4.c b/src/audio/mux/mux_ipc4.c index ed7195fc0e6b..f5ead3a93368 100644 --- a/src/audio/mux/mux_ipc4.c +++ b/src/audio/mux/mux_ipc4.c @@ -80,7 +80,7 @@ static int build_config(struct processing_module *mod, struct mux_data *cfg) * set up param then verify param. BTW for IPC3 path, the param is sent by * host driver. */ -static void set_mux_params(struct processing_module *mod, struct mux_data *cfg) +static int set_mux_params(struct processing_module *mod, struct mux_data *cfg) { struct sof_ipc_stream_params *params = mod->stream_params; struct comp_data *cd = module_get_private_data(mod); @@ -119,6 +119,12 @@ static void set_mux_params(struct processing_module *mod, struct mux_data *cfg) comp_dev_for_each_producer(dev, source) { j = IPC4_SINK_QUEUE_ID(buf_get_id(source)); + /* host-supplied queue ID indexes streams[] */ + if (j >= MUX_MAX_STREAMS) { + comp_err(dev, "invalid source queue ID %d (valid range 0..%d)", + j, MUX_MAX_STREAMS - 1); + return -EINVAL; + } cd->config.streams[j].pipeline_id = buffer_pipeline_id(source); if (j == BASE_CFG_QUEUED_ID) audio_fmt = &cfg->base_cfg.audio_fmt; @@ -130,6 +136,8 @@ static void set_mux_params(struct processing_module *mod, struct mux_data *cfg) } mux_prepare_look_up_table(mod); + + return 0; } int mux_params(struct processing_module *mod) @@ -149,8 +157,6 @@ int mux_params(struct processing_module *mod) if (ret < 0) return ret; - set_mux_params(mod, cfg); - - return ret; + return set_mux_params(mod, cfg); } #endif /* CONFIG_COMP_MUX */ From ab50419f6ce7c6305c2211aae67bb896d89a9798 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Thu, 11 Jun 2026 17:00:18 +0200 Subject: [PATCH 100/303] mux: ipc3: enforce mux_mix_check result mux_set_values() logged a mux_mix_check() failure but still returned success, applying an invalid routing matrix. Return -EINVAL, matching the IPC4 path. Signed-off-by: Adrian Bonislawski --- src/audio/mux/mux_ipc3.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/audio/mux/mux_ipc3.c b/src/audio/mux/mux_ipc3.c index fb00030b5814..b8454043e0fd 100644 --- a/src/audio/mux/mux_ipc3.c +++ b/src/audio/mux/mux_ipc3.c @@ -70,8 +70,10 @@ static int mux_set_values(struct processing_module *mod) } if (cd->comp_type == SOF_COMP_MUX) { - if (mux_mix_check(cfg)) + if (mux_mix_check(cfg)) { comp_err(dev, "mux component is not able to mix channels"); + return -EINVAL; + } } for (i = 0; i < cfg->num_streams; i++) { From a62ee6c4e4b11b1462e7f4ba62a7d22bef019b6d Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Mon, 15 Jun 2026 12:44:31 +0200 Subject: [PATCH 101/303] ipc4: helper: bound host TLV length in DMA config walk Reject a gateway-config TLV whose length overruns the buffer so the uintptr_t walk cannot wrap past the low address space. Signed-off-by: Adrian Bonislawski --- src/ipc/ipc4/helper.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 8065e24e70f6..709063f7a68c 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -1314,12 +1314,18 @@ int ipc4_find_dma_config(struct ipc_config_dai *dai, uint8_t *data_buffer, uint3 int ipc4_find_dma_config_multiple(struct ipc_config_dai *dai, uint8_t *data_buffer, uint32_t size, uint32_t device_id, int dma_cfg_idx) { - uint32_t end_addr = (uint32_t)data_buffer + size; + uintptr_t end_addr = (uintptr_t)data_buffer + size; struct ipc_dma_config *dma_cfg; struct sof_tlv *tlvs; - for (tlvs = (struct sof_tlv *)data_buffer; tlvs && (uint32_t)tlvs < end_addr; + for (tlvs = (struct sof_tlv *)data_buffer; tlvs && (uintptr_t)tlvs < end_addr; tlvs = tlv_next(tlvs)) { + /* Reject a host TLV that overruns the buffer or wraps tlv_next(). */ + uintptr_t remaining = end_addr - (uintptr_t)tlvs; + + if (remaining < sizeof(*tlvs) || tlvs->length > remaining - sizeof(*tlvs)) + return IPC4_INVALID_REQUEST; + dma_cfg = tlv_value_ptr_get(tlvs, GTW_DMA_CONFIG_ID); if (!dma_cfg) continue; From 83504c5aa69bd715fc5dd60dcfce23551ab8f641 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 11 Jun 2026 17:00:05 +0300 Subject: [PATCH 102/303] plugin: tplg: bound pipeline_list against TPLG_MAX_PCM_PIPELINES plug_prepare_widget() appends each new pipeline referenced by a PCM into the fixed-size pipeline_list->pipelines[] array: pipeline_list->pipelines[pipeline_list->count] = comp_info->pipe_info; pipeline_list->count++; The array has only TPLG_MAX_PCM_PIPELINES entries, but the number of pipelines bound to a PCM is dictated by the topology graph, which comes from the .tplg file loaded by the SOF ALSA plugin. With no upper-bound check, a topology that binds more than TPLG_MAX_PCM_PIPELINES pipelines to a single PCM writes past the end of the array. Reject the store with -EINVAL once the list is full, before writing past the end of the array. Signed-off-by: Jyri Sarha --- tools/plugin/alsaplug/tplg.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/plugin/alsaplug/tplg.c b/tools/plugin/alsaplug/tplg.c index 7b05b7ead50a..140a0aac91c6 100644 --- a/tools/plugin/alsaplug/tplg.c +++ b/tools/plugin/alsaplug/tplg.c @@ -999,6 +999,12 @@ static int plug_prepare_widget(snd_sof_plug_t *plug, struct tplg_pcm_info *pcm_i } if (i == pipeline_list->count) { + if (pipeline_list->count >= TPLG_MAX_PCM_PIPELINES) { + SNDERR("error: too many pipelines for PCM, max %d\n", + TPLG_MAX_PCM_PIPELINES); + return -EINVAL; + } + pipeline_list->pipelines[pipeline_list->count] = comp_info->pipe_info; pipeline_list->count++; } From dabc6eb737521602c98127a0aa3d651608c1e415 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:07:54 +0100 Subject: [PATCH 103/303] dts: validate parameter size against remaining config blob The configuration parser advanced through packed parameters using a size field read from the blob without checking it against the bytes remaining, allowing reads past the configuration data. Track the remaining length and reject a header or parameter that does not fit. Signed-off-by: Liam Girdwood --- src/audio/codec/dts/dts.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/audio/codec/dts/dts.c b/src/audio/codec/dts/dts.c index cd1da3363517..bc4795055576 100644 --- a/src/audio/codec/dts/dts.c +++ b/src/audio/codec/dts/dts.c @@ -328,9 +328,15 @@ static int dts_codec_apply_config(struct processing_module *mod) /* Allow for multiple module_params to be packed into the data pointed to by config */ + param_header_size = sizeof(param->id) + sizeof(param->size); for (i = 0; i < config_data_size; param_number++) { + /* Need at least a param header in the remaining bytes to read id/size */ + if (config_data_size - i < param_header_size) { + comp_err(dev, "param header truncated"); + return -EINVAL; + } + param = (struct module_param *)((char *)config->data + i); - param_header_size = sizeof(param->id) + sizeof(param->size); /* If param->size is less than param_header_size, then this param is not valid */ if (param->size < param_header_size) { @@ -338,6 +344,13 @@ static int dts_codec_apply_config(struct processing_module *mod) return -EINVAL; } + /* The whole param (header + data) must fit in the remaining config data */ + if (param->size > config_data_size - i) { + comp_err(dev, "param size %u exceeds remaining %u", + param->size, config_data_size - i); + return -EINVAL; + } + /* Only process param->data if it has size greater than 0 */ if (param->size > param_header_size) { /* Calculate size of param->data */ From 36e6e05283497849011e07a404ccd2996a276e6e Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 28 May 2026 13:12:03 +0200 Subject: [PATCH 104/303] platform: posix: ipc: expose fuzz-case staging-state hooks The libFuzzer entry point in fuzz.c stages each testcase by writing posix_fuzz_buf/sz and raising the fuzz IRQ; fuzz_isr() then drains those bytes into the static fuzz_in[] queue and feeds them into the IPC layer one message at a time. Two pieces of state therefore survive across LLVMFuzzerTestOneInput() calls: * `posix_fuzz_sz` - the raw input length still to consume, * `fuzz_in[] / _sz` - the per-call staging queue. The fuzzer harness has no way to inspect either of them today, which makes it impossible to tell whether a previous testcase fully drained before the next one begins. That is the root cause of the "not reproducible" crashes. Introduce three small helpers, kept in the module that owns the state, with no callers yet: posix_fuzz_case_begin() - drop the staging queue at the start of a new testcase, posix_fuzz_case_pending() - true while either buffer still has bytes to deliver, posix_fuzz_case_abort() - wipe both buffers (used when a case exceeds the simulator tick budget). A follow-up commit wires these into LLVMFuzzerTestOneInput(). This commit is a pure code-addition refactor: no callers, no behaviour change, the build still emits the same object code for the existing entry points. Signed-off-by: Tomasz Leman --- src/platform/posix/include/platform/fuzz.h | 34 ++++++++++++++++++++++ src/platform/posix/ipc.c | 25 ++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/platform/posix/include/platform/fuzz.h diff --git a/src/platform/posix/include/platform/fuzz.h b/src/platform/posix/include/platform/fuzz.h new file mode 100644 index 000000000000..91f28b3285d2 --- /dev/null +++ b/src/platform/posix/include/platform/fuzz.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Copyright(c) 2026 Intel Corporation. All rights reserved. */ + +#ifndef PLATFORM_POSIX_FUZZ_H +#define PLATFORM_POSIX_FUZZ_H + +#include + +/** + * @brief Reset IPC staging state at the start of a new fuzz testcase. + * + * Must be called before staging new fuzz input so that any leftover + * state from a previous testcase that failed to drain within the tick + * budget cannot influence the new one. + */ +void posix_fuzz_case_begin(void); + +/** + * @brief Query whether any staged IPC fuzz input is still pending. + * + * @return true if the raw input buffer or the IPC staging queue is + * non-empty, false when fully drained. + */ +bool posix_fuzz_case_pending(void); + +/** + * @brief Hard-reset all staged IPC fuzz state. + * + * Called when the simulator tick budget is exhausted before the input + * has been fully drained, ensuring the next testcase starts clean. + */ +void posix_fuzz_case_abort(void); + +#endif /* PLATFORM_POSIX_FUZZ_H */ diff --git a/src/platform/posix/ipc.c b/src/platform/posix/ipc.c index 35e203c2d468..feb4bb03873c 100644 --- a/src/platform/posix/ipc.c +++ b/src/platform/posix/ipc.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright(c) 2022 Google LLC. All rights reserved. // Author: Andy Ross +#include #include #include #include @@ -8,6 +9,8 @@ #include #include #include +#include +#include // 6c8f0d53-ff77-4ca1-b825-c0c4e1b0d322 SOF_DEFINE_REG_UUID(ipc_task_posix); @@ -33,6 +36,28 @@ extern size_t posix_fuzz_sz; static uint8_t fuzz_in[65536]; static size_t fuzz_in_sz; +/* + * Testcase-isolation helpers used by the libFuzzer entry point in + * fuzz.c. They keep ownership of the cross-call state in one module + * so a new testcase never observes leftovers from a previous one that + * failed to drain inside the simulator tick budget. + */ +void posix_fuzz_case_begin(void) +{ + fuzz_in_sz = 0; +} + +bool posix_fuzz_case_pending(void) +{ + return posix_fuzz_sz != 0 || fuzz_in_sz != 0; +} + +void posix_fuzz_case_abort(void) +{ + posix_fuzz_sz = 0; + fuzz_in_sz = 0; +} + // The protocol here is super simple: the first byte is a message size // in units of 16 bits (the buffer maximum defaults to 384 bytes, and // I didn't want to waste space early in the buffer lest I confuse the From 019472514d2c3c504c07e463a5c001221e5272e2 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 28 May 2026 13:13:18 +0200 Subject: [PATCH 105/303] platform: posix: drain-or-abort loop for fuzz testcase isolation The libFuzzer harness used to stage the testcase bytes, raise the fuzz IRQ, and then unconditionally run the native_sim scheduler for CONFIG_ZEPHYR_POSIX_FUZZ_TICKS ticks before returning. That has two problems for reproducibility: * If the OS finishes draining the IPC much faster than the tick budget (the common case), we still burn the full budget, which slows exec/s without buying any coverage. * If the OS does NOT finish within the budget (deep handlers, long pipeline walks, large payloads), the staged input buffer plus the per-call fuzz_in[] queue carry over into the next testcase. That leaks state across cases and is the root cause of crashes that disappear when replayed individually. Split the budget into POSIX_FUZZ_DRAIN_QUANTA (=8) quanta and after each one ask the IPC layer whether anything is still pending; return as soon as the queue is empty, otherwise run the abort hook to wipe both the raw fuzz buffer and the staged IPC payload before the next call. Together with the hooks added in the previous commit this guarantees that LLVMFuzzerTestOneInput observes a clean staging state on entry regardless of what the previous case did. No protocol or coverage change is intended; the goal is reproducible crashes and slightly higher throughput on short inputs. Signed-off-by: Tomasz Leman --- src/platform/posix/fuzz.c | 43 +++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/platform/posix/fuzz.c b/src/platform/posix/fuzz.c index e5c515c15a45..e60d8e549035 100644 --- a/src/platform/posix/fuzz.c +++ b/src/platform/posix/fuzz.c @@ -10,27 +10,48 @@ #include #include #include +#include const uint8_t *posix_fuzz_buf; size_t posix_fuzz_sz; +/* Number of simulator quanta the budget is split into for the + * drain-or-abort loop. More quanta = earlier exit on quick testcases. + */ +#define POSIX_FUZZ_DRAIN_QUANTA 8 + /** * Entry point for fuzzing. Works by placing the data * into two known symbols, triggering an app-visible interrupt, and - * then letting the simulator run for a fixed amount of time (intended to be - * "long enough" to handle the event and reach a quiescent state - * again) + * then letting the simulator run for up to a fixed amount of time + * split into small quanta, exiting as soon as the OS has drained the + * staged fuzz input. If the budget is exhausted before drain we drop + * pending state to keep testcases isolated. */ NATIVE_SIMULATOR_IF int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz) { static bool runner_initialized; + uint64_t total_us; + uint64_t quantum_us; + int i; if (!runner_initialized) { nsi_init(0, NULL); runner_initialized = true; } + total_us = k_ticks_to_us_ceil64(CONFIG_ZEPHYR_POSIX_FUZZ_TICKS); + quantum_us = (total_us + POSIX_FUZZ_DRAIN_QUANTA - 1) / POSIX_FUZZ_DRAIN_QUANTA; + if (quantum_us == 0) + quantum_us = 1; + + /* Fresh testcase: drop any leftovers from a previous case before + * staging the new buffer, so state from the prior input cannot + * influence this one. + */ + posix_fuzz_case_begin(); + /* Provide the fuzz data to the embedded OS as an interrupt, with * "DMA-like" data placed into posix_fuzz_buf/sz. */ @@ -38,9 +59,19 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz) posix_fuzz_sz = sz; hw_irq_ctrl_set_irq(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ); - /* Give the OS time to process whatever happened in that - * interrupt and reach an idle state. + /* Bounded drain loop: run simulator in small quanta and exit + * as soon as both the raw input buffer and the staged IPC + * queue are empty. + */ + for (i = 0; i < POSIX_FUZZ_DRAIN_QUANTA; i++) { + nsi_exec_for(quantum_us); + if (!posix_fuzz_case_pending()) + return 0; + } + + /* Budget exhausted without full drain: hard reset so the next + * testcase starts clean. */ - nsi_exec_for(k_ticks_to_us_ceil64(CONFIG_ZEPHYR_POSIX_FUZZ_TICKS)); + posix_fuzz_case_abort(); return 0; } From c448617de34b7dcc5f93cfac8b34e2c22feb08ca Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:07:36 +0100 Subject: [PATCH 106/303] ipc3: bound host page count before page table DMA and parsing The host-supplied ring->pages drives both the page table DMA transfer length (20 bits per page) and the DSP-side descriptor allocations, but only ring->size was sanity checked. A large page count could overflow the fixed-size page table buffer and wrap the page-count multiplication. Reject a zero or too-large page count at the single entry point before any use. Signed-off-by: Liam Girdwood --- src/ipc/ipc3/host-page-table.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/ipc/ipc3/host-page-table.c b/src/ipc/ipc3/host-page-table.c index e90203a79dce..dbd0fd0fe030 100644 --- a/src/ipc/ipc3/host-page-table.c +++ b/src/ipc/ipc3/host-page-table.c @@ -16,6 +16,25 @@ LOG_MODULE_DECLARE(ipc, CONFIG_SOF_LOG_LEVEL); +/* + * Size in bytes of the DSP-side buffer that receives the compressed host page + * table. DSP targets define PLATFORM_PAGE_TABLE_SIZE; the host/library builds + * (e.g. testbench, fuzzer) allocate it as HOST_PAGE_SIZE in drivers/host/ipc.c + * and do not define PLATFORM_PAGE_TABLE_SIZE, so fall back to that. + */ +#ifndef PLATFORM_PAGE_TABLE_SIZE +#define PLATFORM_PAGE_TABLE_SIZE HOST_PAGE_SIZE +#endif + +/* + * The compressed page table stores one 20-bit entry per host page, so the + * number of pages whose entries fit in the page table buffer is + * (buffer bytes * 8 bits/byte) / 20 bits/page. + */ +#define HOST_PAGE_TABLE_BITS_PER_PAGE 20 +#define HOST_PAGE_TABLE_MAX_PAGES \ + (PLATFORM_PAGE_TABLE_SIZE * 8 / HOST_PAGE_TABLE_BITS_PER_PAGE) + /* * Parse the host page tables and create the audio DMA SG configuration * for host audio DMA buffer. This involves creating a dma_sg_elem for each @@ -216,6 +235,19 @@ int ipc_process_host_buffer(struct ipc *ipc, struct ipc_data_host_buffer *data_host_buffer; int err; + /* + * The host-supplied page count is used both to size DSP-side + * allocations and to compute the DMA transfer length for the + * compressed page table. Reject a count that would not fit in the + * page table buffer before doing any arithmetic that could overflow + * (ring->pages * 20). pages == 0 is also invalid and would underflow + * the ring->size sanity check in ipc_parse_page_descriptors(). + */ + if (ring->pages == 0 || ring->pages > HOST_PAGE_TABLE_MAX_PAGES) { + tr_err(&ipc_tr, "ipc: invalid page count %u", ring->pages); + return -EINVAL; + } + data_host_buffer = ipc_platform_get_host_buffer(ipc); dma_sg_init(elem_array); From 8cc6c4ecdc898bfe7857a348baca59c22086137f Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:03 +0100 Subject: [PATCH 107/303] ipc3: avoid overflow in the process size bounds check The bounds check added two host-supplied 32-bit sizes, which could wrap and let an oversized value pass. Compare without adding by subtracting from the maximum instead. Signed-off-by: Liam Girdwood --- src/ipc/ipc3/helper.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ipc/ipc3/helper.c b/src/ipc/ipc3/helper.c index 149ff6008a1a..901ba4d06a3f 100644 --- a/src/ipc/ipc3/helper.c +++ b/src/ipc/ipc3/helper.c @@ -302,7 +302,11 @@ static int comp_specific_builder(struct sof_ipc_comp *comp, if (IPC_TAIL_IS_SIZE_INVALID(*proc)) return -EBADMSG; - if (proc->comp.hdr.size + proc->size > SOF_IPC_MSG_MAX_SIZE) + /* compare without adding the two host-supplied uint32_t values, + * which could wrap and let an oversized proc->size pass + */ + if (proc->comp.hdr.size > SOF_IPC_MSG_MAX_SIZE || + proc->size > SOF_IPC_MSG_MAX_SIZE - proc->comp.hdr.size) return -EBADMSG; config->process.type = proc->type; From 04d0187f03e1a01cb6040dc82946870098729af4 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Mon, 15 Jun 2026 12:28:51 +0200 Subject: [PATCH 108/303] audio: igo_nr: bound host active_channel_idx before adopting config Adopt the blob only when igo_nr_check_config_validity() passes so a host index past process_enable[] cannot reach prepare/process. Signed-off-by: Adrian Bonislawski --- src/audio/igo_nr/igo_nr.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/igo_nr/igo_nr.c b/src/audio/igo_nr/igo_nr.c index e398af7d964b..54980d977f33 100644 --- a/src/audio/igo_nr/igo_nr.c +++ b/src/audio/igo_nr/igo_nr.c @@ -719,9 +719,9 @@ static void igo_nr_set_igo_params(struct processing_module *mod) struct comp_dev *dev = mod->dev; comp_info(dev, "entry"); - igo_nr_check_config_validity(dev, cd); - if (p_config) { + /* Adopt the host blob only when new config is valid */ + if (p_config && igo_nr_check_config_validity(dev, cd) == 0) { comp_info(dev, "New config detected."); cd->config = *p_config; igo_nr_print_config(mod); From fdbf856da858222e6303dea5da0ddd71c0f83296 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Wed, 17 Jun 2026 14:34:13 +0100 Subject: [PATCH 109/303] rtnr: reject out-of-range control switch element counts The SWITCH control get and set handlers looped over a host-supplied element count while reading/writing the control channel array, which could run past the IPC payload. Reject counts larger than the maximum channel count in both handlers before the loop. Signed-off-by: Liam Girdwood --- src/audio/rtnr/rtnr.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/audio/rtnr/rtnr.c b/src/audio/rtnr/rtnr.c index 45ff62a60f8d..13c4f8d12b4d 100644 --- a/src/audio/rtnr/rtnr.c +++ b/src/audio/rtnr/rtnr.c @@ -454,6 +454,11 @@ static int rtnr_get_config(struct processing_module *mod, return rtnr_get_bin_data(mod, cdata, fragment_size); case SOF_CTRL_CMD_SWITCH: + if (cdata->num_elems > SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "num_elems %u > max %u", + cdata->num_elems, SOF_IPC_MAX_CHANNELS); + return -EINVAL; + } for (j = 0; j < cdata->num_elems; j++) { cdata->chanv[j].channel = j; cdata->chanv[j].value = cd->process_enable; @@ -560,6 +565,12 @@ static int32_t rtnr_set_value(struct processing_module *mod, void *ctl_data) uint32_t val = 0; int32_t j; + if (cdata->num_elems > SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "num_elems %u > max %u", + cdata->num_elems, SOF_IPC_MAX_CHANNELS); + return -EINVAL; + } + for (j = 0; j < cdata->num_elems; j++) { val |= cdata->chanv[j].value; comp_dbg(dev, "value = %u", val); From e1f10a16e525a524866c474934f75630194665b1 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:24 +0100 Subject: [PATCH 110/303] lib_manager: bound build info offset to the library size The build info pointer was derived from a manifest-supplied text segment offset without bounds, so a crafted manifest could read outside the library buffer. Validate the offset against the library image size before dereferencing and fail the module type lookup otherwise. Signed-off-by: Liam Girdwood --- src/library_manager/lib_manager.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/library_manager/lib_manager.c b/src/library_manager/lib_manager.c index a1bafdc025df..a691850c2ef6 100644 --- a/src/library_manager/lib_manager.c +++ b/src/library_manager/lib_manager.c @@ -599,6 +599,8 @@ static enum buildinfo_mod_type lib_manager_get_module_type(const struct sof_man_ const struct sof_module_api_build_info *const build_info = (const struct sof_module_api_build_info *)((const char *)desc - SOF_MAN_ELF_TEXT_OFFSET + mod->segment[SOF_MAN_SEGMENT_TEXT].file_offset); + const size_t lib_size = (size_t)desc->header.preload_page_count * PAGE_SZ; + const uint32_t text_off = mod->segment[SOF_MAN_SEGMENT_TEXT].file_offset; /* * llext modules store build info structure in separate section which is not accessible now. @@ -606,6 +608,17 @@ static enum buildinfo_mod_type lib_manager_get_module_type(const struct sof_man_ if (module_is_llext(mod)) return MOD_TYPE_LLEXT; + /* + * build_info is derived from a manifest-supplied file_offset; bound it + * against the library image size before dereferencing so a crafted + * offset cannot read outside the library buffer. + */ + if (text_off > lib_size || lib_size - text_off < sizeof(*build_info)) { + tr_err(&lib_manager_tr, "Invalid TEXT file_offset %u, lib_size %zu", + text_off, lib_size); + return MOD_TYPE_INVALID; + } + tr_info(&lib_manager_tr, "Module API version: %u.%u.%u, format: 0x%x", build_info->api_version_number.fields.major, build_info->api_version_number.fields.middle, From 51f5965f21eb39a21e461d26e8c226fd17d27623 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:24 +0100 Subject: [PATCH 111/303] lib_manager: check context allocation on dram restore The resume path allocated a library context and immediately wrote through it without a NULL check, crashing under memory pressure. Check the allocation and fail the restore gracefully. Signed-off-by: Liam Girdwood --- src/library_manager/llext_manager_dram.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/library_manager/llext_manager_dram.c b/src/library_manager/llext_manager_dram.c index 25c00f47b4e1..2f6cff2b3501 100644 --- a/src/library_manager/llext_manager_dram.c +++ b/src/library_manager/llext_manager_dram.c @@ -200,10 +200,14 @@ int llext_manager_restore_from_dram(void) continue; } - /* Panics on failure - use the same zone as during the first boot */ struct lib_manager_mod_ctx *ctx = rmalloc(SOF_MEM_FLAG_KERNEL | SOF_MEM_FLAG_COHERENT, sizeof(*ctx)); + if (!ctx) { + tr_err(&lib_manager_tr, "library context allocation failure"); + goto nomem; + } + /* Restore the library context */ *ctx = lib_manager_dram.ctx[j++]; From 689e3bf181e8458de9022b056b6f98732e8723a4 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:24 +0100 Subject: [PATCH 112/303] tools/probe: clamp out-of-range sample-rate index to a valid default The 4-bit sample-rate field can select an index past the end of the sample_rate[] table, which has fewer than 16 entries, reading out of bounds. Clamp an out-of-range index to a valid default rate (48000 Hz) so the read stays in bounds and the generated WAV header keeps a usable sample rate. Signed-off-by: Liam Girdwood --- tools/probes/probes_demux.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/probes/probes_demux.c b/tools/probes/probes_demux.c index d220bb3fcd6f..b92aaf88a19b 100644 --- a/tools/probes/probes_demux.c +++ b/tools/probes/probes_demux.c @@ -126,7 +126,17 @@ int init_wave(struct dma_frame_parser *p, uint32_t buffer_id, uint32_t format) p->files[i].header.fmt.subchunk_size = 16; p->files[i].header.fmt.audio_format = 1; p->files[i].header.fmt.num_channels = ((format & PROBE_MASK_NB_CHANNELS) >> PROBE_SHIFT_NB_CHANNELS) + 1; - p->files[i].header.fmt.sample_rate = sample_rate[(format & PROBE_MASK_SAMPLE_RATE) >> PROBE_SHIFT_SAMPLE_RATE]; + /* the sample-rate field is 4 bits (0-15) but the table has fewer + * entries; fall back to a valid default (48000 Hz) for an out-of-range + * index so the read stays in bounds and the WAV header keeps a usable + * rate + */ + uint32_t rate_idx = (format & PROBE_MASK_SAMPLE_RATE) >> PROBE_SHIFT_SAMPLE_RATE; + + if (rate_idx >= sizeof(sample_rate) / sizeof(sample_rate[0])) + p->files[i].header.fmt.sample_rate = 48000; + else + p->files[i].header.fmt.sample_rate = sample_rate[rate_idx]; p->files[i].header.fmt.bits_per_sample = (((format & PROBE_MASK_CONTAINER_SIZE) >> PROBE_SHIFT_CONTAINER_SIZE) + 1) * 8; p->files[i].header.fmt.byte_rate = p->files[i].header.fmt.sample_rate * p->files[i].header.fmt.num_channels * From 1c62c0150ad2e1e3973d2580beac08dfa18f988f Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:24 +0100 Subject: [PATCH 113/303] fft: free the plan on the invalid word length path The plan was allocated before the word-length switch and leaked when the default case rejected an unsupported length. Free it before returning. Signed-off-by: Liam Girdwood --- src/math/fft/fft_common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/math/fft/fft_common.c b/src/math/fft/fft_common.c index 5ce47acd025a..589571881443 100644 --- a/src/math/fft/fft_common.c +++ b/src/math/fft/fft_common.c @@ -53,6 +53,7 @@ struct fft_plan *fft_plan_common_new(struct processing_module *mod, void *inb, break; default: comp_cl_err(mod->dev, "Invalid word length."); + mod_free(mod, plan); return NULL; } From 98b201c14fb3bd4c214f468dcedd5ea2b5ab90c2 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 10 Jun 2026 11:57:33 +0200 Subject: [PATCH 114/303] idc: make two functions cold Make idc_init_thread() and idc_init() cold, they are only invoked during initialisation. Signed-off-by: Guennadi Liakhovetski --- src/idc/idc.c | 5 ++++- src/idc/zephyr_idc.c | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/idc/idc.c b/src/idc/idc.c index bafa44299073..dab0196b634c 100644 --- a/src/idc/idc.c +++ b/src/idc/idc.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -419,10 +420,12 @@ void idc_cmd(struct idc_msg *msg) } /* Runs on each CPU */ -int idc_init(void) +__cold int idc_init(void) { struct idc **idc = idc_get(); + assert_can_be_cold(); + tr_dbg(&idc_tr, "entry"); /* initialize idc data */ diff --git a/src/idc/zephyr_idc.c b/src/idc/zephyr_idc.c index 8ffc770eb6ee..18f47bb2c4a2 100644 --- a/src/idc/zephyr_idc.c +++ b/src/idc/zephyr_idc.c @@ -196,11 +196,13 @@ int idc_send_msg(struct idc_msg *msg, uint32_t mode) return ret; } -void idc_init_thread(void) +__cold void idc_init_thread(void) { char thread_name[] = "idc_p4wq0"; int cpu = cpu_get_id(); + assert_can_be_cold(); + k_p4wq_enable_static_thread(q_zephyr_idc + cpu, _p4threads_q_zephyr_idc + cpu, BIT(cpu)); From fc86dfd7c3abd27e98963b7b3ac5624bae5d70e6 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 10 Jun 2026 12:42:05 +0200 Subject: [PATCH 115/303] tree-wide: make more functions "cold," add missing memory.h Mark more functions, called only from platform_init(), primary_core_init() and secondary_core_init(), as "cold." Those functions themselves are "cold," so this change is safe. Add memory.h and the cold-safety assertion where missing. Signed-off-by: Guennadi Liakhovetski --- src/audio/mic_privacy_manager/mic_privacy_manager_intel.c | 5 ++++- src/debug/telemetry/performance_monitor.c | 5 ++++- src/init/init.c | 6 ++++++ src/lib/agent.c | 4 +++- src/lib/ams.c | 5 +++-- src/lib/notifier.c | 5 ++++- src/platform/intel/ace/lib/watchdog.c | 5 ++++- src/platform/intel/ace/platform.c | 4 ++++ src/schedule/zephyr_dp_schedule.c | 6 +++++- src/schedule/zephyr_ll.c | 5 ++++- src/schedule/zephyr_twb_schedule.c | 5 ++++- zephyr/edf_schedule.c | 5 ++++- zephyr/lib/dma.c | 4 +++- 13 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/audio/mic_privacy_manager/mic_privacy_manager_intel.c b/src/audio/mic_privacy_manager/mic_privacy_manager_intel.c index 5120c2ab4b59..a5ff86a04fd1 100644 --- a/src/audio/mic_privacy_manager/mic_privacy_manager_intel.c +++ b/src/audio/mic_privacy_manager/mic_privacy_manager_intel.c @@ -10,6 +10,7 @@ #include #include #include +#include const struct device *mic_priv_dev; @@ -88,10 +89,12 @@ void mic_privacy_enable_dmic_irq(bool enable_irq) } } -int mic_privacy_manager_init(void) +__cold int mic_privacy_manager_init(void) { mic_priv_dev = DEVICE_DT_GET(DT_NODELABEL(mic_privacy)); + assert_can_be_cold(); + if (!mic_priv_dev) return -EINVAL; diff --git a/src/debug/telemetry/performance_monitor.c b/src/debug/telemetry/performance_monitor.c index b7aedc5ffa7c..f84b122b4d8a 100644 --- a/src/debug/telemetry/performance_monitor.c +++ b/src/debug/telemetry/performance_monitor.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -427,11 +428,13 @@ SYS_BITARRAY_DEFINE_STATIC(io_performance_data_bit_array, PERFORMANCE_DATA_ENTRI static struct io_perf_monitor_ctx perf_monitor_ctx; static struct io_perf_data_item io_perf_data_items[IO_PERFORMANCE_MAX_ENTRIES]; -int io_perf_monitor_init(void) +__cold int io_perf_monitor_init(void) { int ret; struct io_perf_monitor_ctx *self = &perf_monitor_ctx; + assert_can_be_cold(); + k_spinlock_init(&self->lock); k_spinlock_key_t key = k_spin_lock(&self->lock); diff --git a/src/init/init.c b/src/init/init.c index e8d374c810ad..9a99c2d9c27b 100644 --- a/src/init/init.c +++ b/src/init/init.c @@ -110,6 +110,8 @@ __cold int secondary_core_init(struct sof *sof) int err; struct ll_schedule_domain *dma_domain; + assert_can_be_cold(); + /* check whether we are in a cold boot process or not (e.g. D0->D0ix * flow when primary core disables all secondary cores). If not, we do * not have allocate basic structures like e.g. schedulers, notifier, @@ -163,6 +165,8 @@ __cold int secondary_core_init(struct sof *sof) __cold static void print_version_banner(void) { + assert_can_be_cold(); + /* * Non-Zephyr builds emit the version banner in DMA-trace * init and this is done at a later time as otherwise the @@ -193,6 +197,8 @@ static log_timestamp_t default_get_timestamp(void) __cold static int primary_core_init(int argc, char *argv[], struct sof *sof) { + assert_can_be_cold(); + /* setup context */ sof->argc = argc; sof->argv = argv; diff --git a/src/lib/agent.c b/src/lib/agent.c index 1030e88dd7f2..5679fbddfe59 100644 --- a/src/lib/agent.c +++ b/src/lib/agent.c @@ -93,10 +93,12 @@ static enum task_state validate(void *data) return SOF_TASK_STATE_RESCHEDULE; } -void sa_init(struct sof *sof, uint64_t timeout) +__cold void sa_init(struct sof *sof, uint64_t timeout) { uint64_t ticks; + assert_can_be_cold(); + if (timeout > UINT_MAX) tr_warn(&sa_tr, "timeout > %u", UINT_MAX); else diff --git a/src/lib/ams.c b/src/lib/ams.c index 31bca13ce833..722ea40c9453 100644 --- a/src/lib/ams.c +++ b/src/lib/ams.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -578,13 +577,15 @@ static int ams_create_shared_context(struct ams_shared_context *ctx) return 0; } -int ams_init(void) +__cold int ams_init(void) { struct ams_shared_context *ams_shared_ctx; struct async_message_service **ams = arch_ams_get(); struct sof *sof; int ret = 0; + assert_can_be_cold(); + *ams = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(**ams)); if (!*ams) diff --git a/src/lib/notifier.c b/src/lib/notifier.c index e398417a6fd1..3f4d3d07f535 100644 --- a/src/lib/notifier.c +++ b/src/lib/notifier.c @@ -190,10 +190,13 @@ void notifier_event(const void *caller, enum notify_id type, uint32_t core_mask, } } -void init_system_notify(struct sof *sof) +__cold void init_system_notify(struct sof *sof) { struct notify **notify = arch_notify_get(); int i; + + assert_can_be_cold(); + *notify = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(**notify)); if (!*notify) { diff --git a/src/platform/intel/ace/lib/watchdog.c b/src/platform/intel/ace/lib/watchdog.c index dcae80da801d..a9efb2ae0b7e 100644 --- a/src/platform/intel/ace/lib/watchdog.c +++ b/src/platform/intel/ace/lib/watchdog.c @@ -5,6 +5,7 @@ * Author: Adrian Warecki */ +#include #include #include #include @@ -72,7 +73,7 @@ static void watchdog_timeout(const struct device *dev, int core) watchdog_secondary_core_action_on_timeout(); } -void watchdog_init(void) +__cold void watchdog_init(void) { int i, ret; const struct wdt_timeout_cfg watchdog_config = { @@ -80,6 +81,8 @@ void watchdog_init(void) .callback = watchdog_timeout, }; + assert_can_be_cold(); + secondary_timeout_ipc.tx_data = NULL; secondary_timeout_ipc.tx_size = 0; list_init(&secondary_timeout_ipc.list); diff --git a/src/platform/intel/ace/platform.c b/src/platform/intel/ace/platform.c index 1fde2713049d..f19003117e08 100644 --- a/src/platform/intel/ace/platform.c +++ b/src/platform/intel/ace/platform.c @@ -65,6 +65,8 @@ __cold int platform_boot_complete(uint32_t boot_message) { struct ipc_cmd_hdr header; + assert_can_be_cold(); + /* get any IPC specific boot message and optional data */ ipc_boot_complete_msg(&header, 0); header.pri |= boot_message; @@ -92,6 +94,8 @@ __cold int platform_init(struct sof *sof) { int ret; + assert_can_be_cold(); + trace_point(TRACE_BOOT_PLATFORM_CLOCK); platform_clock_init(sof); diff --git a/src/schedule/zephyr_dp_schedule.c b/src/schedule/zephyr_dp_schedule.c index fdb10c7201af..1c6b94d3bb4c 100644 --- a/src/schedule/zephyr_dp_schedule.c +++ b/src/schedule/zephyr_dp_schedule.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -346,9 +347,12 @@ static struct scheduler_ops schedule_dp_ops = { .schedule_task_free = scheduler_dp_task_free, }; -int scheduler_dp_init(void) +__cold int scheduler_dp_init(void) { int ret; + + assert_can_be_cold(); + struct scheduler_dp_data *dp_sch = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(struct scheduler_dp_data)); if (!dp_sch) diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index bfb3b5ed66c0..36bacda0c9ad 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -604,13 +605,15 @@ EXPORT_SYMBOL(zephyr_ll_task_init); /* TODO: low-power mode clock support */ /* Runs on each core during initialisation with the same domain argument */ -int zephyr_ll_scheduler_init(struct ll_schedule_domain *domain) +__cold int zephyr_ll_scheduler_init(struct ll_schedule_domain *domain) { struct zephyr_ll *sch; int core = cpu_get_id(); struct k_heap *heap = sof_sys_heap_get(); int flags = SOF_MEM_FLAG_KERNEL | SOF_MEM_FLAG_COHERENT; + assert_can_be_cold(); + #if CONFIG_SOF_USERSPACE_LL heap = zephyr_ll_user_heap(); flags = SOF_MEM_FLAG_USER; diff --git a/src/schedule/zephyr_twb_schedule.c b/src/schedule/zephyr_twb_schedule.c index aee61360d697..5e85cbf61134 100644 --- a/src/schedule/zephyr_twb_schedule.c +++ b/src/schedule/zephyr_twb_schedule.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -348,12 +349,14 @@ static struct scheduler_ops schedule_twb_ops = { .schedule_task_free = scheduler_twb_task_free, }; -int scheduler_twb_init(void) +__cold int scheduler_twb_init(void) { struct scheduler_twb_data *twb_sch = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(struct scheduler_twb_data)); int ret; + assert_can_be_cold(); + if (!twb_sch) return -ENOMEM; diff --git a/zephyr/edf_schedule.c b/zephyr/edf_schedule.c index 1a0231426c27..a14ed31d060d 100644 --- a/zephyr/edf_schedule.c +++ b/zephyr/edf_schedule.c @@ -5,6 +5,7 @@ // Author: Bartosz Kokoszko #include +#include #include #include #include @@ -99,10 +100,12 @@ static struct scheduler_ops schedule_edf_ops = { .schedule_task_free = schedule_edf_task_free, }; -int scheduler_init_edf(void) +__cold int scheduler_init_edf(void) { struct k_thread *thread = &edf_workq.thread; + assert_can_be_cold(); + scheduler_init(SOF_SCHEDULE_EDF, &schedule_edf_ops, NULL); k_work_queue_start(&edf_workq, diff --git a/zephyr/lib/dma.c b/zephyr/lib/dma.c index 851c05ae77cc..564018e9f260 100644 --- a/zephyr/lib/dma.c +++ b/zephyr/lib/dma.c @@ -270,10 +270,12 @@ const struct dma_info lib_dma = { }; /* Initialize all platform DMAC's */ -int dmac_init(struct sof *sof) +__cold int dmac_init(struct sof *sof) { int i; + assert_can_be_cold(); + sof->dma_info = &lib_dma; /* early lock initialization for ref counting */ From 30af28c77f6e221384e3ae76b3dcee7bdaa62d31 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:08:12 +0100 Subject: [PATCH 116/303] logger: account for entry size in uuid pointer bounds check The uuid pointer bounds check did not reserve space for the full entry, so a pointer near the end of the region could read an entry straddling the buffer end. Require a whole entry to fit before dereferencing. Signed-off-by: Liam Girdwood --- tools/logger/convert.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/logger/convert.c b/tools/logger/convert.c index 4e0a6cdc59ae..834ee5a1b243 100644 --- a/tools/logger/convert.c +++ b/tools/logger/convert.c @@ -126,8 +126,14 @@ static const char *format_uid(uint32_t uid_ptr, int use_colors, bool be, bool up const struct sof_uuid_entry *uid_entry; char *str; + /* + * The whole struct sof_uuid_entry is read at uid_ptr, so require that + * many bytes to remain in the uids region; a bare ">=" upper bound + * would accept a pointer whose entry straddles the end of the buffer. + */ if (uid_ptr < uids_dict->base_address || - uid_ptr >= uids_dict->base_address + uids_dict->data_length) { + uid_ptr + sizeof(struct sof_uuid_entry) > + uids_dict->base_address + uids_dict->data_length) { str = calloc(1, strlen(BAD_PTR_STR) + 1 + 6); if (!str) { log_err("can't allocate memory\n"); From a15d14d9d311cbc85b276a4079ab25392ae0204d Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 11 Jun 2026 17:57:37 +0200 Subject: [PATCH 117/303] idc: zephyr_idc: check target core before taking idc_mutex idc_send_msg() acquired idc_mutex and then returned -EACCES on the disabled-core path without unlocking, leaking the mutex and blocking all later cross-core IDC sends. Move the cpu_is_core_enabled() check ahead of the lock so the early return no longer holds the mutex and the per-core work slot is not touched for a disabled core. Signed-off-by: Tomasz Leman --- src/idc/zephyr_idc.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/idc/zephyr_idc.c b/src/idc/zephyr_idc.c index 18f47bb2c4a2..771ae967bfb1 100644 --- a/src/idc/zephyr_idc.c +++ b/src/idc/zephyr_idc.c @@ -135,6 +135,11 @@ int idc_send_msg(struct idc_msg *msg, uint32_t mode) int ret; int idc_send_memcpy_err __unused; + if (!cpu_is_core_enabled(target_cpu)) { + tr_err(&zephyr_idc_tr, "Core %u is down, cannot send IDC message", target_cpu); + return -EACCES; + } + k_mutex_lock(&idc_mutex, K_FOREVER); if (unlikely(work->thread)) { @@ -153,10 +158,6 @@ int idc_send_msg(struct idc_msg *msg, uint32_t mode) work->handler = idc_handler; work->sync = mode == IDC_BLOCKING; - if (!cpu_is_core_enabled(target_cpu)) { - tr_err(&zephyr_idc_tr, "Core %u is down, cannot sent IDC message", target_cpu); - return -EACCES; - } if (msg->payload) { idc_send_memcpy_err = memcpy_s(payload->data, sizeof(payload->data), msg->payload, msg->size); From a6b37f4cc1a2c81d50a30ba567b91a4a9d36c2fb Mon Sep 17 00:00:00 2001 From: Sivasubramanian678 Date: Tue, 5 May 2026 18:27:56 +0530 Subject: [PATCH 118/303] acp: add TDM DAI and DMA support for ACP 7.0 Add TDM DAI and DMA support for the AMD ACP 7.0 platform with three instances: HS, SP, and BT. Signed-off-by: Siva Subramanian Ravi Saravanan --- app/boards/acp_7_0_adsp.conf | 3 +++ src/audio/dai-zephyr.c | 8 ++++++++ src/include/ipc/dai-amd.h | 1 + src/include/ipc/dai.h | 3 ++- src/ipc/ipc3/dai.c | 31 ++++++++++++++++++++++++------- src/lib/dai.c | 25 ++++++++++++++++++------- zephyr/lib/dma.c | 13 +++++++++++++ 7 files changed, 69 insertions(+), 15 deletions(-) diff --git a/app/boards/acp_7_0_adsp.conf b/app/boards/acp_7_0_adsp.conf index 3c5f791fdcb6..5eb2ff9f7b95 100644 --- a/app/boards/acp_7_0_adsp.conf +++ b/app/boards/acp_7_0_adsp.conf @@ -14,6 +14,8 @@ CONFIG_INTC_AMD_ACP=y CONFIG_DMA_AMD_ACP_HOST=y CONFIG_DMA_AMD_ACP_SDW=y CONFIG_DAI_AMD_SDW=y +CONFIG_DMA_AMD_ACP_TDM=y +CONFIG_DAI_AMD_TDM=y CONFIG_AMS=n CONFIG_WRAP_ACTUAL_POSITION=y CONFIG_TRACE=n @@ -50,3 +52,4 @@ CONFIG_ASRC_SUPPORT_CONVERSION_48000_TO_32000=n CONFIG_ASRC_SUPPORT_CONVERSION_48000_TO_44100=n CONFIG_CORE_COUNT=1 CONFIG_FORMAT_CONVERT_HIFI3=n +CONFIG_COMP_MFCC=n \ No newline at end of file diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index fc2a065741cd..e65457d34383 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -196,6 +196,14 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, cfg.type = DAI_AMD_SDW; cfg_params = &sof_cfg->acpsdw; break; + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_SP: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_BT: + cfg.type = DAI_AMD_TDM; + cfg_params = &sof_cfg->acptdm; + break; case SOF_DAI_INTEL_UAOL: cfg.type = DAI_INTEL_UAOL; cfg.channels = common_config->gtw_fmt->channels_count; diff --git a/src/include/ipc/dai-amd.h b/src/include/ipc/dai-amd.h index ceeb870c163e..b167f03eed7e 100644 --- a/src/include/ipc/dai-amd.h +++ b/src/include/ipc/dai-amd.h @@ -23,6 +23,7 @@ struct sof_ipc_dai_acp_params { uint32_t reserved0; uint32_t fsync_rate; uint32_t tdm_slots; + uint32_t tdm_mode; } __attribute__((packed, aligned(4))); /* ACP Configuration Request - SOF_IPC_DAI_AMD_SDW_CONFIG */ diff --git a/src/include/ipc/dai.h b/src/include/ipc/dai.h index dfc2a3e9ffb0..49ffa5d5fa94 100644 --- a/src/include/ipc/dai.h +++ b/src/include/ipc/dai.h @@ -97,6 +97,7 @@ enum sof_ipc_dai_type { SOF_DAI_IMX_MICFIL, /**< i.MX MICFIL */ SOF_DAI_AMD_SDW, /**< Amd SDW */ SOF_DAI_INTEL_UAOL, /**< Intel UAOL */ + SOF_DAI_AMD_TDM /**< Amd TDM */ }; #define SOF_DAI_CONFIG_HW_SPEC_OFFSET offsetof(struct sof_ipc_dai_config, ssp) @@ -126,7 +127,7 @@ struct sof_ipc_dai_config { struct sof_ipc_dai_acp_params acpbt; struct sof_ipc_dai_acp_params acpsp; struct sof_ipc_dai_acpdmic_params acpdmic; - struct sof_ipc_dai_acp_params acphs; + struct sof_ipc_dai_acp_params acptdm; struct sof_ipc_dai_afe_params afe; struct sof_ipc_dai_micfil_params micfil; struct sof_ipc_dai_acp_sdw_params acpsdw; diff --git a/src/ipc/ipc3/dai.c b/src/ipc/ipc3/dai.c index ce7be827b887..d41fd355847d 100644 --- a/src/ipc/ipc3/dai.c +++ b/src/ipc/ipc3/dai.c @@ -184,21 +184,38 @@ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev) case SOF_DAI_IMX_ESAI: dd->config.burst_elems = dai_get_fifo_depth(dd->dai, dai->direction); break; - case SOF_DAI_AMD_BT: - dev->ipc_config.frame_fmt = SOF_IPC_FRAME_S16_LE; - break; - case SOF_DAI_AMD_SP: - case SOF_DAI_AMD_SP_VIRTUAL: - dev->ipc_config.frame_fmt = SOF_IPC_FRAME_S16_LE; - break; case SOF_DAI_AMD_DMIC: dev->ipc_config.frame_fmt = SOF_IPC_FRAME_S32_LE; if (dd->dma_buffer) audio_stream_set_frm_fmt(&dd->dma_buffer->stream, dev->ipc_config.frame_fmt); break; + case SOF_DAI_AMD_BT: + case SOF_DAI_AMD_SP: + case SOF_DAI_AMD_SP_VIRTUAL: case SOF_DAI_AMD_HS: case SOF_DAI_AMD_HS_VIRTUAL: +#if defined(CONFIG_AMD) && !defined(CONFIG_SOC_ACP_6_0) + { + struct acp_dma_dev_data *tdm_data = dd->dma->z_dev->data; + struct tdm_context *tdm_ctx; + + /* Allocate coherent memory for TDM context shared between + * IPC and DMA contexts. + */ + if (!tdm_data->dai_index_ptr) { + tdm_ctx = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(*tdm_ctx)); + if (!tdm_ctx) + return -ENOMEM; + tdm_data->dai_index_ptr = tdm_ctx; + } else { + tdm_ctx = (struct tdm_context *)tdm_data->dai_index_ptr; + } + tdm_ctx->index = dd->dai->index; + } +#endif + break; case SOF_DAI_AMD_SDW: #if defined(CONFIG_AMD) && !defined(CONFIG_SOC_ACP_6_0) { diff --git a/src/lib/dai.c b/src/lib/dai.c index d51266aa345e..585218104e61 100644 --- a/src/lib/dai.c +++ b/src/lib/dai.c @@ -189,6 +189,9 @@ const struct device *zephyr_dev[] = { #if CONFIG_DAI_AMD_SDW DT_FOREACH_STATUS_OKAY(amd_acp_sdw_dai, GET_DEVICE_LIST) #endif +#if CONFIG_DAI_AMD_TDM + DT_FOREACH_STATUS_OKAY(amd_tdm_dai, GET_DEVICE_LIST) +#endif #if CONFIG_DAI_INTEL_UAOL DT_FOREACH_STATUS_OKAY(intel_uaol_dai, GET_DEVICE_LIST) #endif @@ -223,21 +226,20 @@ static int sof_dai_type_to_zephyr(uint32_t type) return DAI_IMX_SAI; case SOF_DAI_IMX_ESAI: return DAI_IMX_ESAI; - case SOF_DAI_AMD_BT: - return DAI_AMD_BT; - case SOF_DAI_AMD_SP: - return DAI_AMD_SP; case SOF_DAI_AMD_DMIC: return DAI_AMD_DMIC; case SOF_DAI_MEDIATEK_AFE: return DAI_MEDIATEK_AFE; case SOF_DAI_IMX_MICFIL: return DAI_IMX_MICFIL; - case SOF_DAI_AMD_HS: - case SOF_DAI_AMD_SP_VIRTUAL: - case SOF_DAI_AMD_HS_VIRTUAL: case SOF_DAI_AMD_SDW: return DAI_AMD_SDW; + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_SP: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_BT: + return DAI_AMD_TDM; default: return -EINVAL; } @@ -305,6 +307,15 @@ static void dai_set_device_params(struct dai *d) d->dma_dev = SOF_DMA_DEV_SW; d->dma_caps = SOF_DMA_CAP_SW; break; + /* All TDM-capable AMD DAIs share acp_tdm_dma. */ + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_SP: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_BT: + d->dma_dev = SOF_DMA_DEV_HS | SOF_DMA_DEV_SP | SOF_DMA_DEV_BT; + d->dma_caps = SOF_DMA_CAP_HS | SOF_DMA_CAP_SP | SOF_DMA_CAP_BT; + break; case SOF_DAI_MEDIATEK_AFE: d->dma_dev = SOF_DMA_DEV_AFE_MEMIF; break; diff --git a/zephyr/lib/dma.c b/zephyr/lib/dma.c index 564018e9f260..9a2fe540addf 100644 --- a/zephyr/lib/dma.c +++ b/zephyr/lib/dma.c @@ -239,6 +239,19 @@ APP_SYSUSER_DATA SHARED_DATA struct sof_dma dma[] = { }, .z_dev = DEVICE_DT_GET(DT_NODELABEL(acp_sdw_dma)), }, +{ + .plat_data = { + .dir = SOF_DMA_DIR_MEM_TO_DEV | + SOF_DMA_DIR_DEV_TO_MEM, + .devs = SOF_DMA_DEV_HS | SOF_DMA_DEV_SP | SOF_DMA_DEV_BT, + .caps = SOF_DMA_CAP_HS | SOF_DMA_CAP_SP | SOF_DMA_CAP_BT, + .base = DMA0_BASE, + .chan_size = DMA0_SIZE, + .channels = 6, + .period_count = 2, + }, + .z_dev = DEVICE_DT_GET(DT_NODELABEL(acp_tdm_dma)), +}, #endif #if DT_HAS_COMPAT_STATUS_OKAY(mediatek_afe_memif_dma) { From 1ac3dc7508ad09ea34d94a40f88b16a63c0c7220 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:13 +0100 Subject: [PATCH 119/303] dcblock: require blob to cover coefficients before copy The coefficient copy always read a fixed number of bytes from the config blob regardless of its actual size, over-reading adjacent heap for a short blob. Fall back to passthrough unless the blob holds the whole coefficient array. Signed-off-by: Liam Girdwood --- src/audio/dcblock/dcblock.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/audio/dcblock/dcblock.c b/src/audio/dcblock/dcblock.c index 5f0935a2bc29..15e6139b7a89 100644 --- a/src/audio/dcblock/dcblock.c +++ b/src/audio/dcblock/dcblock.c @@ -205,7 +205,11 @@ static int dcblock_prepare(struct processing_module *mod, cd->source_format, cd->sink_format); cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (cd->config && data_size > 0) + /* dcblock_copy_coefficients() copies sizeof(R_coeffs) from the blob, so + * require the blob to actually hold that many bytes; fall back to + * passthrough otherwise instead of over-reading the blob + */ + if (cd->config && data_size >= sizeof(cd->R_coeffs)) dcblock_copy_coefficients(mod); else dcblock_set_passthrough(mod); From d859e5dd544c4b760a4e8cea2ee77987bd1f96f8 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:13 +0100 Subject: [PATCH 120/303] drc: validate config blob size before use DRC setup dereferenced the config blob as a fixed struct without verifying the blob was at least that large, over-reading adjacent heap for a short blob. Require the blob to cover the config struct. Signed-off-by: Liam Girdwood --- src/audio/drc/drc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/audio/drc/drc.c b/src/audio/drc/drc.c index 007336fb2115..7fe86ce01c56 100644 --- a/src/audio/drc/drc.c +++ b/src/audio/drc/drc.c @@ -353,7 +353,10 @@ static int drc_prepare(struct processing_module *mod, /* Initialize DRC */ comp_info(dev, "source_format=%d", cd->source_format); cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (cd->config && data_size > 0) { + /* the blob is dereferenced as a struct sof_drc_config below and in + * drc_setup(), so require it to be at least that large + */ + if (cd->config && data_size >= sizeof(struct sof_drc_config)) { ret = drc_setup(mod, channels, rate); if (ret < 0) { comp_err(dev, "error: drc_setup failed."); From e5b585ce040394d23e3f5a20852d38c83c55a9ed Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:37 +0100 Subject: [PATCH 121/303] multiband_drc: validate config blob covers all bands Setup read a base config struct and per-band coefficients from the blob without a size check, over-reading for a short blob. Require the blob to cover the base struct and num_bands band entries. Signed-off-by: Liam Girdwood --- src/audio/multiband_drc/multiband_drc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/audio/multiband_drc/multiband_drc.c b/src/audio/multiband_drc/multiband_drc.c index 84a079134ea1..1c36c4e5c184 100644 --- a/src/audio/multiband_drc/multiband_drc.c +++ b/src/audio/multiband_drc/multiband_drc.c @@ -369,7 +369,14 @@ static int multiband_drc_prepare(struct processing_module *mod, comp_dbg(dev, "source_format=%d, sink_format=%d", cd->source_format, cd->source_format); cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - if (cd->config && data_size > 0) { + /* the blob holds a base struct followed by num_bands variable-length + * band coefficients; require the base struct first, then the full + * per-band payload, so setup cannot read past the blob + */ + if (cd->config && data_size >= sizeof(struct sof_multiband_drc_config) && + cd->config->num_bands <= SOF_MULTIBAND_DRC_MAX_BANDS && + data_size >= sizeof(struct sof_multiband_drc_config) + + (size_t)cd->config->num_bands * sizeof(struct sof_drc_params)) { ret = multiband_drc_setup(mod, channels, rate); if (ret < 0) { comp_err(dev, "error: multiband_drc_setup failed."); From f10997b33455003ed39eea1526828462396d7825 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:03 +0100 Subject: [PATCH 122/303] acp-dma: clear channel pointer after free on probe failure On a partial channel-init failure the channel array was freed but the pointer was left set, so a later probe retry would free it again. Clear the pointer after freeing. Signed-off-by: Liam Girdwood --- src/drivers/amd/common/acp_dma.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/drivers/amd/common/acp_dma.c b/src/drivers/amd/common/acp_dma.c index bd1e6cba16a2..df68b8bebfb6 100644 --- a/src/drivers/amd/common/acp_dma.c +++ b/src/drivers/amd/common/acp_dma.c @@ -314,6 +314,7 @@ static int acp_dma_probe(struct dma *dma) sizeof(struct acp_dma_chan_data)); if (!acp_dma_chan) { rfree(dma->chan); + dma->chan = NULL; tr_err(&acpdma_tr, "acp-dma: %d channel %d private data alloc failed", dma->plat_data.id, channel); return -ENOMEM; From b09b3ed8b7b485a4b52f03343431607437a6264a Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:03 +0100 Subject: [PATCH 123/303] dw-dma: commit descriptor count only after successful alloc The descriptor count was updated before the descriptor buffer was reallocated, so on allocation failure the count was left larger than the now-NULL buffer and a later config zeroed a NULL pointer using the stale count. Update the count only after a successful allocation and reset it on failure. Signed-off-by: Liam Girdwood --- src/drivers/dw/dma.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/drivers/dw/dma.c b/src/drivers/dw/dma.c index 102cbbe0d7ba..005980454798 100644 --- a/src/drivers/dw/dma.c +++ b/src/drivers/dw/dma.c @@ -532,8 +532,6 @@ static int dw_dma_set_config(struct dma_chan_data *channel, /* do we need to realloc descriptors */ if (config->elem_array.count != channel->desc_count) { - channel->desc_count = config->elem_array.count; - /* * Allocate descriptors for channel. They must be cache-line * size aligned to avoid corrupting adjacent memory when @@ -542,18 +540,25 @@ static int dw_dma_set_config(struct dma_chan_data *channel, * allocations on Zephyr to always force cache-line size * alignment. */ - if (dw_chan->lli) - rfree(dw_chan->lli); + rfree(dw_chan->lli); dw_chan->lli = rmalloc(SOF_MEM_FLAG_KERNEL | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA, - sizeof(struct dw_lli) * channel->desc_count); + sizeof(struct dw_lli) * config->elem_array.count); if (!dw_chan->lli) { tr_err(&dwdma_tr, "dma %d channel %d lli alloc failed", channel->dma->plat_data.id, channel->index); + /* allocation failed, so dw_chan->lli is now NULL; reset + * the count to match it so a later config does not + * bzero() a NULL pointer using a stale count + */ + channel->desc_count = 0; ret = -ENOMEM; goto out; } + + /* only commit the new count once the buffer is allocated */ + channel->desc_count = config->elem_array.count; } /* initialise descriptors */ From f58d583cd6ca773eae634d1bee0a2982b5490f73 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:03 +0100 Subject: [PATCH 124/303] sdma: fix off-by-one in the event bound check The event-number range check used a strict greater-than against the event count, allowing the count value itself to index one past the array. Use greater-or-equal, in both the enable and disable paths. Signed-off-by: Liam Girdwood --- src/drivers/imx/sdma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/drivers/imx/sdma.c b/src/drivers/imx/sdma.c index a613f98b40c5..e14db9e6796e 100644 --- a/src/drivers/imx/sdma.c +++ b/src/drivers/imx/sdma.c @@ -443,7 +443,7 @@ static void sdma_enable_event(struct dma_chan_data *channel, int eventnum) tr_dbg(&sdma_tr, "channel %d, event %d", channel->index, eventnum); - if (eventnum < 0 || eventnum > SDMA_HWEVENTS_COUNT) + if (eventnum < 0 || eventnum >= SDMA_HWEVENTS_COUNT) return; /* No change if request is invalid */ dma_reg_update_bits(channel->dma, SDMA_CHNENBL(eventnum), @@ -461,7 +461,7 @@ static void sdma_disable_event(struct dma_chan_data *channel, int eventnum) { tr_dbg(&sdma_tr, "channel %d, event %d", channel->index, eventnum); - if (eventnum < 0 || eventnum > SDMA_HWEVENTS_COUNT) + if (eventnum < 0 || eventnum >= SDMA_HWEVENTS_COUNT) return; /* No change if request is invalid */ dma_reg_update_bits(channel->dma, SDMA_CHNENBL(eventnum), From 36aa1dc50e4dd9da2f525e4d5086f94bef31e212 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:08:12 +0100 Subject: [PATCH 125/303] rimage: bound string table lookups to the section size A symbol name was duplicated from the string table without verifying a terminator within the section, so an unterminated table could be read past its end. Validate the index and bound the length to the section. Signed-off-by: Liam Girdwood --- tools/rimage/src/elf_file.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tools/rimage/src/elf_file.c b/tools/rimage/src/elf_file.c index 991766d45062..c40d3a69abfb 100644 --- a/tools/rimage/src/elf_file.c +++ b/tools/rimage/src/elf_file.c @@ -531,10 +531,23 @@ int elf_strings_read_by_index(const struct elf_file *elf, int index, struct elf_ int elf_strings_get(const struct elf_strings *strings, int index, char **str) { - if (index >= strings->section.header.data.size) + size_t size = strings->section.header.data.size; + const char *base = (const char *)strings->section.data; + + if (index < 0 || (size_t)index >= size) + return -EINVAL; + + /* + * A crafted ELF may provide a string table that is not NUL-terminated; + * make sure a terminator exists within the section before strdup() so + * it cannot read past the end of the mapped section. + */ + if (strnlen(base + index, size - index) == size - index) { + fprintf(stderr, "error: unterminated string in string table\n"); return -EINVAL; + } - *str = strdup((const char *)strings->section.data + index); + *str = strdup(base + index); if (!*str) return -ENOMEM; From 3435f1c50654e03877486a07101a7230c2f83da2 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:08:12 +0100 Subject: [PATCH 126/303] rimage: reject reversed manifest size fields The signed module size was computed as a difference of two header fields from the image without an ordering check, underflowing to a huge value that was then hashed past the buffer. Reject a header whose size is less than its header length. Signed-off-by: Liam Girdwood --- tools/rimage/src/pkcs1_5.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tools/rimage/src/pkcs1_5.c b/tools/rimage/src/pkcs1_5.c index de063ee03bec..cbd31bc101bf 100644 --- a/tools/rimage/src/pkcs1_5.c +++ b/tools/rimage/src/pkcs1_5.c @@ -929,9 +929,28 @@ int ri_manifest_verify_v1_8(struct image *image) MAN_RSA_SIGNATURE_LEN); char *const data2 = (char *)man + MAN_SIG_PKG_OFFSET_V1_8; + + /* css.size and css.header_len come from the (untrusted) image; reject + * a reversed ordering that would underflow the unsigned size2 below. + */ + if (man->css.size < man->css.header_len) { + fprintf(stderr, "error: invalid css size %u < header_len %u\n", + man->css.size, man->css.header_len); + return -EINVAL; + } + unsigned const size2 = (man->css.size - man->css.header_len) * sizeof(uint32_t); + /* size2 is derived from untrusted css fields and is hashed starting at + * data2; make sure that range stays within the verified image buffer. + */ + if (image->image_end < MAN_SIG_PKG_OFFSET_V1_8 || + size2 > image->image_end - MAN_SIG_PKG_OFFSET_V1_8) { + fprintf(stderr, "error: signed payload size 0x%x exceeds image\n", size2); + return -EINVAL; + } + return pkcs_v1_5_verify_man_v1_8(image, man, data1, size1, data2, size2); } @@ -946,9 +965,28 @@ int ri_manifest_verify_v2_5(struct image *image) MAN_RSA_SIGNATURE_LEN_2_5); char *const data2 = (char *)man + MAN_SIG_PKG_OFFSET_V2_5; + + /* css.size and css.header_len come from the (untrusted) image; reject + * a reversed ordering that would underflow the unsigned size2 below. + */ + if (man->css.size < man->css.header_len) { + fprintf(stderr, "error: invalid css size %u < header_len %u\n", + man->css.size, man->css.header_len); + return -EINVAL; + } + unsigned const size2 = (man->css.size - man->css.header_len) * sizeof(uint32_t); + /* size2 is derived from untrusted css fields and is hashed starting at + * data2; make sure that range stays within the verified image buffer. + */ + if (image->image_end < MAN_SIG_PKG_OFFSET_V2_5 || + size2 > image->image_end - MAN_SIG_PKG_OFFSET_V2_5) { + fprintf(stderr, "error: signed payload size 0x%x exceeds image\n", size2); + return -EINVAL; + } + return pkcs_v1_5_verify_man_v2_5(image, man, data1, size1, data2, size2); } From ca37d0ca99aa4c35ae9ae28f10524e05adf30b82 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:09:30 +0100 Subject: [PATCH 127/303] rimage: bound module manifest count from input The number of module manifests packed in a section came from the input file and drove writes into the fixed-size module descriptor array. Reject a count above the maximum before the write loop. Signed-off-by: Liam Girdwood --- tools/rimage/src/manifest.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/rimage/src/manifest.c b/tools/rimage/src/manifest.c index fb30dc7ab219..b3d6b0cba449 100644 --- a/tools/rimage/src/manifest.c +++ b/tools/rimage/src/manifest.c @@ -534,6 +534,20 @@ static int man_module_create_reloc(struct image *image, struct manifest_module * return -ENOEXEC; } + /* + * n_mod comes from the (potentially untrusted) ELF and each manifest + * consumes a sof_man_module descriptor slot written into fw_image. + * Bound the cumulative count across all input modules (this ELF adds + * n_mod to the running output_mod_cfg_count) so a crafted .module + * section cannot overflow the fixed manifest descriptor array. + */ + if (modules->output_mod_cfg_count + n_mod > MAX_MODULES) { + fprintf(stderr, "error: too many module manifests (%u + %u > %u).\n", + modules->output_mod_cfg_count, n_mod, MAX_MODULES); + elf_section_free(§ion); + return -ENOEXEC; + } + unsigned int i; for (i = 0, sof_mod = section.data; i < n_mod; i++, sof_mod++) { From 7e9bfd18db9ec6aed65e834c016d5066ac95c26b Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:09:30 +0100 Subject: [PATCH 128/303] rimage: propagate verification result to the exit code Verify mode logged signature failures and a missing header but returned success, so callers using the exit code as a gate would accept a tampered image. Return an error when no header is found and propagate the verification result. Signed-off-by: Liam Girdwood --- tools/rimage/src/manifest.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tools/rimage/src/manifest.c b/tools/rimage/src/manifest.c index b3d6b0cba449..b167531d4d8c 100644 --- a/tools/rimage/src/manifest.c +++ b/tools/rimage/src/manifest.c @@ -1682,7 +1682,21 @@ int verify_image(struct image *image) /* find CSE header marker "$CPD" */ if (*(uint32_t *)(buffer + i) == CSE_HEADER_MAKER) { image->fw_image = buffer + i; + /* size of the image from the CSE header to the end of the + * file, used by v1.5 verification and the signed-payload + * bounds checks + */ + image->image_end = size - i; ret = image->adsp->verify_firmware(image); + /* verify_firmware() returns 1 = valid, 0 = invalid and + * < 0 = error (OpenSSL RSA_verify semantics); map valid + * to 0 and invalid to -EINVAL, and leave a < 0 error code + * as-is, so the CLI exit code reflects the result + */ + if (ret == 1) + ret = 0; + else if (ret == 0) + ret = -EINVAL; goto out; } } @@ -1690,9 +1704,13 @@ int verify_image(struct image *image) /* no header found */ fprintf(stderr, "error: could not find valid CSE header $CPD in %s\n", image->verify_file); + ret = -EINVAL; out: fclose(in_file); - return 0; + /* propagate verification result so callers (and the exit code) can + * detect a failed/missing verification instead of always seeing success + */ + return ret; } From 16efc2c55a1a07f46ce145e7533fef002f299396 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:24 +0100 Subject: [PATCH 129/303] rimage: bound extended manifest header read to the section The extended-manifest validator copied a fixed header from an offset that could leave fewer than a header's worth of bytes, reading past the section. Require a whole header to remain before the copy. Signed-off-by: Liam Girdwood --- tools/rimage/src/ext_manifest.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/rimage/src/ext_manifest.c b/tools/rimage/src/ext_manifest.c index 03a157c08397..8beaa4943a7b 100644 --- a/tools/rimage/src/ext_manifest.c +++ b/tools/rimage/src/ext_manifest.c @@ -69,6 +69,12 @@ static int ext_man_validate(uint32_t section_size, const void *section_data) /* copy each head to local struct to omit memory align issues */ while (offset < section_size) { + /* make sure a whole header remains before copying it out */ + if (offset + sizeof(head) > section_size) { + fprintf(stderr, + "error: extended manifest header straddles section end\n"); + return -EINVAL; + } memcpy(&head, &sbuf[offset], sizeof(head)); fprintf(stdout, "Extended manifest found module, type: 0x%04X size: 0x%04X (%4d) offset: 0x%04X\n", head.type, head.elem_size, head.elem_size, offset); @@ -150,10 +156,12 @@ int ext_man_write(struct image *image) /* validate metadata section */ ret = ext_man_validate(ext_man->full_size - ext_man->header_size, (char *)ext_man + ext_man->header_size); - if (ret) { - ret = -errno; + if (ret) + /* ext_man_validate() already returns a negative errno; do not + * overwrite it with -errno, which is not set on validation + * failure and would mask the error + */ goto out; - } /* write extended metadata to file */ count = fwrite(ext_man, 1, ext_man->full_size, image->out_ext_man_fd); From 0edea46a850f766f2d65c5eea6906fd79a24894e Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:42 +0100 Subject: [PATCH 130/303] rimage: fix inverted hash output buffer size check The output-buffer guard was reversed: it rejected oversized buffers and accepted undersized ones, so a short output buffer could be overflowed by the digest write. Reject when the buffer is smaller than the digest. Signed-off-by: Liam Girdwood --- tools/rimage/src/hash.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/rimage/src/hash.c b/tools/rimage/src/hash.c index 0604670e9c6e..9aff3ae3625a 100644 --- a/tools/rimage/src/hash.c +++ b/tools/rimage/src/hash.c @@ -174,7 +174,10 @@ int hash_single(const void *data, size_t size, const EVP_MD *algo, void *output, if (algo_out_size <= 0) return -EINVAL; - if (output_len > algo_out_size) + /* EVP_Digest writes algo_out_size bytes into output, so the buffer + * must be at least that large; reject an undersized output buffer + */ + if (output_len < (size_t)algo_out_size) return -ENOBUFS; if (!EVP_Digest(data, size, output, NULL, algo, NULL)) { From 6e6748ec1f43b444aac91d62e95c67facbc46e10 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:42 +0100 Subject: [PATCH 131/303] rimage: avoid reading past the buffer scanning for the marker The marker scan read a 32-bit word at each offset up to the last byte, reading a few bytes past the buffer at the tail. Stop once fewer than a word remains, in both scan sites. Signed-off-by: Liam Girdwood --- tools/rimage/src/manifest.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/rimage/src/manifest.c b/tools/rimage/src/manifest.c index b167531d4d8c..304e12f47a17 100644 --- a/tools/rimage/src/manifest.c +++ b/tools/rimage/src/manifest.c @@ -1678,7 +1678,7 @@ int verify_image(struct image *image) ret = file_error("unable to read whole file", image->verify_file); goto out; } - for (i = 0; i < size; i += sizeof(uint32_t)) { + for (i = 0; i + sizeof(uint32_t) <= size; i += sizeof(uint32_t)) { /* find CSE header marker "$CPD" */ if (*(uint32_t *)(buffer + i) == CSE_HEADER_MAKER) { image->fw_image = buffer + i; @@ -1749,7 +1749,7 @@ int resign_image(struct image *image) fclose(in_file); - for (i = 0; i < size; i += sizeof(uint32_t)) { + for (i = 0; i + sizeof(uint32_t) <= size; i += sizeof(uint32_t)) { /* find CSE header marker "$CPD" */ if (*(uint32_t *)(buffer + i) == CSE_HEADER_MAKER) { image->fw_image = buffer + i; From f72dbb2b7e055c9760761ce233c74b6308e4439d Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 11 Jun 2026 15:16:45 +0300 Subject: [PATCH 132/303] audio: module_adapter_ipc4: add range check to module_get_large_config() In a multi-block get case, if the host sends data_off_size > md->cfg.size, the calculation of the last fragment size is incorrect if a sufficiently large value is passed. Add validation to catch this case and return an error data_off_size is too large. Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module_adapter_ipc4.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/audio/module_adapter/module_adapter_ipc4.c b/src/audio/module_adapter/module_adapter_ipc4.c index 07b7039c3a40..57b918b20f86 100644 --- a/src/audio/module_adapter/module_adapter_ipc4.c +++ b/src/audio/module_adapter/module_adapter_ipc4.c @@ -271,10 +271,16 @@ int module_get_large_config(struct comp_dev *dev, uint32_t param_id, bool first_ else fragment_size = SOF_IPC_MSG_MAX_SIZE; } else { - if (!last_block) + if (!last_block) { fragment_size = SOF_IPC_MSG_MAX_SIZE; - else + } else { + if (*data_offset_size > md->cfg.size) { + comp_err(dev, "invalid data_offset_size %u > cfg size %zu", + *data_offset_size, md->cfg.size); + return -EINVAL; + } fragment_size = md->cfg.size - *data_offset_size; + } } if (interface->get_configuration) From 542052923779eb08a666026d7d4ed9dffdee1436 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:09:56 +0100 Subject: [PATCH 133/303] smex: validate ELF string section index and size The section name string table was located using a header index without bounding it against the section count, and was null terminated at size minus one without checking for a zero-size section. Reject an out-of-range index and a zero-size string section before use. Signed-off-by: Liam Girdwood --- smex/elf.c | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/smex/elf.c b/smex/elf.c index eb0994530c6b..b0b3002379c7 100644 --- a/smex/elf.c +++ b/smex/elf.c @@ -44,8 +44,29 @@ static int elf_read_sections(struct elf_module *module, bool verbose) return count > 0 ? -ENODATA : -errno; } - /* read in strings */ - module->strings = calloc(1, section[hdr->shstrndx].size); + /* the string-table section index comes from the ELF header and is used + * to index section[]; reject an out-of-range value before dereferencing + */ + if (hdr->shstrndx >= hdr->shnum) { + fprintf(stderr, "error: %s invalid shstrndx %u >= shnum %u\n", + module->elf_file, hdr->shstrndx, hdr->shnum); + return -ENOEXEC; + } + + /* a zero-size string section leaves module->strings unusable and would + * break later string lookups; reject it explicitly + */ + if (section[hdr->shstrndx].size == 0) { + fprintf(stderr, "error: %s has zero-size string section\n", + module->elf_file); + return -ENOEXEC; + } + + /* read in strings; allocate one extra byte (calloc zeroes it) so the + * table is always NUL-terminated and string lookups cannot run off the + * end even if the section itself lacks a terminator + */ + module->strings = calloc(1, section[hdr->shstrndx].size + 1); if (!module->strings) { fprintf(stderr, "error: failed %s to read ELF strings for %d\n", module->elf_file, -errno); @@ -392,8 +413,22 @@ int elf_find_section(const struct elf_module *module, const char *name) return -EINVAL; } + if (hdr->shstrndx >= hdr->shnum) { + fprintf(stderr, "error: invalid shstrndx %u >= shnum %u\n", + hdr->shstrndx, hdr->shnum); + return -EINVAL; + } + section = &module->section[hdr->shstrndx]; + /* a zero-size string section would make the buffer[size - 1] + * NUL-termination below write before the allocation + */ + if (section->size == 0) { + fprintf(stderr, "error: zero-size string section\n"); + return -EINVAL; + } + /* alloc data data */ buffer = calloc(1, section->size); if (!buffer) From a695ea6789acdcbc95c386d09013367832f8d837 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:09:56 +0100 Subject: [PATCH 134/303] smex: clear freed section buffer pointer on error On a read error the section reader freed the output buffer but left the caller's pointer set, so a caller cleanup path could free it again. Clear the pointer after freeing. Signed-off-by: Liam Girdwood --- smex/elf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smex/elf.c b/smex/elf.c index b0b3002379c7..9558b1e781b9 100644 --- a/smex/elf.c +++ b/smex/elf.c @@ -517,6 +517,10 @@ int elf_read_section(const struct elf_module *module, const char *section_name, error: free(*dst_buff); + /* clear the caller's pointer so a caller cleanup path (e.g. ldc.c's + * "if (buffer) free(buffer)") does not free the same buffer again + */ + *dst_buff = NULL; return ret; } From ac570e1ac5cc23d98b3cdd5d7aad4aacd4d670c3 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:13 +0100 Subject: [PATCH 135/303] smex: bound section name offsets to the string table Section names are used as offsets into the string table; an out-of-range name offset from a crafted ELF read past the table. Validate every section name offset against the string-table size after loading the sections. Signed-off-by: Liam Girdwood --- smex/elf.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/smex/elf.c b/smex/elf.c index 9558b1e781b9..6069f12f6b92 100644 --- a/smex/elf.c +++ b/smex/elf.c @@ -88,6 +88,18 @@ static int elf_read_sections(struct elf_module *module, bool verbose) return count > 0 ? -ENODATA : -errno; } + /* every section name is used as an offset into the string table; make + * sure each stays within it so later "module->strings + name" reads + * cannot run past the table + */ + for (i = 0; i < hdr->shnum; i++) { + if (section[i].name >= section[hdr->shstrndx].size) { + fprintf(stderr, "error: %s section %d name offset %u out of range\n", + module->elf_file, i, section[i].name); + return -ENOEXEC; + } + } + module->bss_index = elf_find_section(module, ".bss"); if (module->bss_index < 0) { fprintf(stderr, "Can't find .bss section in %s", From 33310c77a48c7b35ec999f9696da3f1793a22aa0 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:32:42 +0100 Subject: [PATCH 136/303] smex: reject undersized fw-ready section The fw-ready section was cast to a struct and field-read without checking its size, reading past a short section. Reject a section smaller than the struct. Signed-off-by: Liam Girdwood --- smex/ldc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/smex/ldc.c b/smex/ldc.c index 4eccdb1c0918..737b1d05869f 100644 --- a/smex/ldc.c +++ b/smex/ldc.c @@ -26,6 +26,13 @@ static int fw_version_copy(const struct elf_module *src, if (section_size < 0) return section_size; + if ((size_t)section_size < sizeof(struct sof_ipc_fw_ready)) { + fprintf(stderr, "error: .fw_ready section too small: %d\n", + section_size); + free(buffer); + return -EINVAL; + } + memcpy(&header->version, &((struct sof_ipc_fw_ready *)buffer)->version, sizeof(header->version)); From e87b9af4529c6ccb2effed303380b2a536e3bcba Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:32:42 +0100 Subject: [PATCH 137/303] smex: bound the extended manifest walk The extended-manifest walk advanced by an element size read from the section without validating it, so a zero size looped forever and a large size read past the section. Stop on a zero size or one that would leave the section. Signed-off-by: Liam Girdwood --- smex/ldc.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/smex/ldc.c b/smex/ldc.c index 737b1d05869f..a44b67a05973 100644 --- a/smex/ldc.c +++ b/smex/ldc.c @@ -57,13 +57,37 @@ static int fw_version_copy(const struct elf_module *src, return section_size; ext_hdr = (struct ext_man_elem_header *)buffer; - while ((uintptr_t)ext_hdr < (uintptr_t)buffer + section_size) { + while ((uintptr_t)ext_hdr + sizeof(*ext_hdr) <= + (uintptr_t)buffer + section_size) { if (ext_hdr->type == EXT_MAN_ELEM_DBG_ABI) { + /* make sure the whole dbg-abi element is within the + * section before reading it + */ + if (ext_hdr->elem_size < sizeof(struct ext_man_dbg_abi) || + (uintptr_t)ext_hdr + sizeof(struct ext_man_dbg_abi) > + (uintptr_t)buffer + section_size) { + fprintf(stderr, "error: %s truncated dbg-abi element\n", + src->elf_file); + free(buffer); + return -ENOEXEC; + } header->version.abi_version = ((struct ext_man_dbg_abi *) ext_hdr)->dbg_abi.abi_dbg_version; break; } + /* a malformed element size would loop forever (0) or advance + * the cursor past the section; reject the image rather than + * silently stopping + */ + if (ext_hdr->elem_size == 0 || + (uintptr_t)ext_hdr + ext_hdr->elem_size > + (uintptr_t)buffer + section_size) { + fprintf(stderr, "error: %s malformed ext-manifest element\n", + src->elf_file); + free(buffer); + return -ENOEXEC; + } //move to the next entry ext_hdr = (struct ext_man_elem_header *) ((uint8_t *)ext_hdr + ext_hdr->elem_size); From 121c9c728c7522aaad2fc35c69979e04f3a7a257 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Fri, 12 Jun 2026 20:25:08 +0300 Subject: [PATCH 138/303] audio: crossover: Improve robustness for invalid configuration crossover_validate_config() was only run on the initial blob in crossover_prepare(). Runtime IPC updates fetched in crossover_process_audio_stream() were applied without revalidation, so a bad blob could drive crossover_setup() with an out-of-range num_sinks. Validate the new blob before crossover_setup() runs. Also extend the validator to cross-check config->size against the size reported by the framework and to require enough payload for the LR4 biquads that crossover_init_coef_ch() reads (1 pair for 2-way, 3 pairs for 3-way and 4-way). Signed-off-by: Seppo Ingalsuo --- src/audio/crossover/crossover.c | 87 +++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 15 deletions(-) diff --git a/src/audio/crossover/crossover.c b/src/audio/crossover/crossover.c index 5c1dca7cc9aa..cbc45a04ec50 100644 --- a/src/audio/crossover/crossover.c +++ b/src/audio/crossover/crossover.c @@ -350,16 +350,40 @@ static int crossover_free(struct processing_module *mod) * \brief Verifies that the config is formatted correctly. * * The function can only be called after the buffers have been initialized. + * + * \param[in] mod Processing module owning the configuration. The blob + * under validation is read from the module's private data + * (\c cd->config); callers must store it there beforehand. + * \param[in] new_size Size in bytes reported by the framework for the blob; + * must match the size field embedded in the blob header. + * \return 0 on success, negative errno on invalid configuration. */ -static int crossover_validate_config(struct processing_module *mod, - struct sof_crossover_config *config) +static int crossover_validate_config(struct processing_module *mod, size_t new_size) { + struct comp_data *cd = module_get_private_data(mod); + struct sof_crossover_config *config = cd->config; struct comp_dev *dev = mod->dev; - uint32_t size = config->size; + size_t required_size; int32_t num_assigned_sinks; + int32_t num_lr4s; + uint32_t size; + + if (!config) { + comp_err(dev, "NULL config blob"); + return -EINVAL; + } - if (size > SOF_CROSSOVER_MAX_SIZE || !size) { - comp_err(dev, "size %d is invalid", size); + /* Reject truncated blobs before touching config->size: the framework- + * reported new_size must cover at least the fixed header. + */ + if (new_size < sizeof(*config)) { + comp_err(dev, "size %u is smaller than header", (uint32_t)new_size); + return -EINVAL; + } + + size = config->size; + if (size > SOF_CROSSOVER_MAX_SIZE || size != new_size) { + comp_err(dev, "size %u is invalid", size); return -EINVAL; } @@ -370,6 +394,17 @@ static int crossover_validate_config(struct processing_module *mod, return -EINVAL; } + /* Each channel reads 2 * num_lr4s biquads from config->coef[]; the + * runtime uses 1 LR4 pair for 2-way and 3 LR4 pairs otherwise. + * See tune/sof_crossover_generate_config.m script. + */ + num_lr4s = (config->num_sinks == CROSSOVER_2WAY_NUM_SINKS) ? 1 : 3; + required_size = sizeof(*config) + (size_t)num_lr4s * 2 * sizeof(struct sof_eq_iir_biquad); + if (size < required_size) { + comp_err(dev, "size %u too small for num_sinks %u", size, config->num_sinks); + return -EINVAL; + } + /* Align the crossover's sinks, to their respective configuration in * the config. */ @@ -448,6 +483,9 @@ static int crossover_process_audio_stream(struct processing_module *mod, uint32_t frames = input_buffers[0].size; uint32_t frame_bytes = audio_stream_frame_bytes(input_buffers[0].data); uint32_t processed_bytes; + struct sof_crossover_config *prev_config; + uint32_t prev_num_sinks; + size_t cfg_size; int ret; int i; @@ -455,7 +493,26 @@ static int crossover_process_audio_stream(struct processing_module *mod, /* Check for changed configuration */ if (comp_is_new_data_blob_available(cd->model_handler)) { - cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + prev_config = cd->config; + prev_num_sinks = prev_config ? prev_config->num_sinks : 0; + + cd->config = comp_get_data_blob(cd->model_handler, &cfg_size, NULL); + ret = crossover_validate_config(mod, cfg_size); + if (ret < 0) + return ret; + + /* num_sinks must not change at runtime: cd->crossover_split was + * selected for the previous count, and a smaller value would + * cause the split function to write past the sink array. + * Reject the blob; the previous config and split stay in use. + */ + if (prev_num_sinks && cd->config->num_sinks != prev_num_sinks) { + comp_err(dev, "runtime num_sinks change %u -> %u not supported", + prev_num_sinks, cd->config->num_sinks); + cd->config = prev_config; + return -EINVAL; + } + ret = crossover_setup(mod, audio_stream_get_channels(source)); if (ret < 0) { comp_err(dev, "failed Crossover setup"); @@ -513,6 +570,7 @@ static int crossover_prepare(struct processing_module *mod, struct comp_buffer *source, *sink; size_t data_size; int channels; + int ret; comp_info(dev, "entry"); @@ -545,17 +603,16 @@ static int crossover_prepare(struct processing_module *mod, cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); - /* Initialize Crossover */ - if (cd->config && - (!data_size || crossover_validate_config(mod, cd->config) < 0)) { - /* If the configuration is invalid fail the prepare */ - comp_err(dev, "invalid binary config format"); - return -EINVAL; - } - + /* A NULL cd->config is a supported operating mode: the component runs + * in passthrough (see the else-branch below), so only validate when a + * blob is actually present. + */ if (cd->config) { - int ret = crossover_setup(mod, channels); + ret = crossover_validate_config(mod, data_size); + if (ret < 0) + return ret; + ret = crossover_setup(mod, channels); if (ret < 0) { comp_err(dev, "setup failed"); return ret; From 33bc59c78f30bc1480651af7da60ba45953f31ba Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Mon, 15 Jun 2026 14:00:55 +0300 Subject: [PATCH 139/303] math: fft: fix error path in mod_fft_multi_plan_new() The error path of mod_fft_multi_plan_new() had two related bugs. With num_ffts == 1 the temporary input buffer aliases the caller-provided inb, but the err_free_buffer label freed plan->tmp_i32[0] unconditionally, which would have released the caller's buffer. With num_ffts == 3, a failure in the second or third fft_plan_common_new() call only freed the shared scratch buffer and the bit-reverse table, leaking the previously allocated fft_plan entries. Collapse all error labels into a single err: that calls mod_fft_multi_plan_free(). That helper already walks fft_plan[] (NULL slots from the initial mod_zalloc() are no-ops in mod_free()) and only frees tmp_i32[0] when num_ffts > 1, so both issues are handled in one place. Signed-off-by: Seppo Ingalsuo --- src/math/fft/fft_multi.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/math/fft/fft_multi.c b/src/math/fft/fft_multi.c index b16decca012d..794eb3002c47 100644 --- a/src/math/fft/fft_multi.c +++ b/src/math/fft/fft_multi.c @@ -75,7 +75,7 @@ struct fft_multi_plan *mod_fft_multi_plan_new(struct processing_module *mod, voi plan->tmp_i32[0] = mod_balloc(mod, tmp_size); if (!plan->tmp_i32[0]) { comp_cl_err(mod->dev, "Failed to allocate FFT buffers"); - goto err_free_bit_reverse; + goto err; } /* Set up buffers */ @@ -95,7 +95,7 @@ struct fft_multi_plan *mod_fft_multi_plan_new(struct processing_module *mod, voi plan->tmp_o32[i], plan->fft_size, 32); if (!plan->fft_plan[i]) - goto err_free_buffer; + goto err; plan->fft_plan[i]->bit_reverse_idx = plan->bit_reverse_idx; } @@ -110,14 +110,13 @@ struct fft_multi_plan *mod_fft_multi_plan_new(struct processing_module *mod, voi plan->fft_plan[0]->len); return plan; -err_free_buffer: - mod_free(mod, plan->tmp_i32[0]); - -err_free_bit_reverse: - mod_free(mod, plan->bit_reverse_idx); - err: - mod_free(mod, plan); + /* mod_fft_multi_plan_free() handles partial state safely: + * - fft_plan[i] entries left NULL by mod_zalloc are skipped by mod_free, + * - tmp_i32[0] is freed only when num_ffts > 1, so it does not free + * the caller-provided inb in the single-FFT case. + */ + mod_fft_multi_plan_free(mod, plan); return NULL; } From c3c5ee540d5bccc8477e40e3de87c186acff0fa5 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:03 +0100 Subject: [PATCH 140/303] ams: copy the payload struct size into the message slot The slot copy length came from a macro that adds the message length, which over-read past the payload struct since the message data is referenced by pointer, not stored inline. Copy exactly the struct size, which is what the slot stores and the consumer reads back. Signed-off-by: Liam Girdwood --- src/lib/ams.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/ams.c b/src/lib/ams.c index 722ea40c9453..79bb13764fde 100644 --- a/src/lib/ams.c +++ b/src/lib/ams.c @@ -284,9 +284,14 @@ static uint32_t ams_push_slot(struct ams_shared_context __sparse_cache *ctx_shar for (uint32_t i = 0; i < ARRAY_SIZE(ctx_shared->slots); ++i) { if (ctx_shared->slot_uses[i] == 0) { + /* the slot stores the payload struct (read back via + * u.msg); AMS_MESSAGE_SIZE() adds message_length, which + * over-reads past the struct since message is a pointer, + * not inline data + */ err = memcpy_s((__sparse_force void *)ctx_shared->slots[i].u.msg_raw, sizeof(ctx_shared->slots[i].u.msg_raw), - msg, AMS_MESSAGE_SIZE(msg)); + msg, sizeof(*msg)); if (err != 0) return AMS_INVALID_SLOT; From 823b79902ea4316e30a25613ac4fd33b23baf083 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:03 +0100 Subject: [PATCH 141/303] clk: clamp frequency index when no threshold matches When the requested frequency was at least every table entry, the search index ran off the end of the table and the following access read one element past it. Clamp the index to the last valid entry. Signed-off-by: Liam Girdwood --- src/lib/cpu-clk-manager.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/cpu-clk-manager.c b/src/lib/cpu-clk-manager.c index 59744063e4b9..df917fab4f9d 100644 --- a/src/lib/cpu-clk-manager.c +++ b/src/lib/cpu-clk-manager.c @@ -28,6 +28,13 @@ static int request_freq_change(unsigned int core, int freq) break; } + /* if no entry was larger than the requested frequency the loop ends with + * selected_freq_id == clk->freqs_num; clamp to the last valid entry + * before indexing + */ + if (selected_freq_id == clk->freqs_num) + selected_freq_id = clk->freqs_num - 1; + /* don't change clock frequency if already using proper clock */ current_freq = clock_get_freq(core); if (clk->freqs[selected_freq_id].freq != current_freq) From 3bbc01e6d18aceae600b5d30e3ee86b55a3e32f4 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 12 Jun 2026 15:20:58 +0100 Subject: [PATCH 142/303] ams: remove unused message-size macros AMS_MESSAGE_SIZE() and AMS_SLOT_SIZE() assumed the message payload was stored inline after the header, but ams_message_payload.message is a pointer to a caller-owned buffer (set in ams_helper_prepare_payload) and the slot only carries the fixed-size struct. The last use of AMS_MESSAGE_SIZE() was replaced by sizeof(*msg); AMS_SLOT_SIZE() had no users. Remove both as dead and misleading. Signed-off-by: Liam Girdwood --- src/include/sof/lib/ams.h | 4 ---- src/lib/ams.c | 7 +++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/include/sof/lib/ams.h b/src/include/sof/lib/ams.h index 8cb3ae217400..e43d6208f875 100644 --- a/src/include/sof/lib/ams.h +++ b/src/include/sof/lib/ams.h @@ -29,10 +29,6 @@ /* Space allocated for async message content*/ #define AMS_MAX_MSG_SIZE 0x1000 -/* Size of slots message, module id and instance id */ -#define AMS_SLOT_SIZE(msg) (AMS_MESSAGE_SIZE(msg) + sizeof(uint16_t) * 2) -#define AMS_MESSAGE_SIZE(msg) (sizeof(*msg) - sizeof(char) + (sizeof(char) * (msg->message_length))) - /** * \brief IXC message payload * diff --git a/src/lib/ams.c b/src/lib/ams.c index 79bb13764fde..7f4e6ebb8664 100644 --- a/src/lib/ams.c +++ b/src/lib/ams.c @@ -284,10 +284,9 @@ static uint32_t ams_push_slot(struct ams_shared_context __sparse_cache *ctx_shar for (uint32_t i = 0; i < ARRAY_SIZE(ctx_shared->slots); ++i) { if (ctx_shared->slot_uses[i] == 0) { - /* the slot stores the payload struct (read back via - * u.msg); AMS_MESSAGE_SIZE() adds message_length, which - * over-reads past the struct since message is a pointer, - * not inline data + /* the slot only carries the payload struct (read back + * via u.msg); message points to a caller-owned buffer + * rather than inline data, so copy exactly the struct */ err = memcpy_s((__sparse_force void *)ctx_shared->slots[i].u.msg_raw, sizeof(ctx_shared->slots[i].u.msg_raw), From 676796545f841d3c27b19be090358c30294bbb8b Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:42 +0100 Subject: [PATCH 143/303] tplg: reject zero-size vendor array A vendor array with zero size never advanced the parse cursor, looping forever on a malformed topology. Reject a zero-size array. Signed-off-by: Liam Girdwood --- tools/tplg_parser/object.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/tplg_parser/object.c b/tools/tplg_parser/object.c index 4592031c7c01..ddc31e96f896 100644 --- a/tools/tplg_parser/object.c +++ b/tools/tplg_parser/object.c @@ -62,6 +62,14 @@ int tplg_create_object(struct tplg_context *ctx, return -EINVAL; } + /* a zero-size array never advances the cursor below: bail out + * instead of looping forever on a malformed topology + */ + if (array->size == 0) { + fprintf(stderr, "error: load %s zero-size array\n", name); + return -EINVAL; + } + for (i = 0; i < ipc->num_groups; i++) { const struct sof_topology_token *tokens = ipc->grp[i].tokens; int num_tokens = ipc->grp[i].num_tokens; From af6c200847a9a85c22cb6d87446d7104fdca1d14 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:42 +0100 Subject: [PATCH 144/303] tplg: reject undersized process private data The private-data size had the ABI header length subtracted without checking it was at least that large, underflowing the computed size. Reject private data smaller than the header in both append paths. Signed-off-by: Liam Girdwood --- tools/tplg_parser/process.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/tplg_parser/process.c b/tools/tplg_parser/process.c index 5f0d8cdddaf2..97f7f5b7022b 100644 --- a/tools/tplg_parser/process.c +++ b/tools/tplg_parser/process.c @@ -151,6 +151,11 @@ static int process_append_data3(void *_process_ipc, /* Size is process IPC plus private data minus ABI header */ bytes_ctl = (struct snd_soc_tplg_bytes_control *)ctl; + if (bytes_ctl->priv.size < sizeof(struct sof_abi_hdr)) { + fprintf(stderr, "error: process priv data too small: %u\n", + bytes_ctl->priv.size); + return -EINVAL; + } size = bytes_ctl->priv.size - sizeof(struct sof_abi_hdr); ipc_size = sizeof(struct sof_ipc_comp_process) + UUID_SIZE + process_ipc->size + size; @@ -184,6 +189,11 @@ static int process_append_data4(void *_process_ipc, /* Size is process IPC plus private data minus ABI header */ bytes_ctl = (struct snd_soc_tplg_bytes_control *)ctl; + if (bytes_ctl->priv.size < sizeof(struct sof_abi_hdr)) { + fprintf(stderr, "error: process priv data too small: %u\n", + bytes_ctl->priv.size); + return -EINVAL; + } size = bytes_ctl->priv.size - sizeof(struct sof_abi_hdr); /* validate if everything will fit */ From b9ebb0ea7242074e7210660472472ad625d72d7b Mon Sep 17 00:00:00 2001 From: Eddy Hsu Date: Thu, 28 May 2026 18:07:31 +0000 Subject: [PATCH 145/303] Audio: add CTC to PTL cs42l43 path. Include CTC component in PTL with cs42l43. Signed-off-by: Eddy Hsu --- tools/topology/topology2/cavs-sdw.conf | 10 +- ...-gain-dax-ctc-alh-dai-copier-playback.conf | 57 ++ .../platform/intel/sdw-amp-dax-ctc.conf | 742 ++++++++++++++++++ .../production/tplg-targets-ace3.cmake | 21 + 4 files changed, 829 insertions(+), 1 deletion(-) create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-dax-ctc-alh-dai-copier-playback.conf create mode 100644 tools/topology/topology2/platform/intel/sdw-amp-dax-ctc.conf diff --git a/tools/topology/topology2/cavs-sdw.conf b/tools/topology/topology2/cavs-sdw.conf index 6932543c06e5..f2e163f4bb8c 100644 --- a/tools/topology/topology2/cavs-sdw.conf +++ b/tools/topology/topology2/cavs-sdw.conf @@ -13,7 +13,9 @@ + + @@ -39,6 +41,7 @@ + @@ -199,7 +202,12 @@ IncludeByKey.SDW_AMP_XOVER { } } IncludeByKey.NUM_SDW_AMP_CTC_LINKS { - "[1-3]" "platform/intel/sdw-amp-generic-ctc.conf" + "[1-3]" { + IncludeByKey.SDW_AMP_PIPELINE_SRC { + "dax" "platform/intel/sdw-amp-dax-ctc.conf" + "generic" "platform/intel/sdw-amp-generic-ctc.conf" + } + } } } "true" { diff --git a/tools/topology/topology2/include/pipelines/cavs/mixout-gain-dax-ctc-alh-dai-copier-playback.conf b/tools/topology/topology2/include/pipelines/cavs/mixout-gain-dax-ctc-alh-dai-copier-playback.conf new file mode 100644 index 000000000000..887aad30ef78 --- /dev/null +++ b/tools/topology/topology2/include/pipelines/cavs/mixout-gain-dax-ctc-alh-dai-copier-playback.conf @@ -0,0 +1,57 @@ +# +# BE playback pipeline: mixout-gain-dax-ctc-alh-dai-copier. +# + + + + + +Class.Pipeline."mixout-gain-dax-ctc-alh-dai-copier-playback" { + SubTreeCopy.baseclass { + # this class extends the mixout-gain-dax-alh-dai-copier-playback class definition + source "Class.Pipeline.mixout-gain-dax-alh-dai-copier-playback" + + tree { + Object.Widget { + ctc."1" { + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 48000 + in_bit_depth 32 + in_valid_bit_depth 32 + ibs "$[(256 * ($[($in_bit_depth / 8)])) * ($in_channels)]" + } + ] + Object.Base.output_audio_format [ + { + out_rate 48000 + out_bit_depth 32 + out_valid_bit_depth 32 + obs "$[(256 * ($[($out_bit_depth / 8)])) * ($out_channels)]" + } + ] + } + } + + Object.Base { + ! route [ + { + source gain.$index.1 + sink dolby-dax.$index.1 + } + { + source dolby-dax.$index.1 + sink ctc.$index.1 + } + { + source mixout.$index.1 + sink gain.$index.1 + } + ] + } + } + } +} diff --git a/tools/topology/topology2/platform/intel/sdw-amp-dax-ctc.conf b/tools/topology/topology2/platform/intel/sdw-amp-dax-ctc.conf new file mode 100644 index 000000000000..f53e87b09218 --- /dev/null +++ b/tools/topology/topology2/platform/intel/sdw-amp-dax-ctc.conf @@ -0,0 +1,742 @@ +# route and pipeline index start from pcm id * 10 + +Define { + SDW_SPK_STREAM 'SDW1-Playback' + SDW_SPK_IN_STREAM 'SDW1-Capture' + ALH_2ND_SPK_ID 22 + ALH_3RD_SPK_ID 23 + ALH_2ND_SPK_IN_ID 32 + ALH_3RD_SPK_IN_ID 33 + SDW_AMP_BE_ID 2 + SDW_AMP_IN_BE_ID 3 + AMP_FEEDBACK_CH 2 + AMP_FEEDBACK_CH_PER_LINK 2 + SDW_AMP_FEEDBACK true + AMP_PLAYBACK_NAME 'Speaker Playback' +} + +# include deep buffer config if buffer size is in 1 - 1000 ms. +IncludeByKey.DEEPBUFFER_FW_DMA_MS { + "([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|1000)" { + IncludeByKey.DEEP_BUF_SPK { + "true" { + #deep-buffer-spk.conf is included in deep-buffer.conf + #and deep-buffer.conf is included if SDW_JACK is true. + #Therefore, only include deep-buffer-spk.conf when + #SDW_JACK is false to avoid duplicated. + IncludeByKey.SDW_JACK { + "false" "platform/intel/deep-buffer-spk.conf" + } + } + } + } +} + +Object.Dai.ALH [ + { + dai_index 20 + id $SDW_AMP_BE_ID + direction "playback" + name $SDW_SPK_STREAM + default_hw_conf_id 0 + rate 48000 + channels 2 + + Object.Base.hw_config.1 { + id 0 + name "ALH514" + } + } +] + +Object.Widget.module-copier."22" { + index 21 + num_input_pins 1 + num_output_pins 2 + num_input_audio_formats 1 + num_output_audio_formats 1 + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] +} + +Object.Pipeline { + host-copier-gain-mixin-playback [ + { + index 20 + + Object.Widget.host-copier.1 { + stream_name "sdw amplifiers" + pcm_id 2 + IncludeByKey.PCM_FORMAT_ALL { + "true" { + num_input_audio_formats 5 + Object.Base.input_audio_format [ + { + in_bit_depth 16 + in_valid_bit_depth 16 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + { + in_bit_depth 32 + in_valid_bit_depth 24 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + in_sample_type $SAMPLE_TYPE_FLOAT + } + { + in_bit_depth 8 + in_valid_bit_depth 8 + in_sample_type $SAMPLE_TYPE_UNSIGNED_INTEGER + } + ] + } + "false" { + num_input_audio_formats 3 + Object.Base.input_audio_format [ + { + in_bit_depth 16 + in_valid_bit_depth 16 + } + { + in_bit_depth 32 + in_valid_bit_depth 24 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + } + } + } + Object.Widget.gain.1 { + Object.Control.mixer.1 { + name 'Pre Mixer $AMP_PLAYBACK_NAME Volume' + } + } + } + ] + + mixout-gain-dax-ctc-alh-dai-copier-playback [ + { + index 21 + + Object.Widget.alh-copier.1 { + stream_name $SDW_SPK_STREAM + node_type $ALH_LINK_OUTPUT_CLASS + num_input_audio_formats 3 + Object.Base.input_audio_format [ + { + in_bit_depth 16 + in_valid_bit_depth 16 + } + { + in_bit_depth 32 + in_valid_bit_depth 24 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + num_output_audio_formats 1 + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256))]" + } + ] + } + Object.Widget.gain.1 { + Object.Control.mixer.1 { + name 'Post Mixer $AMP_PLAYBACK_NAME Volume' + } + } + Object.Widget.dolby-dax.1 { + core_id $DOLBY_DAX_CORE_ID + Object.Control { + mixer."1" { + name 'DAX Speaker Switch' + } + mixer."2" { + name 'DAX Speaker Switch CP' + } + mixer."3" { + name 'DAX Speaker Switch CTC' + } + mixer."4" { + name 'DAX Speaker Volume' + } + enum."1" { + name 'DAX Speaker Profile' + } + enum."2" { + name 'DAX Speaker Device' + } + bytes."1" { + name 'DAX Speaker Tuning' + max 8192 + } + } + } + } + ] +} + +IncludeByKey.NUM_SDW_AMP_LINKS { +"2" { + Define { + AMP_FEEDBACK_CH 4 + AMP_FEEDBACK_CH_PER_LINK 2 + } + + Object.Widget { + alh-copier [ + { + index $ALH_2ND_SPK_ID + type dai_in + stream_name $SDW_SPK_STREAM + dai_index 1 + type "dai_in" + direction "playback" + node_type $ALH_LINK_OUTPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_input_pins 1 + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256))]" + } + ] + } + ] + IncludeByKey.SDW_AMP_FEEDBACK { + "true" { + alh-copier [ + { + index $ALH_2ND_SPK_IN_ID + type dai_out + stream_name $SDW_SPK_IN_STREAM + dai_index 1 + type "dai_out" + direction "capture" + node_type $ALH_LINK_INPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_output_pins 1 + + Object.Base.input_audio_format [ + { + in_channels $AMP_FEEDBACK_CH + in_bit_depth 32 + in_valid_bit_depth $SDW_LINK_VALID_BITS + in_sample_type $SAMPLE_TYPE_MSB_INTEGER + in_fmt_cfg "$[($in_channels | ($in_valid_bit_depth * 256))]" + } + ] + Object.Base.output_audio_format [ + { + out_channels $AMP_FEEDBACK_CH + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + ] + } + } + pipeline [ + { + index $ALH_2ND_SPK_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + { + index $ALH_2ND_SPK_IN_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + ] + # Add a virtual widget to connect the aggregated 2nd DAI copier + virtual [ + { + name 'virtual.sdw-amp' + type output + index $ALH_2ND_SPK_ID + } + ] + } + + Object.Base.route [ + { + source "ctc.21.1" + sink "virtual.sdw-amp" + } + { + source "virtual.sdw-amp" + sink "alh-copier.$SDW_SPK_STREAM.1" + } + ] + } + +"3" { + Define { + AMP_FEEDBACK_CH 6 + AMP_FEEDBACK_CH_PER_LINK 2 + } + + Object.Widget { + alh-copier [ + { + index $ALH_2ND_SPK_ID + type dai_in + stream_name $SDW_SPK_STREAM + dai_index 1 + type "dai_in" + direction "playback" + node_type $ALH_LINK_OUTPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_input_pins 1 + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256))]" + } + ] + } + { + index $ALH_3RD_SPK_ID + type dai_in + stream_name $SDW_SPK_STREAM + dai_index 2 + type "dai_in" + direction "playback" + node_type $ALH_LINK_OUTPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_input_pins 1 + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256))]" + } + ] + } + ] + IncludeByKey.SDW_AMP_FEEDBACK { + "true" { + alh-copier [ + { + index $ALH_2ND_SPK_IN_ID + type dai_out + stream_name $SDW_SPK_IN_STREAM + dai_index 1 + type "dai_out" + direction "capture" + node_type $ALH_LINK_INPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_output_pins 1 + + Object.Base.input_audio_format [ + { + in_channels $AMP_FEEDBACK_CH + in_bit_depth 32 + in_valid_bit_depth $SDW_LINK_VALID_BITS + in_sample_type $SAMPLE_TYPE_MSB_INTEGER + in_fmt_cfg "$[($in_channels | ($in_valid_bit_depth * 256))]" + } + ] + Object.Base.output_audio_format [ + { + out_channels $AMP_FEEDBACK_CH + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + { + index $ALH_3RD_SPK_IN_ID + type dai_out + stream_name $SDW_SPK_IN_STREAM + dai_index 2 + type "dai_out" + direction "capture" + node_type $ALH_LINK_INPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_output_pins 1 + + Object.Base.input_audio_format [ + { + in_channels $AMP_FEEDBACK_CH + in_bit_depth 32 + in_valid_bit_depth $SDW_LINK_VALID_BITS + in_sample_type $SAMPLE_TYPE_MSB_INTEGER + in_fmt_cfg "$[($in_channels | ($in_valid_bit_depth * 256))]" + } + ] + Object.Base.output_audio_format [ + { + out_channels $AMP_FEEDBACK_CH + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + ] + } + } + pipeline [ + { + index $ALH_2ND_SPK_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + { + index $ALH_3RD_SPK_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + { + index $ALH_2ND_SPK_IN_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + { + index $ALH_3RD_SPK_IN_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + ] + virtual [ + { + name 'virtual.sdw-amp' + type output + index $ALH_2ND_SPK_ID + } + { + name 'virtual.sdw-amp' + type output + index $ALH_3RD_SPK_ID + } + ] + } + + Object.Base.route [ + { + source "ctc.21.1" + sink "virtual.sdw-amp" + } + { + source "virtual.sdw-amp" + sink "alh-copier.$SDW_SPK_STREAM.1" + } + { + source "virtual.sdw-amp" + sink "alh-copier.$SDW_SPK_STREAM.2" + } + ] + } +} + +Object.PCM.pcm [ + { + name "Speaker" + id 2 + direction "playback" + Object.Base.fe_dai.1 { + name "Speaker" + } + + Object.PCM.pcm_caps.1 { + name "sdw amplifiers" + IncludeByKey.PCM_FORMAT_ALL { + "true" { + formats 'S16_LE,S24_LE,S32_LE,U8,FLOAT_LE' + } + "false" { + formats 'S16_LE,S24_LE,S32_LE' + } + } + } + } +] + +Object.Base.route [ + { + source "ctc.21.1" + sink "module-copier.21.22" + } + { + source "module-copier.21.22" + sink "alh-copier.$SDW_SPK_STREAM.0" + } + { + source 'mixin.20.1' + sink 'mixout.21.1' + } + { + source 'host-copier.2.playback' + sink 'gain.20.1' + } +] + +IncludeByKey.SDW_AMP_FEEDBACK { + "true" { + Object.Dai.ALH [ + { + dai_index 30 + id $SDW_AMP_IN_BE_ID + direction "capture" + name $SDW_SPK_IN_STREAM + default_hw_conf_id 1 + rate 48000 + channels $AMP_FEEDBACK_CH_PER_LINK + + Object.Base.hw_config.1 { + id 1 + name "ALH515" + } + } + ] + Object.Pipeline { + host-gateway-capture [ + { + index 30 + + Object.Widget.host-copier.1 { + stream_name "amp feedback" + pcm_id 3 + + IncludeByKey.AMP_FEEDBACK_CH { + "6" { + num_input_audio_formats 1 + num_output_audio_formats 3 + Object.Base.input_audio_format [ + { + in_channels 6 + in_bit_depth 32 + in_valid_bit_depth 32 + in_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + in_ch_map $CHANNEL_MAP_5_POINT_1 + } + ] + Object.Base.output_audio_format [ + { + out_channels 6 + out_bit_depth 32 + out_valid_bit_depth 24 + out_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + out_ch_map $CHANNEL_MAP_5_POINT_1 + } + { + out_channels 6 + out_bit_depth 32 + out_valid_bit_depth 32 + out_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + out_ch_map $CHANNEL_MAP_5_POINT_1 + } + { + out_channels 6 + out_bit_depth 16 + out_valid_bit_depth 16 + out_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + out_ch_map $CHANNEL_MAP_5_POINT_1 + } + ] + } + "8" { + num_input_audio_formats 1 + num_output_audio_formats 3 + Object.Base.input_audio_format [ + { + in_channels 8 + in_bit_depth 32 + in_valid_bit_depth 32 + in_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + in_ch_map $CHANNEL_MAP_7_POINT_1 + } + ] + Object.Base.output_audio_format [ + { + out_channels 8 + out_bit_depth 32 + out_valid_bit_depth 24 + out_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + out_ch_map $CHANNEL_MAP_7_POINT_1 + } + { + out_channels 8 + out_bit_depth 32 + out_valid_bit_depth 32 + out_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + out_ch_map $CHANNEL_MAP_7_POINT_1 + } + { + out_channels 8 + out_bit_depth 16 + out_valid_bit_depth 16 + out_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + out_ch_map $CHANNEL_MAP_7_POINT_1 + } + ] + } + } + } + } + ] + } + Object.Widget { + alh-copier [ + { + index 31 + type dai_out + stream_name $SDW_SPK_IN_STREAM + type "dai_out" + direction "capture" + node_type $ALH_LINK_INPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_output_pins 1 + + Object.Base.input_audio_format [ + { + in_channels $AMP_FEEDBACK_CH + in_bit_depth 32 + in_valid_bit_depth $SDW_LINK_VALID_BITS + + IncludeByKey.AMP_FEEDBACK_CH { + "2" { + in_ch_cfg $CHANNEL_CONFIG_STEREO + in_ch_map $CHANNEL_MAP_STEREO + } + "4" { + in_ch_cfg $CHANNEL_CONFIG_3_POINT_1 + in_ch_map $CHANNEL_MAP_3_POINT_1 + } + "6" { + in_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + in_ch_map $CHANNEL_MAP_5_POINT_1 + } + "8" { + in_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + in_ch_map $CHANNEL_MAP_7_POINT_1 + } + } + + in_sample_type $SAMPLE_TYPE_MSB_INTEGER + in_fmt_cfg "$[($in_channels | ($in_valid_bit_depth * 256))]" + } + ] + Object.Base.output_audio_format [ + { + out_channels $AMP_FEEDBACK_CH + out_bit_depth 32 + out_valid_bit_depth 32 + + IncludeByKey.AMP_FEEDBACK_CH { + "2" { + out_ch_cfg $CHANNEL_CONFIG_STEREO + out_ch_map $CHANNEL_MAP_STEREO + } + "4" { + out_ch_cfg $CHANNEL_CONFIG_3_POINT_1 + out_ch_map $CHANNEL_MAP_3_POINT_1 + } + "6" { + out_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + out_ch_map $CHANNEL_MAP_5_POINT_1 + } + "8" { + out_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + out_ch_map $CHANNEL_MAP_7_POINT_1 + } + } + } + ] + } + ] + pipeline [ + { + index 31 + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + ] + } + Object.PCM.pcm [ + { + name "Amp feedback" + id 3 + direction "capture" + Object.Base.fe_dai.1 { + name "Amp feedback" + } + + Object.PCM.pcm_caps.1 { + name "amp feedback" + formats 'S16_LE,S24_LE,S32_LE' + channels_min $AMP_FEEDBACK_CH + channels_max $AMP_FEEDBACK_CH + } + } + ] + Object.Base.route [ + { + source "alh-copier.$SDW_SPK_IN_STREAM.0" + sink "host-copier.3.capture" + } + ] + } +} diff --git a/tools/topology/topology2/production/tplg-targets-ace3.cmake b/tools/topology/topology2/production/tplg-targets-ace3.cmake index 953964b068a9..c64f9774dd89 100644 --- a/tools/topology/topology2/production/tplg-targets-ace3.cmake +++ b/tools/topology/topology2/production/tplg-targets-ace3.cmake @@ -175,12 +175,24 @@ SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ DEEPBUFFER_FW_DMA_MS=10,DEEP_BUF_SPK=true,BT_PCM_ID=20,BT_ID=8,BT_PCM_NAME=Bluetooth,ADD_BT=true,\ SDW_SPK_ENHANCED_PLAYBACK=false" +"cavs-sdw\;sof-ptl-cs42l43-agg-l3-cs35l56-l2-ctc\;PLATFORM=ptl,SDW_DMIC=1,NUM_SDW_AMP_CTC_LINKS=2,\ +SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture-SmartMic,\ +SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ +DEEPBUFFER_FW_DMA_MS=10,DEEP_BUF_SPK=true,BT_PCM_ID=20,BT_ID=8,BT_PCM_NAME=Bluetooth,ADD_BT=true,\ +SDW_SPK_ENHANCED_PLAYBACK=false" + "cavs-sdw\;sof-ptl-cs42l43-agg-l3-cs35l56-l2-dax\;PLATFORM=ptl,SDW_DMIC=1,NUM_SDW_AMP_LINKS=2,\ SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture-SmartMic,\ SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ DEEPBUFFER_FW_DMA_MS=10,DEEP_BUF_SPK=true,BT_PCM_ID=20,BT_ID=8,BT_PCM_NAME=Bluetooth,ADD_BT=true,\ SDW_AMP_PIPELINE_SRC=dax,SDW_JACK_PIPELINE_SRC=dax,DOLBY_DAX_CORE_ID=0" +"cavs-sdw\;sof-ptl-cs42l43-agg-l3-cs35l56-l2-dax-ctc\;PLATFORM=ptl,SDW_DMIC=1,NUM_SDW_AMP_CTC_LINKS=2,\ +SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture-SmartMic,\ +SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ +DEEPBUFFER_FW_DMA_MS=10,DEEP_BUF_SPK=true,BT_PCM_ID=20,BT_ID=8,BT_PCM_NAME=Bluetooth,ADD_BT=true,\ +SDW_AMP_PIPELINE_SRC=dax,DOLBY_DAX_CORE_ID=0" + "cavs-sdw\;sof-ptl-cs42l43-agg-l3-cs35l56-l2-4ch\;PLATFORM=ptl,NUM_SDW_AMP_LINKS=2,NUM_DMICS=4,\ PDM1_MIC_A_ENABLE=1,PDM1_MIC_B_ENABLE=1,DMIC0_ID=3,DMIC1_ID=4,\ SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,\ @@ -190,6 +202,15 @@ DMIC0_ENHANCED_CAPTURE=true,EFX_DMIC0_TDFB_PARAMS=line4_pass,EFX_DMIC0_DRC_PARAM DEEPBUFFER_FW_DMA_MS=10,DEEP_BUF_SPK=true,BT_PCM_ID=20,BT_ID=8,BT_PCM_NAME=Bluetooth,ADD_BT=true,\ SDW_SPK_ENHANCED_PLAYBACK=false" +"cavs-sdw\;sof-ptl-cs42l43-agg-l3-cs35l56-l2-4ch-ctc\;PLATFORM=ptl,NUM_SDW_AMP_CTC_LINKS=2,NUM_DMICS=4,\ +PDM1_MIC_A_ENABLE=1,PDM1_MIC_B_ENABLE=1,DMIC0_ID=3,DMIC1_ID=4,\ +SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,\ +SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ +PREPROCESS_PLUGINS=nhlt,NHLT_BIN=nhlt-sof-ptl-cs42l43-agg-l3-cs35l56-l2-4ch-ctc.bin,\ +DMIC0_ENHANCED_CAPTURE=true,EFX_DMIC0_TDFB_PARAMS=line4_pass,EFX_DMIC0_DRC_PARAMS=dmic_default,\ +DEEPBUFFER_FW_DMA_MS=10,DEEP_BUF_SPK=true,BT_PCM_ID=20,BT_ID=8,BT_PCM_NAME=Bluetooth,ADD_BT=true,\ +SDW_SPK_ENHANCED_PLAYBACK=false" + # SSP codec topologies for PTL # ES83x6 codec alone without HDMI-in capture "cavs-es83x6\;sof-ptl-es8336-ssp1\;PLATFORM=ptl,PREPROCESS_PLUGINS=nhlt,\ From 59d63f65ac4ee7509ae92ca5b0e432a500d70d09 Mon Sep 17 00:00:00 2001 From: Eddy Hsu Date: Thu, 28 May 2026 18:38:02 +0000 Subject: [PATCH 146/303] Audio: add CTC to PTL rt721 path. Include CTC component in PTL with rt721. Signed-off-by: Eddy Hsu --- tools/topology/topology2/cavs-sdw.conf | 2 + ...eqiir-dts-ctc-alh-dai-copier-playback.conf | 60 ++ .../platform/intel/sdw-amp-dts-ctc.conf | 922 ++++++++++++++++++ .../production/tplg-targets-ace3.cmake | 9 + 4 files changed, 993 insertions(+) create mode 100644 tools/topology/topology2/include/pipelines/cavs/mixout-gain-eqiir-dts-ctc-alh-dai-copier-playback.conf create mode 100644 tools/topology/topology2/platform/intel/sdw-amp-dts-ctc.conf diff --git a/tools/topology/topology2/cavs-sdw.conf b/tools/topology/topology2/cavs-sdw.conf index f2e163f4bb8c..492cffaacb52 100644 --- a/tools/topology/topology2/cavs-sdw.conf +++ b/tools/topology/topology2/cavs-sdw.conf @@ -20,6 +20,7 @@ + @@ -205,6 +206,7 @@ IncludeByKey.SDW_AMP_XOVER { "[1-3]" { IncludeByKey.SDW_AMP_PIPELINE_SRC { "dax" "platform/intel/sdw-amp-dax-ctc.conf" + "dts" "platform/intel/sdw-amp-dts-ctc.conf" "generic" "platform/intel/sdw-amp-generic-ctc.conf" } } diff --git a/tools/topology/topology2/include/pipelines/cavs/mixout-gain-eqiir-dts-ctc-alh-dai-copier-playback.conf b/tools/topology/topology2/include/pipelines/cavs/mixout-gain-eqiir-dts-ctc-alh-dai-copier-playback.conf new file mode 100644 index 000000000000..693d5a04378f --- /dev/null +++ b/tools/topology/topology2/include/pipelines/cavs/mixout-gain-eqiir-dts-ctc-alh-dai-copier-playback.conf @@ -0,0 +1,60 @@ +# +# BE playback pipeline: mixout-gain-eqiir-dts-ctc-alh-dai-copier. +# + + + + +Class.Pipeline."mixout-gain-eqiir-dts-ctc-alh-dai-copier-playback" { + SubTreeCopy.baseclass { + # this class extends the mixout-gain-eqiir-dts-alh-dai-copier-playback class definition + source "Class.Pipeline.mixout-gain-eqiir-dts-alh-dai-copier-playback" + + tree { + Object.Widget { + ctc."1" { + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 48000 + in_bit_depth 32 + in_valid_bit_depth 32 + ibs "$[(256 * ($[($in_bit_depth / 8)])) * ($in_channels)]" + } + ] + Object.Base.output_audio_format [ + { + out_rate 48000 + out_bit_depth 32 + out_valid_bit_depth 32 + obs "$[(256 * ($[($out_bit_depth / 8)])) * ($out_channels)]" + } + ] + } + } + + Object.Base { + ! route [ + { + source gain.$index.1 + sink eqiir.$index.1 + } + { + source eqiir.$index.1 + sink dts.$index.1 + } + { + source dts.$index.1 + sink ctc.$index.1 + } + { + source mixout.$index.1 + sink gain.$index.1 + } + ] + } + } + } +} diff --git a/tools/topology/topology2/platform/intel/sdw-amp-dts-ctc.conf b/tools/topology/topology2/platform/intel/sdw-amp-dts-ctc.conf new file mode 100644 index 000000000000..e567b4c0b34c --- /dev/null +++ b/tools/topology/topology2/platform/intel/sdw-amp-dts-ctc.conf @@ -0,0 +1,922 @@ +# route and pipeline index start from pcm id * 10 + +Define { + SDW_SPK_STREAM 'SDW1-Playback' + SDW_SPK_IN_STREAM 'SDW1-Capture' + ALH_2ND_SPK_ID 22 + ALH_3RD_SPK_ID 23 + ALH_2ND_SPK_IN_ID 32 + ALH_3RD_SPK_IN_ID 33 + SDW_AMP_BE_ID 2 + SDW_AMP_IN_BE_ID 3 + AMP_FEEDBACK_CH 2 + AMP_FEEDBACK_CH_PER_LINK 2 + SDW_AMP_FEEDBACK true + AMP_PLAYBACK_NAME 'Speaker Playback' +} + +# include deep buffer config if buffer size is in 1 - 1000 ms. +IncludeByKey.PASSTHROUGH { +"false" { + IncludeByKey.DEEPBUFFER_FW_DMA_MS { + "([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|1000)" { + IncludeByKey.DEEP_BUF_SPK { + "true" { + #deep-buffer-spk.conf is included in deep-buffer.conf + #and deep-buffer.conf is included if SDW_JACK is true. + #Therefore, only include deep-buffer-spk.conf when + #SDW_JACK is false to avoid duplicated. + IncludeByKey.SDW_JACK { + "false" "platform/intel/deep-buffer-spk.conf" + } + } + } + } + } +} +} + +Object.Dai.ALH [ + { + dai_index 20 + id $SDW_AMP_BE_ID + direction "playback" + name $SDW_SPK_STREAM + default_hw_conf_id 0 + rate 48000 + channels 2 + + Object.Base.hw_config.1 { + id 0 + name "ALH514" + } + } +] + +Object.Widget.module-copier."22" { + index 21 + num_input_pins 1 + num_output_pins 2 + num_input_audio_formats 1 + num_output_audio_formats 1 + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] +} + +IncludeByKey.PASSTHROUGH { +"false" { + Object.Pipeline { + host-copier-gain-mixin-playback [ + { + index 20 + + Object.Widget.host-copier.1 { + stream_name "sdw amplifiers" + pcm_id 2 + IncludeByKey.PCM_FORMAT_ALL { + "true" { + num_input_audio_formats 5 + Object.Base.input_audio_format [ + { + in_bit_depth 16 + in_valid_bit_depth 16 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + { + in_bit_depth 32 + in_valid_bit_depth 24 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + in_sample_type $SAMPLE_TYPE_FLOAT + } + { + in_bit_depth 8 + in_valid_bit_depth 8 + in_sample_type $SAMPLE_TYPE_UNSIGNED_INTEGER + } + ] + } + "false" { + num_input_audio_formats 3 + Object.Base.input_audio_format [ + { + in_bit_depth 16 + in_valid_bit_depth 16 + } + { + in_bit_depth 32 + in_valid_bit_depth 24 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + } + } + } + Object.Widget.gain.1 { + Object.Control.mixer.1 { + name 'Pre Mixer $AMP_PLAYBACK_NAME Volume' + } + } + } + ] + + IncludeByKey.SDW_SPK_ENHANCED_PLAYBACK { + "true" { + mixout-gain-eqiir-dts-ctc-alh-dai-copier-playback [ + { + index 21 + + Object.Widget.alh-copier.1 { + stream_name $SDW_SPK_STREAM + node_type $ALH_LINK_OUTPUT_CLASS + num_input_audio_formats 3 + Object.Base.input_audio_format [ + { + in_bit_depth 16 + in_valid_bit_depth 16 + } + { + in_bit_depth 32 + in_valid_bit_depth 24 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + num_output_audio_formats 1 + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256)) | ($out_sample_type * 65536)]" + } + ] + } + Object.Widget.gain.1 { + Object.Control.mixer.1 { + name 'Post Mixer $AMP_PLAYBACK_NAME Volume' + } + } + + Object.Widget.eqiir.1 { + Object.Control.bytes."1" { + name 'Post Mixer $AMP_PLAYBACK_NAME IIR Eq bytes' + } + } + + Object.Widget.dts.1 { + Object.Control { + bytes."1" { + name 'Post Mixer $AMP_PLAYBACK_NAME DTS bytes' + max 2048 + } + } + } + } + ] + } + "false" { + mixout-gain-alh-dai-copier-playback [ + { + index 21 + + Object.Widget.alh-copier.1 { + stream_name $SDW_SPK_STREAM + node_type $ALH_LINK_OUTPUT_CLASS + num_input_audio_formats 3 + Object.Base.input_audio_format [ + { + in_bit_depth 16 + in_valid_bit_depth 16 + } + { + in_bit_depth 32 + in_valid_bit_depth 24 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + num_output_audio_formats 1 + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256)) | ($out_sample_type * 65536)]" + } + ] + } + Object.Widget.gain.1 { + Object.Control.mixer.1 { + name 'Post Mixer $AMP_PLAYBACK_NAME Volume' + } + } + } + ] + } + } + } +} +"true" { + Object.Pipeline.host-gateway-playback [ + { + index 20 + Object.Widget.host-copier.1 { + stream_name "sdw amplifiers" + pcm_id 2 + num_input_audio_formats 3 + Object.Base.input_audio_format [ + { + in_bit_depth 16 + in_valid_bit_depth 16 + } + { + in_bit_depth 32 + in_valid_bit_depth 24 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + num_output_audio_formats 3 + Object.Base.output_audio_format [ + { + out_bit_depth 16 + out_valid_bit_depth 16 + } + { + out_bit_depth 32 + out_valid_bit_depth 24 + } + { + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + } + ] + + Object.Widget { + alh-copier [ + { + stream_name $SDW_SPK_STREAM + node_type $ALH_LINK_OUTPUT_CLASS + num_input_pins 1 + direction playback + type dai_in + index 21 + num_input_audio_formats 3 + Object.Base.input_audio_format [ + { + in_bit_depth 16 + in_valid_bit_depth 16 + } + { + in_bit_depth 32 + in_valid_bit_depth 24 + } + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + num_output_audio_formats 1 + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256))]" + } + ] + } + ] + pipeline [ + { + index 21 + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + ] + } + } +} + +IncludeByKey.NUM_SDW_AMP_LINKS { +"2" { + Define { + AMP_FEEDBACK_CH 4 + AMP_FEEDBACK_CH_PER_LINK 2 + } + + Object.Widget { + alh-copier [ + { + index $ALH_2ND_SPK_ID + stream_name $SDW_SPK_STREAM + dai_index 1 + type "dai_in" + direction "playback" + node_type $ALH_LINK_OUTPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_input_pins 1 + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256))]" + } + ] + } + ] + IncludeByKey.SDW_AMP_FEEDBACK { + "true" { + alh-copier [ + { + index $ALH_2ND_SPK_IN_ID + stream_name $SDW_SPK_IN_STREAM + dai_index 1 + type "dai_out" + direction "capture" + node_type $ALH_LINK_INPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_output_pins 1 + + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth $SDW_LINK_VALID_BITS + in_sample_type $SAMPLE_TYPE_MSB_INTEGER + in_fmt_cfg "$[($in_channels | ($in_valid_bit_depth * 256))]" + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + ] + } + } + pipeline [ + { + index $ALH_2ND_SPK_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + { + index $ALH_2ND_SPK_IN_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + ] + # Add a virtual widget to connect the aggregated 2nd DAI copier + virtual [ + { + name 'virtual.sdw-amp' + type output + index $ALH_2ND_SPK_ID + } + ] + } + + # Add the connection from the gain module to the aggregated 2nd DAI copier + # via the virtual widget. The virtual widget ensures that the routes between + # the gain and copier do not get established in the firmware. These are purely + # to show the existence of aggregation in the topology graph. + IncludeByKey.PASSTHROUGH { + "false" { + Object.Base.route [ + { + source "ctc.21.1" + sink "virtual.sdw-amp" + } + ] + } + "true" { + Object.Base.route [ + { + source "host-copier.2.playback" + sink "virtual.sdw-amp" + } + ] + } + } + Object.Base.route [ + { + source "virtual.sdw-amp" + sink "alh-copier.$SDW_SPK_STREAM.1" + } + ] + } + +"3" { + Define { + AMP_FEEDBACK_CH 6 + AMP_FEEDBACK_CH_PER_LINK 2 + } + + Object.Widget { + alh-copier [ + { + index $ALH_2ND_SPK_ID + stream_name $SDW_SPK_STREAM + dai_index 1 + type "dai_in" + direction "playback" + node_type $ALH_LINK_OUTPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_input_pins 1 + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256))]" + } + ] + } + { + index $ALH_3RD_SPK_ID + stream_name $SDW_SPK_STREAM + dai_index 2 + type "dai_in" + direction "playback" + node_type $ALH_LINK_OUTPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_input_pins 1 + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth $SDW_LINK_VALID_BITS + out_sample_type $SAMPLE_TYPE_MSB_INTEGER + out_fmt_cfg "$[($out_channels | ($out_valid_bit_depth * 256))]" + } + ] + } + ] + IncludeByKey.SDW_AMP_FEEDBACK { + "true" { + alh-copier [ + { + index $ALH_2ND_SPK_IN_ID + stream_name $SDW_SPK_IN_STREAM + dai_index 1 + type "dai_out" + direction "capture" + node_type $ALH_LINK_INPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_output_pins 1 + + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth $SDW_LINK_VALID_BITS + in_sample_type $SAMPLE_TYPE_MSB_INTEGER + in_fmt_cfg "$[($in_channels | ($in_valid_bit_depth * 256))]" + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + { + index $ALH_3RD_SPK_IN_ID + stream_name $SDW_SPK_IN_STREAM + dai_index 2 + type "dai_out" + direction "capture" + node_type $ALH_LINK_INPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_output_pins 1 + + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth $SDW_LINK_VALID_BITS + in_sample_type $SAMPLE_TYPE_MSB_INTEGER + in_fmt_cfg "$[($in_channels | ($in_valid_bit_depth * 256))]" + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + ] + } + } + pipeline [ + { + index $ALH_2ND_SPK_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + { + index $ALH_3RD_SPK_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + { + index $ALH_2ND_SPK_IN_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + { + index $ALH_3RD_SPK_IN_ID + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + ] + virtual [ + { + name 'virtual.sdw-amp' + type output + index $ALH_2ND_SPK_ID + } + { + name 'virtual.sdw-amp' + type output + index $ALH_3RD_SPK_ID + } + ] + } + + # Add the connection from the gain module to the aggregated 2nd DAI copier + # via the virtual widget. The virtual widget ensures that the routes between + # the gain and copier do not get established in the firmware. These are purely + # to show the existence of aggregation in the topology graph. + IncludeByKey.PASSTHROUGH { + "false" { + Object.Base.route [ + { + source "ctc.21.1" + sink "virtual.sdw-amp" + } + ] + } + "true" { + Object.Base.route [ + { + source "host-copier.2.playback" + sink "virtual.sdw-amp" + } + ] + } + } + Object.Base.route [ + { + source "virtual.sdw-amp" + sink "alh-copier.$SDW_SPK_STREAM.1" + } + { + source "virtual.sdw-amp" + sink "alh-copier.$SDW_SPK_STREAM.2" + } + ] + } +} + +Object.PCM.pcm [ + { + name "Speaker" + id 2 + direction "playback" + Object.Base.fe_dai.1 { + name "Speaker" + } + + Object.PCM.pcm_caps.1 { + name "sdw amplifiers" + IncludeByKey.PCM_FORMAT_ALL { + "true" { + formats 'S16_LE,S24_LE,S32_LE,U8,FLOAT_LE' + } + "false" { + formats 'S16_LE,S24_LE,S32_LE' + } + } + } + } +] + +IncludeByKey.PASSTHROUGH { +"false" { + IncludeByKey.SDW_SPK_ENHANCED_PLAYBACK { + "true" { + Object.Base.route [ + { + source "ctc.21.1" + sink "module-copier.21.22" + } + { + source "module-copier.21.22" + sink "alh-copier.$SDW_SPK_STREAM.0" + } + ] + } + "false" { + Object.Base.route [ + { + source "gain.21.1" + sink "module-copier.21.22" + } + { + source "module-copier.21.22" + sink "alh-copier.$SDW_SPK_STREAM.0" + } + ] + } + } + Object.Base.route [ + { + source 'mixin.20.1' + sink 'mixout.21.1' + } + { + source 'host-copier.2.playback' + sink 'gain.20.1' + } + ] +} +"true" { + Object.Base.route [ + { + source "host-copier.2.playback" + sink "alh-copier.$SDW_SPK_STREAM.0" + } + ] +} +} + +IncludeByKey.SDW_AMP_FEEDBACK { + "true" { + Object.Dai.ALH [ + { + dai_index 30 + id $SDW_AMP_IN_BE_ID + direction "capture" + name $SDW_SPK_IN_STREAM + default_hw_conf_id 1 + rate 48000 + channels $AMP_FEEDBACK_CH_PER_LINK + + Object.Base.hw_config.1 { + id 1 + name "ALH515" + } + } + ] + Object.Pipeline { + host-gateway-capture [ + { + index 30 + + Object.Widget.host-copier.1 { + stream_name "amp feedback" + pcm_id 3 + + IncludeByKey.AMP_FEEDBACK_CH { + "6" { + num_input_audio_formats 1 + num_output_audio_formats 3 + Object.Base.input_audio_format [ + { + in_channels 6 + in_bit_depth 32 + in_valid_bit_depth 32 + in_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + in_ch_map $CHANNEL_MAP_5_POINT_1 + } + ] + Object.Base.output_audio_format [ + { + out_channels 6 + out_bit_depth 32 + out_valid_bit_depth 24 + out_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + out_ch_map $CHANNEL_MAP_5_POINT_1 + } + { + out_channels 6 + out_bit_depth 32 + out_valid_bit_depth 32 + out_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + out_ch_map $CHANNEL_MAP_5_POINT_1 + } + { + out_channels 6 + out_bit_depth 16 + out_valid_bit_depth 16 + out_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + out_ch_map $CHANNEL_MAP_5_POINT_1 + } + ] + } + "8" { + num_input_audio_formats 1 + num_output_audio_formats 3 + Object.Base.input_audio_format [ + { + in_channels 8 + in_bit_depth 32 + in_valid_bit_depth 32 + in_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + in_ch_map $CHANNEL_MAP_7_POINT_1 + } + ] + Object.Base.output_audio_format [ + { + out_channels 8 + out_bit_depth 32 + out_valid_bit_depth 24 + out_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + out_ch_map $CHANNEL_MAP_7_POINT_1 + } + { + out_channels 8 + out_bit_depth 32 + out_valid_bit_depth 32 + out_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + out_ch_map $CHANNEL_MAP_7_POINT_1 + } + { + out_channels 8 + out_bit_depth 16 + out_valid_bit_depth 16 + out_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + out_ch_map $CHANNEL_MAP_7_POINT_1 + } + ] + } + } + } + } + ] + } + Object.Widget { + alh-copier [ + { + index 31 + stream_name $SDW_SPK_IN_STREAM + type "dai_out" + direction "capture" + node_type $ALH_LINK_INPUT_CLASS + num_input_audio_formats 1 + num_output_audio_formats 1 + num_output_pins 1 + + Object.Base.input_audio_format [ + { + in_channels $AMP_FEEDBACK_CH + in_bit_depth 32 + in_valid_bit_depth $SDW_LINK_VALID_BITS + + IncludeByKey.AMP_FEEDBACK_CH { + "2" { + in_ch_cfg $CHANNEL_CONFIG_STEREO + in_ch_map $CHANNEL_MAP_STEREO + } + "4" { + in_ch_cfg $CHANNEL_CONFIG_3_POINT_1 + in_ch_map $CHANNEL_MAP_3_POINT_1 + } + "6" { + in_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + in_ch_map $CHANNEL_MAP_5_POINT_1 + } + "8" { + in_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + in_ch_map $CHANNEL_MAP_7_POINT_1 + } + } + + in_sample_type $SAMPLE_TYPE_MSB_INTEGER + in_fmt_cfg "$[($in_channels | ($in_valid_bit_depth * 256))]" + } + ] + Object.Base.output_audio_format [ + { + out_channels $AMP_FEEDBACK_CH + out_bit_depth 32 + out_valid_bit_depth 32 + + IncludeByKey.AMP_FEEDBACK_CH { + "2" { + out_ch_cfg $CHANNEL_CONFIG_STEREO + out_ch_map $CHANNEL_MAP_STEREO + } + "4" { + out_ch_cfg $CHANNEL_CONFIG_3_POINT_1 + out_ch_map $CHANNEL_MAP_3_POINT_1 + } + "6" { + out_ch_cfg $CHANNEL_CONFIG_5_POINT_1 + out_ch_map $CHANNEL_MAP_5_POINT_1 + } + "8" { + out_ch_cfg $CHANNEL_CONFIG_7_POINT_1 + out_ch_map $CHANNEL_MAP_7_POINT_1 + } + } + } + ] + } + ] + pipeline [ + { + index 31 + priority 0 + lp_mode 0 + dynamic_pipeline 1 + } + ] + } + Object.PCM.pcm [ + { + name "Amp feedback" + id 3 + direction "capture" + Object.Base.fe_dai.1 { + name "Amp feedback" + } + + Object.PCM.pcm_caps.1 { + name "amp feedback" + formats 'S16_LE,S24_LE,S32_LE' + channels_min $AMP_FEEDBACK_CH + channels_max $AMP_FEEDBACK_CH + } + } + ] + Object.Base.route [ + { + source "alh-copier.$SDW_SPK_IN_STREAM.0" + sink "host-copier.3.capture" + } + ] + } +} diff --git a/tools/topology/topology2/production/tplg-targets-ace3.cmake b/tools/topology/topology2/production/tplg-targets-ace3.cmake index c64f9774dd89..3b5dc858fd20 100644 --- a/tools/topology/topology2/production/tplg-targets-ace3.cmake +++ b/tools/topology/topology2/production/tplg-targets-ace3.cmake @@ -76,6 +76,15 @@ DMIC0_ENHANCED_CAPTURE=true,EFX_DMIC0_TDFB_PARAMS=line4_pass,EFX_DMIC0_DRC_PARAM DEEP_BUF_SPK=true,BT_PCM_ID=8,BT_ID=8,BT_PCM_NAME=Bluetooth,ADD_BT=true,SDW_AMP_PIPELINE_SRC=dts,\ SDW_JACK_PIPELINE_SRC=dts" +"cavs-sdw\;sof-ptl-rt721-l3-rt1320-l3-4ch-ssp2-bt-dts-ctc\;PLATFORM=ptl,NUM_SDW_AMP_CTC_LINKS=1,NUM_DMICS=4,\ +PDM1_MIC_A_ENABLE=1,PDM1_MIC_B_ENABLE=1,DMIC0_ID=3,DMIC1_ID=4,\ +SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,\ +SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ +PREPROCESS_PLUGINS=nhlt,NHLT_BIN=nhlt-sof-ptl-rt721-l3-rt1320-l3-4ch-ssp2-bt-dts-ctc.bin,\ +DMIC0_ENHANCED_CAPTURE=true,EFX_DMIC0_TDFB_PARAMS=line4_pass,EFX_DMIC0_DRC_PARAMS=dmic_default,\ +DEEP_BUF_SPK=true,BT_PCM_ID=8,BT_ID=8,BT_PCM_NAME=Bluetooth,ADD_BT=true,SDW_AMP_PIPELINE_SRC=dts,\ +SDW_JACK_PIPELINE_SRC=dts" + "cavs-sdw\;sof-ptl-rt722\;PLATFORM=ptl,SDW_DMIC=1,NUM_SDW_AMP_LINKS=1,\ SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture-SmartMic,\ SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack" From 21db11451cec65f69834a4643cbd674919068028 Mon Sep 17 00:00:00 2001 From: Eddy Hsu Date: Thu, 4 Jun 2026 16:00:37 +0000 Subject: [PATCH 147/303] Audio: Fix NUM_SDW_AMP_CTC_LINKS in ctc paths Fix NUM_SDW_AMP_CTC_LINKS definitions in CTC related topologies. Signed-off-by: Eddy Hsu --- tools/topology/topology2/cavs-sdw.conf | 9 +++++++++ .../topology2/platform/intel/sdw-amp-dax-ctc.conf | 2 +- .../topology2/platform/intel/sdw-amp-dts-ctc.conf | 2 +- .../topology2/platform/intel/sdw-amp-generic-ctc.conf | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tools/topology/topology2/cavs-sdw.conf b/tools/topology/topology2/cavs-sdw.conf index 492cffaacb52..3117bde753de 100644 --- a/tools/topology/topology2/cavs-sdw.conf +++ b/tools/topology/topology2/cavs-sdw.conf @@ -248,6 +248,15 @@ IncludeByKey.NUM_SDW_AMP_LINKS { } } +IncludeByKey.NUM_SDW_AMP_CTC_LINKS { + "[1-3]" { + Define { + # Enable compressed audio for Speaker + COMPRESSED_2 true + } + } +} + IncludeByKey.COMPRESSED { "true" "platform/intel/compr.conf" } diff --git a/tools/topology/topology2/platform/intel/sdw-amp-dax-ctc.conf b/tools/topology/topology2/platform/intel/sdw-amp-dax-ctc.conf index f53e87b09218..6e05f327a1c0 100644 --- a/tools/topology/topology2/platform/intel/sdw-amp-dax-ctc.conf +++ b/tools/topology/topology2/platform/intel/sdw-amp-dax-ctc.conf @@ -200,7 +200,7 @@ Object.Pipeline { ] } -IncludeByKey.NUM_SDW_AMP_LINKS { +IncludeByKey.NUM_SDW_AMP_CTC_LINKS { "2" { Define { AMP_FEEDBACK_CH 4 diff --git a/tools/topology/topology2/platform/intel/sdw-amp-dts-ctc.conf b/tools/topology/topology2/platform/intel/sdw-amp-dts-ctc.conf index e567b4c0b34c..9e401196f2a7 100644 --- a/tools/topology/topology2/platform/intel/sdw-amp-dts-ctc.conf +++ b/tools/topology/topology2/platform/intel/sdw-amp-dts-ctc.conf @@ -327,7 +327,7 @@ IncludeByKey.PASSTHROUGH { } } -IncludeByKey.NUM_SDW_AMP_LINKS { +IncludeByKey.NUM_SDW_AMP_CTC_LINKS { "2" { Define { AMP_FEEDBACK_CH 4 diff --git a/tools/topology/topology2/platform/intel/sdw-amp-generic-ctc.conf b/tools/topology/topology2/platform/intel/sdw-amp-generic-ctc.conf index dd78692f1aa5..5844a0c40729 100644 --- a/tools/topology/topology2/platform/intel/sdw-amp-generic-ctc.conf +++ b/tools/topology/topology2/platform/intel/sdw-amp-generic-ctc.conf @@ -258,7 +258,7 @@ IncludeByKey.PASSTHROUGH { } } -IncludeByKey.NUM_SDW_AMP_LINKS { +IncludeByKey.NUM_SDW_AMP_CTC_LINKS { "2" { Define { AMP_FEEDBACK_CH 4 From 79cbe266026fdff714595757500bb21cfeae573b Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 2 Jun 2026 14:37:19 +0300 Subject: [PATCH 148/303] scripts: build-alsa-tools.sh: Update alsa-lib and alsa-utils This change updates to Release v1.2.16, needed for extended sample rates add to SOF topologies. Signed-off-by: Seppo Ingalsuo --- scripts/build-alsa-tools.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build-alsa-tools.sh b/scripts/build-alsa-tools.sh index 5e1e7f75d39d..01cdb761ee1b 100755 --- a/scripts/build-alsa-tools.sh +++ b/scripts/build-alsa-tools.sh @@ -16,8 +16,8 @@ declare -a REPOS=( # the repository if this commit ID is not found. Leave empty to skip. # This array order must align with REPO array above. declare -a COMMIT_ID=( - "df8f1cc1ec9d9ee15be5e2c23ad25b9389fd8766" - "09550cd393b1a7d307ee6f26637b1ed7bd275e38" + "08b532cd3da9ac8f683bcb4e4beb9b74c39c1782" + "9feb22ba45b48729729c8d194aaf1bc082a6842a" # Add more IDs here... ) From b6673fc5b4ae88a9b2d766589acbc38cbc340213 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 2 Jun 2026 17:31:25 +0300 Subject: [PATCH 149/303] github: workflows: testbench: Update alsa-lib and alsa-lib This patch updates the commits to Release v1.2.16. It is needed to build topologies with extended sample rates. The autoconf install is needed due to dependency from alsa-lib. Signed-off-by: Seppo Ingalsuo --- .github/workflows/testbench.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testbench.yml b/.github/workflows/testbench.yml index 290fe8e56dee..d54b1db331db 100644 --- a/.github/workflows/testbench.yml +++ b/.github/workflows/testbench.yml @@ -49,12 +49,27 @@ jobs: octave octave-signal automake autoconf libtool gettext libasound2-dev + # Ubuntu 24.04 ships autoconf 2.71, but current alsa-lib/alsa-utils + # require >= 2.72. Build a newer autoconf from the GNU release + # tarball and install it into /usr/local (ahead of /usr/bin in PATH). + - name: Install autoconf 2.72 + run: | + cd ${GITHUB_WORKSPACE} + wget -q https://ftp.gnu.org/gnu/autoconf/autoconf-2.72.tar.gz + tar xf autoconf-2.72.tar.gz + cd autoconf-2.72 + ./configure --prefix=/usr/local + make -j"$(nproc)" + sudo make install + hash -r + autoconf --version | head -n1 + - name: Build Alsa-lib run: | cd ${GITHUB_WORKSPACE} git clone https://github.com/thesofproject/alsa-lib.git cd alsa-lib - git checkout df8f1cc1ec9d9ee15be5e2c23ad25b9389fd8766 -b build + git checkout 08b532cd3da9ac8f683bcb4e4beb9b74c39c1782 -b build ./gitcompile --prefix=${GITHUB_WORKSPACE}/tools make install @@ -63,7 +78,7 @@ jobs: cd ${GITHUB_WORKSPACE} git clone https://github.com/thesofproject/alsa-utils.git cd alsa-utils - git checkout 0ffa105942a06cdfa98e5918b8dc82e3cac12792 -b build + git checkout 9feb22ba45b48729729c8d194aaf1bc082a6842a -b build ./gitcompile --prefix=${GITHUB_WORKSPACE}/tools \ --with-alsa-prefix=${GITHUB_WORKSPACE}/tools \ --with-alsa-inc-prefix=${GITHUB_WORKSPACE}/tools/include \ From 3ecc89bcd3ca99ac3d9ec3d4d1f85b74b835c94f Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Fri, 5 Jun 2026 18:13:51 +0300 Subject: [PATCH 150/303] workflows: tools: build natively instead of in docker container The pre-built thesofproject/sof docker image carries an older alsa-lib / alsa-utils that no longer matches the versions required to build the current SOF user-space tools (in particular alsatplg used by the topology2 build). This patch moves the user-space tools build off docker and build it directly on the ubuntu-24.04 runner. - install the toolchain via apt - bootstrap autoconf 2.72 (Ubuntu 24.04 ships 2.71) - build the SOF forks of alsa-lib and alsa-utils at the same pinned commits used by testbench.yml - run scripts/build-tools.sh with PKG_CONFIG_PATH / LD_LIBRARY_PATH / PATH pointed at the freshly built ALSA tree The SOF-alsa-plugin job in the same workflow was already native and is left unchanged. The only consumer of this reusable workflow (daily-tests.yml) is unaffected by the internal switch. Signed-off-by: Seppo Ingalsuo --- .github/workflows/tools.yml | 66 +++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 7c70e93ddb5f..825b571321d8 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -13,25 +13,71 @@ permissions: jobs: # This is not the same as building every ./build-tools.sh option. top-level_default_CMake_target_ALL: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 with: filter: 'tree:0' + path: sof + + - name: apt get + run: sudo apt-get update && + sudo apt-get -y install ninja-build + automake autoconf libtool gettext libasound2-dev + pkg-config + + # Ubuntu 24.04 ships autoconf 2.71, but current alsa-lib/alsa-utils + # require >= 2.72. Build a newer autoconf from the GNU release + # tarball and install it into /usr/local (ahead of /usr/bin in PATH). + - name: Install autoconf 2.72 + run: | + cd ${GITHUB_WORKSPACE} + wget -q https://ftp.gnu.org/gnu/autoconf/autoconf-2.72.tar.gz + tar xf autoconf-2.72.tar.gz + cd autoconf-2.72 + ./configure --prefix=/usr/local + make -j"$(nproc)" + sudo make install + hash -r + autoconf --version | head -n1 + + - name: Build Alsa-lib + run: | + cd ${GITHUB_WORKSPACE} + git clone https://github.com/thesofproject/alsa-lib.git + cd alsa-lib + git checkout 08b532cd3da9ac8f683bcb4e4beb9b74c39c1782 -b build + ./gitcompile --prefix=${GITHUB_WORKSPACE}/tools + make install - # The ALSA version in Ubuntu 20.04 is buggy - # (https://github.com/thesofproject/sof/issues/2543) and likely - # getting out of date soon - - name: docker - run: docker pull thesofproject/sof && docker tag thesofproject/sof sof + - name: Build Alsa-utils + run: | + cd ${GITHUB_WORKSPACE} + git clone https://github.com/thesofproject/alsa-utils.git + cd alsa-utils + git checkout 9feb22ba45b48729729c8d194aaf1bc082a6842a -b build + ./gitcompile --prefix=${GITHUB_WORKSPACE}/tools \ + --with-alsa-prefix=${GITHUB_WORKSPACE}/tools \ + --with-alsa-inc-prefix=${GITHUB_WORKSPACE}/tools/include \ + --with-sysroot=${GITHUB_WORKSPACE}/tools \ + --with-udev-rules-dir=${GITHUB_WORKSPACE}/tools \ + PKG_CONFIG_PATH=${GITHUB_WORKSPACE}/tools \ + LDFLAGS=-L${GITHUB_WORKSPACE}/tools/lib \ + --disable-old-symbols \ + --enable-alsatopology \ + --with-asound-state-dir=${GITHUB_WORKSPACE}/tools/var/lib/alsa \ + --with-systemdsystemunitdir=${GITHUB_WORKSPACE}/tools/lib/systemd/system + make install # For some reason gcc has more warnings in Release mode - name: build-tools - run: CMAKE_BUILD_TYPE=Release ./scripts/docker-run.sh - ./scripts/build-tools.sh || + env: + PKG_CONFIG_PATH: ${{ github.workspace }}/tools/lib/pkgconfig + LD_LIBRARY_PATH: ${{ github.workspace }}/tools/lib + PATH: ${{ github.workspace }}/tools/bin:/usr/local/bin:/usr/bin:/bin + run: CMAKE_BUILD_TYPE=Release ./sof/scripts/build-tools.sh || VERBOSE=1 NO_PROCESSORS=1 USE_XARGS=no - CMAKE_BUILD_TYPE=Release ./scripts/docker-run.sh - ./scripts/build-tools.sh + CMAKE_BUILD_TYPE=Release ./sof/scripts/build-tools.sh SOF-alsa-plugin: From f73304db74edfc52e0878db06590eb58092b2747 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 4 Mar 2026 14:39:06 +0200 Subject: [PATCH 151/303] Tools: Topology2: Add 24 kHz rate to topologies with SRC This patch adds the missing 24 kHz rate that is used with MPEG-2 standard based and other codecs. The support started with Linux kernel 6.12. Note: Build of these topologies needs alsa-lib version TBD. Signed-off-by: Seppo Ingalsuo --- tools/topology/topology2/cavs-benchmark-hda.conf | 4 ++-- tools/topology/topology2/cavs-benchmark-sdw.conf | 4 ++-- tools/topology/topology2/cavs-nocodec.conf | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/topology/topology2/cavs-benchmark-hda.conf b/tools/topology/topology2/cavs-benchmark-hda.conf index 95ab67431812..e06f21ea9d49 100644 --- a/tools/topology/topology2/cavs-benchmark-hda.conf +++ b/tools/topology/topology2/cavs-benchmark-hda.conf @@ -195,7 +195,7 @@ Object.PCM.pcm [ direction "playback" name $ANALOG_PLAYBACK_PCM formats 'S32_LE,S24_LE,S16_LE' - rates "8000,11025,16000,22050,32000,44100,48000,64000,88200,96000,176400,192000" + rates "8000,11025,16000,22050,24000,32000,44100,48000,64000,88200,96000,176400,192000" channels_min $BENCH_PCM_CHANNELS_MIN channels_max $BENCH_PCM_CHANNELS_MAX } @@ -203,7 +203,7 @@ Object.PCM.pcm [ direction "capture" name $ANALOG_CAPTURE_PCM formats 'S32_LE,S24_LE,S16_LE' - rates "8000,11025,16000,22050,32000,44100,48000,64000,88200,96000,176400,192000" + rates "8000,11025,16000,22050,24000,32000,44100,48000,64000,88200,96000,176400,192000" channels_min $BENCH_PCM_CHANNELS_MIN channels_max $BENCH_PCM_CHANNELS_MAX } diff --git a/tools/topology/topology2/cavs-benchmark-sdw.conf b/tools/topology/topology2/cavs-benchmark-sdw.conf index 1705facb4cc0..ee81baf23772 100644 --- a/tools/topology/topology2/cavs-benchmark-sdw.conf +++ b/tools/topology/topology2/cavs-benchmark-sdw.conf @@ -137,7 +137,7 @@ Object.PCM.pcm [ Object.PCM.pcm_caps.1 { name $ANALOG_PLAYBACK_PCM formats 'S16_LE,S24_LE,S32_LE' - rates "8000,11025,16000,22050,32000,44100,48000,64000,88200,96000,176400,192000" + rates "8000,11025,16000,22050,24000,32000,44100,48000,64000,88200,96000,176400,192000" channels_min $BENCH_PCM_CHANNELS_MIN channels_max $BENCH_PCM_CHANNELS_MAX } @@ -153,7 +153,7 @@ Object.PCM.pcm [ Object.PCM.pcm_caps.1 { name $ANALOG_CAPTURE_PCM formats 'S16_LE,S24_LE,S32_LE' - rates "8000,11025,16000,22050,32000,44100,48000,64000,88200,96000,176400,192000" + rates "8000,11025,16000,22050,24000,32000,44100,48000,64000,88200,96000,176400,192000" channels_min $BENCH_PCM_CHANNELS_MIN channels_max $BENCH_PCM_CHANNELS_MAX } diff --git a/tools/topology/topology2/cavs-nocodec.conf b/tools/topology/topology2/cavs-nocodec.conf index fea906f98741..1d3f50a989dd 100644 --- a/tools/topology/topology2/cavs-nocodec.conf +++ b/tools/topology/topology2/cavs-nocodec.conf @@ -1698,14 +1698,14 @@ IncludeByKey.PASSTHROUGH { direction "playback" name "SSP2 Playback" formats 'S16_LE,S24_LE,S32_LE' - rates "8000,11025,16000,22050,32000,44100,48000,64000,88200,96000,176400,192000" + rates "8000,11025,16000,22050,24000,32000,44100,48000,64000,88200,96000,176400,192000" } Object.PCM.pcm_caps.2 { direction "capture" name "SSP2 Capture" formats 'S16_LE,S24_LE,S32_LE' - rates "8000,11025,16000,22050,32000,44100,48000,64000,88200,96000,176400,192000" + rates "8000,11025,16000,22050,24000,32000,44100,48000,64000,88200,96000,176400,192000" } } ] From eceddccd672535eea9bbe85727aba04dad003ada Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Fri, 22 May 2026 17:41:39 +0300 Subject: [PATCH 152/303] audio: mfcc: switch to source/sink API, int32 output, and DTX Switch from process_audio_stream to source/sink API. Add compress PCM output mode (variable-size frames, no zero padding) alongside legacy mode (full period with zero-fill). Unify all output to int32 Q9.23 regardless of source format. Remove out_data_ptr_32, mel_spectra int16 copy, mfcc_func typedef, and per-format output functions from mfcc_common/hifi3/hifi4. Add DTX for compress mode: suppress silence frames after configurable trailing count, with optional periodic keepalive. Signed-off-by: Seppo Ingalsuo --- src/audio/mfcc/mfcc.c | 167 ++++- src/audio/mfcc/mfcc_common.c | 856 ++++++++++++++++--------- src/audio/mfcc/mfcc_generic.c | 180 +----- src/audio/mfcc/mfcc_hifi3.c | 206 +----- src/audio/mfcc/mfcc_hifi4.c | 185 +----- src/audio/mfcc/mfcc_setup.c | 143 ++++- src/include/sof/audio/mfcc/mfcc_comp.h | 66 +- src/include/user/mfcc.h | 9 +- 8 files changed, 916 insertions(+), 896 deletions(-) diff --git a/src/audio/mfcc/mfcc.c b/src/audio/mfcc/mfcc.c index fc81f69c3a10..724d3d7faf06 100644 --- a/src/audio/mfcc/mfcc.c +++ b/src/audio/mfcc/mfcc.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -36,29 +38,39 @@ LOG_MODULE_REGISTER(mfcc, CONFIG_SOF_LOG_LEVEL); SOF_DEFINE_REG_UUID(mfcc); -__cold_rodata const struct mfcc_func_map mfcc_fm[] = { +/** \brief Source/sink API based source copy function map. */ +struct mfcc_source_func_map { + uint8_t source_fmt; + mfcc_source_func func; +}; + +__cold_rodata static const struct mfcc_source_func_map mfcc_sfm[] = { #if CONFIG_FORMAT_S16LE - {SOF_IPC_FRAME_S16_LE, mfcc_s16_default}, -#endif /* CONFIG_FORMAT_S16LE */ + {SOF_IPC_FRAME_S16_LE, mfcc_source_copy_s16}, +#endif #if CONFIG_FORMAT_S24LE - {SOF_IPC_FRAME_S24_4LE, mfcc_s24_default}, -#endif /* CONFIG_FORMAT_S24LE */ + {SOF_IPC_FRAME_S24_4LE, mfcc_source_copy_s24}, +#endif #if CONFIG_FORMAT_S32LE - {SOF_IPC_FRAME_S32_LE, mfcc_s32_default}, -#endif /* CONFIG_FORMAT_S32LE */ + {SOF_IPC_FRAME_S32_LE, mfcc_source_copy_s32}, +#endif }; -static mfcc_func mfcc_find_func(enum sof_ipc_frame source_format, - enum sof_ipc_frame sink_format, - const struct mfcc_func_map *map, - int n) +/** + * \brief Look up the source copy function for a given input format. + * + * \param[in] source_format SOF IPC frame format of the input stream. + * + * \return Pointer to the matching mfcc_source_func, or NULL if the + * format is not supported in this build. + */ +static mfcc_source_func mfcc_find_source_func(enum sof_ipc_frame source_format) { int i; - /* Find suitable processing function from map. */ - for (i = 0; i < n; i++) { - if (source_format == map[i].source) - return map[i].func; + for (i = 0; i < ARRAY_SIZE(mfcc_sfm); i++) { + if (source_format == mfcc_sfm[i].source_fmt) + return mfcc_sfm[i].func; } return NULL; @@ -68,6 +80,16 @@ static mfcc_func mfcc_find_func(enum sof_ipc_frame source_format, * End of MFCC setup code. Next the standard component methods. */ +/** + * \brief MFCC module init callback. + * + * Allocates the component private data and the configuration blob + * handler used to receive the MFCC configuration from user space. + * + * \param[in,out] mod Processing module being initialized. + * + * \return 0 on success, -ENOMEM on allocation failure. + */ static int mfcc_init(struct processing_module *mod) { struct module_data *md = &mod->priv; @@ -92,6 +114,16 @@ static int mfcc_init(struct processing_module *mod) return 0; } +/** + * \brief MFCC module free callback. + * + * Releases the IPC notification message, configuration blob handler, + * runtime buffers and the private data structure. + * + * \param[in,out] mod Processing module being freed. + * + * \return 0 on success. + */ static int mfcc_free(struct processing_module *mod) { struct mfcc_comp_data *cd = module_get_private_data(mod); @@ -105,28 +137,76 @@ static int mfcc_free(struct processing_module *mod) return 0; } - +/** + * \brief Source/sink API based process function for MFCC. + * + * Reads input audio from sof_source, runs the STFT/Mel/DCT stage, and + * delegates output formatting and commit handling to mfcc_common.c. + * + * \param[in,out] mod Processing module. + * \param[in,out] sources Source array; only sources[0] is used. + * \param[in] num_of_sources Number of sources (must be 1). + * \param[in,out] sinks Sink array; only sinks[0] is used. + * \param[in] num_of_sinks Number of sinks (must be 1). + * + * \return 0 on success, or a negative error code from the STFT or output stages. + */ static int mfcc_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, int num_input_buffers, - struct output_stream_buffer *output_buffers, int num_output_buffers) + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { struct mfcc_comp_data *cd = module_get_private_data(mod); - struct audio_stream *source = input_buffers->data; - struct audio_stream *sink = output_buffers->data; - int frames = input_buffers->size; + struct comp_dev *dev = mod->dev; + struct mfcc_state *state = &cd->state; + bool pending; + size_t source_avail; + int frames; + int num_ceps; + + comp_dbg(dev, "start"); + + /* In compress mode, retry pending output first and avoid producing + * new frames until previous frame has been committed. In legacy + * mode, pending output is held in a dedicated staging buffer + * (state->out_stage) that STFT does not touch, so input processing + * can continue while the previous period is drained. + */ + pending = state->header_pending || state->out_remain > 0; + if (cd->config->compress_output && pending) + return mfcc_process_output(mod, cd, sources, sinks, 0, 0); - comp_dbg(mod->dev, "start"); + source_avail = source_get_data_frames_available(sources[0]); + frames = MIN(source_avail, cd->max_frames); + if (!frames) + return 0; - frames = MIN(frames, cd->max_frames); - cd->mfcc_func(mod, input_buffers, output_buffers, frames); + /* Copy input audio from source to MFCC internal circular buffer */ + cd->source_func(sources[0], &state->buf, &state->emph, frames, state->source_channel); - /* TODO: use module_update_buffer_position() from #6194 */ - input_buffers->consumed += audio_stream_frame_bytes(source) * frames; - output_buffers->size += audio_stream_frame_bytes(sink) * frames; - comp_dbg(mod->dev, "done"); - return 0; + /* Run STFT and Mel/DCT processing */ + num_ceps = mfcc_stft_process(mod, cd); + if (num_ceps < 0) + return num_ceps; + + return mfcc_process_output(mod, cd, sources, sinks, num_ceps, frames); } +/** + * \brief MFCC module prepare callback. + * + * Validates the source/sink connection, reads the configuration blob, + * initializes the MFCC processing state via mfcc_setup(), selects the + * input copy function for the source frame format, and prepares the + * VAD switch control notification when enabled. + * + * \param[in,out] mod Processing module being prepared. + * \param[in,out] sources Source array; only sources[0] is used. + * \param[in] num_of_sources Number of sources (must be 1). + * \param[in,out] sinks Sink array; only sinks[0] is used. + * \param[in] num_of_sinks Number of sinks (must be 1). + * + * \return 0 on success or a negative error code. + */ static int mfcc_prepare(struct processing_module *mod, struct sof_source **sources, int num_of_sources, struct sof_sink **sinks, int num_of_sinks) @@ -172,13 +252,21 @@ static int mfcc_prepare(struct processing_module *mod, return -EINVAL; } - cd->mfcc_func = mfcc_find_func(source_format, sink_format, mfcc_fm, ARRAY_SIZE(mfcc_fm)); - if (!cd->mfcc_func) { - comp_err(dev, "No proc func"); + cd->source_func = mfcc_find_source_func(source_format); + if (!cd->source_func) { + comp_err(dev, "No source func"); mfcc_free_buffers(mod); return -EINVAL; } + cd->source_format = source_format; + + if (cd->config->compress_output) + comp_info(dev, "compress PCM output mode enabled"); + + if (cd->config->enable_dtx && !cd->config->compress_output) + comp_warn(dev, "enable_dtx ignored in normal PCM mode, only applies to compress"); + /* Initialize VAD switch control notification if enabled */ if (cd->config->enable_vad && cd->config->update_controls) { if (!cd->msg) { @@ -194,6 +282,17 @@ static int mfcc_prepare(struct processing_module *mod, return 0; } +/** + * \brief MFCC module reset callback. + * + * Frees runtime buffers (NULLing their pointers) so a subsequent + * mfcc_prepare() can re-allocate cleanly. The configuration blob + * handler and IPC notification message are preserved. + * + * \param[in,out] mod Processing module being reset. + * + * \return 0 on success. + */ static int mfcc_reset(struct processing_module *mod) { struct mfcc_comp_data *cd = module_get_private_data(mod); @@ -206,7 +305,7 @@ static int mfcc_reset(struct processing_module *mod) mfcc_free_buffers(mod); /* Reset to similar state as init() */ - cd->mfcc_func = NULL; + cd->source_func = NULL; return 0; } @@ -215,7 +314,7 @@ static const struct module_interface mfcc_interface = { .free = mfcc_free, .set_configuration = mfcc_set_config, .get_configuration = mfcc_get_config, - .process_audio_stream = mfcc_process, + .process = mfcc_process, .prepare = mfcc_prepare, .reset = mfcc_reset, }; diff --git a/src/audio/mfcc/mfcc_common.c b/src/audio/mfcc/mfcc_common.c index 0e6efc2e8ba4..3890671165fd 100644 --- a/src/audio/mfcc/mfcc_common.c +++ b/src/audio/mfcc/mfcc_common.c @@ -7,7 +7,8 @@ #include #include -#include +#include +#include #include #include #include @@ -20,16 +21,262 @@ #include #include #include +#include #include LOG_MODULE_REGISTER(mfcc_common, CONFIG_SOF_LOG_LEVEL); +/* + * Source/sink API based source copy functions. + * These use sof_source API and are compiled on all platforms (generic, HiFi3, HiFi4). + */ + +#if CONFIG_FORMAT_S16LE +/** + * \brief Copy S16_LE input frames from source to MFCC circular buffer. + * + * Reads \p frames frames from \p source, selects channel \p source_channel, + * optionally applies the pre-emphasis filter \p emph, and writes Q1.15 mono + * samples to the MFCC input circular buffer \p buf. The source read is + * released after the copy completes. On source read failure the function + * returns without modifying \p buf state. + * + * \param[in,out] source Audio source providing interleaved S16_LE frames. + * \param[in,out] buf MFCC input circular buffer (mono Q1.15). + * \param[in,out] emph Pre-emphasis filter state; updated when enabled. + * \param[in] frames Number of input frames to copy. + * \param[in] source_channel Channel index to extract from the source. + */ +void mfcc_source_copy_s16(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel) +{ + int16_t const *x; + int16_t const *x_start; + int16_t const *x_end; + int x_size; + int num_channels = source_get_channels(source); + size_t req_bytes = frames * num_channels * sizeof(int16_t); + int16_t *w = buf->w_ptr; + int src_without_wrap; + int dst_without_wrap; + int samples_without_wrap; + int remaining = frames; + int32_t s; + int ret; + int i; + + ret = source_get_data_s16(source, req_bytes, &x, &x_start, &x_size); + if (ret) + return; + + x += source_channel; + x_end = x_start + x_size; + + while (remaining) { + src_without_wrap = (x_end - x) / num_channels; + dst_without_wrap = buf->end_addr - w; + samples_without_wrap = MIN(src_without_wrap, dst_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining); + if (emph->enable) { + for (i = 0; i < samples_without_wrap; i++) { + s = (int32_t)emph->delay * emph->coef + + Q_SHIFT_LEFT(*x, 15, 30); + *w = sat_int16(Q_SHIFT_RND(s, 30, 15)); + emph->delay = *x; + x += num_channels; + w++; + } + } else { + for (i = 0; i < samples_without_wrap; i++) { + *w = *x; + x += num_channels; + w++; + } + } + x = (x >= x_end) ? x - x_size : x; + w = mfcc_buffer_wrap(buf, w); + remaining -= samples_without_wrap; + } + + buf->s_avail += frames; + buf->s_free -= frames; + buf->w_ptr = w; + source_release_data(source, req_bytes); +} +#endif /* CONFIG_FORMAT_S16LE */ + +#if CONFIG_FORMAT_S24LE +/** + * \brief Copy S24_4LE input frames from source to MFCC circular buffer. + * + * Same as mfcc_source_copy_s16() but accepts S24_4LE input (24-bit data + * sign-extended in a 32-bit container). Samples are downscaled to Q1.15 + * for the MFCC pipeline. + * + * \param[in,out] source Audio source providing interleaved S24_4LE frames. + * \param[in,out] buf MFCC input circular buffer (mono Q1.15). + * \param[in,out] emph Pre-emphasis filter state; updated when enabled. + * \param[in] frames Number of input frames to copy. + * \param[in] source_channel Channel index to extract from the source. + */ +void mfcc_source_copy_s24(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel) +{ + int32_t const *x; + int32_t const *x_start; + int32_t const *x_end; + int x_size; + int num_channels = source_get_channels(source); + size_t req_bytes = frames * num_channels * sizeof(int32_t); + int16_t *w = buf->w_ptr; + int src_without_wrap; + int dst_without_wrap; + int samples_without_wrap; + int remaining = frames; + int32_t s, tmp; + int ret; + int i; + + ret = source_get_data_s32(source, req_bytes, &x, &x_start, &x_size); + if (ret) + return; + + x += source_channel; + x_end = x_start + x_size; + + while (remaining) { + src_without_wrap = (x_end - x) / num_channels; + dst_without_wrap = buf->end_addr - w; + samples_without_wrap = MIN(src_without_wrap, dst_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining); + if (emph->enable) { + for (i = 0; i < samples_without_wrap; i++) { + s = (int32_t)((uint32_t)*x << 8); + tmp = (int32_t)emph->delay * emph->coef + + Q_SHIFT(s, 31, 30); + *w = sat_int16(Q_SHIFT_RND(tmp, 30, 15)); + emph->delay = sat_int16(Q_SHIFT_RND(s, 31, 15)); + x += num_channels; + w++; + } + } else { + for (i = 0; i < samples_without_wrap; i++) { + s = (int32_t)((uint32_t)*x << 8); + *w = sat_int16(Q_SHIFT_RND(s, 31, 15)); + x += num_channels; + w++; + } + + } + x = (x >= x_end) ? x - x_size : x; + w = mfcc_buffer_wrap(buf, w); + remaining -= samples_without_wrap; + } + + buf->s_avail += frames; + buf->s_free -= frames; + buf->w_ptr = w; + source_release_data(source, req_bytes); +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S32LE +/** + * \brief Copy S32_LE input frames from source to MFCC circular buffer. + * + * Same as mfcc_source_copy_s16() but accepts S32_LE input. Samples are + * downscaled to Q1.15 for the MFCC pipeline. + * + * \param[in,out] source Audio source providing interleaved S32_LE frames. + * \param[in,out] buf MFCC input circular buffer (mono Q1.15). + * \param[in,out] emph Pre-emphasis filter state; updated when enabled. + * \param[in] frames Number of input frames to copy. + * \param[in] source_channel Channel index to extract from the source. + */ +void mfcc_source_copy_s32(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel) +{ + int32_t const *x; + int32_t const *x_start; + int32_t const *x_end; + int x_size; + int num_channels = source_get_channels(source); + size_t req_bytes = frames * num_channels * sizeof(int32_t); + int16_t *w = buf->w_ptr; + int src_without_wrap; + int dst_without_wrap; + int samples_without_wrap; + int remaining = frames; + int32_t s; + int ret; + int i; + + ret = source_get_data_s32(source, req_bytes, &x, &x_start, &x_size); + if (ret) + return; + + x += source_channel; + x_end = x_start + x_size; + while (remaining) { + src_without_wrap = (x_end - x) / num_channels; + dst_without_wrap = buf->end_addr - w; + samples_without_wrap = MIN(src_without_wrap, dst_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining); + if (emph->enable) { + for (i = 0; i < samples_without_wrap; i++) { + s = (int32_t)emph->delay * emph->coef + + Q_SHIFT(*x, 31, 30); + *w = sat_int16(Q_SHIFT_RND(s, 30, 15)); + emph->delay = sat_int16(Q_SHIFT_RND(*x, 31, 15)); + x += num_channels; + w++; + } + } else { + for (i = 0; i < samples_without_wrap; i++) { + *w = sat_int16(Q_SHIFT_RND(*x, 31, 15)); + x += num_channels; + w++; + } + } + + x = (x >= x_end) ? x - x_size : x; + w = mfcc_buffer_wrap(buf, w); + remaining -= samples_without_wrap; + } + + buf->s_avail += frames; + buf->s_free -= frames; + buf->w_ptr = w; + source_release_data(source, req_bytes); +} +#endif /* CONFIG_FORMAT_S32LE */ + /* * The main processing function for MFCC */ -static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_data *cd) +/** + * \brief Run one STFT hop and Mel/DCT processing. + * + * Waits until the input circular buffer has at least one FFT window of + * samples, then for each available hop: copies the overlap-add window + * into the FFT scratch buffer, applies the analysis window, runs the + * 32-bit FFT, computes Mel log spectra, and either keeps the Mel output + * (when \c state->mel_only is true) or applies DCT and cepstral lifter + * to produce cepstral coefficients. Optionally runs VAD on the Mel + * spectrum and sends an IPC notification on VAD state changes. + * + * \param[in,out] mod Processing module. + * \param[in,out] cd MFCC component data; state and configuration updated + * in-place. Output values land in \c state->mel_log_32 + * (Mel mode) or \c state->cepstral_coef (cepstral mode). + * + * \return Number of int32_t output values produced this call (0 if not + * enough input data was available, or no error condition was + * detected). + */ +int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_data *cd) { const struct comp_dev *dev = mod->dev; struct sof_mfcc_config *config = cd->config; @@ -45,6 +292,7 @@ static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_dat int32_t mel_value; int32_t peak; int32_t clamp_value; + bool vad_now; /* Phase 1, wait until whole fft_size is filled with valid data. This way * first output cepstral coefficients originate from streamed data and not @@ -147,11 +395,6 @@ static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_dat sat_int32(Q_MULTSR_32X32(s, config->mel_scale, 23, 12, 23)); } - /* Store Q9.7 version in mel_spectra for s16 output mode */ - for (j = 0; j < state->dct.num_in; j++) - state->mel_spectra->data[j] = - sat_int16(state->mel_log_32[j] >> 16); - /* Enable this to check mmax decay */ comp_dbg(dev, "state->mmax = %d", state->mmax); } else { @@ -191,7 +434,7 @@ static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_dat /* Send notification when VAD state changes */ if (config->enable_vad && config->update_controls) { - bool vad_now = cd->vad.is_speech; + vad_now = cd->vad.is_speech; if (vad_now != cd->vad_prev) { mfcc_send_vad_notification(mod, vad_now ? 1 : 0); @@ -203,357 +446,362 @@ static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_dat return cc_count; } -void mfcc_fill_fft_buffer(struct mfcc_state *state) +/** + * \brief Write bytes into a possibly wrapped sink buffer. + * + * Caller must ensure max_bytes <= buf_size; otherwise the wrap branch + * would write past the end of the buffer. Returns 0 on bound violation. + */ +static size_t mfcc_sink_write_bytes(uint8_t **dst, uint8_t *buf_start, + size_t buf_size, const uint8_t *src, + size_t max_bytes) { - struct mfcc_buffer *buf = &state->buf; - struct mfcc_fft *fft = &state->fft; - int32_t *d = &fft->fft_buf[fft->fft_fill_start_idx].real; - const int fft_elem_inc = sizeof(fft->fft_buf[0]) / sizeof(int32_t); - int16_t *prev = state->prev_data; - int16_t *prev_end = prev + state->prev_data_size; - int16_t *r = buf->r_ptr; - int copied; - int nmax; - int n; - int j; - - /* Copy overlapped samples from state buffer. The fft_buf has been - * cleared by caller so imaginary part remains zero. - */ - while (prev < prev_end) { - *d = *prev++; - d += fft_elem_inc; - } + uint8_t *buf_end = buf_start + buf_size; + size_t chunk; - /* Copy hop size of new data from circular buffer */ - for (copied = 0; copied < fft->fft_hop_size; copied += n) { - nmax = fft->fft_hop_size - copied; - n = mfcc_buffer_samples_without_wrap(buf, r); - n = MIN(n, nmax); - for (j = 0; j < n; j++) { - *d = *r++; - d += fft_elem_inc; - } - r = mfcc_buffer_wrap(buf, r); - } + if (max_bytes == 0) + return 0; - buf->s_avail -= copied; - buf->s_free += copied; - buf->r_ptr = r; + /* Guard: a single write must not exceed total sink buffer size. */ + if (max_bytes > buf_size) + return 0; - /* Copy for next time data back to overlap buffer */ - d = (int32_t *)&fft->fft_buf[fft->fft_fill_start_idx + fft->fft_hop_size].real; - prev = state->prev_data; - while (prev < prev_end) { - *prev++ = *d; - d += fft_elem_inc; - } -} - -#if CONFIG_FORMAT_S16LE -static int16_t *mfcc_sink_copy_zero_s16(const struct audio_stream *sink, int16_t *w_ptr, - int samples) -{ - int copied; - int nmax; - int n; - - for (copied = 0; copied < samples; copied += n) { - nmax = samples - copied; - n = audio_stream_samples_without_wrap_s16(sink, w_ptr); - n = MIN(n, nmax); - memset(w_ptr, 0, n * sizeof(int16_t)); - w_ptr = audio_stream_wrap(sink, w_ptr + n); + chunk = MIN(max_bytes, (size_t)(buf_end - *dst)); + memcpy(*dst, src, chunk); + if (chunk < max_bytes) { + memcpy(buf_start, src + chunk, max_bytes - chunk); + *dst = buf_start + (max_bytes - chunk); + } else { + *dst += chunk; + if (*dst >= buf_end) + *dst = buf_start; } - return w_ptr; + return max_bytes; } -static int16_t *mfcc_sink_copy_data_s16(const struct audio_stream *sink, int16_t *w_ptr, - int samples, int16_t *r_ptr) +/** + * \brief Prepare the next MFCC output frame after STFT processing. + * + * Copies Mel (int32 Q9.23) or cepstral (int16 Q9.7 widened to int32 Q9.23) + * output into \c state->out_stage, sets up the read pointer for staged output, + * and marks the data header as pending. + * + * \param[in,out] state MFCC state. + * \param[in] num_ceps Number of int32_t values to publish; no-op if <= 0. + */ +static void mfcc_prepare_output(struct mfcc_state *state, int num_ceps) { - int copied; - int nmax; - int n; + int k; - for (copied = 0; copied < samples; copied += n) { - nmax = samples - copied; - n = audio_stream_samples_without_wrap_s16(sink, w_ptr); - n = MIN(n, nmax); - /* Not using memcpy_s() due to speed need */ - memcpy(w_ptr, r_ptr, n * sizeof(int16_t)); - w_ptr = audio_stream_wrap(sink, w_ptr + n); - r_ptr += n; + if (num_ceps <= 0) + return; + + /* Copy into out_stage so the next STFT hop may freely reuse + * mel_log_32 / cepstral_coef while this frame is still pending. + */ + if (state->mel_only) { + for (k = 0; k < num_ceps; k++) + state->out_stage[k] = state->mel_log_32[k]; + } else { + /* Widen int16 Q9.7 cepstral coefficients to int32 Q9.23. */ + for (k = 0; k < num_ceps; k++) + state->out_stage[k] = (int32_t)state->cepstral_coef->data[k] << 16; } - return w_ptr; + state->out_data_ptr = state->out_stage; + state->out_remain = num_ceps; + state->header_pending = true; } -void mfcc_s16_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames) +/** + * \brief Commit MFCC output in compress mode. + * + * Writes the pending data header (if any) followed by the pending + * int32_t Mel/cepstral payload into the sink as a contiguous blob, + * without zero padding. When VAD + DTX are enabled, suppresses silence + * frames after a configurable trailing-silence count and optionally + * sends periodic keep-alive silence updates. + * + * \param[in,out] mod Processing module. + * \param[in,out] cd MFCC component data. + * \param[in,out] sinks Sink array; only sinks[0] is used. + * \param[in] num_ceps Number of int32_t values prepared for this hop. + * + * \return 0 on success, -ENOSPC if the sink cannot accept the frame, + * or a negative error from the sink API. + */ +static int mfcc_output_compress(struct processing_module *mod, struct mfcc_comp_data *cd, + struct sof_sink **sinks, int num_ceps) { - struct audio_stream *sink = bsink->data; - struct mfcc_comp_data *cd = module_get_private_data(mod); struct mfcc_state *state = &cd->state; - struct mfcc_buffer *buf = &cd->state.buf; - int16_t *w_ptr = audio_stream_get_wptr(sink); - const int num_header_s16 = sizeof(state->header) / sizeof(int16_t); - int num_ceps; - int sink_samples; - int to_copy; - - /* Get samples from source buffer */ - mfcc_source_copy_s16(bsource, buf, &state->emph, frames, state->source_channel); - - /* Run STFT and processing after FFT: Mel auditory filter and DCT. */ - num_ceps = mfcc_stft_process(mod, cd); + size_t out_bytes; + size_t commit_bytes; + void *sink_ptr; + void *sink_start; + size_t sink_buf_size; + uint8_t *dst; + int ret; + bool pending; + bool send_periodic; + + pending = state->header_pending || state->out_remain > 0; + if (!pending) + return 0; + + if (num_ceps > 0 && cd->config->enable_vad && !cd->vad.is_speech) { + state->vad_silence_count++; + /* With DTX enabled, send trailing silence frames + * (configurable count) then suppress. After trailing + * frames, optionally send periodic silence updates + * at the configured interval. This gives the host + * enough silence to detect end-of-speech while + * keeping alive updates during long silence. + * Without DTX, output every frame regardless of VAD. + */ + if (cd->config->enable_dtx && + state->vad_silence_count > state->dtx_trailing_silence) { + send_periodic = false; + + /* Check periodic silence frame send */ + if (state->dtx_silence_interval > 0) { + state->dtx_silence_counter++; + if (state->dtx_silence_counter >= + state->dtx_silence_interval) { + state->dtx_silence_counter = 0; + send_periodic = true; + } + } - /* If new output produced, set up pointer into scratch data and mark header pending */ - if (num_ceps > 0) { - if (state->mel_only) { - state->out_data_ptr = state->mel_spectra->data; - } else { - state->out_data_ptr = state->cepstral_coef->data; + if (!send_periodic) { + /* Suppress this frame */ + state->header_pending = false; + state->out_remain = 0; + return 0; + } } - - state->out_remain = num_ceps; - state->header_pending = true; + } else { + state->vad_silence_count = 0; + state->dtx_silence_counter = 0; } - /* Write to sink, limited by period size */ - sink_samples = frames * audio_stream_get_channels(sink); + out_bytes = (state->header_pending ? sizeof(state->header) : 0) + + state->out_remain * sizeof(int32_t); + if (out_bytes == 0) + return 0; - /* Write data header first if pending */ - if (state->header_pending) { - if (sink_samples < num_header_s16) { - /* Not enough sink space for header, defer entire frame */ - mfcc_sink_copy_zero_s16(sink, w_ptr, sink_samples); - return; - } + commit_bytes = out_bytes; + if (sink_get_free_size(sinks[0]) < commit_bytes) + return -ENOSPC; - w_ptr = mfcc_sink_copy_data_s16(sink, w_ptr, num_header_s16, - (int16_t *)&state->header); - sink_samples -= num_header_s16; - state->header_pending = false; - } + ret = sink_get_buffer(sinks[0], commit_bytes, &sink_ptr, + &sink_start, &sink_buf_size); + if (ret) + return ret; - /* Write cepstral/mel data from scratch buffer */ - to_copy = MIN(state->out_remain, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s16(sink, w_ptr, to_copy, state->out_data_ptr); - state->out_data_ptr += to_copy; - state->out_remain -= to_copy; - sink_samples -= to_copy; + dst = sink_ptr; + if (state->header_pending) { + mfcc_sink_write_bytes(&dst, sink_start, sink_buf_size, + (uint8_t *)&state->header, + sizeof(state->header)); } - /* Zero-fill remaining sink samples */ - w_ptr = mfcc_sink_copy_zero_s16(sink, w_ptr, sink_samples); -} -#endif /* CONFIG_FORMAT_S16LE */ - -#if CONFIG_FORMAT_S24LE || CONFIG_FORMAT_S32LE -static int32_t *mfcc_sink_copy_zero_s32(const struct audio_stream *sink, int32_t *w_ptr, - int samples) -{ - int copied; - int nmax; - int n; - - for (copied = 0; copied < samples; copied += n) { - nmax = samples - copied; - n = audio_stream_samples_without_wrap_s32(sink, w_ptr); - n = MIN(n, nmax); - memset(w_ptr, 0, n * sizeof(int32_t)); - w_ptr = audio_stream_wrap(sink, w_ptr + n); + if (state->out_remain > 0) { + mfcc_sink_write_bytes(&dst, sink_start, sink_buf_size, + (uint8_t *)state->out_data_ptr, + state->out_remain * sizeof(int32_t)); } - return w_ptr; -} + ret = sink_commit_buffer(sinks[0], commit_bytes); + if (ret) + return ret; -static int32_t *mfcc_sink_copy_data_s32(const struct audio_stream *sink, int32_t *w_ptr, - int samples, int32_t *r_ptr) -{ - int copied; - int nmax; - int n; - - for (copied = 0; copied < samples; copied += n) { - nmax = samples - copied; - n = audio_stream_samples_without_wrap_s32(sink, w_ptr); - n = MIN(n, nmax); - /* Not using memcpy_s() due to speed need */ - memcpy(w_ptr, r_ptr, n * sizeof(int32_t)); - w_ptr = audio_stream_wrap(sink, w_ptr + n); - r_ptr += n; - } - - return w_ptr; + state->header_pending = false; + state->out_remain = 0; + return 0; } -#endif /* CONFIG_FORMAT_S24LE || CONFIG_FORMAT_S32LE */ -#if CONFIG_FORMAT_S24LE -void mfcc_s24_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames) +/** + * \brief Commit MFCC output in legacy PCM mode. + * + * Acquires a sink period sized as \p frames audio frames, zero-fills it, + * then writes any pending data header and pending int32_t Mel/cepstral + * payload. Any payload that does not fit in the current period stays in + * \c state->out_data_ptr / \c state->out_remain for the next call. + * + * \param[in,out] mod Processing module. + * \param[in,out] cd MFCC component data. + * \param[in,out] sources Sources array (unused; reserved for symmetry). + * \param[in,out] sinks Sink array; only sinks[0] is used. + * \param[in] frames Number of audio frames the sink expects this period. + * + * \return 0 on success, -ENOSPC if the sink cannot accept the period, + * or a negative error from the sink API. + */ +static int mfcc_output_legacy(struct processing_module *mod, struct mfcc_comp_data *cd, + struct sof_source **sources, struct sof_sink **sinks, + int frames) { - struct audio_stream *sink = bsink->data; - struct mfcc_comp_data *cd = module_get_private_data(mod); struct mfcc_state *state = &cd->state; - struct mfcc_buffer *buf = &cd->state.buf; - int32_t *w_ptr = audio_stream_get_wptr(sink); - const int num_header_s32 = sizeof(state->header) / sizeof(int32_t); - int num_ceps; - int sink_samples; - int remain_s32; - int to_copy; - int k; - - /* Get samples from source buffer */ - mfcc_source_copy_s24(bsource, buf, &state->emph, frames, state->source_channel); + size_t commit_bytes; + size_t bytes_to_end; + size_t avail; + size_t hdr_size; + size_t data_bytes; + size_t to_write; + void *sink_ptr; + void *sink_start; + size_t sink_buf_size; + uint8_t *dst; + int n32; + int ret; + + /* The MFCC sink is treated as an opaque byte container: the period + * carries an MFCC blob (header + int32 features), not PCM audio. + * Sizing the commit as sink_frame_bytes * frames keeps the period + * size matched to whatever the sink advertises (S16_LE / S24_4LE / + * S32_LE), so no format-specific conversion is needed. Any payload + * that does not fit in the current period is carried over via + * state->out_remain and drained in the next call(s). The host side + * decodes the bytes as MFCC features regardless of the sink's + * nominal PCM format, so non-S32 sinks (e.g. bench S16 topologies) + * remain supported. + */ + commit_bytes = sink_get_frame_bytes(sinks[0]) * frames; + if (sink_get_free_size(sinks[0]) < commit_bytes) + return -ENOSPC; - /* Run STFT and processing after FFT */ - num_ceps = mfcc_stft_process(mod, cd); + ret = sink_get_buffer(sinks[0], commit_bytes, &sink_ptr, + &sink_start, &sink_buf_size); + if (ret) + return ret; - /* If new output produced, set up pointer into scratch data */ - if (num_ceps > 0) { - if (state->mel_only) { - /* Convert mel_log_32 from Q9.23 to Q9.15 in-place */ - for (k = 0; k < num_ceps; k++) - state->mel_log_32[k] >>= 8; + /* Zero-fill entire period first */ + bytes_to_end = (size_t)((uint8_t *)sink_start + sink_buf_size - + (uint8_t *)sink_ptr); - state->out_data_ptr_32 = state->mel_log_32; - } else { - state->out_data_ptr = state->cepstral_coef->data; - } - - state->out_remain = num_ceps; - state->header_pending = true; + if (bytes_to_end >= commit_bytes) { + memset(sink_ptr, 0, commit_bytes); + } else { + memset(sink_ptr, 0, bytes_to_end); + memset(sink_start, 0, commit_bytes - bytes_to_end); } - /* Write to sink, limited by period size */ - sink_samples = frames * audio_stream_get_channels(sink); + dst = sink_ptr; + avail = commit_bytes; - /* Write data header first if pending */ - if (state->header_pending) { - if (sink_samples < num_header_s32) { - /* Not enough sink space for header, defer entire frame */ - mfcc_sink_copy_zero_s32(sink, w_ptr, sink_samples); - return; - } - - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, num_header_s32, - (int32_t *)&state->header); - sink_samples -= num_header_s32; - state->header_pending = false; - } + /* Write pending header */ + if (state->header_pending && avail > 0) { + hdr_size = sizeof(state->header); - if (state->mel_only) { - /* Write 32-bit mel data Q9.15, one value per int32_t */ - to_copy = MIN(state->out_remain, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, to_copy, - state->out_data_ptr_32); - state->out_data_ptr_32 += to_copy; - state->out_remain -= to_copy; - sink_samples -= to_copy; + if (avail >= hdr_size) { + mfcc_sink_write_bytes(&dst, sink_start, sink_buf_size, + (uint8_t *)&state->header, hdr_size); + avail -= hdr_size; + state->header_pending = false; } - } else { - /* Write cepstral data packed as int32_t from scratch buffer */ - remain_s32 = (state->out_remain + 1) / 2; - to_copy = MIN(remain_s32, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, to_copy, - (int32_t *)state->out_data_ptr); - state->out_data_ptr += to_copy * 2; - state->out_remain -= to_copy * 2; - if (state->out_remain < 0) - state->out_remain = 0; + } - sink_samples -= to_copy; + /* Write pending feature data (always int32) */ + if (state->out_remain > 0 && avail > 0) { + data_bytes = state->out_remain * sizeof(int32_t); + to_write = MIN(data_bytes, avail) & ~(size_t)3; + if (to_write > 0) { + mfcc_sink_write_bytes(&dst, sink_start, sink_buf_size, + (uint8_t *)state->out_data_ptr, + to_write); + n32 = to_write / sizeof(int32_t); + state->out_data_ptr += n32; + state->out_remain -= n32; } } - /* Zero-fill remaining sink samples */ - w_ptr = mfcc_sink_copy_zero_s32(sink, w_ptr, sink_samples); + return sink_commit_buffer(sinks[0], commit_bytes); } -#endif /* CONFIG_FORMAT_S24LE */ -#if CONFIG_FORMAT_S32LE -void mfcc_s32_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames) +int mfcc_process_output(struct processing_module *mod, struct mfcc_comp_data *cd, + struct sof_source **sources, struct sof_sink **sinks, + int num_ceps, int frames) { - struct audio_stream *sink = bsink->data; - struct mfcc_comp_data *cd = module_get_private_data(mod); - struct mfcc_state *state = &cd->state; - struct mfcc_buffer *buf = &cd->state.buf; - int32_t *w_ptr = audio_stream_get_wptr(sink); - const int num_header_s32 = sizeof(state->header) / sizeof(int32_t); - int num_ceps; - int sink_samples; - int remain_s32; - int to_copy; - - /* Get samples from source buffer */ - mfcc_source_copy_s32(bsource, buf, &state->emph, frames, state->source_channel); - - /* Run STFT and processing after FFT */ - num_ceps = mfcc_stft_process(mod, cd); - - /* If new output produced, set up pointer into scratch data */ - if (num_ceps > 0) { - if (state->mel_only) { - state->out_data_ptr_32 = state->mel_log_32; - } else { - state->out_data_ptr = state->cepstral_coef->data; - } + bool pending; + + pending = cd->state.header_pending || cd->state.out_remain > 0; - state->out_remain = num_ceps; - state->header_pending = true; + if (cd->config->compress_output) { + /* Keep retrying pending frame first; don't overwrite pending state. */ + if (!pending && num_ceps > 0) + mfcc_prepare_output(&cd->state, num_ceps); + else if (pending) + num_ceps = 0; + + return mfcc_output_compress(mod, cd, sinks, num_ceps); } - /* Write to sink, limited by period size */ - sink_samples = frames * audio_stream_get_channels(sink); + /* Legacy PCM mode: same guard. Overwriting out_data_ptr / out_remain + * while a previous period's data is still pending would drop samples. + */ + if (!pending && num_ceps > 0) + mfcc_prepare_output(&cd->state, num_ceps); - /* Write data header first if pending */ - if (state->header_pending) { - if (sink_samples < num_header_s32) { - /* Not enough sink space for header, defer entire frame */ - mfcc_sink_copy_zero_s32(sink, w_ptr, sink_samples); - return; - } + return mfcc_output_legacy(mod, cd, sources, sinks, frames); +} - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, num_header_s32, - (int32_t *)&state->header); - sink_samples -= num_header_s32; - state->header_pending = false; - } +/** + * \brief Refill the FFT input scratch from the input circular buffer. + * + * Copies the saved overlap (\c state->prev_data) into the first samples + * of \c fft->fft_buf, appends one \c fft_hop_size of new data from the + * input circular buffer (consuming it), and finally saves the trailing + * window of the FFT input back to \c state->prev_data for the next hop. + * The caller is expected to have zeroed \c fft->fft_buf so that the + * complex imaginary parts remain 0. + * + * \param[in,out] state MFCC state. Input buffer pointers/counters and + * \c prev_data are updated; \c fft->fft_buf is filled. + */ +void mfcc_fill_fft_buffer(struct mfcc_state *state) +{ + struct mfcc_buffer *buf = &state->buf; + struct mfcc_fft *fft = &state->fft; + int32_t *d = &fft->fft_buf[fft->fft_fill_start_idx].real; + const int fft_elem_inc = sizeof(fft->fft_buf[0]) / sizeof(int32_t); + int16_t *prev = state->prev_data; + int16_t *prev_end = prev + state->prev_data_size; + int16_t *r = buf->r_ptr; + int copied; + int nmax; + int n; + int j; - if (state->mel_only) { - /* Write 32-bit mel data Q9.23, one value per int32_t */ - to_copy = MIN(state->out_remain, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, to_copy, - state->out_data_ptr_32); - state->out_data_ptr_32 += to_copy; - state->out_remain -= to_copy; - sink_samples -= to_copy; - } - } else { - /* Write cepstral data packed as int32_t from scratch buffer */ - remain_s32 = (state->out_remain + 1) / 2; - to_copy = MIN(remain_s32, sink_samples); - if (to_copy > 0) { - w_ptr = mfcc_sink_copy_data_s32(sink, w_ptr, to_copy, - (int32_t *)state->out_data_ptr); - state->out_data_ptr += to_copy * 2; - state->out_remain -= to_copy * 2; - if (state->out_remain < 0) - state->out_remain = 0; + /* Copy overlapped samples from state buffer. The fft_buf has been + * cleared by caller so imaginary part remains zero. + */ + while (prev < prev_end) { + *d = *prev++; + d += fft_elem_inc; + } - sink_samples -= to_copy; + /* Copy hop size of new data from circular buffer */ + for (copied = 0; copied < fft->fft_hop_size; copied += n) { + nmax = fft->fft_hop_size - copied; + n = mfcc_buffer_samples_without_wrap(buf, r); + n = MIN(n, nmax); + for (j = 0; j < n; j++) { + *d = *r++; + d += fft_elem_inc; } + r = mfcc_buffer_wrap(buf, r); } - /* Zero-fill remaining sink samples */ - w_ptr = mfcc_sink_copy_zero_s32(sink, w_ptr, sink_samples); + buf->s_avail -= copied; + buf->s_free += copied; + buf->r_ptr = r; + + /* Copy for next time data back to overlap buffer */ + d = (int32_t *)&fft->fft_buf[fft->fft_fill_start_idx + fft->fft_hop_size].real; + prev = state->prev_data; + while (prev < prev_end) { + *prev++ = *d; + d += fft_elem_inc; + } } -#endif /* CONFIG_FORMAT_S32LE */ diff --git a/src/audio/mfcc/mfcc_generic.c b/src/audio/mfcc/mfcc_generic.c index 73ac49272ed4..65f4760a0ea3 100644 --- a/src/audio/mfcc/mfcc_generic.c +++ b/src/audio/mfcc/mfcc_generic.c @@ -8,7 +8,6 @@ #ifdef MFCC_GENERIC #include -#include #include #include #include @@ -26,6 +25,17 @@ * MFCC algorithm code */ +/** + * \brief Generic-C copy of the overlap window from the input circular buffer. + * + * Copies \p prev_data_length samples from the front of \p buf into + * \p prev_data and advances the read pointer. Used once on first frame + * to seed the overlap region for subsequent STFT hops. + * + * \param[in,out] buf Input circular buffer (Q1.15 mono). + * \param[out] prev_data Destination overlap buffer. + * \param[in] prev_data_length Number of samples to copy. + */ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, int prev_data_length) { @@ -51,6 +61,17 @@ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, buf->r_ptr = r; } +/** + * \brief Generic-C window function application on the FFT input buffer. + * + * Multiplies the real part of \c fft->fft_buf (sized \c fft->fft_size, + * Q1.15 input upcast to int32) by \c state->window (Q1.15) in-place and + * left-shifts by \p input_shift + 1 to produce Q1.31 fixed-point input + * to the FFT. + * + * \param[in,out] state MFCC state; \c state->fft.fft_buf is updated in-place. + * \param[in] input_shift Additional left shift applied to the windowed sample. + */ void mfcc_apply_window(struct mfcc_state *state, int input_shift) { struct mfcc_fft *fft = &state->fft; @@ -64,161 +85,4 @@ void mfcc_apply_window(struct mfcc_state *state, int input_shift) fft->fft_buf[i + j].real = (fft->fft_buf[i + j].real * state->window[j]) << s; } -#if CONFIG_FORMAT_S16LE -void mfcc_source_copy_s16(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int32_t s; - int16_t *x0; - int16_t *x = audio_stream_get_rptr(source); - int16_t *w = buf->w_ptr; - int copied; - int nmax; - int n1; - int n2; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - */ - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n1 = audio_stream_frames_without_wrap(source, x); - n2 = mfcc_buffer_samples_without_wrap(buf, w); - n = MIN(n1, n2); - n = MIN(n, nmax); - x0 = x + source_channel; - for (i = 0; i < n; i++) { - if (emph->enable) { - /* Q1.15 x Q1.15 -> Q2.30 */ - s = (int32_t)emph->delay * emph->coef + Q_SHIFT_LEFT(*x0, 15, 30); - *w = sat_int16(Q_SHIFT_RND(s, 30, 15)); - emph->delay = *x0; - } else { - *w = *x0; - } - x0 += num_channels; - w++; - } - - x = audio_stream_wrap(source, x + n * audio_stream_get_channels(source)); - w = mfcc_buffer_wrap(buf, w); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = w; -} -#endif /* CONFIG_FORMAT_S16LE */ - -#if CONFIG_FORMAT_S24LE - -void mfcc_source_copy_s24(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int32_t tmp, s; - int32_t *x0; - int32_t *x = audio_stream_get_rptr(source); - int16_t *w = buf->w_ptr; - int copied; - int nmax; - int n1; - int n2; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - * S24_4LE data is in 32-bit container, shift left by 8 to Q1.31, - * then convert to Q1.15 with rounding. - */ - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n1 = audio_stream_frames_without_wrap(source, x); - n2 = mfcc_buffer_samples_without_wrap(buf, w); - n = MIN(n1, n2); - n = MIN(n, nmax); - x0 = x + source_channel; - for (i = 0; i < n; i++) { - if (emph->enable) { - /* Convert to Q1.31, ignore highest byte */ - s = (int32_t)((uint32_t)*x0 << 8); - /* Q1.15 x Q1.15 -> Q2.30 */ - tmp = (int32_t)emph->delay * emph->coef + Q_SHIFT(s, 31, 30); - *w = sat_int16(Q_SHIFT_RND(tmp, 30, 15)); - emph->delay = sat_int16(Q_SHIFT_RND(s, 31, 15)); - } else { - /* Convert to Q1.31, ignore highest byte */ - s = (int32_t)((uint32_t)*x0 << 8); - *w = sat_int16(Q_SHIFT_RND(s, 31, 15)); - } - x0 += num_channels; - w++; - } - - x = audio_stream_wrap(source, x + n * audio_stream_get_channels(source)); - w = mfcc_buffer_wrap(buf, w); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = w; -} - -#endif /* CONFIG_FORMAT_S24LE */ - -#if CONFIG_FORMAT_S32LE - -void mfcc_source_copy_s32(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int32_t s; - int32_t *x0; - int32_t *x = audio_stream_get_rptr(source); - int16_t *w = buf->w_ptr; - int copied; - int nmax; - int n1; - int n2; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - * S32 data is in 32-bit container, shift right by 16 to get 16-bit. - */ - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n1 = audio_stream_frames_without_wrap(source, x); - n2 = mfcc_buffer_samples_without_wrap(buf, w); - n = MIN(n1, n2); - n = MIN(n, nmax); - x0 = x + source_channel; - for (i = 0; i < n; i++) { - if (emph->enable) { - /* Q1.15 x Q1.15 -> Q2.30 */ - s = (int32_t)emph->delay * emph->coef + Q_SHIFT(*x0, 31, 30); - *w = sat_int16(Q_SHIFT_RND(s, 30, 15)); - emph->delay = sat_int16(Q_SHIFT_RND(*x0, 31, 15)); - } else { - *w = sat_int16(Q_SHIFT_RND(*x0, 31, 15)); - } - x0 += num_channels; - w++; - } - - x = audio_stream_wrap(source, x + n * audio_stream_get_channels(source)); - w = mfcc_buffer_wrap(buf, w); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = w; -} -#endif /* CONFIG_FORMAT_S32LE */ - #endif /* MFCC_GENERIC */ diff --git a/src/audio/mfcc/mfcc_hifi3.c b/src/audio/mfcc/mfcc_hifi3.c index 80c384ad6c64..fdf8e47d2deb 100644 --- a/src/audio/mfcc/mfcc_hifi3.c +++ b/src/audio/mfcc/mfcc_hifi3.c @@ -9,7 +9,6 @@ #ifdef MFCC_HIFI3 #include -#include #include #include #include @@ -35,66 +34,16 @@ static inline void set_circular_buf0(const void *start, const void *end) * MFCC algorithm code */ -#if CONFIG_FORMAT_S16LE -void mfcc_source_copy_s16(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int copied; - int nmax; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - ae_int16 *in; - ae_int16 *x = (ae_int16 *)audio_stream_get_rptr(source); - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef = emph->coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int16) * num_channels; - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - */ - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n = audio_stream_frames_without_wrap(source, x); - n = MIN(n, nmax); - nmax = mfcc_buffer_samples_without_wrap(buf, (int16_t *)out); - n = MIN(n, nmax); - in = x + source_channel; - if (emph->enable) { - delay = emph->delay; - for (i = 0; i < n; i++) { - AE_L16_XP(sample, in, in_inc); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - /* 2 = sizeof(ae_int16)*/ - AE_S16_0_IP(sample, out, 2); - } - emph->delay = delay; - - } else { - for (i = 0; i < n; i++) { - AE_L16_XP(sample, in, in_inc); - /* 2 = sizeof(ae_int16)*/ - AE_S16_0_IP(sample, out, 2); - } - } - - x = audio_stream_wrap(source, x + n * num_channels); - out = (ae_int16 *)mfcc_buffer_wrap(buf, (int16_t *)out); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S16LE */ - +/** + * \brief HiFi3 copy of the overlap window from the input circular buffer. + * + * HiFi3 implementation equivalent to the generic mfcc_fill_prev_samples(); + * uses xtensa circular addressing for the source pointer. + * + * \param[in,out] buf Input circular buffer (Q1.15 mono). + * \param[out] prev_data Destination overlap buffer. + * \param[in] prev_data_length Number of samples to copy. + */ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, int prev_data_length) { @@ -128,6 +77,16 @@ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, buf->r_ptr = (void *)in; /* int16_t pointer but direct cast is not possible */ } +/** + * \brief HiFi3 window function application on the FFT input buffer. + * + * HiFi3 implementation equivalent to the generic mfcc_apply_window(); + * uses fractional multiply with saturating shift to produce Q1.31 + * FFT input data. + * + * \param[in,out] state MFCC state; \c state->fft.fft_buf is updated in-place. + * \param[in] input_shift Additional left shift applied to the windowed sample. + */ void mfcc_apply_window(struct mfcc_state *state, int input_shift) { struct mfcc_fft *fft = &state->fft; @@ -152,129 +111,4 @@ void mfcc_apply_window(struct mfcc_state *state, int input_shift) } } -#if CONFIG_FORMAT_S24LE -void mfcc_source_copy_s24(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int copied; - int nmax; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - ae_int32 *in; - ae_int32 *x = (ae_int32 *)audio_stream_get_rptr(source); - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int32x2 sample32; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef = emph->coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int32) * num_channels; - - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n = audio_stream_frames_without_wrap(source, x); - n = MIN(n, nmax); - nmax = mfcc_buffer_samples_without_wrap(buf, (int16_t *)out); - n = MIN(n, nmax); - in = x + source_channel; - if (emph->enable) { - delay = emph->delay; - for (i = 0; i < n; i++) { - AE_L32_XP(sample32, in, in_inc); - /* Shift left by 8 to sign-extend to Q1.31 */ - sample32 = AE_SLAI32(sample32, 8); - /* Then shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_IP(sample, out, 2); - } - emph->delay = delay; - } else { - for (i = 0; i < n; i++) { - AE_L32_XP(sample32, in, in_inc); - /* Shift left by 8 to sign-extend to Q1.31 */ - sample32 = AE_SLAI32(sample32, 8); - /* Then shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - AE_S16_0_IP(sample, out, 2); - } - } - - x = audio_stream_wrap(source, x + n * num_channels); - out = (ae_int16 *)mfcc_buffer_wrap(buf, (int16_t *)out); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S24LE */ - -#if CONFIG_FORMAT_S32LE -void mfcc_source_copy_s32(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int copied; - int nmax; - int n; - int i; - int num_channels = audio_stream_get_channels(source); - ae_int32 *in; - ae_int32 *x = (ae_int32 *)audio_stream_get_rptr(source); - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int32x2 sample32; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef = emph->coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int32) * num_channels; - - for (copied = 0; copied < frames; copied += n) { - nmax = frames - copied; - n = audio_stream_frames_without_wrap(source, x); - n = MIN(n, nmax); - nmax = mfcc_buffer_samples_without_wrap(buf, (int16_t *)out); - n = MIN(n, nmax); - in = x + source_channel; - if (emph->enable) { - delay = emph->delay; - for (i = 0; i < n; i++) { - AE_L32_XP(sample32, in, in_inc); - /* S32: shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_IP(sample, out, 2); - } - emph->delay = delay; - } else { - for (i = 0; i < n; i++) { - AE_L32_XP(sample32, in, in_inc); - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - AE_S16_0_IP(sample, out, 2); - } - } - - x = audio_stream_wrap(source, x + n * num_channels); - out = (ae_int16 *)mfcc_buffer_wrap(buf, (int16_t *)out); - } - buf->s_avail += copied; - buf->s_free -= copied; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S32LE */ - #endif /* MFCC_HIFI3 */ diff --git a/src/audio/mfcc/mfcc_hifi4.c b/src/audio/mfcc/mfcc_hifi4.c index 63986870793b..e86d066b408b 100644 --- a/src/audio/mfcc/mfcc_hifi4.c +++ b/src/audio/mfcc/mfcc_hifi4.c @@ -9,7 +9,6 @@ #ifdef MFCC_HIFI4 #include -#include #include #include #include @@ -31,66 +30,21 @@ static inline void set_circular_buf0(const void *start, const void *end) AE_SETCEND0(end); } -/* Setup circular for buffer 1 */ -static inline void set_circular_buf1(const void *start, const void *end) -{ - AE_SETCBEGIN1(start); - AE_SETCEND1(end); -} - /* * MFCC algorithm code */ -#if CONFIG_FORMAT_S16LE -void mfcc_source_copy_s16(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int num_channels = audio_stream_get_channels(source); - ae_int16 *in = (ae_int16 *)source->r_ptr + source_channel; - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int16) * num_channels; - const int out_inc = sizeof(ae_int16); - int i; - - set_circular_buf1(buf->addr, buf->end_addr); - set_circular_buf0(source->addr, source->end_addr); - - /* Copy from source to pre-buffer for FFT. - * The pre-emphasis filter is done in this step. - */ - if (emph->enable) { - delay = emph->delay; - coef = emph->coef; - for (i = 0; i < frames; i++) { - AE_L16_XC(sample, in, in_inc); - - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_XC1(sample, out, out_inc); - } - emph->delay = delay; - } else { - for (i = 0; i < frames; i++) { - AE_L16_XC(sample, in, in_inc); - AE_S16_0_XC1(sample, out, out_inc); - } - } - - buf->s_avail += frames; - buf->s_free -= frames; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S16LE */ - +/** + * \brief HiFi4 copy of the overlap window from the input circular buffer. + * + * HiFi4/HiFi5 implementation equivalent to the generic + * mfcc_fill_prev_samples(); uses xtensa circular addressing for the + * source pointer. + * + * \param[in,out] buf Input circular buffer (Q1.15 mono). + * \param[out] prev_data Destination overlap buffer. + * \param[in] prev_data_length Number of samples to copy. + */ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, int prev_data_length) { @@ -124,6 +78,16 @@ void mfcc_fill_prev_samples(struct mfcc_buffer *buf, int16_t *prev_data, buf->r_ptr = (int16_t *)in; } +/** + * \brief HiFi4 window function application on the FFT input buffer. + * + * HiFi4/HiFi5 implementation equivalent to the generic + * mfcc_apply_window(); uses fractional multiply with saturating shift to + * produce Q1.31 FFT input data. + * + * \param[in,out] state MFCC state; \c state->fft.fft_buf is updated in-place. + * \param[in] input_shift Additional left shift applied to the windowed sample. + */ void mfcc_apply_window(struct mfcc_state *state, int input_shift) { struct mfcc_fft *fft = &state->fft; @@ -148,111 +112,4 @@ void mfcc_apply_window(struct mfcc_state *state, int input_shift) } } -#if CONFIG_FORMAT_S24LE -void mfcc_source_copy_s24(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int num_channels = audio_stream_get_channels(source); - ae_int32 *in = (ae_int32 *)source->r_ptr + source_channel; - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int32x2 sample32; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int32) * num_channels; - const int out_inc = sizeof(ae_int16); - int i; - - set_circular_buf1(buf->addr, buf->end_addr); - set_circular_buf0(source->addr, source->end_addr); - - if (emph->enable) { - delay = emph->delay; - coef = emph->coef; - for (i = 0; i < frames; i++) { - AE_L32_XC(sample32, in, in_inc); - /* Shift left by 8 to sign-extend to Q1.31 */ - sample32 = AE_SLAI32(sample32, 8); - /* Then shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_XC1(sample, out, out_inc); - } - emph->delay = delay; - } else { - for (i = 0; i < frames; i++) { - AE_L32_XC(sample32, in, in_inc); - /* Shift left by 8 to sign-extend to Q1.31 */ - sample32 = AE_SLAI32(sample32, 8); - /* Then shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - AE_S16_0_XC1(sample, out, out_inc); - } - } - - buf->s_avail += frames; - buf->s_free -= frames; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S24LE */ - -#if CONFIG_FORMAT_S32LE -void mfcc_source_copy_s32(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel) -{ - struct audio_stream *source = bsource->data; - int num_channels = audio_stream_get_channels(source); - ae_int32 *in = (ae_int32 *)source->r_ptr + source_channel; - ae_int16 *out = (ae_int16 *)buf->w_ptr; - ae_int32x2 sample32; - ae_int16x4 sample; - ae_int32x2 temp; - ae_int16x4 coef; - ae_int16x4 delay; - const int in_inc = sizeof(ae_int32) * num_channels; - const int out_inc = sizeof(ae_int16); - int i; - - set_circular_buf1(buf->addr, buf->end_addr); - set_circular_buf0(source->addr, source->end_addr); - - if (emph->enable) { - delay = emph->delay; - coef = emph->coef; - for (i = 0; i < frames; i++) { - AE_L32_XC(sample32, in, in_inc); - /* S32: shift right by 16 to get 16-bit */ - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - /* Q1.15 -> Q1.31 */ - temp = AE_CVT32X2F16_10(sample); - AE_MULAF16SS_00(temp, delay, coef); - delay = sample; - sample = AE_ROUND16X4F32SSYM(temp, temp); - AE_S16_0_XC1(sample, out, out_inc); - } - emph->delay = delay; - } else { - for (i = 0; i < frames; i++) { - AE_L32_XC(sample32, in, in_inc); - sample32 = AE_SRAI32(sample32, 16); - sample = AE_SAT16X4(sample32, sample32); - AE_S16_0_XC1(sample, out, out_inc); - } - } - - buf->s_avail += frames; - buf->s_free -= frames; - buf->w_ptr = (int16_t *)out; -} -#endif /* CONFIG_FORMAT_S32LE */ - #endif /* MFCC_HIFI4 */ diff --git a/src/audio/mfcc/mfcc_setup.c b/src/audio/mfcc/mfcc_setup.c index 73e69f6408a8..1d062b2a5206 100644 --- a/src/audio/mfcc/mfcc_setup.c +++ b/src/audio/mfcc/mfcc_setup.c @@ -27,6 +27,13 @@ LOG_MODULE_REGISTER(mfcc_setup, CONFIG_SOF_LOG_LEVEL); +/** + * \brief Initialize an MFCC input circular buffer descriptor. + * + * \param[out] buf Circular buffer descriptor to initialize. + * \param[in] base Backing storage (Q1.15 samples). + * \param[in] size Buffer length in samples. + */ static void mfcc_init_buffer(struct mfcc_buffer *buf, int16_t *base, int size) { buf->addr = base; @@ -38,6 +45,15 @@ static void mfcc_init_buffer(struct mfcc_buffer *buf, int16_t *base, int size) buf->s_length = size; } +/** + * \brief Fill \c state->window with the configured analysis window. + * + * \param[in,out] state MFCC state; \c state->window is written, sized + * \c state->fft.fft_size and in Q1.15. + * \param[in] name Window type identifier from the MFCC configuration. + * + * \return 0 on success, -EINVAL for an unsupported window type. + */ static int mfcc_get_window(struct mfcc_state *state, enum sof_mfcc_fft_window_type name) { struct mfcc_fft *fft = &state->fft; @@ -103,6 +119,24 @@ static int mfcc_get_cepstral_lifter(struct processing_module *mod, struct mfcc_c /* TODO mfcc setup needs to use the config blob, not hard coded parameters. * Also this is a too long function. Split to STFT, Mel filter, etc. parts. */ +/** + * \brief Allocate and initialize MFCC runtime state from the config blob. + * + * Validates the supplied configuration, allocates the input circular + * buffer, overlap buffer, window vector, FFT scratch, Mel filterbank, + * DCT/cepstral lifter matrices (when \c num_ceps > 0) and VAD state + * (when enabled). All allocations are freed on any failure path. + * + * \param[in,out] mod Processing module that owns the MFCC instance. + * \param[in] max_frames Maximum input frames the pipeline will deliver + * per call; sizes the input buffer. + * \param[in] sample_rate Input sample rate in Hz; must match + * \c config->sample_frequency. + * \param[in] channels Number of channels in the input stream. + * + * \return 0 on success or a negative error code (\c -EINVAL for bad + * configuration, \c -ENOMEM for an allocation failure). + */ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, int channels) { struct mfcc_comp_data *cd = module_get_private_data(mod); @@ -112,6 +146,10 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i struct mfcc_fft *fft = &state->fft; struct psy_mel_filterbank *fb = &state->melfb; struct dct_plan_16 *dct = &state->dct; + int mel_log_32_space; + int max_out_per_hop; + int out_per_hop; + int sink_per_hop; int ret; comp_dbg(dev, "entry"); @@ -129,8 +167,9 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i return -EINVAL; } - if (sample_rate > MFCC_MAX_SAMPLE_RATE) { - comp_err(dev, "Sample rate %d exceeds max %d Hz", sample_rate, MFCC_MAX_SAMPLE_RATE); + if (sample_rate <= 0 || sample_rate > MFCC_MAX_SAMPLE_RATE) { + comp_err(dev, "Sample rate %d out of range (1..%d Hz)", + sample_rate, MFCC_MAX_SAMPLE_RATE); return -EINVAL; } @@ -139,6 +178,37 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i return -EINVAL; } + /* Validate configuration parameters up front so that runtime code + * (executed every hop in the DSP) can skip divide-by-zero / range + * checks. Any later division by these fields is safe after this. + */ + if (config->frame_length <= 0 || config->frame_shift <= 0 || + config->frame_shift > config->frame_length) { + comp_err(dev, "Illegal frame_length %d or frame_shift %d", + config->frame_length, config->frame_shift); + return -EINVAL; + } + + if (config->num_mel_bins <= 0) { + comp_err(dev, "Illegal num_mel_bins %d", config->num_mel_bins); + return -EINVAL; + } + + if (config->num_ceps < 0) { + comp_err(dev, "Illegal num_ceps %d", config->num_ceps); + return -EINVAL; + } + + /* cepstral_lifter is used as a divisor in mfcc_get_cepstral_lifter(). + * Either disabled (== 0) or strictly positive. Negative values are + * rejected to keep runtime math (every hop in DSP) free of guards. + */ + if (config->num_ceps > 0 && config->cepstral_lifter < 0) { + comp_err(dev, "Illegal cepstral_lifter %d (must be >= 0)", + config->cepstral_lifter); + return -EINVAL; + } + cd->max_frames = max_frames; state->sample_rate = sample_rate; state->low_freq = config->low_freq; @@ -271,10 +341,14 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i state->lifter.num_ceps = config->num_ceps; state->lifter.cepstral_lifter = config->cepstral_lifter; /* Q7.9 max 64.0*/ - ret = mfcc_get_cepstral_lifter(mod, &state->lifter); - if (ret < 0) { - comp_err(dev, "Failed cepstral lifter"); - goto free_dct_matrix; + if (state->lifter.cepstral_lifter > 0) { + ret = mfcc_get_cepstral_lifter(mod, &state->lifter); + if (ret < 0) { + comp_err(dev, "Failed cepstral lifter"); + goto free_dct_matrix; + } + } else { + state->lifter.matrix = NULL; } state->mel_only = false; @@ -311,7 +385,7 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i state->mel_log_32 = &state->power_spectra[fft->half_fft_size]; /* Check that mel_log_32 fits in the remaining fft_buf scratch space */ - int mel_log_32_space = (int)(fft->fft_buffer_size / sizeof(int32_t)) - fft->half_fft_size; + mel_log_32_space = (int)(fft->fft_buffer_size / sizeof(int32_t)) - fft->half_fft_size; if (config->num_mel_bins > mel_log_32_space) { comp_err(dev, "num_mel_bins %d exceeds mel_log_32 scratch space %d", @@ -331,24 +405,38 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i /* Allocate output buffer for multi-period output. Size allows for * current output data plus leftover from previous period. */ - int max_out_per_hop = state->mel_only ? dct->num_in : dct->num_out; + max_out_per_hop = state->mel_only ? dct->num_in : dct->num_out; /* Check that output data can be drained within the periods spanned by one * FFT hop. Each hop consumes fft_hop_size input samples and produces - * max_out_per_hop + 12 (magic header) int16_t output values. The sink provides - * at least fft_hop_size * channels int16_t samples per hop (worst case s16). + * max_out_per_hop + header int32_t output values. The sink provides + * at least fft_hop_size * channels int32_t samples per hop (worst case s32). * If output exceeds this, data accumulates and will eventually overflow. + * This check is not needed in compress output mode where only actual data + * bytes are committed without zero padding. */ - int out_per_hop = max_out_per_hop + sizeof(state->header) / sizeof(int16_t); - int sink_per_hop = fft->fft_hop_size * channels; + out_per_hop = max_out_per_hop + sizeof(state->header) / sizeof(int32_t); + sink_per_hop = fft->fft_hop_size * channels; - if (out_per_hop > sink_per_hop) { - comp_err(dev, "Output %d int16 per hop exceeds sink capacity %d (hop %d x ch %d)", + if (!config->compress_output && out_per_hop > sink_per_hop) { + comp_err(dev, "Output %d int32 per hop exceeds sink capacity %d (hop %d x ch %d)", out_per_hop, sink_per_hop, fft->fft_hop_size, channels); ret = -EINVAL; goto free_lifter; } + /* Staging buffer for pending output. Decouples committed data from + * the FFT scratch (mel_log_32) so the next STFT hop can run while a + * previous frame is still being drained across multiple periods. + */ + state->out_stage_size = max_out_per_hop; + state->out_stage = mod_zalloc(mod, max_out_per_hop * sizeof(int32_t)); + if (!state->out_stage) { + comp_err(dev, "Failed output staging buffer allocate"); + ret = -ENOMEM; + goto free_lifter; + } + /* Set initial state for STFT */ state->waiting_fill = true; state->prev_samples_valid = false; @@ -357,20 +445,26 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i memset(&state->header, 0, sizeof(state->header)); state->header.magic = MFCC_MAGIC; state->out_data_ptr = NULL; - state->out_data_ptr_32 = NULL; state->out_remain = 0; + state->vad_silence_count = 0; + state->dtx_trailing_silence = config->dtx_trailing_silence_hops; + state->dtx_silence_interval = config->dtx_silence_hops_interval; + state->dtx_silence_counter = 0; if (config->enable_vad) { ret = mfcc_vad_init(&cd->vad, config->num_mel_bins, sample_rate, mod); if (ret < 0) { comp_err(dev, "Failed VAD init"); - goto free_lifter; + goto free_out_stage; } } comp_dbg(dev, "done"); return 0; +free_out_stage: + mod_free(mod, state->out_stage); + free_lifter: mod_free(mod, state->lifter.matrix); @@ -396,6 +490,12 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i return ret; } +/** + * \brief Free a single MFCC allocation and clear the caller's pointer. + * + * \param[in,out] mod Processing module owning the allocation. + * \param[in,out] ptr Address of the pointer to free; set to NULL on return. + */ static void mfcc_free_and_null(struct processing_module *mod, void **ptr) { mod_free(mod, *ptr); @@ -405,6 +505,16 @@ static void mfcc_free_and_null(struct processing_module *mod, void **ptr) /* Free MFCC buffers to prevent leaks on reset->prepare cycles. * mfcc_free_buffers() NULLs the pointers after free. */ +/** + * \brief Release all MFCC runtime allocations. + * + * Frees FFT plan, scratch buffers, input/overlap buffers, Mel + * filterbank data, DCT matrix, cepstral lifter matrix, and VAD state. + * All freed pointers are set to NULL so the function is safe to call + * across repeated reset/prepare cycles. + * + * \param[in,out] mod Processing module that owns the MFCC instance. + */ void mfcc_free_buffers(struct processing_module *mod) { struct mfcc_comp_data *cd = module_get_private_data(mod); @@ -417,6 +527,7 @@ void mfcc_free_buffers(struct processing_module *mod) mfcc_free_and_null(mod, (void **)&cd->state.melfb.data); mfcc_free_and_null(mod, (void **)&cd->state.dct.matrix); mfcc_free_and_null(mod, (void **)&cd->state.lifter.matrix); + mfcc_free_and_null(mod, (void **)&cd->state.out_stage); mfcc_free_and_null(mod, (void **)&cd->vad.noise_floor); mfcc_free_and_null(mod, (void **)&cd->vad.weights); } diff --git a/src/include/sof/audio/mfcc/mfcc_comp.h b/src/include/sof/audio/mfcc/mfcc_comp.h index 4e41c8a4df08..885339004fc0 100644 --- a/src/include/sof/audio/mfcc/mfcc_comp.h +++ b/src/include/sof/audio/mfcc/mfcc_comp.h @@ -54,18 +54,6 @@ struct mfcc_data_header { int32_t vad_flag; /**< VAD decision: 1 = speech, 0 = silence */ }; -/** \brief Type definition for processing function select return value. */ -typedef void (*mfcc_func)(struct processing_module *mod, - struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, - int frames); - -/** \brief MFCC processing functions map item. */ -struct mfcc_func_map { - uint8_t source; /**< source frame format */ - mfcc_func func; /**< processing function */ -}; - struct mfcc_buffer { int16_t *addr; int16_t *end_addr; @@ -82,6 +70,10 @@ struct mfcc_pre_emph { int enable; }; +/** \brief Type definition for source/sink based input copy function. */ +typedef void (*mfcc_source_func)(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel); + struct mfcc_fft { struct icomplex32 *fft_buf; /**< fft_padded_size */ struct icomplex32 *fft_out; /**< fft_padded_size */ @@ -130,10 +122,15 @@ struct mfcc_state { bool header_pending; /**< True when data header not yet written for current output */ struct mfcc_data_header header; /**< Data header for current output frame */ size_t sample_buffers_size; /**< bytes */ - int16_t *out_data_ptr; /**< Read pointer into scratch data for multi-period output */ - int32_t *out_data_ptr_32; /**< Read pointer for 32-bit mel-only output */ - int out_remain; /**< Remaining int16_t samples to write to sink from scratch */ + int32_t *out_data_ptr; /**< Read pointer into staging data for multi-period output */ + int out_remain; /**< Remaining int32_t samples to write to sink from staging */ + int32_t *out_stage; /**< Dedicated staging buffer for pending output, decoupled from STFT scratch */ + int out_stage_size; /**< Capacity of out_stage in int32_t samples */ uint32_t hop_count; /**< FFT hop counter, increments every processed hop */ + int vad_silence_count; /**< Consecutive VAD=0 hops since last speech */ + int16_t dtx_trailing_silence; /**< Number of trailing silence hops to send, from config */ + int16_t dtx_silence_interval; /**< Send silence frame every Nth hop, 0 = disable */ + int dtx_silence_counter; /**< Counter for periodic silence frame send */ }; /* MFCC component private data */ @@ -144,8 +141,9 @@ struct mfcc_comp_data { struct sof_mfcc_config *config; struct ipc_msg *msg; /**< IPC notification for VAD switch control */ int max_frames; + enum sof_ipc_frame source_format; /**< Source audio format for output sizing */ bool vad_prev; /**< Previous VAD state for edge detection */ - mfcc_func mfcc_func; /**< processing function */ + mfcc_source_func source_func; /**< source copy function */ }; static inline int mfcc_buffer_samples_without_wrap(struct mfcc_buffer *buffer, int16_t *ptr) @@ -172,31 +170,37 @@ void mfcc_fill_fft_buffer(struct mfcc_state *state); void mfcc_apply_window(struct mfcc_state *state, int input_shift); -#if CONFIG_FORMAT_S16LE +/** + * \brief Run STFT and Mel/DCT processing. + * \return Number of output coefficients produced, or 0 if not enough data. + */ +int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_data *cd); -void mfcc_source_copy_s16(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, - struct mfcc_pre_emph *emph, int frames, int source_channel); +/** + * \brief Prepare and commit MFCC output data after STFT processing. + * + * This handles the output data conversion and dispatches to either the + * compress-output or legacy PCM-output path. + * + * \return 0 on success or a negative error code. + */ +int mfcc_process_output(struct processing_module *mod, struct mfcc_comp_data *cd, + struct sof_source **sources, struct sof_sink **sinks, + int num_ceps, int frames); -void mfcc_s16_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames); +#if CONFIG_FORMAT_S16LE +void mfcc_source_copy_s16(struct sof_source *source, struct mfcc_buffer *buf, + struct mfcc_pre_emph *emph, int frames, int source_channel); #endif #if CONFIG_FORMAT_S24LE - -void mfcc_source_copy_s24(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, +void mfcc_source_copy_s24(struct sof_source *source, struct mfcc_buffer *buf, struct mfcc_pre_emph *emph, int frames, int source_channel); - -void mfcc_s24_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames); #endif #if CONFIG_FORMAT_S32LE - -void mfcc_source_copy_s32(struct input_stream_buffer *bsource, struct mfcc_buffer *buf, +void mfcc_source_copy_s32(struct sof_source *source, struct mfcc_buffer *buf, struct mfcc_pre_emph *emph, int frames, int source_channel); - -void mfcc_s32_default(struct processing_module *mod, struct input_stream_buffer *bsource, - struct output_stream_buffer *bsink, int frames); #endif #if CONFIG_IPC_MAJOR_4 diff --git a/src/include/user/mfcc.h b/src/include/user/mfcc.h index a2f3717daa52..286ee4f5e985 100644 --- a/src/include/user/mfcc.h +++ b/src/include/user/mfcc.h @@ -54,7 +54,9 @@ struct sof_mfcc_config { int16_t mel_scale; /**< Q4.12 default 1.0, use 0.25 for Whisper */ int16_t mmax_init; /**< Q8.7 default 0, with dynamic_mmax false, can sim. Whisper mmax */ int16_t mmax_coef; /**< Q1.15 decay coefficient for dynamic mmax, a small value for slow */ - uint32_t reserved[6]; + uint16_t dtx_trailing_silence_hops; /**< DTX: number of silence hops to send after speech, 0 = send first only */ + uint16_t dtx_silence_hops_interval; /**< DTX: send silence frame every Nth hop during VAD=0, 0 = disable */ + uint32_t reserved[5]; int32_t sample_frequency; /**< Hz. e.g. 16000 */ int32_t pmin; /**< Q1.31 linear power, limit minimum Mel energy, e.g. 1e-9 */ enum sof_mfcc_mel_log_type mel_log; /**< Use MEL_LOG_IS_LOG, LOG10 or DB*/ @@ -87,9 +89,10 @@ struct sof_mfcc_config { bool use_energy; /**< Must be false (0) */ bool dynamic_mmax; /**< Track max Mel value for clamp with top_db value */ bool enable_vad; /**< Run VAD algorithm */ - bool enable_dtx; /**< Reserved (stream once per second non-speech frames) */ + bool enable_dtx; /**< Discontinuous transmission: suppress silence after trailing frames */ bool update_controls; /**< Update controls with VAD decision */ - bool reserved_bool[5]; /* Reserved for future boolean flags, set to false (0) */ + bool compress_output; /**< Use compress PCM output: variable size, no zero padding */ + bool reserved_bool[4]; /* Reserved for future boolean flags, set to false (0) */ } __attribute__((packed)); #endif /* __USER_MFCC_H__ */ From e1cd4491f933c61ccb3b62df38df795bc065cec8 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 26 May 2026 15:44:06 +0300 Subject: [PATCH 153/303] base_fw: advertise BESPOKE codec for MFCC compress capture Register SND_AUDIOCODEC_BESPOKE capture in codec info TLV when CONFIG_COMP_MFCC is enabled so the kernel detects compress capture support via IPC4_SOF_CODEC_INFO. Signed-off-by: Seppo Ingalsuo --- src/audio/base_fw.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/audio/base_fw.c b/src/audio/base_fw.c index 309392ea3e9c..6ae45f72c209 100644 --- a/src/audio/base_fw.c +++ b/src/audio/base_fw.c @@ -100,6 +100,10 @@ static void get_codec_info(struct sof_tlv **tuple) codec_info.items[codec_info.count++] = SET_CODEC_INFO_ITEM(SND_AUDIOCODEC_VORBIS, SOF_IPC_STREAM_PLAYBACK); #endif +#ifdef CONFIG_COMP_MFCC + codec_info.items[codec_info.count++] = + SET_CODEC_INFO_ITEM(SND_AUDIOCODEC_BESPOKE, SOF_IPC_STREAM_CAPTURE); +#endif if (!codec_info.count) return; From 44aff8aa541fac051345c9f9d4a39b009f17b345 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 26 May 2026 15:44:23 +0300 Subject: [PATCH 154/303] audio: mfcc: update decode tools and add Python compress scripts Update Octave decode scripts for int32 Q9.23 output and DTX gap filling. Add DTX blob generation to setup_mfcc.m. Add Python compress capture tools: sof_mel_spectrogram_compress.py, sof_ceps_spectrogram_compress.py, sof_mel_to_text_live_compress.py. Refactor sof_mel_to_text_live_dsp_vad.py to use shared compress capture code. Add README with usage examples. Signed-off-by: Seppo Ingalsuo --- src/audio/mfcc/tune/README.md | 121 +++- src/audio/mfcc/tune/decode_all.m | 13 +- src/audio/mfcc/tune/decode_ceps.m | 97 ++- src/audio/mfcc/tune/decode_mel.m | 141 ++-- src/audio/mfcc/tune/setup_mfcc.m | 41 +- .../tune/sof_ceps_spectrogram_compress.py | 374 +++++++++++ .../mfcc/tune/sof_mel_spectrogram_compress.py | 625 ++++++++++++++++++ .../tune/sof_mel_to_text_live_compress.py | 490 ++++++++++++++ .../mfcc/tune/sof_mel_to_text_live_dsp_vad.py | 97 +-- .../components/mfcc/ceps13_compress_dtx.conf | 24 + .../include/components/mfcc/default.conf | 2 +- .../include/components/mfcc/mel80.conf | 2 +- .../components/mfcc/mel80_compress.conf | 24 + .../components/mfcc/mel80_compress_dtx.conf | 24 + 14 files changed, 1856 insertions(+), 219 deletions(-) create mode 100755 src/audio/mfcc/tune/sof_ceps_spectrogram_compress.py create mode 100755 src/audio/mfcc/tune/sof_mel_spectrogram_compress.py create mode 100755 src/audio/mfcc/tune/sof_mel_to_text_live_compress.py mode change 100644 => 100755 src/audio/mfcc/tune/sof_mel_to_text_live_dsp_vad.py create mode 100644 tools/topology/topology2/include/components/mfcc/ceps13_compress_dtx.conf create mode 100644 tools/topology/topology2/include/components/mfcc/mel80_compress.conf create mode 100644 tools/topology/topology2/include/components/mfcc/mel80_compress_dtx.conf diff --git a/src/audio/mfcc/tune/README.md b/src/audio/mfcc/tune/README.md index e50b74d84785..422f849b56b4 100644 --- a/src/audio/mfcc/tune/README.md +++ b/src/audio/mfcc/tune/README.md @@ -5,7 +5,7 @@ MFCC component. It's simply run in Matlab or Octave with command `setup_mfcc`. The MFCC configuration parameters can be edited from the script. -## Testbench +## Testbench run The configuration can be test run with testbench. First the test topologies need to be created with `scripts/build-tools.sh -t`. Next the testbench @@ -69,18 +69,30 @@ The 80 bands Mel output can be visualized with command: The directory contains a Python script `sof_mel_to_text_live_dsp_vad.py`. It can be used with development topologies -`sof-arl-cs42l43-l0-cs35l56-l23-mfcc.tplg` and -`sof-mtl-rt713-l0-rt1316-l12-mfcc.tplg`. It captures from default audio -device `hw:0,47` (headset microphone) Mel audio features and VAD flags. -The captured frames with detected speech are sent to Whisper speech -recognizer model for conversion to text. +`sof-arl-cs42l43-l0-cs35l56-l23-mfcc-mel-normal.tplg` and +`sof-mtl-rt713-l0-rt1316-l12-mfcc-mel-normal.tplg`. + +It captures from default audio device `hw:0,47` (headset microphone PCM). +The Mel audio features and VAD flags are packed to be compatible with a +normal PCM stream. The captured frames with detected speech are sent to the +Whisper speech recognizer model for conversion to text. + +The more efficient method for audio features capture uses the compress PCM without +continuous redundant data. Such MFCC development topologies are: + +- `sof-mtl-rt713-l0-rt1316-l12-mfcc-mel-compr.tplg` +- `sof-mtl-rt713-l0-rt1316-l12-mfcc-ceps-compr.tplg` +- `sof-arl-cs42l43-l0-cs35l56-l23-mfcc-mel-compr.tplg` +- `sof-arl-cs42l43-l0-cs35l56-l23-mfcc-ceps-compr.tplg` + +E.g. the script `sof_mel_spectrogram_compress.py` uses the mfcc-mel-compr topology version. ### Prerequisites The script needs OpenVINO. Please follow the install procedure from . -The following Python pip installs are needed into the same OpenVINO venv: +The following Python pip installs are needed into the same OpenVINO venv. ```bash pip install openvino openvino-tokenizers openvino-genai @@ -89,6 +101,9 @@ pip install transformers pip install huggingface_hub ``` +The real-time spectrogram viewers in this directory use GTK 4; their setup is described +under [Live Spectrogram Viewers](#live-spectrogram-viewers) below. + ### NPU / GPU Support The script by default runs the Whisper encoder model in the NPU. To @@ -150,3 +165,95 @@ Whisper model: whisper-medium-int4-ov (encoder: CPU, decoder: CPU) >> "Hello computer" ``` +## Live Whisper Transcription with Compress PCM + +The `sof_mel_to_text_live_compress.py` script captures Mel spectrogram +frames from a SOF compress PCM device and performs live Whisper +transcription using OpenVINO. Unlike `sof_mel_to_text_live_dsp_vad.py` +which uses `arecord`, this script reads directly from the compress PCM +device with discontinuous frames handling. + +The same OpenVINO prerequisites and pip packages apply as described above +for `sof_mel_to_text_live_dsp_vad.py`. + +```bash +# Microphone compress audio features +python3 sof_mel_to_text_live_compress.py --card 0 --device 54 --model whisper-medium-int4-ov + +# Jack In compress audio features +python3 sof_mel_to_text_live_compress.py --card 0 --device 53 --model whisper-medium-int4-ov +``` + +### Compress PCM device IDs + +The compress audio-features capture PCMs in the topology2 platform +configs use the following IDs (see +`tools/topology/topology2/platform/intel/sdw-*-audio-feature-compress.conf`): + +| Device | Name | PCM ID | +|--------|-----------------------------------|--------| +| Jack | "Jack In Compress Audio Features" | 53 | +| Mic | "Microphone Compress Audio Features" | 54 | + +The non-compress PCMs (`47` jack, `48` mic, "Audio Features") are PCM +streams intended for `arecord`/`hw:0,N` (used by +`sof_mel_to_text_live_dsp_vad.py` above) and will not work with the +compress scripts below. + +## Live Spectrogram Viewers + +These viewers are helpful for interactively checking VAD operation and +developing improvements, or for educational visualization of Mel and +cepstral-coefficient spectrograms. + +#### Additional dependencies to install + +GTK 4 and the introspection bindings are shipped by the distribution. +On Debian / Ubuntu (24.04 or newer): + +```bash +sudo apt install python3-gi gir1.2-gtk-4.0 python3-numpy +sudo apt install libgirepository-2.0-dev libcairo2-dev pkg-config \ + python3-dev gir1.2-gtk-4.0 build-essential +pip install PyGObject pycairo numpy +``` + +#### Renderer selection + +GSK auto-selects the renderer; to force the GL or Vulkan back-ends +(usually the smoothest): + +```bash +GSK_RENDERER=ngl python3 sof_mel_spectrogram_compress.py ... +GSK_RENDERER=vulkan python3 sof_mel_spectrogram_compress.py ... +``` + +### Mel Spectrogram + +The `sof_mel_spectrogram_compress.py` script captures Mel spectrogram +frames from a SOF compress PCM device and displays them as a live +scrolling spectrogram with VAD status in a GTK 4 window. This is a +lightweight viewer that does not run Whisper inference. + +```bash +# Microphone compress audio features +python3 sof_mel_spectrogram_compress.py --card 0 --device 54 --width 300 + +# Jack In compress audio features +python3 sof_mel_spectrogram_compress.py --card 0 --device 53 --width 300 +``` + +### Cepstral Spectrogram + +The `sof_ceps_spectrogram_compress.py` script is the counterpart +that displays cepstral coefficients (MFCC) instead of Mel bands. It +imports the GPU-accelerated widgets from `sof_mel_spectrogram_compress.py`, +so both files must remain in the same directory. + +```bash +# Microphone compress audio features +python3 sof_ceps_spectrogram_compress.py --card 0 --device 54 --num-ceps 13 --width 300 + +# Jack In compress audio features +python3 sof_ceps_spectrogram_compress.py --card 0 --device 53 --num-ceps 13 --width 300 +``` diff --git a/src/audio/mfcc/tune/decode_all.m b/src/audio/mfcc/tune/decode_all.m index f5c7e1a06db4..4c377bf5029a 100644 --- a/src/audio/mfcc/tune/decode_all.m +++ b/src/audio/mfcc/tune/decode_all.m @@ -6,12 +6,11 @@ num_ceps = 13; num_mel = 80; -% MFCC cepstral output files +% MFCC cepstral output files (all int32 output, Q9.23) ceps_files = {'mfcc_s16.raw', 'mfcc_s24.raw', 'mfcc_s32.raw'}; -% Mel output files with corresponding format +% Mel output files (all int32 output, Q9.23) mel_files = {'mel_s16.raw', 'mel_s24.raw', 'mel_s32.raw'}; -mel_fmts = {'s16', 's24', 's32'}; % Xtensa prefixed variants xt_ceps_files = {'xt_mfcc_s16.raw', 'xt_mfcc_s24.raw', 'xt_mfcc_s32.raw'}; @@ -19,21 +18,21 @@ all_ceps_files = [ceps_files, xt_ceps_files]; all_mel_files = [mel_files, xt_mel_files]; -all_mel_fmts = [mel_fmts, mel_fmts]; for i = 1:length(all_ceps_files) fn = all_ceps_files{i}; if exist(fn, 'file') fprintf('Decoding MFCC ceps: %s\n', fn); - [ceps, t, n, vad, energy, noise_energy, frame_num] = decode_ceps(fn, num_ceps); + [ceps, t, n, vad, energy, noise_energy, frame_num] = ... + decode_ceps(fn, num_ceps); end end for i = 1:length(all_mel_files) fn = all_mel_files{i}; - fmt = all_mel_fmts{i}; if exist(fn, 'file') fprintf('Decoding Mel: %s\n', fn); - [mel, t, n, vad, energy, noise_energy, frame_num] = decode_mel(fn, num_mel, fmt); + [mel, t, n, vad, energy, noise_energy, frame_num] = ... + decode_mel(fn, num_mel); end end diff --git a/src/audio/mfcc/tune/decode_ceps.m b/src/audio/mfcc/tune/decode_ceps.m index c094ced7c0e1..480eadea2945 100644 --- a/src/audio/mfcc/tune/decode_ceps.m +++ b/src/audio/mfcc/tune/decode_ceps.m @@ -1,9 +1,10 @@ -% [ceps, t, n, vad, energy, noise_energy, frame_number] = decode_ceps(fn, num_ceps, num_channels) +% [ceps, t, n, vad, energy, noise_energy, frame_number] = decode_ceps(fn, num_ceps, hop, num_channels) % % Input % fn - File with MFCC data in .raw or .wav format % num_ceps - number of cepstral coefficients per frame -% num_channels - needed for .raw format, omit for .wav +% hop - STFT hop in seconds, defaults to 10e-3 for 10 ms +% num_channels - needed for .raw format, omit for .wav, default 1 % % Outputs % ceps - cepstral coefficients @@ -18,39 +19,34 @@ % Copyright(c) 2022-2026 Intel Corporation. All rights reserved. function [ceps, t, n, vad, energy, noise_energy, frame_number] = ... - decode_ceps(fn, num_ceps, num_channels) + decode_ceps(fn, num_ceps, hop, num_channels) if nargin < 3 + hop = 10e-3; +end +if nargin < 4 num_channels = 1; end % MFCC stream -fs = 16e3; -qformat = 7; -magic = [25443 28006]; % ASCII 'mfcc' as int16 -num_magic = 2; % magic word is 2 x int16 +qformat = 23; % Q9.23 in int32 +magic = int32(1835426659); % 0x6D666363 as int32 +num_magic = 1; % magic word is 1 x int32 -% Load output data +% Load output data (always int32) [data, num_channels] = get_file(fn, num_channels); -idx1 = find(data == magic(1)); -idx = []; -for i = 1:length(idx1) - if data(idx1(i) + 1) == magic(2) - idx = [idx idx1(i)]; - end -end +idx = find(data == magic); if isempty(idx) error('No magic value markers found from stream'); end -period_ceps = idx(2)-idx(1); num_frames = length(idx); % Header after magic is [frame_number, reserved, energy, noise_energy, vad_flag] -% as int32 (10 int16 slots), followed by num_ceps coefficients. -payload_len = 10 + num_ceps; % 5 int32 = 10 int16, then ceps data +% as int32, followed by num_ceps coefficients (int32). +payload_len = 5 + num_ceps; % Last frame can be incomplete due to span over multiple periods last = idx(end) + num_magic + payload_len - 1; @@ -58,10 +54,6 @@ num_frames = num_frames - 1; end -t_ceps = period_ceps / num_channels / fs; -t = (0:num_frames -1) * t_ceps; -n = 1:num_ceps; - payload = zeros(payload_len, num_frames); for i = 1:num_frames i1 = idx(i) + num_magic; @@ -69,14 +61,51 @@ payload(:,i) = double(data(i1:i2)); end -% Reassemble int32 from pairs of int16 (little-endian). -% Low half must be treated as unsigned with mod() to handle negative int16. -frame_number = mod(payload(1,:), 65536) + payload(2,:) * 65536; -% payload(3:4,:) is reserved, skip -energy = (mod(payload(5,:), 65536) + payload(6,:) * 65536) / 2^23; -noise_energy = (mod(payload(7,:), 65536) + payload(8,:) * 65536) / 2^23; -vad = mod(payload(9,:), 65536) + payload(10,:) * 65536; -ceps = payload(11:payload_len, :) / 2^qformat; +frame_number = payload(1, :); +% payload(2,:) is reserved, skip +energy = payload(3, :) / 2^23; +noise_energy = payload(4, :) / 2^23; +vad = payload(5, :); +ceps = payload(6:payload_len, :) / 2^qformat; + +% Fill gaps from DTX-suppressed VAD=0 frames to create continuous timeline. +% Missing frames are filled with the minimum ceps value found in the data. +first_frame = frame_number(1); +last_frame = frame_number(end); +total_frames = last_frame - first_frame + 1; +if total_frames > num_frames + ceps_fill = min(ceps(:)); + ceps_full = ones(num_ceps, total_frames) * ceps_fill; + vad_full = zeros(1, total_frames); + energy_full = zeros(1, total_frames); + noise_energy_full = zeros(1, total_frames); + frame_number_full = first_frame:last_frame; + has_data = false(1, total_frames); + for i = 1:num_frames + fi = frame_number(i) - first_frame + 1; + ceps_full(:, fi) = ceps(:, i); + vad_full(fi) = vad(i); + energy_full(fi) = energy(i); + noise_energy_full(fi) = noise_energy(i); + has_data(fi) = true; + end + % Forward-fill gaps with last received values + for fi = 2:total_frames + if ~has_data(fi) + ceps_full(:, fi) = ceps_full(:, fi - 1); + energy_full(fi) = energy_full(fi - 1); + noise_energy_full(fi) = noise_energy_full(fi - 1); + end + end + ceps = ceps_full; + vad = vad_full; + energy = energy_full; + noise_energy = noise_energy_full; + frame_number = frame_number_full; +end + +t = (frame_number - first_frame) * hop; +n = 1:num_ceps; figure; surf(t, n, ceps, 'EdgeColor', 'none'); @@ -96,18 +125,18 @@ switch lower(ext) case '.raw' fh = fopen(fn, 'r'); - data = fread(fh, 'int16'); + data = fread(fh, 'int32'); fclose(fh); case '.wav' tmp = audioread(fn, 'native'); t = whos('tmp'); - if ~strcmp(t.class, 'int16') - error('Only 16-bit wav file format is supported'); + if ~strcmp(t.class, 'int32') + error('Expected 32-bit wav for int32 MFCC output format'); end s = size(tmp); num_channels = s(2); if num_channels > 1 - data = int16(zeros(prod(s), 1)); + data = int32(zeros(prod(s), 1)); for i = 1:num_channels data(i:num_channels:end) = tmp(:, i); end diff --git a/src/audio/mfcc/tune/decode_mel.m b/src/audio/mfcc/tune/decode_mel.m index 0b9b8d09c5a8..0aca1e35ec8d 100644 --- a/src/audio/mfcc/tune/decode_mel.m +++ b/src/audio/mfcc/tune/decode_mel.m @@ -1,10 +1,10 @@ -% [mel, t, n, vad, energy, noise_energy, frame_number] = decode_mel(fn, num_mel, fmt, num_channels) +% [mel, t, n, vad, energy, noise_energy, frame_number] = decode_mel(fn, num_mel, hop, num_channels) % % Input % fn - File with Mel data in .raw or .wav format +% hop - STFT hop in seconds, defaults to 10e-3 for 10 ms % num_mel - number of Mel coefficients per frame -% fmt - format of the Mel data ('s16', 's24', 's32') -% num_channels - needed for .raw format, omit for .wav, default 2 +% num_channels - needed for .raw format, omit for .wav, default 1 % % Outputs % mel - Mel coefficients @@ -19,57 +19,32 @@ % Copyright(c) 2026 Intel Corporation. function [mel, t, n, vad, energy, noise_energy, frame_number] = ... - decode_mel(fn, num_mel, fmt, num_channels) + decode_mel(fn, num_mel, hop, num_channels) if nargin < 3 - fmt = 's16'; + hop = 10e-3; end - if nargin < 4 - num_channels = 2; + num_channels = 1; end % MFCC stream fs = 16e3; +qformat = 23; % Q9.23 in int32 -switch fmt - case 's16' - qformat = 7; - magic = [25443 28006]; % ASCII 'mfcc' as two int16 - word_size_multiplier = 2; - case 's24' - qformat = 15; - magic = int32(1835426659); % 0x6D666363 as int32 - word_size_multiplier = 1; - case 's32' - qformat = 23; - magic = int32(1835426659); % 0x6D666363 as int32 - word_size_multiplier = 1; - otherwise - error("Use 's16', 's24', or 's32' as format."); -end - -num_magic = word_size_multiplier; % magic word is 2 x int16 or 1 x int32 -num_other_header = 5 * word_size_multiplier; % frame_number, reserved, energy, noise, vad +magic = int32(1835426659); % 0x6D666363 as int32 +num_magic = 1; % magic word is 1 x int32 +num_other_header = 5; % frame_number, reserved, energy, noise, vad (all int32) -% Load output data -[data, num_channels] = get_file(fn, num_channels, fmt); +% Load output data (always int32) +[data, num_channels] = get_file(fn, num_channels); -if strcmp(fmt, 's16') - idx1 = find(data == magic(1)); - idx = []; - for i = 1:length(idx1) - next_word = idx1(i) + 1; - if next_word <= length(data) - if data(next_word) == magic(2) - idx = [idx idx1(i)]; - end - end - end -else - idx = find(data == magic); +if isempty(data) + error('File %s is empty', fn); end +idx = find(data == magic); + if isempty(idx) error('No magic value markers found from stream'); end @@ -95,26 +70,50 @@ payload(:,i) = double(data(i1:i2)); end -if strcmp(fmt, 's16') - % Reassemble int32 from pairs of int16 (little-endian). - % Low half must be treated as unsigned with mod() to handle negative int16. - frame_number = mod(payload(1,:), 65536) + payload(2,:) * 65536; - % payload(3:4,:) is reserved, skip - energy = (mod(payload(5,:), 65536) + payload(6,:) * 65536) / 2^23; - noise_energy = (mod(payload(7,:), 65536) + payload(8,:) * 65536) / 2^23; - vad = mod(payload(9,:), 65536) + payload(10,:) * 65536; - mel = payload(11:payload_len, :) / 2^qformat; -else - frame_number = payload(1, :); - % payload(2,:) is reserved, skip - energy = payload(3, :) / 2^23; - noise_energy = payload(4, :) / 2^23; - vad = payload(5, :); - mel = payload(6:payload_len, :) / 2^qformat; +frame_number = payload(1, :); +% payload(2,:) is reserved, skip +energy = payload(3, :) / 2^23; +noise_energy = payload(4, :) / 2^23; +vad = payload(5, :); +mel = payload(6:payload_len, :) / 2^qformat; + +% Fill gaps from DTX-suppressed VAD=0 frames to create continuous timeline. +% Missing frames are filled with the minimum Mel value found in the data. +first_frame = frame_number(1); +last_frame = frame_number(end); +total_frames = last_frame - first_frame + 1; +if total_frames > num_frames + mel_fill = min(mel(:)); + mel_full = ones(num_mel, total_frames) * mel_fill; + vad_full = zeros(1, total_frames); + energy_full = zeros(1, total_frames); + noise_energy_full = zeros(1, total_frames); + frame_number_full = first_frame:last_frame; + has_data = false(1, total_frames); + for i = 1:num_frames + fi = frame_number(i) - first_frame + 1; + mel_full(:, fi) = mel(:, i); + vad_full(fi) = vad(i); + energy_full(fi) = energy(i); + noise_energy_full(fi) = noise_energy(i); + has_data(fi) = true; + end + % Forward-fill gaps with last received values + for fi = 2:total_frames + if ~has_data(fi) + mel_full(:, fi) = mel_full(:, fi - 1); + energy_full(fi) = energy_full(fi - 1); + noise_energy_full(fi) = noise_energy_full(fi - 1); + end + end + mel = mel_full; + vad = vad_full; + energy = energy_full; + noise_energy = noise_energy_full; + frame_number = frame_number_full; end -t_mel = period_mel / num_channels / fs; -t = (0:num_frames -1) * t_mel; +t = (frame_number - first_frame) * hop; n = 1:num_mel; figure @@ -145,36 +144,20 @@ end -function [data, num_channels] = get_file(fn, num_channels, fmt) +function [data, num_channels] = get_file(fn, num_channels) [~, ~, ext] = fileparts(fn); -switch fmt - case 's16' - read_fmt = 'int16'; - case {'s24', 's32'} - read_fmt = 'int32'; - otherwise - error("Use 's16', 's24', or 's32' as format."); -end - switch lower(ext) case '.raw' fh = fopen(fn, 'r'); - data = fread(fh, read_fmt); + data = fread(fh, 'int32'); fclose(fh); case '.wav' tmp = audioread(fn, 'native'); t = whos('tmp'); - switch fmt - case 's16' - if ~strcmp(t.class, 'int16') - error('Expected 16-bit wav for s16 format'); - end - case {'s24', 's32'} - if ~strcmp(t.class, 'int32') - error('Expected 32-bit wav for %s format', fmt); - end + if ~strcmp(t.class, 'int32') + error('Expected 32-bit wav for int32 MFCC output format'); end s = size(tmp); num_channels = s(2); diff --git a/src/audio/mfcc/tune/setup_mfcc.m b/src/audio/mfcc/tune/setup_mfcc.m index 3cda3221a4fc..dbf69587a74f 100644 --- a/src/audio/mfcc/tune/setup_mfcc.m +++ b/src/audio/mfcc/tune/setup_mfcc.m @@ -25,6 +25,32 @@ function setup_mfcc() setup.tplg_fn = 'mel80.conf'; export_mfcc_setup(gen_cfg, setup); + % Blob for mel spectrogram with compress PCM output + setup = get_mel_spectrogram_config(); + setup.compress_output = true; + setup.tplg_fn = 'mel80_compress.conf'; + export_mfcc_setup(gen_cfg, setup); + + % Blob for mel spectrogram with compress PCM output and DTX + setup = get_mel_spectrogram_config(); + setup.compress_output = true; + setup.enable_dtx = true; + setup.dtx_trailing_silence_hops = 20; + setup.dtx_silence_hops_interval = 500; + setup.tplg_fn = 'mel80_compress_dtx.conf'; + export_mfcc_setup(gen_cfg, setup); + + % Default MFCC (cepstral) with compress PCM output + setup = get_mfcc_default_config(); + setup.compress_output = true; + setup.enable_vad = true; + setup.enable_dtx = true; + setup.dtx_trailing_silence_hops = 20; + setup.dtx_silence_hops_interval = 500; + setup.update_controls = true; + setup.tplg_fn = 'ceps13_compress_dtx.conf'; + export_mfcc_setup(gen_cfg, setup); + end function cfg = get_mfcc_default_config() @@ -64,7 +90,10 @@ function setup_mfcc() cfg.dynamic_mmax = false; % same cfg.enable_vad = false; cfg.enable_dtx = false; + cfg.dtx_trailing_silence_hops = 0; + cfg.dtx_silence_hops_interval = 0; cfg.update_controls = false; + cfg.compress_output = false; end function cfg = get_mel_spectrogram_config() @@ -104,7 +133,10 @@ function setup_mfcc() cfg.dynamic_mmax = true; cfg.enable_vad = true; cfg.enable_dtx = false; + cfg.dtx_trailing_silence_hops = 0; + cfg.dtx_silence_hops_interval = 0; cfg.update_controls = true; + cfg.compress_output = false; end function export_mfcc_setup(gen_cfg, cfg) @@ -139,8 +171,10 @@ function export_mfcc_setup(gen_cfg, cfg) v = q_convert(cfg.mmax_init, 7); [b8, j] = add_w16b(v, b8, j); v = q_convert(cfg.mmax_coef, 15); [b8, j] = add_w16b(v, b8, j); +v = cfg.dtx_trailing_silence_hops; [b8, j] = add_w16b(v, b8, j); % DTX trailing silence hops +v = cfg.dtx_silence_hops_interval; [b8, j] = add_w16b(v, b8, j); % DTX silence frame interval % Reserved -for i = 1:6 +for i = 1:5 [b8, j] = add_w32b(0, b8, j); end @@ -181,8 +215,9 @@ function export_mfcc_setup(gen_cfg, cfg) v = cfg.enable_vad; [b8, j] = add_w8b(v, b8, j); % bool v = cfg.enable_dtx; [b8, j] = add_w8b(v, b8, j); % bool v = cfg.update_controls; [b8, j] = add_w8b(v, b8, j); % bool -% reserved_bool[5] -for i = 1:5 +v = cfg.compress_output; [b8, j] = add_w8b(v, b8, j); % bool +% reserved_bool[4] +for i = 1:4 [b8, j] = add_w8b(0, b8, j); end diff --git a/src/audio/mfcc/tune/sof_ceps_spectrogram_compress.py b/src/audio/mfcc/tune/sof_ceps_spectrogram_compress.py new file mode 100755 index 000000000000..8cb8190c56b4 --- /dev/null +++ b/src/audio/mfcc/tune/sof_ceps_spectrogram_compress.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2026, Intel Corporation. + +"""Live scrolling cepstral coefficient viewer for SOF compress PCM capture. + +GTK 4 application that displays a live scrolling MFCC cepstral +spectrogram with VAD status from a SOF compress audio-features PCM. +Rendering goes through ``Gtk.Snapshot`` with ``Gdk.MemoryTexture`` for +the spectrogram and native GSK render nodes (``Gsk.Path`` strokes, +``append_color``, ``append_layout``) for the strip plots, so the +GL/Vulkan renderer handles compositing on the GPU. Vsync-aligned +redraws (``add_tick_callback``) and sub-pixel scrolling keep the 100 Hz +data stream looking smooth on a 60+ Hz display. + +Dependencies (Debian/Ubuntu): + sudo apt install python3-gi gir1.2-gtk-4.0 + pip install numpy + +Frame format: [magic(int32), frame_number(uint32), reserved(int32), + energy(int32), noise_energy(int32), vad_flag(int32), + ceps[0..N-1](int32)] + +Cepstral coefficients are in Q9.23 fixed-point format. + +Usage: + python sof_ceps_spectrogram_compress.py [--card 0] [--device 54] + python sof_ceps_spectrogram_compress.py --num-ceps 13 --width 300 +""" + +import argparse +import os +import queue +import signal +import struct +import subprocess +import sys +import threading +import time + +import numpy as np + +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Gdk', '4.0') +gi.require_version('Gsk', '4.0') +gi.require_version('Graphene', '1.0') +gi.require_version('Pango', '1.0') +from gi.repository import GLib, Gtk # noqa: E402 + +# Reuse the GPU-accelerated widgets from the mel viewer. +from sof_mel_spectrogram_compress import ( # noqa: E402 + MelSpectrogramArea as SpectrogramArea, + CurveStripArea, + GUI_REFRESH_MS, +) + +# SOF compress frame format constants (with DSP data header) +SOF_MAGIC_BYTES = struct.pack(' 3: + del buf[:-3] + return None, None, None, None, None + end = idx + frame_bytes + if end > len(buf): + del buf[:idx] + return None, None, None, None, None + + frame_number = struct.unpack_from('= 64: + break + + if self._last_frame_mono is None: + sub = 0.0 + else: + sub = (time.monotonic() - self._last_frame_mono) / self._frame_period + if sub < 0.0: + sub = 0.0 + elif sub > 1.0: + sub = 1.0 + self.ceps_area.set_sub_offset(sub) + self.vad_area.set_sub_offset(sub) + self.level_area.set_sub_offset(sub) + + if eof: + self._on_eof() + return False + return True + + def _on_tick_clock(self, _widget, _frame_clock): + return True if self._drain_and_redraw() else False + + def _on_timer(self): + return GLib.SOURCE_CONTINUE if self._drain_and_redraw() \ + else GLib.SOURCE_REMOVE + + def _ingest(self, frame_number, vad_flag, energy, noise, ceps_ints): + self.recv_frames += 1 + self._last_frame_mono = time.monotonic() + ceps = decode_ceps_frame(ceps_ints) + speech = vad_flag != 0 + + if speech != self.prev_speech: + t = frame_number * SOF_HOP_SEC + tag = "SPEECH" if speech else "SILENCE" + print(f" [{t:7.2f}s] {tag} (hop {frame_number})", flush=True) + self.prev_speech = speech + + # Scroll left and append new frame on the right. + self.ceps_buf[:-1] = self.ceps_buf[1:] + self.ceps_buf[-1] = ceps + self.vad_buf[:-1] = self.vad_buf[1:] + self.vad_buf[-1] = 1.0 if speech else 0.0 + self.energy_buf[:-1] = self.energy_buf[1:] + self.energy_buf[-1] = energy + self.noise_buf[:-1] = self.noise_buf[1:] + self.noise_buf[-1] = noise + + def _on_eof(self): + if self._closing: + return + if self.proc.poll() is None: + self.proc.terminate() + try: + rc = self.proc.wait(timeout=2) + except subprocess.TimeoutExpired: + self.proc.kill() + rc = self.proc.wait() + try: + stderr_out = self.proc.stderr.read().decode(errors='replace') + except Exception: + stderr_out = '' + print(f"\ncrecord exited with code {rc}") + if stderr_out: + print(f"stderr: {stderr_out}") + self.window.close() + + # --- shutdown ----------------------------------------------------- + def _on_close(self, *_args): + self._closing = True + if self._tick_id: + self.ceps_area.remove_tick_callback(self._tick_id) + self._tick_id = 0 + if self._timer_id: + GLib.source_remove(self._timer_id) + self._timer_id = 0 + if self.proc.poll() is None: + self.proc.terminate() + try: + self.proc.wait(timeout=3) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait() + print(f"\nCapture stopped. Received {self.recv_frames} frames.") + return False + + +def main(): + parser = argparse.ArgumentParser( + description="Live scrolling MFCC cepstral coefficient viewer " + "from SOF compress PCM capture (GTK 4 / GSK)") + parser.add_argument('--card', '-c', type=int, default=0, + help='ALSA card number (default: 0)') + parser.add_argument('--device', '-d', type=int, default=54, + help='ALSA compress device number (default: 54)') + parser.add_argument('--width', '-w', type=int, default=SPECTROGRAM_WIDTH, + help=f'Spectrogram width in frames (default: {SPECTROGRAM_WIDTH})') + parser.add_argument('--num-ceps', '-n', type=int, default=DEFAULT_NUM_CEPS, + help=f'Number of cepstral coefficients (default: {DEFAULT_NUM_CEPS})') + args = parser.parse_args() + + print("=== SOF MFCC Cepstral Coefficient Viewer (Compress PCM, GTK 4) ===\n") + + signal.signal(signal.SIGINT, signal.SIG_DFL) + + app = Gtk.Application(application_id='org.sof.ceps_spectrogram') + holder = {} + + def on_activate(application): + viewer = CepsViewer(application, args.card, args.device, + args.width, args.num_ceps) + viewer.window.present() + holder['viewer'] = viewer + + app.connect('activate', on_activate) + sys.exit(app.run(None)) + + +if __name__ == '__main__': + main() diff --git a/src/audio/mfcc/tune/sof_mel_spectrogram_compress.py b/src/audio/mfcc/tune/sof_mel_spectrogram_compress.py new file mode 100755 index 000000000000..e85e0494cf22 --- /dev/null +++ b/src/audio/mfcc/tune/sof_mel_spectrogram_compress.py @@ -0,0 +1,625 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2026, Intel Corporation. + +"""Live scrolling mel spectrogram viewer for SOF compress PCM capture. + +GTK 4 application that displays a live scrolling mel spectrogram with +VAD status from a SOF compress audio-features PCM. Rendering uses +``Gtk.Snapshot`` with a ``Gdk.MemoryTexture`` for the spectrogram so +that the per-frame upload is handed to the GSK GPU renderer; the small +VAD / level strips use Cairo snapshot nodes. + +Dependencies (Debian/Ubuntu): + sudo apt install python3-gi gir1.2-gtk-4.0 + pip install numpy + +Frame format: [magic(int32), frame_number(uint32), reserved(int32), + energy(int32), noise_energy(int32), vad_flag(int32), + mel[0..79](int32)] + +Usage: + python sof_mel_spectrogram_compress.py [--card 0] [--device 54] + python sof_mel_spectrogram_compress.py --width 400 +""" + +import argparse +import os +import queue +import signal +import struct +import subprocess +import sys +import threading +import time + +import numpy as np + +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Gdk', '4.0') +gi.require_version('Gsk', '4.0') +gi.require_version('Graphene', '1.0') +gi.require_version('Pango', '1.0') +from gi.repository import Gdk, GLib, Gsk, Gtk, Graphene, Pango # noqa: E402 + +# SOF compress mel frame format constants (with DSP data header) +SOF_MAGIC_BYTES = struct.pack(' 3: + del buf[:-3] + return None, None, None, None, None + end = idx + SOF_FRAME_BYTES + if end > len(buf): + del buf[:idx] + return None, None, None, None, None + + frame_number = struct.unpack_from(' 1.0: + sub = 1.0 + if sub != self._sub: + self._sub = sub + self.queue_draw() + + def _build_texture(self): + bins = self._mel_buf.shape[1] + frames = self._mel_buf.shape[0] + norm = (self._mel_buf - self._vmin) / (self._vmax - self._vmin) + np.clip(norm, 0.0, 1.0, out=norm) + idx = (norm * 255.0).astype(np.uint8) # (frames, bins) + # Extend on the right by holding the latest column constant so the + # sub-pixel translate has something to reveal at the right edge. + idx_ext = np.empty((frames + 1, bins), dtype=np.uint8) + idx_ext[:frames] = idx + idx_ext[frames] = idx[-1] + idx_img = idx_ext.T[::-1, :] # (bins, frames+1) + rgba = self._lut[idx_img] # (bins, frames+1, 4) + data = rgba.tobytes() + gbytes = GLib.Bytes.new(data) + return Gdk.MemoryTexture.new( + frames + 1, bins, + Gdk.MemoryFormat.R8G8B8A8, + gbytes, + (frames + 1) * 4, + ) + + def do_snapshot(self, snapshot): + w = self.get_width() + h = self.get_height() + if w <= 0 or h <= 0: + return + frames = self._mel_buf.shape[0] + col_w = w / frames + offset_x = -self._sub * col_w + ext_w = w + col_w + clip_rect = Graphene.Rect().init(0.0, 0.0, float(w), float(h)) + draw_rect = Graphene.Rect().init(offset_x, 0.0, float(ext_w), float(h)) + texture = self._build_texture() + snapshot.push_clip(clip_rect) + if hasattr(snapshot, 'append_scaled_texture'): + snapshot.append_scaled_texture( + texture, Gsk.ScalingFilter.LINEAR, draw_rect) + else: + snapshot.append_texture(texture, draw_rect) + snapshot.pop() + + +class CurveStripArea(Gtk.Widget): + """Strip plot rendered with GSK render nodes (GPU path). + + All drawing goes through native ``Gsk`` nodes so the GL/Vulkan + renderer handles compositing on the GPU: + + * background & grid -> ``snapshot.append_color()`` + * curves -> ``snapshot.append_stroke()`` with ``Gsk.Path`` + * tick / title text -> ``snapshot.append_layout()`` + + Requires GTK 4.14+ (for ``Gsk.PathBuilder`` and + ``Gtk.Snapshot.append_stroke``). + """ + + def __init__(self, get_data, *, ymin=None, ymax=None, autoscale=False, + lines=(), background=(0.10, 0.10, 0.12), + yticks=None, label=None, height_request=80): + super().__init__() + self._get_data = get_data + self._ymin = ymin + self._ymax = ymax + self._autoscale = autoscale + self._lines = lines + self._bg = self._mk_rgba((background[0], background[1], background[2], 1.0)) + self._grid_color = self._mk_rgba((0.4, 0.4, 0.45, 0.6)) + self._text_color = self._mk_rgba((0.85, 0.85, 0.85, 0.9)) + self._line_colors = [self._mk_rgba(rgba) for _, rgba in lines] + self._stroke = Gsk.Stroke.new(1.5) + self._stroke.set_line_cap(Gsk.LineCap.ROUND) + self._stroke.set_line_join(Gsk.LineJoin.ROUND) + self._yticks = yticks + self._label = label + self._tick_font = Pango.FontDescription.from_string("sans-serif 9") + self._title_font = Pango.FontDescription.from_string("sans-serif 10") + self._tick_layouts = None # built lazily once we have a Pango context + self._title_layout = None + self._sub = 0.0 + self.set_hexpand(True) + self.set_size_request(-1, height_request) + + def set_sub_offset(self, sub): + if sub < 0.0: + sub = 0.0 + elif sub > 1.0: + sub = 1.0 + if sub != self._sub: + self._sub = sub + self.queue_draw() + + @staticmethod + def _mk_rgba(t): + c = Gdk.RGBA() + c.red, c.green, c.blue, c.alpha = ( + float(t[0]), float(t[1]), float(t[2]), float(t[3])) + return c + + def _ensure_layouts(self): + if self._tick_layouts is not None: + return + pctx = self.get_pango_context() + self._tick_layouts = [] + if self._yticks: + for yv, txt in self._yticks: + layout = Pango.Layout(pctx) + layout.set_font_description(self._tick_font) + layout.set_text(str(txt), -1) + self._tick_layouts.append((yv, layout)) + if self._label: + self._title_layout = Pango.Layout(pctx) + self._title_layout.set_font_description(self._title_font) + self._title_layout.set_text(self._label, -1) + + def _yrange(self, arrays): + if not self._autoscale: + return self._ymin, self._ymax + vmin = min(float(a.min()) for a in arrays if a.size) + vmax = max(float(a.max()) for a in arrays if a.size) + if vmax - vmin < 1e-6: + vmax = vmin + 1.0 + margin = 0.05 * (vmax - vmin) + return vmin - margin, vmax + margin + + @staticmethod + def _build_path(arr, x_scale, h, ymin, yspan): + """Build a stroke path covering [0, n*x_scale] (one extra sample held).""" + builder = Gsk.PathBuilder.new() + n = arr.shape[0] + ys = h - (arr - ymin) / yspan * h + builder.move_to(0.0, float(ys[0])) + for i in range(1, n): + builder.line_to(float(i * x_scale), float(ys[i])) + # Hold last value for one extra column so the slide-in area is filled. + builder.line_to(float(n * x_scale), float(ys[-1])) + return builder.to_path() + + def do_snapshot(self, snapshot): + w = self.get_width() + h = self.get_height() + if w <= 0 or h <= 0: + return + + bg_rect = Graphene.Rect().init(0.0, 0.0, float(w), float(h)) + snapshot.append_color(self._bg, bg_rect) + + data = self._get_data() + arrays = [data[k] for k, _ in self._lines] + if not arrays or arrays[0].size == 0: + return + n = arrays[0].size + ymin, ymax = self._yrange(arrays) + yspan = (ymax - ymin) or 1.0 + x_scale = w / max(n - 1, 1) + offset_x = -self._sub * x_scale + + # Y grid lines as 1px coloured rectangles (GSK color nodes). + if self._yticks: + for yv, _ in self._yticks: + py = h - (yv - ymin) / yspan * h + grid_rect = Graphene.Rect().init(0.0, py - 0.5, float(w), 1.0) + snapshot.append_color(self._grid_color, grid_rect) + + # Curves -> GSK stroke nodes (GPU rasterised), translated for smooth scroll. + clip_rect = Graphene.Rect().init(0.0, 0.0, float(w), float(h)) + snapshot.push_clip(clip_rect) + snapshot.save() + snapshot.translate(Graphene.Point().init(offset_x, 0.0)) + for (key, _), color in zip(self._lines, self._line_colors): + path = self._build_path(data[key], x_scale, h, ymin, yspan) + snapshot.append_stroke(path, self._stroke, color) + snapshot.restore() + snapshot.pop() + + # Text -> Pango layouts via append_layout (no scroll offset). + self._ensure_layouts() + if self._tick_layouts: + for yv, layout in self._tick_layouts: + py = h - (yv - ymin) / yspan * h + _, ext = layout.get_pixel_extents() + ty = max(0.0, min(float(h) - ext.height, py - ext.height - 1.0)) + snapshot.save() + snapshot.translate(Graphene.Point().init(4.0, ty)) + snapshot.append_layout(layout, self._text_color) + snapshot.restore() + + if self._title_layout is not None: + _, ext = self._title_layout.get_pixel_extents() + snapshot.save() + snapshot.translate( + Graphene.Point().init(float(w) - ext.width - 6.0, 2.0)) + snapshot.append_layout(self._title_layout, self._text_color) + snapshot.restore() + + +class MelViewer: + """Top-level controller: capture process + GTK window.""" + + def __init__(self, app, card, device, width): + self.app = app + self.width_frames = width + + # Ring buffer stored as (frames, mel_bins) for cheap shifts along axis 0. + self.mel_buf = np.zeros((width, SOF_NUM_MEL), dtype=np.float32) + self.vad_buf = np.zeros(width, dtype=np.float32) + self.energy_buf = np.zeros(width, dtype=np.float32) + self.noise_buf = np.zeros(width, dtype=np.float32) + + self.recv_frames = 0 + self.prev_speech = None + self._closing = False + self._last_frame_mono = None # timestamp of most recent ingest + self._frame_period = SOF_HOP_SEC # nominal 10 ms per hop + + self._build_ui() + + # Start capture + self.proc = start_crecord(card, device) + self.frame_q = queue.Queue() + self.reader = threading.Thread(target=self._reader_thread, daemon=True) + self.reader.start() + + # Vsync-aligned redraws via the frame clock — fires once per monitor + # refresh while the widget is mapped. Falls back to a periodic timer + # if add_tick_callback isn't available for some reason. + self._tick_id = 0 + self._timer_id = 0 + if hasattr(self.mel_area, 'add_tick_callback'): + self._tick_id = self.mel_area.add_tick_callback(self._on_tick_clock) + else: + self._timer_id = GLib.timeout_add(GUI_REFRESH_MS, self._on_timer) + + print(f"Spectrogram width: {width} frames ({width * SOF_HOP_SEC:.1f}s)") + if self._tick_id: + print("GUI refresh: vsync-aligned via frame clock, " + "sub-pixel scroll enabled") + else: + print(f"GUI refresh: fallback timer at {GUI_REFRESH_MS} ms " + f"({1000.0 / GUI_REFRESH_MS:.0f} FPS)") + print() + + def _build_ui(self): + self.window = Gtk.ApplicationWindow(application=self.app) + self.window.set_title("SOF Mel Spectrogram (compress PCM) — DSP VAD") + self.window.set_default_size(1100, 600) + self.window.connect('close-request', self._on_close) + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) + box.set_margin_start(4) + box.set_margin_end(4) + box.set_margin_top(4) + box.set_margin_bottom(4) + self.window.set_child(box) + + self.mel_area = MelSpectrogramArea(self.mel_buf, MEL_VMIN, MEL_VMAX) + self.vad_area = CurveStripArea( + lambda: {'vad': self.vad_buf}, + ymin=-0.1, ymax=1.1, + lines=[('vad', (0.2, 0.9, 0.2, 1.0))], + yticks=[(0, 'Silent'), (1, 'Speech')], + label='VAD', + height_request=70, + ) + self.level_area = CurveStripArea( + lambda: {'energy': self.energy_buf, 'noise': self.noise_buf}, + autoscale=True, + lines=[ + ('energy', (1.0, 0.9, 0.2, 1.0)), + ('noise', (0.2, 0.9, 1.0, 1.0)), + ], + label='Level (Y=speech, C=noise)', + height_request=110, + ) + + # Vertical stretch: spectrogram dominates. + self.mel_area.set_vexpand(True) + box.append(self.mel_area) + box.append(self.vad_area) + box.append(self.level_area) + + # --- I/O thread --------------------------------------------------- + def _reader_thread(self): + buf = bytearray() + raw_fd = self.proc.stdout.fileno() + try: + while True: + data = os.read(raw_fd, SOF_FRAME_BYTES * 4) + if not data: + break + buf.extend(data) + while True: + fn, vf, en, no, mi = parse_frame(buf) + if mi is None: + break + self.frame_q.put((fn, vf, en, no, mi)) + except OSError: + pass + self.frame_q.put(None) + + # --- GUI tick ----------------------------------------------------- + def _drain_and_redraw(self): + """Drain queued frames, update sub-pixel offset, queue redraws. + + Returns True while running, False when EOF. + """ + drained = 0 + eof = False + while True: + try: + item = self.frame_q.get_nowait() + except queue.Empty: + break + if item is None: + eof = True + break + fn, vf, en, no, mi = item + self._ingest(fn, vf, en, no, mi) + drained += 1 + if drained >= 64: + break + + # Sub-pixel scroll position: time since last ingest as a fraction of + # the nominal frame period, clamped to [0, 1] so we never overshoot. + if self._last_frame_mono is None: + sub = 0.0 + else: + sub = (time.monotonic() - self._last_frame_mono) / self._frame_period + if sub < 0.0: + sub = 0.0 + elif sub > 1.0: + sub = 1.0 + self.mel_area.set_sub_offset(sub) + self.vad_area.set_sub_offset(sub) + self.level_area.set_sub_offset(sub) + + # If no new data arrived, still queue a redraw so the sub-pixel + # translate advances each vsync. set_sub_offset only does it on + # a change; force it when sub is at 1.0 (no-op) and frames were 0. + if drained == 0 and sub >= 1.0: + # We've slid the maximum distance; nothing to redraw. + pass + + if eof: + self._on_eof() + return False + return True + + def _on_tick_clock(self, _widget, _frame_clock): + return True if self._drain_and_redraw() else False + + def _on_timer(self): + return GLib.SOURCE_CONTINUE if self._drain_and_redraw() \ + else GLib.SOURCE_REMOVE + + def _ingest(self, frame_number, vad_flag, energy, noise, mel_ints): + self.recv_frames += 1 + self._last_frame_mono = time.monotonic() + mel = decode_mel_frame(mel_ints) + speech = vad_flag != 0 + + if speech != self.prev_speech: + t = frame_number * SOF_HOP_SEC + tag = "SPEECH" if speech else "SILENCE" + print(f" [{t:7.2f}s] {tag} (hop {frame_number})", flush=True) + self.prev_speech = speech + + # Scroll left and append new frame on the right. + self.mel_buf[:-1] = self.mel_buf[1:] + self.mel_buf[-1] = mel + self.vad_buf[:-1] = self.vad_buf[1:] + self.vad_buf[-1] = 1.0 if speech else 0.0 + self.energy_buf[:-1] = self.energy_buf[1:] + self.energy_buf[-1] = energy + self.noise_buf[:-1] = self.noise_buf[1:] + self.noise_buf[-1] = noise + + def _on_eof(self): + if self._closing: + return + if self.proc.poll() is None: + self.proc.terminate() + try: + rc = self.proc.wait(timeout=2) + except subprocess.TimeoutExpired: + self.proc.kill() + rc = self.proc.wait() + try: + stderr_out = self.proc.stderr.read().decode(errors='replace') + except Exception: + stderr_out = '' + print(f"\ncrecord exited with code {rc}") + if stderr_out: + print(f"stderr: {stderr_out}") + self.window.close() + + # --- shutdown ----------------------------------------------------- + def _on_close(self, *_args): + self._closing = True + if self._tick_id: + self.mel_area.remove_tick_callback(self._tick_id) + self._tick_id = 0 + if self._timer_id: + GLib.source_remove(self._timer_id) + self._timer_id = 0 + if self.proc.poll() is None: + self.proc.terminate() + try: + self.proc.wait(timeout=3) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait() + print(f"\nCapture stopped. Received {self.recv_frames} frames.") + return False # allow the window to close + + +def main(): + parser = argparse.ArgumentParser( + description="Live scrolling mel spectrogram viewer " + "from SOF compress PCM capture (GTK 4 / GSK)") + parser.add_argument('--card', '-c', type=int, default=0, + help='ALSA card number (default: 0)') + parser.add_argument('--device', '-d', type=int, default=54, + help='ALSA compress device number (default: 54)') + parser.add_argument('--width', '-w', type=int, default=SPECTROGRAM_WIDTH, + help=f'Spectrogram width in frames (default: {SPECTROGRAM_WIDTH})') + args = parser.parse_args() + + print("=== SOF Mel Spectrogram Viewer (Compress PCM, GTK 4) ===\n") + + # Let Python receive Ctrl-C while the GLib loop is running. + signal.signal(signal.SIGINT, signal.SIG_DFL) + + app = Gtk.Application(application_id='org.sof.mel_spectrogram') + holder = {} + + def on_activate(application): + viewer = MelViewer(application, args.card, args.device, args.width) + viewer.window.present() + holder['viewer'] = viewer + + app.connect('activate', on_activate) + sys.exit(app.run(None)) + + +if __name__ == '__main__': + main() diff --git a/src/audio/mfcc/tune/sof_mel_to_text_live_compress.py b/src/audio/mfcc/tune/sof_mel_to_text_live_compress.py new file mode 100755 index 000000000000..6f0032d241fa --- /dev/null +++ b/src/audio/mfcc/tune/sof_mel_to_text_live_compress.py @@ -0,0 +1,490 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2026, Intel Corporation. + +"""Live SOF mel capture from compress PCM with DTX-aware Whisper transcription. + +Captures mel frames from ALSA compress device (crecord) with embedded VAD flag. +Frame format: [magic(int32), frame_number(uint32), reserved(int32), + energy(int32), noise_energy(int32), vad_flag(int32), + mel[0..79](int32)] + +With DTX enabled, the DSP sends a configurable number of trailing silence +frames (e.g. 20 = 200ms) after each speech-to-silence transition, then +suppresses further silence. This gives the host enough silence to detect +end-of-speech via a wall-clock patience timer. + +Usage: + python sof_mel_to_text_live_compress.py [--card 0] [--device 54] [--model whisper-medium-int4-ov] +""" + +import argparse +import os +import queue +import struct +import subprocess +import threading +import time +import numpy as np +import openvino as ov +import huggingface_hub as hf_hub +from pathlib import Path + +# SOF compress mel frame format constants (with DSP data header) +SOF_MAGIC_BYTES = struct.pack(' 3: + del buf[:-3] + return None, None, None + end = idx + SOF_FRAME_BYTES + if end > len(buf): + del buf[:idx] + return None, None, None + + # Parse header fields + frame_number = struct.unpack_from('> \"{text}\"\n", flush=True) + else: + print(" [Whisper] empty result", flush=True) + + def flush_speech(t_now): + """Flush speech buffer to Whisper.""" + nonlocal speech_buffer, silence_time, pending_queue, pending_t + if not speech_buffer: + silence_time = None + return + if not try_transcribe(transcriber, speech_buffer, t_now, + on_transcription): + pending_queue = list(speech_buffer) + pending_t = t_now + speech_buffer.clear() + silence_time = None + + try: + while True: + # Calculate queue timeout based on patience timer + get_timeout = 0.1 # default polling interval + if silence_time is not None: + remaining = patience - (time.monotonic() - silence_time) + get_timeout = max(remaining, 0.01) + + try: + item = frame_q.get(timeout=get_timeout) + except queue.Empty: + # Patience expired — flush speech to Whisper + if silence_time is not None: + elapsed = time.monotonic() - silence_time + if elapsed >= patience: + t = last_hop * SOF_HOP_SEC + flush_speech(t) + + # Drain pending queue when Whisper becomes free + if pending_queue is not None and not transcriber.is_busy(): + print(f" [{pending_t:7.2f}s] Whisper free, sending " + f"{len(pending_queue)} queued frames", flush=True) + transcriber.transcribe_async(pending_queue, on_transcription) + pending_queue = None + continue + + if item is None: + # Reader thread ended (crecord exited) + stderr_out = proc.stderr.read().decode(errors='replace') + rc = proc.wait() + print(f"\ncrecord exited with code {rc}") + if stderr_out: + print(f"stderr: {stderr_out}") + break + + frame_number, vad_flag, frame_ints = item + recv_frames += 1 + last_hop = frame_number + mel = decode_mel_frame(frame_ints) + speech = vad_flag != 0 + t = frame_number * SOF_HOP_SEC + + # Print VAD transitions + if speech != prev_speech: + tag = "SPEECH" if speech else "SILENCE" + print(f" [{t:7.2f}s] {tag} (hop {frame_number}, " + f"received {recv_frames})", flush=True) + prev_speech = speech + + # Drain pending queue when Whisper becomes free + if pending_queue is not None and not transcriber.is_busy(): + print(f" [{pending_t:7.2f}s] Whisper free, sending " + f"{len(pending_queue)} queued frames", flush=True) + transcriber.transcribe_async(pending_queue, on_transcription) + pending_queue = None + + # --- Speech buffering logic --- + if speech: + if len(speech_buffer) >= MAX_SPEECH_FRAMES: + n = len(speech_buffer) + duration = n * SOF_HOP_SEC + print(f" [{t:7.2f}s] Buffer full ({duration:.1f}s), " + f"forcing transcription", flush=True) + flush_speech(t) + + speech_buffer.append(mel.copy()) + silence_time = None # speech resumed, cancel patience timer + + else: + # VAD=0: start patience timer if we have buffered speech. + # Don't refresh if already running so trailing silence + # frames don't extend the wait. + if speech_buffer and silence_time is None: + silence_time = time.monotonic() + + except (KeyboardInterrupt, BrokenPipeError): + pass + finally: + # Flush remaining speech + if speech_buffer: + t = last_hop * SOF_HOP_SEC + flush_speech(t) + if proc.poll() is None: + proc.terminate() + try: + proc.wait(timeout=3) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait() + print(f"\n\nCapture stopped. Received {recv_frames} frames.") + + +def main(): + parser = argparse.ArgumentParser( + description="Live SOF mel capture from compress PCM with DTX-aware " + "Whisper transcription") + parser.add_argument('--card', '-c', type=int, default=0, + help='ALSA card number (default: 0)') + parser.add_argument('--device', '-d', type=int, default=54, + help='ALSA compress device number (default: 54)') + parser.add_argument('--model', '-m', default='whisper-medium-int4-ov', + help='Path to Whisper OpenVINO model directory') + parser.add_argument('--encoder-device', default='NPU', + help='OpenVINO device for encoder (default: NPU)') + parser.add_argument('--decoder-device', default='CPU', + help='OpenVINO device for decoder (default: CPU)') + parser.add_argument('--patience', type=float, default=SILENCE_PATIENCE_S, + help=f'Seconds of silence patience before triggering ' + f'transcription (default: {SILENCE_PATIENCE_S})') + args = parser.parse_args() + + model_id = "OpenVINO/" + os.path.basename(args.model) + if not os.path.isdir(args.model): + print(f"Downloading model {model_id} ...") + hf_hub.snapshot_download(model_id, local_dir=args.model) + + print("=== Live SOF Mel → Whisper Transcription (Compress PCM, DTX) ===\n") + run_capture(args.card, args.device, args.model, args.encoder_device, + args.decoder_device, patience=args.patience) + + +if __name__ == '__main__': + main() diff --git a/src/audio/mfcc/tune/sof_mel_to_text_live_dsp_vad.py b/src/audio/mfcc/tune/sof_mel_to_text_live_dsp_vad.py old mode 100644 new mode 100755 index 5c267a57ca79..90941b42822a --- a/src/audio/mfcc/tune/sof_mel_to_text_live_dsp_vad.py +++ b/src/audio/mfcc/tune/sof_mel_to_text_live_dsp_vad.py @@ -1,3 +1,9 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2026, Intel Corporation. + """Live SOF mel capture with DSP VAD-triggered Whisper transcription. Captures mel frames from ALSA with embedded VAD flag from the DSP. @@ -8,7 +14,6 @@ Usage: python sof_mel_to_text_live_dsp_vad.py [--device hw:0,47] [--model whisper-medium-int4-ov] - python sof_mel_to_text_live_dsp_vad.py --plot # with live spectrogram """ import argparse @@ -22,10 +27,6 @@ import huggingface_hub as hf_hub from pathlib import Path -# Graphics imports deferred until --plot is used -matplotlib = None -plt = None - # SOF mel_s32.raw format constants (with DSP data header) SOF_MAGIC_BYTES = struct.pack('= MAX_SPEECH_FRAMES: @@ -429,11 +359,6 @@ def on_transcription(text): except subprocess.TimeoutExpired: proc.kill() proc.wait() - if plotter is not None: - try: - plt.close(plotter.fig) - except Exception: - pass print("\n\nCapture stopped.") @@ -450,8 +375,6 @@ def main(): help='OpenVINO device for encoder (default: NPU)') parser.add_argument('--decoder-device', default='CPU', help='OpenVINO device for decoder (default: CPU)') - parser.add_argument('--plot', action='store_true', - help='Show live scrolling mel spectrogram and VAD plot') args = parser.parse_args() model_id = "OpenVINO/" + os.path.basename(args.model) if not os.path.isdir(args.model): @@ -460,7 +383,7 @@ def main(): print("=== Live SOF Mel → Whisper Transcription (DSP VAD) ===\n") run_capture(args.device, args.rate, args.model, args.encoder_device, - args.decoder_device, enable_plot=args.plot) + args.decoder_device) if __name__ == '__main__': diff --git a/tools/topology/topology2/include/components/mfcc/ceps13_compress_dtx.conf b/tools/topology/topology2/include/components/mfcc/ceps13_compress_dtx.conf new file mode 100644 index 000000000000..7056b9e7cb4b --- /dev/null +++ b/tools/topology/topology2/include/components/mfcc/ceps13_compress_dtx.conf @@ -0,0 +1,24 @@ +# Exported MFCC configuration 26-May-2026 +# cd src/audio/mfcc/tune; octave setup_mfcc.m +Object.Base.data."mfcc_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x74,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x14,0x00,0xf4,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0x3e,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0xc3,0x35,0x00,0x2c,0x00,0x00,0x00,0x00, + 0x90,0x01,0xa0,0x00,0x00,0x00,0x14,0x00, + 0x0d,0x00,0x17,0x00,0x00,0x00,0x00,0x64, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01, + 0x01,0x00,0x00,0x00,0x01,0x01,0x01,0x01, + 0x00,0x00,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/mfcc/default.conf b/tools/topology/topology2/include/components/mfcc/default.conf index 3bbd72696806..0ac19fa71d04 100644 --- a/tools/topology/topology2/include/components/mfcc/default.conf +++ b/tools/topology/topology2/include/components/mfcc/default.conf @@ -1,4 +1,4 @@ -# Exported MFCC configuration 19-May-2026 +# Exported MFCC configuration 26-May-2026 # cd src/audio/mfcc/tune; octave setup_mfcc.m Object.Base.data."mfcc_config" { bytes " diff --git a/tools/topology/topology2/include/components/mfcc/mel80.conf b/tools/topology/topology2/include/components/mfcc/mel80.conf index 480725c2d24f..b18baadd459b 100644 --- a/tools/topology/topology2/include/components/mfcc/mel80.conf +++ b/tools/topology/topology2/include/components/mfcc/mel80.conf @@ -1,4 +1,4 @@ -# Exported MFCC configuration 19-May-2026 +# Exported MFCC configuration 26-May-2026 # cd src/audio/mfcc/tune; octave setup_mfcc.m Object.Base.data."mfcc_config" { bytes " diff --git a/tools/topology/topology2/include/components/mfcc/mel80_compress.conf b/tools/topology/topology2/include/components/mfcc/mel80_compress.conf new file mode 100644 index 000000000000..f26f2af6980c --- /dev/null +++ b/tools/topology/topology2/include/components/mfcc/mel80_compress.conf @@ -0,0 +1,24 @@ +# Exported MFCC configuration 26-May-2026 +# cd src/audio/mfcc/tune; octave setup_mfcc.m +Object.Base.data."mfcc_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x74,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x74,0x00,0x00,0x00,0x00,0x02,0x00,0x04, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0x3e,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x90,0x01,0xa0,0x00,0x40,0x1f,0x00,0x00, + 0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x04, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x01,0x00,0x00,0x01,0x01,0x00,0x01,0x01, + 0x00,0x00,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/mfcc/mel80_compress_dtx.conf b/tools/topology/topology2/include/components/mfcc/mel80_compress_dtx.conf new file mode 100644 index 000000000000..d225811ca4d1 --- /dev/null +++ b/tools/topology/topology2/include/components/mfcc/mel80_compress_dtx.conf @@ -0,0 +1,24 @@ +# Exported MFCC configuration 26-May-2026 +# cd src/audio/mfcc/tune; octave setup_mfcc.m +Object.Base.data."mfcc_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x74,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x74,0x00,0x00,0x00,0x00,0x02,0x00,0x04, + 0x00,0x00,0x00,0x00,0x14,0x00,0xf4,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0x3e,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x90,0x01,0xa0,0x00,0x40,0x1f,0x00,0x00, + 0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x04, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x01,0x00,0x00,0x01,0x01,0x01,0x01,0x01, + 0x00,0x00,0x00,0x00" +} From 66f2b6526d15777a1a7bd03db69d6e44600b76bc Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 26 May 2026 15:44:45 +0300 Subject: [PATCH 155/303] tools: topology: add MFCC compress capture for jack and DMIC This patch adds build of two development topologies with discontinous audio features data capture with compress PCMs. Add sdw-jack-audio-feature-compress.conf (PCM 53, pipeline 132) and sdw-dmic-audio-feature-compress.conf (PCM 54, pipeline 133) for compress MFCC capture with DTX blobs. Fix buffer sizes: set MFCC obs and host-copier ibs/obs to 344 bytes (24-byte header + 80 x int32). Add mel and ceps compress topology targets for MTL and ARL. Rename normal MFCC topologies to *-mfcc-mel-normal for clarity. Signed-off-by: Seppo Ingalsuo --- tools/topology/topology2/cavs-sdw.conf | 8 +++ .../topology2/development/tplg-targets.cmake | 26 ++++++- .../include/common/common_definitions.conf | 2 + .../topology2/include/components/mfcc.conf | 1 - .../cavs/host-gateway-src-mfcc-capture.conf | 12 ++++ .../sdw-dmic-audio-feature-compress.conf | 71 +++++++++++++++++++ .../intel/sdw-dmic-audio-feature.conf | 3 + .../sdw-jack-audio-feature-compress.conf | 71 +++++++++++++++++++ .../intel/sdw-jack-audio-feature.conf | 3 + 9 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 tools/topology/topology2/platform/intel/sdw-dmic-audio-feature-compress.conf create mode 100644 tools/topology/topology2/platform/intel/sdw-jack-audio-feature-compress.conf diff --git a/tools/topology/topology2/cavs-sdw.conf b/tools/topology/topology2/cavs-sdw.conf index 3117bde753de..bcec2711a333 100644 --- a/tools/topology/topology2/cavs-sdw.conf +++ b/tools/topology/topology2/cavs-sdw.conf @@ -273,6 +273,14 @@ IncludeByKey.SDW_JACK_AUDIO_FEATURE_CAPTURE { "true" "platform/intel/sdw-jack-audio-feature.conf" } +IncludeByKey.SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE { + "true" "platform/intel/sdw-jack-audio-feature-compress.conf" +} + IncludeByKey.SDW_DMIC_AUDIO_FEATURE_CAPTURE { "true" "platform/intel/sdw-dmic-audio-feature.conf" } + +IncludeByKey.SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE { + "true" "platform/intel/sdw-dmic-audio-feature-compress.conf" +} diff --git a/tools/topology/topology2/development/tplg-targets.cmake b/tools/topology/topology2/development/tplg-targets.cmake index a906852d04f0..155176c16347 100644 --- a/tools/topology/topology2/development/tplg-targets.cmake +++ b/tools/topology/topology2/development/tplg-targets.cmake @@ -479,11 +479,33 @@ SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture- SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,COMPRESSED=true" # Soundwire topologies with MFCC audio features capture -"cavs-sdw\;sof-mtl-rt713-l0-rt1316-l12-mfcc\;PLATFORM=mtl,NUM_SDW_AMP_LINKS=2,\ +"cavs-sdw\;sof-mtl-rt713-l0-rt1316-l12-mfcc-mel-normal\;PLATFORM=mtl,NUM_SDW_AMP_LINKS=2,\ HDMI1_ID=4,HDMI2_ID=5,HDMI3_ID=6,SDW_JACK_AUDIO_FEATURE_CAPTURE=true" -"cavs-sdw\;sof-arl-cs42l43-l0-cs35l56-l23-mfcc\;PLATFORM=mtl,NUM_SDW_AMP_LINKS=2,SDW_DMIC=1,\ +"cavs-sdw\;sof-arl-cs42l43-l0-cs35l56-l23-mfcc-mel-normal\;PLATFORM=mtl,NUM_SDW_AMP_LINKS=2,SDW_DMIC=1,\ SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture-SmartMic,\ SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ SDW_JACK_AUDIO_FEATURE_CAPTURE=true,SDW_DMIC_AUDIO_FEATURE_CAPTURE=true" + +# Soundwire topologies with compress MFCC mel audio features capture +"cavs-sdw\;sof-mtl-rt713-l0-rt1316-l12-mfcc-mel-compr\;PLATFORM=mtl,NUM_SDW_AMP_LINKS=2,\ +HDMI1_ID=4,HDMI2_ID=5,HDMI3_ID=6,SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE=true,\ +MFCC_FRAME_BYTES=344,MFCC_BLOB=mel" + +# Soundwire topologies with compress MFCC cepstral audio features capture +"cavs-sdw\;sof-mtl-rt713-l0-rt1316-l12-mfcc-ceps-compr\;PLATFORM=mtl,NUM_SDW_AMP_LINKS=2,\ +HDMI1_ID=4,HDMI2_ID=5,HDMI3_ID=6,SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE=true,\ +MFCC_FRAME_BYTES=76,MFCC_BLOB=ceps" + +"cavs-sdw\;sof-arl-cs42l43-l0-cs35l56-l23-mfcc-mel-compr\;PLATFORM=mtl,NUM_SDW_AMP_LINKS=2,SDW_DMIC=1,\ +SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture-SmartMic,\ +SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ +SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE=true,SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE=true,\ +MFCC_FRAME_BYTES=344,MFCC_BLOB=mel" + +"cavs-sdw\;sof-arl-cs42l43-l0-cs35l56-l23-mfcc-ceps-compr\;PLATFORM=mtl,NUM_SDW_AMP_LINKS=2,SDW_DMIC=1,\ +SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture-SmartMic,\ +SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ +SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE=true,SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE=true,\ +MFCC_FRAME_BYTES=76,MFCC_BLOB=ceps" ) diff --git a/tools/topology/topology2/include/common/common_definitions.conf b/tools/topology/topology2/include/common/common_definitions.conf index 87c69dd41e41..06f0f425c5e2 100644 --- a/tools/topology/topology2/include/common/common_definitions.conf +++ b/tools/topology/topology2/include/common/common_definitions.conf @@ -72,5 +72,7 @@ Define { SDW_JACK_ECHO_REF false # No echo reference for 3.5mm jack SDW_SPK_ECHO_REF false # No echo reference for speaker SDW_JACK_AUDIO_FEATURE_CAPTURE false # No audio features capture for jack + SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE false # No compress audio features capture for jack SDW_DMIC_AUDIO_FEATURE_CAPTURE false # No audio features capture for microphone + SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE false # No compress audio features capture for microphone } diff --git a/tools/topology/topology2/include/components/mfcc.conf b/tools/topology/topology2/include/components/mfcc.conf index 775f0d79b1f5..bf908e685048 100644 --- a/tools/topology/topology2/include/components/mfcc.conf +++ b/tools/topology/topology2/include/components/mfcc.conf @@ -47,7 +47,6 @@ Class.Widget."mfcc" { !immutable [ "uuid" - "type" ] !deprecated [ "preload_count" diff --git a/tools/topology/topology2/include/pipelines/cavs/host-gateway-src-mfcc-capture.conf b/tools/topology/topology2/include/pipelines/cavs/host-gateway-src-mfcc-capture.conf index 793f71b883ab..fe6249018ef1 100644 --- a/tools/topology/topology2/include/pipelines/cavs/host-gateway-src-mfcc-capture.conf +++ b/tools/topology/topology2/include/pipelines/cavs/host-gateway-src-mfcc-capture.conf @@ -22,6 +22,12 @@ +Define { + # Default MFCC output frame size (header + coefficients). + # Can be overridden by feature/platform includes or CMake variable overrides. + MFCC_FRAME_BYTES 344 +} + Class.Pipeline."host-gateway-src-mfcc-capture" { @@ -85,6 +91,9 @@ Class.Pipeline."host-gateway-src-mfcc-capture" { out_bit_depth 32 out_valid_bit_depth 32 out_rate 16000 + # Compress output frame: header + coefficients. + # Size set by MFCC_FRAME_BYTES Define. + obs $MFCC_FRAME_BYTES } ] } @@ -101,6 +110,8 @@ Class.Pipeline."host-gateway-src-mfcc-capture" { in_bit_depth 32 in_valid_bit_depth 32 in_rate 16000 + # Match MFCC compress output frame size + ibs $MFCC_FRAME_BYTES } ] Object.Base.output_audio_format [ @@ -108,6 +119,7 @@ Class.Pipeline."host-gateway-src-mfcc-capture" { out_bit_depth 32 out_valid_bit_depth 32 out_rate 16000 + obs $MFCC_FRAME_BYTES } ] } diff --git a/tools/topology/topology2/platform/intel/sdw-dmic-audio-feature-compress.conf b/tools/topology/topology2/platform/intel/sdw-dmic-audio-feature-compress.conf new file mode 100644 index 000000000000..9e307043830b --- /dev/null +++ b/tools/topology/topology2/platform/intel/sdw-dmic-audio-feature-compress.conf @@ -0,0 +1,71 @@ +Define { + SDW_DMIC_MODULE_COPIER_ID 41 + SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME "Microphone Compress Audio Features" + SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PCM_ID 54 + SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_STREAM_NAME "Microphone Compress Audio Features Stream" + SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PIPELINE_ID 133 + # MFCC compress output frame size in bytes: + # Mel-only (80 bins): 24 + 80*4 = 344 + # Cepstral (13 ceps): 24 + 13*4 = 76 + MFCC_FRAME_BYTES 344 + # MFCC config blob: mel or ceps + MFCC_BLOB mel +} + +Object.Pipeline.host-gateway-src-mfcc-capture [ + { + index $SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PIPELINE_ID + + Object.Widget.host-copier.1 { + stream_name "$SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_STREAM_NAME" + pcm_id $SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PCM_ID + } + + Object.Widget.mfcc.1 { + type "encoder" + Object.Control { + bytes."1" { + name "$SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME MFCC bytes" + IncludeByKey.MFCC_BLOB { + "mel" "include/components/mfcc/mel80_compress_dtx.conf" + "ceps" "include/components/mfcc/ceps13_compress_dtx.conf" + } + } + mixer."1" { + name "$SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME MFCC VAD" + } + } + } + } +] +Object.Base.route [ + { + source "module-copier.$SDW_DMIC_MODULE_COPIER_ID.0" + sink "src.$SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PIPELINE_ID.1" + } + { + source "mfcc.$SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PIPELINE_ID.1" + sink "host-copier.$SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PCM_ID.capture" + } +] + +Object.PCM.pcm [ + { + name "$SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME" + id $SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PCM_ID + direction "capture" + compress "true" + + Object.Base.fe_dai.1 { + name "$SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME" + } + + Object.PCM.pcm_caps.1 { + name "$SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE_STREAM_NAME" + formats 'S32_LE' + rates '16000' + channels_min 2 + channels_max 2 + } + } +] diff --git a/tools/topology/topology2/platform/intel/sdw-dmic-audio-feature.conf b/tools/topology/topology2/platform/intel/sdw-dmic-audio-feature.conf index 623574db1784..7d39c11772c1 100644 --- a/tools/topology/topology2/platform/intel/sdw-dmic-audio-feature.conf +++ b/tools/topology/topology2/platform/intel/sdw-dmic-audio-feature.conf @@ -4,6 +4,9 @@ Define { SDW_DMIC_AUDIO_FEATURE_CAPTURE_PCM_ID 48 SDW_DMIC_AUDIO_FEATURE_CAPTURE_STREAM_NAME "Microphone Audio Features Stream" SDW_DMIC_AUDIO_FEATURE_CAPTURE_PIPELINE_ID 131 + # MFCC output frame size in bytes (24-byte header + coefficients): + # Mel-only (80 bins): 24 + 80*4 = 344 + MFCC_FRAME_BYTES 344 } Object.Pipeline.host-gateway-src-mfcc-capture [ diff --git a/tools/topology/topology2/platform/intel/sdw-jack-audio-feature-compress.conf b/tools/topology/topology2/platform/intel/sdw-jack-audio-feature-compress.conf new file mode 100644 index 000000000000..286af8be0323 --- /dev/null +++ b/tools/topology/topology2/platform/intel/sdw-jack-audio-feature-compress.conf @@ -0,0 +1,71 @@ +Define { + SDW_JACK_MODULE_COPIER_ID 11 + SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME "Jack In Compress Audio Features" + SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PCM_ID 53 + SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_STREAM_NAME "Jack In Compress Audio Features Stream" + SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PIPELINE_ID 132 + # MFCC compress output frame size in bytes: + # Mel-only (80 bins): 24 + 80*4 = 344 + # Cepstral (13 ceps): 24 + 13*4 = 76 + MFCC_FRAME_BYTES 344 + # MFCC config blob: mel or ceps + MFCC_BLOB mel +} + +Object.Pipeline.host-gateway-src-mfcc-capture [ + { + index $SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PIPELINE_ID + + Object.Widget.host-copier.1 { + stream_name "$SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_STREAM_NAME" + pcm_id $SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PCM_ID + } + + Object.Widget.mfcc.1 { + type "encoder" + Object.Control { + bytes."1" { + name "$SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME MFCC bytes" + IncludeByKey.MFCC_BLOB { + "mel" "include/components/mfcc/mel80_compress_dtx.conf" + "ceps" "include/components/mfcc/ceps13_compress_dtx.conf" + } + } + mixer."1" { + name "$SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME MFCC VAD" + } + } + } + } +] +Object.Base.route [ + { + source "module-copier.$SDW_JACK_MODULE_COPIER_ID.0" + sink "src.$SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PIPELINE_ID.1" + } + { + source "mfcc.$SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PIPELINE_ID.1" + sink "host-copier.$SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PCM_ID.capture" + } +] + +Object.PCM.pcm [ + { + name "$SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME" + id $SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PCM_ID + direction "capture" + compress "true" + + Object.Base.fe_dai.1 { + name "$SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_PCM_NAME" + } + + Object.PCM.pcm_caps.1 { + name "$SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE_STREAM_NAME" + formats 'S32_LE' + rates '16000' + channels_min $SDW_JACK_CAPTURE_CH + channels_max $SDW_JACK_CAPTURE_CH + } + } +] diff --git a/tools/topology/topology2/platform/intel/sdw-jack-audio-feature.conf b/tools/topology/topology2/platform/intel/sdw-jack-audio-feature.conf index e9652f49c17f..a0a44eae4d87 100644 --- a/tools/topology/topology2/platform/intel/sdw-jack-audio-feature.conf +++ b/tools/topology/topology2/platform/intel/sdw-jack-audio-feature.conf @@ -4,6 +4,9 @@ Define { SDW_JACK_AUDIO_FEATURE_CAPTURE_PCM_ID 47 SDW_JACK_AUDIO_FEATURE_CAPTURE_STREAM_NAME "Jack In Audio Features Stream" SDW_JACK_AUDIO_FEATURE_CAPTURE_PIPELINE_ID 130 + # MFCC output frame size in bytes (24-byte header + coefficients): + # Mel-only (80 bins): 24 + 80*4 = 344 + MFCC_FRAME_BYTES 344 } Object.Pipeline.host-gateway-src-mfcc-capture [ From 9c1d229b2c549fc8570cfe8d58e930dddb70e5e2 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Thu, 11 Jun 2026 17:32:20 +0300 Subject: [PATCH 156/303] audio: eq_iir: Improve robustness for invalid configuration Harden the EQ IIR setup path against malformed IPC configuration blobs. The blob length returned by comp_get_data_blob() is now stored and checked against the expected range every time a new blob is taken, and the blob's self-declared size is cross-checked against it before use. The per-response walk that previously trusted num_sections from the blob now bounds the header and biquad data against the blob, so a bad length can no longer push the lookup pointer past the allocation. The df1 and df2t delay-size helpers also gained a range check on num_sections_in_series, which strides the delay line and was previously unchecked. Signed-off-by: Seppo Ingalsuo --- src/audio/eq_iir/eq_iir.c | 21 ++++++-- src/audio/eq_iir/eq_iir.h | 1 + src/audio/eq_iir/eq_iir_generic.c | 82 ++++++++++++++++++++++++++++--- src/math/iir_df1.c | 8 ++- src/math/iir_df2t.c | 8 ++- 5 files changed, 105 insertions(+), 15 deletions(-) diff --git a/src/audio/eq_iir/eq_iir.c b/src/audio/eq_iir/eq_iir.c index 016c2c8caf82..2c90855ab061 100644 --- a/src/audio/eq_iir/eq_iir.c +++ b/src/audio/eq_iir/eq_iir.c @@ -107,6 +107,16 @@ static int eq_iir_get_config(struct processing_module *mod, return comp_data_blob_get_cmd(cd->model_handler, cdata, fragment_size); } +static int eq_iir_check_blob_size(struct comp_dev *dev, size_t size) +{ + if (size < sizeof(struct sof_eq_iir_config) || size > SOF_EQ_IIR_MAX_SIZE) { + comp_err(dev, "invalid configuration blob, size %zu", size); + return -EINVAL; + } + + return 0; +} + static int eq_iir_process(struct processing_module *mod, struct input_stream_buffer *input_buffers, int num_input_buffers, struct output_stream_buffer *output_buffers, int num_output_buffers) @@ -119,7 +129,9 @@ static int eq_iir_process(struct processing_module *mod, /* Check for changed configuration */ if (comp_is_new_data_blob_available(cd->model_handler)) { - cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); + if (!cd->config || eq_iir_check_blob_size(mod->dev, cd->config_size) < 0) + return -EINVAL; ret = eq_iir_new_blob(mod, audio_stream_get_frm_fmt(source), audio_stream_get_frm_fmt(sink), audio_stream_get_channels(source)); @@ -158,7 +170,6 @@ static int eq_iir_prepare(struct processing_module *mod, struct comp_dev *dev = mod->dev; enum sof_ipc_frame source_format; enum sof_ipc_frame sink_format; - size_t data_size; int channels; int ret = 0; @@ -183,7 +194,7 @@ static int eq_iir_prepare(struct processing_module *mod, source_format = audio_stream_get_frm_fmt(&sourceb->stream); sink_format = audio_stream_get_frm_fmt(&sinkb->stream); - cd->config = comp_get_data_blob(cd->model_handler, &data_size, NULL); + cd->config = comp_get_data_blob(cd->model_handler, &cd->config_size, NULL); /* Initialize EQ */ comp_info(dev, "source_format=%d, sink_format=%d", @@ -192,7 +203,9 @@ static int eq_iir_prepare(struct processing_module *mod, eq_iir_set_passthrough_func(cd, source_format, sink_format); /* Initialize EQ */ - if (cd->config && data_size > 0) { + if (cd->config && cd->config_size > 0) { + if (eq_iir_check_blob_size(dev, cd->config_size) < 0) + return -EINVAL; ret = eq_iir_new_blob(mod, source_format, sink_format, channels); if (ret) return ret; diff --git a/src/audio/eq_iir/eq_iir.h b/src/audio/eq_iir/eq_iir.h index 12d888d66594..aa325a913005 100644 --- a/src/audio/eq_iir/eq_iir.h +++ b/src/audio/eq_iir/eq_iir.h @@ -39,6 +39,7 @@ struct comp_data { struct comp_data_blob_handler *model_handler; struct sof_eq_iir_config *config; int32_t *iir_delay; /**< pointer to allocated RAM */ + size_t config_size; /**< configuration size */ size_t iir_delay_size; /**< allocated size */ eq_iir_func eq_iir_func; /**< processing function */ }; diff --git a/src/audio/eq_iir/eq_iir_generic.c b/src/audio/eq_iir/eq_iir_generic.c index fd3485a28eca..6eebcc4e0ed3 100644 --- a/src/audio/eq_iir/eq_iir_generic.c +++ b/src/audio/eq_iir/eq_iir_generic.c @@ -180,6 +180,65 @@ void eq_iir_s32_default(struct processing_module *mod, struct input_stream_buffe } #endif /* CONFIG_FORMAT_S32LE */ +static int eq_iir_blob_words_max(struct comp_dev *dev, + const struct sof_eq_iir_config *config, + size_t blob_size, + uint32_t *coef_words_max) +{ + size_t payload_bytes; + + /* Compute the size of the coefficient area in int32_t words from the + * framework-reported blob size. The blob layout is: + * sizeof(*config) header bytes + * channels_in_config int32_t assign_response[] + * coefficient data[] + * channels_in_config is bounded above, so the multiply fits in size_t. + * The blob's self-declared config->size is cross-checked against the + * authoritative blob_size so all later parsing stays within the buffer. + */ + if (blob_size < sizeof(*config) || config->size != blob_size) { + comp_err(dev, "blob size %zu / header size %u mismatch or too small", + blob_size, config->size); + return -EINVAL; + } + payload_bytes = blob_size - sizeof(*config); + if (payload_bytes % sizeof(int32_t) || + payload_bytes < (size_t)config->channels_in_config * sizeof(int32_t)) { + comp_err(dev, "blob size %zu misaligned or too small", blob_size); + return -EINVAL; + } + *coef_words_max = payload_bytes / sizeof(int32_t) - config->channels_in_config; + return 0; +} + +static int eq_iir_init_response(struct comp_dev *dev, int idx, + int32_t *coef_data, uint32_t coef_words_max, + uint32_t *j, struct sof_eq_iir_header **eq_out) +{ + struct sof_eq_iir_header *eq; + uint32_t header_end = *j + SOF_EQ_IIR_NHEADER; + uint32_t section_end; + + /* Header must fit before reading num_sections */ + if (header_end > coef_words_max) { + comp_err(dev, "response %d header out of bounds", idx); + return -EINVAL; + } + eq = (struct sof_eq_iir_header *)&coef_data[*j]; + /* Bound num_sections so the multiply cannot overflow and the section + * data stays within the blob. + */ + section_end = header_end + (uint32_t)SOF_EQ_IIR_NBIQUAD * eq->num_sections; + if (eq->num_sections > SOF_EQ_IIR_BIQUADS_MAX || section_end > coef_words_max) { + comp_err(dev, "response %d num_sections %u out of bounds", + idx, eq->num_sections); + return -EINVAL; + } + *eq_out = eq; + *j = section_end; + return 0; +} + static int eq_iir_init_coef(struct processing_module *mod, int nch) { struct comp_data *cd = module_get_private_data(mod); @@ -187,13 +246,15 @@ static int eq_iir_init_coef(struct processing_module *mod, int nch) struct iir_state_df1 *iir = cd->iir; struct sof_eq_iir_header *lookup[SOF_EQ_IIR_MAX_RESPONSES]; struct sof_eq_iir_header *eq; + uint32_t coef_words_max; int32_t *assign_response; int32_t *coef_data; int size_sum = 0; int resp = 0; int i; - int j; + uint32_t j; int s; + int ret; comp_info(mod->dev, "%u responses, %u channels, stream %d channels", config->number_of_responses, config->channels_in_config, nch); @@ -210,17 +271,21 @@ static int eq_iir_init_coef(struct processing_module *mod, int nch) return -EINVAL; } + ret = eq_iir_blob_words_max(mod->dev, config, cd->config_size, &coef_words_max); + if (ret < 0) + return ret; + /* Collect index of response start positions in all_coefficients[] */ j = 0; assign_response = ASSUME_ALIGNED(&config->data[0], 4); - coef_data = ASSUME_ALIGNED(&config->data[config->channels_in_config], - 4); + coef_data = ASSUME_ALIGNED(&config->data[config->channels_in_config], 4); for (i = 0; i < SOF_EQ_IIR_MAX_RESPONSES; i++) { if (i < config->number_of_responses) { - eq = (struct sof_eq_iir_header *)&coef_data[j]; + ret = eq_iir_init_response(mod->dev, i, coef_data, + coef_words_max, &j, &eq); + if (ret < 0) + return ret; lookup[i] = eq; - j += SOF_EQ_IIR_NHEADER - + SOF_EQ_IIR_NBIQUAD * eq->num_sections; } else { lookup[i] = NULL; } @@ -318,7 +383,10 @@ int eq_iir_setup(struct processing_module *mod, int nch) /* Free existing IIR channels data if it was allocated */ eq_iir_free_delaylines(mod); - /* Set coefficients for each channel EQ from coefficient blob */ + /* Set coefficients for each channel EQ from coefficient blob. + * eq_iir_init_coef() / eq_iir_blob_words_max() perform all blob size + * sanity checks, including config->size vs cd->config_size. + */ delay_size = eq_iir_init_coef(mod, nch); if (delay_size < 0) return delay_size; /* Contains error code */ diff --git a/src/math/iir_df1.c b/src/math/iir_df1.c index 861c5cd2cf19..5e4d13b8cc70 100644 --- a/src/math/iir_df1.c +++ b/src/math/iir_df1.c @@ -16,9 +16,13 @@ int iir_delay_size_df1(struct sof_eq_iir_header *config) { - int n = config->num_sections; /* One section uses two unit delays */ + uint32_t n = config->num_sections; /* One section uses four unit delays */ - if (n > SOF_EQ_IIR_BIQUADS_MAX || n < 1) + if (!n || n > SOF_EQ_IIR_BIQUADS_MAX) + return -EINVAL; + + if (!config->num_sections_in_series || + config->num_sections_in_series > n) return -EINVAL; return 4 * n * sizeof(int32_t); diff --git a/src/math/iir_df2t.c b/src/math/iir_df2t.c index b2adb69be03f..ed2b50a21a4e 100644 --- a/src/math/iir_df2t.c +++ b/src/math/iir_df2t.c @@ -16,9 +16,13 @@ int iir_delay_size_df2t(struct sof_eq_iir_header *config) { - int n = config->num_sections; /* One section uses two unit delays */ + uint32_t n = config->num_sections; /* One section uses two unit delays */ - if (n > SOF_EQ_IIR_BIQUADS_MAX || n < 1) + if (!n || n > SOF_EQ_IIR_BIQUADS_MAX) + return -EINVAL; + + if (!config->num_sections_in_series || + config->num_sections_in_series > n) return -EINVAL; return 2 * n * sizeof(int64_t); From 4d06cdc8797c18695b4d37de1e160eca7f80ca87 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Thu, 11 Jun 2026 14:56:31 +0200 Subject: [PATCH 157/303] boards: wcl: enable library authentication Enable CONFIG_LIBRARY_AUTH_SUPPORT for WCL Signed-off-by: Adrian Bonislawski --- app/boards/intel_adsp_ace30_wcl.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/app/boards/intel_adsp_ace30_wcl.conf b/app/boards/intel_adsp_ace30_wcl.conf index a46b4dfaa08f..2196af333e65 100644 --- a/app/boards/intel_adsp_ace30_wcl.conf +++ b/app/boards/intel_adsp_ace30_wcl.conf @@ -28,6 +28,7 @@ CONFIG_COLD_STORE_EXECUTE_DRAM=y # SOF / loadable modules CONFIG_INTEL_MODULES=y CONFIG_LIBRARY_MANAGER=y +CONFIG_LIBRARY_AUTH_SUPPORT=y CONFIG_LIBRARY_BASE_ADDRESS=0xa0688000 CONFIG_LIBRARY_BUILD_LIB=y CONFIG_LIBRARY_DEFAULT_MODULAR=y From 15e9c2d5ace19a821e5982145bc325de6e58b3ed Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Tue, 5 May 2026 11:41:07 +0200 Subject: [PATCH 158/303] audio: move cir_buf_ptr to common header This moves struct cir_buf_ptr from mixin_mixout to a common header so it can be reused by other modules. Signed-off-by: Serhiy Katsyuba --- src/audio/mixin_mixout/mixin_mixout.h | 7 ------- src/include/sof/audio/audio_stream.h | 9 +++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/audio/mixin_mixout/mixin_mixout.h b/src/audio/mixin_mixout/mixin_mixout.h index 97aa0e12cdab..5cfb30f26c43 100644 --- a/src/audio/mixin_mixout/mixin_mixout.h +++ b/src/audio/mixin_mixout/mixin_mixout.h @@ -99,13 +99,6 @@ struct ipc4_mixer_mode_config { struct ipc4_mixer_mode_sink_config mixer_mode_sink_configs[1]; } __packed __aligned(4); -/* Pointer to data in circular buffer together with buffer boundaries */ -struct cir_buf_ptr { - void *buf_start; - void *buf_end; - void *ptr; -}; - /** * \brief mixin processing function interface */ diff --git a/src/include/sof/audio/audio_stream.h b/src/include/sof/audio/audio_stream.h index dd81c157b8d8..5c6f15392800 100644 --- a/src/include/sof/audio/audio_stream.h +++ b/src/include/sof/audio/audio_stream.h @@ -66,6 +66,15 @@ struct audio_stream { struct sof_audio_stream_params runtime_stream_params; }; +/* A pointer to data in a ring buffer. Just for convenience to reduce the number of typical + * processing function parameters: e.g., just 3 parameters (in, out ptr, and size) instead of 7. + */ +struct cir_buf_ptr { + void *buf_start; + void *buf_end; + void *ptr; +}; + void audio_stream_recalc_align(struct audio_stream *stream); static inline void *audio_stream_get_rptr(const struct audio_stream *buf) From 317e234fed8ef3986a447a4e00df6f98792c1a77 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Wed, 27 May 2026 15:35:27 +0200 Subject: [PATCH 159/303] intel: base_fw: uaol: move UAOL code out of base_fw This moves UAOL-related code to a dedicated intel_uaol.c file, which will be extended with additional UAOL functionality in the future. Signed-off-by: Serhiy Katsyuba --- src/audio/CMakeLists.txt | 3 ++ src/audio/base_fw_intel.c | 79 ++---------------------------- src/audio/intel_uaol.c | 79 ++++++++++++++++++++++++++++++ src/include/sof/audio/intel_uaol.h | 20 ++++++++ 4 files changed, 106 insertions(+), 75 deletions(-) create mode 100644 src/audio/intel_uaol.c create mode 100644 src/include/sof/audio/intel_uaol.h diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 29e602871af7..4fc676dfae3d 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -126,6 +126,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_TONE) add_subdirectory(tone) endif() + if(CONFIG_UAOL_INTEL_ADSP) + add_local_sources(sof intel_uaol.c) + endif() if(CONFIG_ZEPHYR_NATIVE_DRIVERS) list(APPEND base_files host-zephyr.c) sof_list_append_ifdef(CONFIG_COMP_DAI base_files dai-zephyr.c) diff --git a/src/audio/base_fw_intel.c b/src/audio/base_fw_intel.c index 67b87a0afed4..bcb9fca8e1f2 100644 --- a/src/audio/base_fw_intel.c +++ b/src/audio/base_fw_intel.c @@ -24,10 +24,6 @@ #include #include -#if CONFIG_UAOL_INTEL_ADSP -#include -#endif - #include #include #include @@ -37,6 +33,10 @@ #include #endif +#if CONFIG_UAOL_INTEL_ADSP +#include +#endif + struct ipc4_modules_info { uint32_t modules_count; struct sof_man_module modules[0]; @@ -46,22 +46,6 @@ struct ipc4_modules_info { STATIC_ASSERT(sizeof(struct ipc4_modules_info) < SOF_IPC_MSG_MAX_SIZE, invalid_modules_info_struct_size); -#if CONFIG_UAOL_INTEL_ADSP -struct ipc4_uaol_link_capabilities { - uint32_t input_streams_supported : 4; - uint32_t output_streams_supported : 4; - uint32_t bidirectional_streams_supported : 5; - uint32_t rsvd : 19; - uint32_t max_tx_fifo_size; - uint32_t max_rx_fifo_size; -} __packed __aligned(4); - -struct ipc4_uaol_capabilities { - uint32_t link_count; - struct ipc4_uaol_link_capabilities link_caps[]; -} __packed __aligned(4); -#endif /* CONFIG_UAOL_INTEL_ADSP */ - /* * TODO: default to value of ACE1.x platforms. This is defined * in multiple places in Zephyr, mm_drv_intel_adsp.h and @@ -103,61 +87,6 @@ __cold int basefw_vendor_fw_config(uint32_t *data_offset, char *data) return 0; } -#if CONFIG_UAOL_INTEL_ADSP -#define DEV_AND_COMMA(node) DEVICE_DT_GET(node), -static const struct device *uaol_devs[] = { - DT_FOREACH_STATUS_OKAY(intel_adsp_uaol, DEV_AND_COMMA) -}; - -#if !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY -__cold static void tlv_value_set_uaol_caps(struct sof_tlv *tuple, uint32_t type) -{ - const size_t dev_count = ARRAY_SIZE(uaol_devs); - struct uaol_capabilities dev_cap; - struct ipc4_uaol_capabilities *caps = (struct ipc4_uaol_capabilities *)tuple->value; - size_t caps_size = offsetof(struct ipc4_uaol_capabilities, link_caps[dev_count]); - size_t i; - int ret; - - assert_can_be_cold(); - - memset(caps, 0, caps_size); - - caps->link_count = dev_count; - for (i = 0; i < dev_count; i++) { - ret = uaol_get_capabilities(uaol_devs[i], &dev_cap); - if (ret) - continue; - - caps->link_caps[i].input_streams_supported = dev_cap.input_streams; - caps->link_caps[i].output_streams_supported = dev_cap.output_streams; - caps->link_caps[i].bidirectional_streams_supported = dev_cap.bidirectional_streams; - caps->link_caps[i].max_tx_fifo_size = dev_cap.max_tx_fifo_size; - caps->link_caps[i].max_rx_fifo_size = dev_cap.max_rx_fifo_size; - } - - tlv_value_set(tuple, type, caps_size, caps); -} -#endif /* CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY */ - -__cold static int uaol_stream_id_to_hda_link_stream_id(int uaol_stream_id) -{ - size_t dev_count = ARRAY_SIZE(uaol_devs); - size_t i; - - assert_can_be_cold(); - - for (i = 0; i < dev_count; i++) { - int hda_link_stream_id = uaol_get_mapped_hda_link_stream_id(uaol_devs[i], - uaol_stream_id); - if (hda_link_stream_id >= 0) - return hda_link_stream_id; - } - - return -1; -} -#endif /* CONFIG_UAOL_INTEL_ADSP */ - __cold int basefw_vendor_hw_config(uint32_t *data_offset, char *data) { struct sof_tlv *tuple = (struct sof_tlv *)data; diff --git a/src/audio/intel_uaol.c b/src/audio/intel_uaol.c new file mode 100644 index 000000000000..baeb4a217103 --- /dev/null +++ b/src/audio/intel_uaol.c @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2026 Intel Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(uaol, CONFIG_SOF_LOG_LEVEL); + +struct ipc4_uaol_link_capabilities { + uint32_t input_streams_supported : 4; + uint32_t output_streams_supported : 4; + uint32_t bidirectional_streams_supported : 5; + uint32_t rsvd : 19; + uint32_t max_tx_fifo_size; + uint32_t max_rx_fifo_size; +} __packed __aligned(4); + +struct ipc4_uaol_capabilities { + uint32_t link_count; + struct ipc4_uaol_link_capabilities link_caps[]; +} __packed __aligned(4); + +#define DEV_AND_COMMA(node) DEVICE_DT_GET(node), +static const struct device *uaol_devs[] = { + DT_FOREACH_STATUS_OKAY(intel_adsp_uaol, DEV_AND_COMMA) +}; + +#if !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY +__cold void tlv_value_set_uaol_caps(struct sof_tlv *tuple, uint32_t type) +{ + const size_t dev_count = ARRAY_SIZE(uaol_devs); + struct uaol_capabilities dev_cap; + struct ipc4_uaol_capabilities *caps = (struct ipc4_uaol_capabilities *)tuple->value; + size_t caps_size = offsetof(struct ipc4_uaol_capabilities, link_caps[dev_count]); + size_t i; + int ret; + + assert_can_be_cold(); + + memset(caps, 0, caps_size); + + caps->link_count = dev_count; + for (i = 0; i < dev_count; i++) { + ret = uaol_get_capabilities(uaol_devs[i], &dev_cap); + if (ret) + continue; + + caps->link_caps[i].input_streams_supported = dev_cap.input_streams; + caps->link_caps[i].output_streams_supported = dev_cap.output_streams; + caps->link_caps[i].bidirectional_streams_supported = dev_cap.bidirectional_streams; + caps->link_caps[i].max_tx_fifo_size = dev_cap.max_tx_fifo_size; + caps->link_caps[i].max_rx_fifo_size = dev_cap.max_rx_fifo_size; + } + + tlv_value_set(tuple, type, caps_size, caps); +} +#endif /* !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY */ + +__cold int uaol_stream_id_to_hda_link_stream_id(int uaol_stream_id) +{ + size_t dev_count = ARRAY_SIZE(uaol_devs); + size_t i; + + assert_can_be_cold(); + + for (i = 0; i < dev_count; i++) { + int hda_link_stream_id = uaol_get_mapped_hda_link_stream_id(uaol_devs[i], + uaol_stream_id); + if (hda_link_stream_id >= 0) + return hda_link_stream_id; + } + + return -1; +} diff --git a/src/include/sof/audio/intel_uaol.h b/src/include/sof/audio/intel_uaol.h new file mode 100644 index 000000000000..e8964d2d7450 --- /dev/null +++ b/src/include/sof/audio/intel_uaol.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2026 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_AUDIO_INTEL_UAOL_H__ +#define __SOF_AUDIO_INTEL_UAOL_H__ + +#include +#include + +struct sof_tlv; + +#if !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY +__cold void tlv_value_set_uaol_caps(struct sof_tlv *tuple, uint32_t type); +#endif /* !CONFIG_SOF_OS_LINUX_COMPAT_PRIORITY */ + +__cold int uaol_stream_id_to_hda_link_stream_id(int uaol_stream_id); + +#endif /* __SOF_AUDIO_INTEL_UAOL_H__ */ From 45efb5ccee7ea9c81c2b3a82df1fe08d45be8d65 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Wed, 27 May 2026 18:43:21 +0200 Subject: [PATCH 160/303] ipc4: uaol: add ipc4_find_all_dma_configs_tlvs_only() This adds yet another function to parse DMA config supplied by the host. Unfortunately, we now have three functions for this purpose. Unlike the existing ipc4_find_dma_config(), ipc4_find_all_dma_configs_tlvs_only() can find multiple DMA configs. For example, a UAOL copier may use two DMA channels: one for the audio USB endpoint and one for the feedback USB endpoint. ipc4_find_all_dma_configs_tlvs_only() can only work when all data in data_buffer is in TLV format; unfortunately, this is not always the case with IPC4 for all gateway types. Therefore, the existing ipc4_find_dma_config() is still needed as it can skip non-TLV blob data at the beginning of data_buffer. The other function, ipc4_find_dma_config_multiple(), works differently: it searches for DMA config for a given ALH stream ID in ALH multi-gateway case. Hence, this third function -- ipc4_find_all_dma_configs_tlvs_only() -- is added to the family. Signed-off-by: Serhiy Katsyuba --- src/audio/copier/copier_dai.c | 2 +- src/include/sof/ipc/topology.h | 2 ++ src/ipc/ipc4/helper.c | 43 ++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/audio/copier/copier_dai.c b/src/audio/copier/copier_dai.c index 13ceae32e4b5..109bc2a4a6b2 100644 --- a/src/audio/copier/copier_dai.c +++ b/src/audio/copier/copier_dai.c @@ -337,7 +337,7 @@ __cold int copier_dai_create(struct comp_dev *dev, struct copier_data *cd, dai.type = SOF_DAI_INTEL_UAOL; dai.is_config_blob = true; cd->gtw_type = ipc4_gtw_alh; - ret = ipc4_find_dma_config(&dai, gtw_cfg_data, gtw_cfg_size); + ret = ipc4_find_all_dma_configs_tlvs_only(&dai, gtw_cfg_data, gtw_cfg_size); if (ret != IPC4_SUCCESS) { comp_err(dev, "No uaol dma_config found in blob!"); return -EINVAL; diff --git a/src/include/sof/ipc/topology.h b/src/include/sof/ipc/topology.h index 3503c7a407d8..84df84abb069 100644 --- a/src/include/sof/ipc/topology.h +++ b/src/include/sof/ipc/topology.h @@ -56,6 +56,8 @@ int ipc4_trigger_chain_dma(struct ipc *ipc, struct ipc4_chain_dma *cdma, bool *d int ipc4_process_on_core(uint32_t core, bool blocking); int ipc4_pipeline_complete(struct ipc *ipc, uint32_t comp_id, uint32_t cmd); int ipc4_find_dma_config(struct ipc_config_dai *dai, uint8_t *data_buffer, uint32_t size); +int ipc4_find_all_dma_configs_tlvs_only(struct ipc_config_dai *dai, + uint8_t *data_buffer, size_t size); int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd); int ipc4_pipeline_trigger(struct ipc_comp_dev *ppl_icd, uint32_t cmd, bool *delayed); int ipc4_find_dma_config_multiple(struct ipc_config_dai *dai, uint8_t *data_buffer, diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 709063f7a68c..9e0846f6bc02 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -1311,6 +1311,49 @@ int ipc4_find_dma_config(struct ipc_config_dai *dai, uint8_t *data_buffer, uint3 return IPC4_SUCCESS; } +/* Unlike the above ipc4_find_dma_config(), this can find multiple DMA configs. + * For example, a UAOL copier may use two DMA channels: one for audio and one + * for clock feedback. This function can only work when all data in data_buffer + * is in TLV format; however, this is not always the case for all gateway types. + * Therefore, the above ipc4_find_dma_config() is still used as it can skip non-TLV + * blob data at the beginning of data_buffer. + */ +int ipc4_find_all_dma_configs_tlvs_only(struct ipc_config_dai *dai, + uint8_t *data_buffer, size_t size) +{ + uintptr_t end_addr = (uintptr_t)data_buffer + size; + struct sof_tlv *tlvs; + struct ipc_dma_config *dma_cfg; + int count = 0; + + for (tlvs = (struct sof_tlv *)data_buffer; tlvs && (uintptr_t)tlvs < end_addr; + tlvs = tlv_next(tlvs)) { + if ((uintptr_t)tlvs->value + tlvs->length > end_addr) { + tr_err(&ipc_tr, "Unexpected TLV length %d: exceeds buffer size", tlvs->length); + return IPC4_INVALID_REQUEST; + } + + dma_cfg = tlv_value_ptr_get(tlvs, GTW_DMA_CONFIG_ID); + if (!dma_cfg) + continue; + + if (count >= GTW_DMA_DEVICE_MAX_COUNT) { + tr_err(&ipc_tr, "Unexpected DMA config count %d, max %d", + count, GTW_DMA_DEVICE_MAX_COUNT); + return IPC4_INVALID_REQUEST; + } + + dai->host_dma_config[count++] = dma_cfg; + } + + if (count == 0) { + tr_err(&ipc_tr, "No DMA config found"); + return IPC4_INVALID_REQUEST; + } + + return IPC4_SUCCESS; +} + int ipc4_find_dma_config_multiple(struct ipc_config_dai *dai, uint8_t *data_buffer, uint32_t size, uint32_t device_id, int dma_cfg_idx) { From 10c8e6b1a9137009a4ae6bbb8ad36c5102d1f974 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 12 May 2026 19:31:41 +0300 Subject: [PATCH 161/303] lib: vregion: lazy interim heap creation from remaining lifetime space Refactor the vregion memory layout to use a single contiguous buffer instead of two separately page-aligned partitions. The vregion lifetime allocations start at the base and growing upward. The interim k_heap is created lazily when vregion_set_interim() is called for whatever space remains after lifetime allocations. This eliminates the rigid partition boundary that previously wasted memory when lifetime usage was smaller or larger than pre-configured. The interim heap creation is deferred until actually needed, at which point the lifetime region is sealed and any further allocation requests are redirected to the interim. The vregion tracks internal allocation mode (lifetime or interim). All allocations start in lifetime mode. The vregion_set_interim() function switches to interim mode. The enum vregion_mem_type parameter has been removed from the vregion_alloc*() API since the mode is now internal state. vregion_set_interim() is called in pipeline_comp_complete() in case the component's processing domain is Data Processing. The DP components are so far the only place where vregions are used at the moment. Add a guard to skip k_heap_init() in interim_heap_init() if the remaining interim size is too small (< 1024 bytes), which would otherwise trigger an assert failure in sys_heap_init(). Mark the vregion type to VREGION_MEM_TYPE_INVALID if that happens. Key changes: - vregion_create(): Takes single memsize argument instead of separate lifetime_size and interim_size - New vregion_set_interim(): Switches allocation mode from lifetime to interim, warns on repeated calls - vregion_alloc*(): No longer take enum vregion_mem_type parameter, use internal state instead - New interim_heap_init(): Called lazily, page-aligns interim start, logs lifetime used and interim available at INFO level - vregion_free(): Guards interim pointer range - pipeline_comp_completee(): Calls vregion_set_interim() to switch mode Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 14 +- src/audio/pipeline/pipeline-graph.c | 10 +- src/include/sof/lib/vregion.h | 51 ++++--- src/lib/objpool.c | 4 +- zephyr/include/rtos/alloc.h | 5 +- zephyr/lib/fast-get.c | 2 +- zephyr/lib/vregion.c | 155 +++++++++++++++------- zephyr/test/vregion.c | 37 ++++-- 8 files changed, 176 insertions(+), 102 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index d9518de7e13e..3da4079d757e 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -65,13 +65,7 @@ static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config * /* FIXME: the size will be derived from configuration */ const size_t buf_size = 28 * 1024; - /* - * A 1-to-1 replacement of the original heap implementation would be to - * have "lifetime size" equal to 0. But (1) this is invalid for - * vregion_create() and (2) we gradually move objects, that are simple - * to move to the lifetime buffer. Make it 4k for the beginning. - */ - return vregion_create(4096, buf_size - 4096); + return vregion_create(buf_size); } static struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, @@ -114,9 +108,9 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv if (!mod_vreg) mod = sof_heap_alloc(mod_heap, flags, sizeof(*mod), 0); else if (flags & SOF_MEM_FLAG_COHERENT) - mod = vregion_alloc_coherent(mod_vreg, VREGION_MEM_TYPE_LIFETIME, sizeof(*mod)); + mod = vregion_alloc_coherent(mod_vreg, sizeof(*mod)); else - mod = vregion_alloc(mod_vreg, VREGION_MEM_TYPE_LIFETIME, sizeof(*mod)); + mod = vregion_alloc(mod_vreg, sizeof(*mod)); if (!mod) { comp_cl_err(drv, "failed to allocate memory for module"); @@ -141,7 +135,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv * single-core configurations. */ if (mod_vreg) - dev = vregion_alloc_coherent(mod_vreg, VREGION_MEM_TYPE_LIFETIME, sizeof(*dev)); + dev = vregion_alloc_coherent(mod_vreg, sizeof(*dev)); else dev = sof_heap_alloc(mod_heap, SOF_MEM_FLAG_COHERENT, sizeof(*dev), 0); diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 8494f17998bd..915987051275 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -7,8 +7,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -294,8 +296,14 @@ static int pipeline_comp_complete(struct comp_dev *current, * It will be calculated during module prepare operation * either by the module or to default value based on module's OBS */ - if (current->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL) + if (current->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL) { current->period = ppl_data->p->period; + } else { + struct processing_module *mod = comp_mod(current); + + if (mod && mod->priv.resources.alloc) + vregion_set_interim(mod->priv.resources.alloc->vreg); + } current->priority = ppl_data->p->priority; diff --git a/src/include/sof/lib/vregion.h b/src/include/sof/lib/vregion.h index d64249ed64c8..5c066c90dbc8 100644 --- a/src/include/sof/lib/vregion.h +++ b/src/include/sof/lib/vregion.h @@ -24,6 +24,7 @@ struct vregion; enum vregion_mem_type { VREGION_MEM_TYPE_INTERIM, /* interim allocation that can be freed */ VREGION_MEM_TYPE_LIFETIME, /* lifetime allocation */ + VREGION_MEM_TYPE_INVALID, /* Interim heap initialization failed */ }; #if CONFIG_SOF_VREGIONS @@ -31,14 +32,24 @@ enum vregion_mem_type { /** * @brief Create a new virtual region instance. * - * Create a new virtual region instance with specified static and dynamic partitions. - * Total size is the sum of static and dynamic sizes. + * Create a new virtual region instance with specified memory size. + * Allocations start in LIFETIME mode. * - * @param[in] lifetime_size Size of the virtual region lifetime partition. - * @param[in] interim_size Size of the virtual region interim partition. + * @param[in] memsize Total size of the virtual region memory. * @return struct vregion* Pointer to the new virtual region instance, or NULL on failure. */ -struct vregion *vregion_create(size_t lifetime_size, size_t interim_size); +struct vregion *vregion_create(size_t memsize); + +/** + * @brief Switch virtual region allocations to interim mode. + * + * After this call, all allocations from this vregion will use the interim + * heap. The interim heap is created lazily from remaining lifetime space. + * Multiple calls are allowed but log a warning. + * + * @param[in] vr Pointer to the virtual region instance. + */ +void vregion_set_interim(struct vregion *vr); /** * @brief Increment virtual region's user count. @@ -66,36 +77,33 @@ struct vregion *vregion_put(struct vregion *vr); * @brief Allocate memory from the specified virtual region. * * @param[in] vr Pointer to the virtual region instance. - * @param[in] type Type of memory to allocate (lifetime or interim). * @param[in] size Size of memory to allocate in bytes. * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size); +void *vregion_alloc(struct vregion *vr, size_t size); /** * @brief like vregion_alloc() but allocates coherent memory */ -void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, size_t size); +void *vregion_alloc_coherent(struct vregion *vr, size_t size); /** * @brief Allocate aligned memory from the specified virtual region. * - * Allocate aligned memory from the specified virtual region based on the memory type. + * Allocate aligned memory from the specified virtual region using the + * current allocation mode (lifetime or interim). * * @param[in] vr Pointer to the virtual region instance. - * @param[in] type Type of memory to allocate (lifetime or interim). * @param[in] size Size of memory to allocate in bytes. * @param[in] alignment Alignment of memory to allocate in bytes. * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment); +void *vregion_alloc_align(struct vregion *vr, size_t size, size_t alignment); /** * @brief like vregion_alloc_align() but allocates coherent memory */ -void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment); +void *vregion_alloc_coherent_align(struct vregion *vr, size_t size, size_t alignment); /** * @brief Free memory allocated from the specified virtual region. @@ -129,10 +137,11 @@ struct vregion { unsigned int use_count; }; -static inline struct vregion *vregion_create(size_t lifetime_size, size_t interim_size) +static inline struct vregion *vregion_create(size_t memsize) { return NULL; } +static inline void vregion_set_interim(struct vregion *vr) {} static inline struct vregion *vregion_get(struct vregion *vr) { return vr; @@ -141,22 +150,20 @@ static inline struct vregion *vregion_put(struct vregion *vr) { return vr; } -static inline void *vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size) +static inline void *vregion_alloc(struct vregion *vr, size_t size) { return NULL; } -static inline void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, - size_t size) +static inline void *vregion_alloc_coherent(struct vregion *vr, size_t size) { return NULL; } -static inline void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment) +static inline void *vregion_alloc_align(struct vregion *vr, size_t size, size_t alignment) { return NULL; } -static inline void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment) +static inline void *vregion_alloc_coherent_align(struct vregion *vr, size_t size, + size_t alignment) { return NULL; } diff --git a/src/lib/objpool.c b/src/lib/objpool.c index 6925e6f7070a..9636e018d3d1 100644 --- a/src/lib/objpool.c +++ b/src/lib/objpool.c @@ -44,10 +44,10 @@ static int objpool_add(struct objpool_head *head, unsigned int n, size_t size, u pobjpool = sof_heap_alloc(head->heap, flags, aligned_size + sizeof(*pobjpool), 0); else if (flags & SOF_MEM_FLAG_COHERENT) - pobjpool = vregion_alloc_coherent(head->vreg, VREGION_MEM_TYPE_INTERIM, + pobjpool = vregion_alloc_coherent(head->vreg, aligned_size + sizeof(*pobjpool)); else - pobjpool = vregion_alloc(head->vreg, VREGION_MEM_TYPE_INTERIM, + pobjpool = vregion_alloc(head->vreg, aligned_size + sizeof(*pobjpool)); if (!pobjpool) diff --git a/zephyr/include/rtos/alloc.h b/zephyr/include/rtos/alloc.h index 98deb489f91d..b727d1e700fb 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -189,10 +189,9 @@ static inline void *sof_ctx_alloc(struct mod_alloc_ctx *ctx, uint32_t flags, return sof_heap_alloc(ctx ? ctx->heap : NULL, flags, size, alignment); if (flags & SOF_MEM_FLAG_COHERENT) - return vregion_alloc_coherent_align(ctx->vreg, VREGION_MEM_TYPE_INTERIM, - size, alignment); + return vregion_alloc_coherent_align(ctx->vreg, size, alignment); - return vregion_alloc_align(ctx->vreg, VREGION_MEM_TYPE_INTERIM, size, alignment); + return vregion_alloc_align(ctx->vreg, size, alignment); } /** diff --git a/zephyr/lib/fast-get.c b/zephyr/lib/fast-get.c index 0477129fd57c..cb0a2cfb07c7 100644 --- a/zephyr/lib/fast-get.c +++ b/zephyr/lib/fast-get.c @@ -203,7 +203,7 @@ const void *fast_get(struct mod_alloc_ctx *alloc, const void *dram_ptr, size_t s if (alloc && alloc->vreg && size <= FAST_GET_MAX_COPY_SIZE) /* A userspace allocation, that won't be shared */ - ret = vregion_alloc_align(alloc->vreg, VREGION_MEM_TYPE_INTERIM, alloc_size, + ret = vregion_alloc_align(alloc->vreg, alloc_size, alloc_align); else ret = sof_heap_alloc(heap, alloc_flags, alloc_size, alloc_align); diff --git a/zephyr/lib/vregion.c b/zephyr/lib/vregion.c index 15683925196d..9c8c94c23f97 100644 --- a/zephyr/lib/vregion.c +++ b/zephyr/lib/vregion.c @@ -5,6 +5,7 @@ * Author: Liam Girdwood */ +#include #include #include #include @@ -87,7 +88,10 @@ struct vregion { struct k_mutex lock; /* protect vregion heaps and use-count */ unsigned int use_count; - /* interim heap */ + /* current allocation mode */ + enum vregion_mem_type type; /* LIFETIME at creation, switch to INTERIM */ + + /* interim heap - created lazily on first interim allocation */ struct interim_heap interim; /* interim heap */ /* lifetime heap */ @@ -97,30 +101,31 @@ struct vregion { /** * @brief Create a new virtual region instance. * - * Create a new VIRTUAL REGION instance with specified static and dynamic - * sizes. Total size is their sum. + * Create a new VIRTUAL REGION instance with specified memory size. + * Allocations start in LIFETIME mode. * - * @param[in] lifetime_size Size of the virtual region lifetime partition. - * @param[in] interim_size Size of the virtual region interim partition. + * @param[in] memsize Total size of the virtual region memory. * @return struct vregion* Pointer to the new virtual region instance, or NULL on failure. */ -struct vregion *vregion_create(size_t lifetime_size, size_t interim_size) +struct vregion *vregion_create(size_t memsize) { struct vregion *vr; unsigned int pages; size_t total_size; uint8_t *vregion_base; - if (!lifetime_size || !interim_size) { - LOG_ERR("error: invalid vregion lifetime size %zu or interim size %zu", - lifetime_size, interim_size); + if (!memsize) { + LOG_ERR("error: invalid vregion memsize %zu", memsize); return NULL; } - /* Align up lifetime sizes and interim sizes to nearest page */ - lifetime_size = ALIGN_UP(lifetime_size, CONFIG_MM_DRV_PAGE_SIZE); - interim_size = ALIGN_UP(interim_size, CONFIG_MM_DRV_PAGE_SIZE); - total_size = lifetime_size + interim_size; + /* + * Align up the memory size to nearest page. Lifetime + * allocations growing upward from the base. The interim + * k_heap is created lazily from the remaining space when + * initialization phase is ready. + */ + total_size = ALIGN_UP(memsize, CONFIG_MM_DRV_PAGE_SIZE); /* allocate vregion metadata separately to keep it inaccessible to the user */ vr = rmalloc(0, sizeof(*vr)); @@ -140,31 +145,23 @@ struct vregion *vregion_create(size_t lifetime_size, size_t interim_size) vr->size = total_size; vr->pages = pages; - /* set partition sizes */ - vr->interim.heap.heap.init_bytes = interim_size; - vr->lifetime.size = lifetime_size; - - /* set base addresses for partitions */ - vr->interim.heap.heap.init_mem = vr->base; - vr->lifetime.base = vr->base + interim_size; - - /* set alloc ptr addresses for lifetime linear partitions */ - vr->lifetime.ptr = vr->lifetime.base; + /* lifetime linear allocator starts at the beginning of the vregion memory */ + vr->lifetime.base = vregion_base; + vr->lifetime.size = total_size; + vr->lifetime.ptr = vregion_base; vr->lifetime.used = 0; vr->lifetime.free_count = 0; - /* init interim heaps */ - k_heap_init(&vr->interim.heap, vr->interim.heap.heap.init_mem, interim_size); + /* start in lifetime allocation mode */ + vr->type = VREGION_MEM_TYPE_LIFETIME; k_mutex_init(&vr->lock); /* The creator is the first user */ vr->use_count = 1; /* log the new vregion */ - LOG_INF("new at base %p size %#zx pages %u struct embedded at %p", + LOG_INF("new at base %p size %#zx pages %u metadata at %p", (void *)vr->base, total_size, pages, (void *)vr); - LOG_DBG(" interim size %#zx at %p", interim_size, (void *)vr->interim.heap.heap.init_mem); - LOG_DBG(" lifetime size %#zx at %p", lifetime_size, (void *)vr->lifetime.base); return vr; } @@ -212,6 +209,68 @@ struct vregion *vregion_put(struct vregion *vr) return NULL; } +/** + * @brief Initialize the interim heap from remaining vregion space. + * + * Called on first interim allocation. Creates the k_heap from all buffer + * space not yet consumed by lifetime allocations. + * + * @param[in] vr Pointer to the virtual region instance. + */ +static void interim_heap_init(struct vregion *vr) +{ + uint8_t *interim_base; + ssize_t interim_size; + + if (vr->type == VREGION_MEM_TYPE_INTERIM) { + LOG_WRN("vregion %p already in interim mode", (void *)vr); + return; + } + + /* interim heap starts right after current lifetime pointer, page-aligned */ + interim_base = UINT_TO_POINTER(ALIGN_UP(POINTER_TO_UINT(vr->lifetime.ptr), + CONFIG_DCACHE_LINE_SIZE)); + interim_size = (vr->base + vr->size) - interim_base; + /* + * Calling k_heap_init() with too small size causes an assert + * failure. Exact limit is hard to deduce from sys_heap_init() + * code, but 1024 seems to be enough. + */ + if (interim_size < 1024) { + LOG_WRN("Too little memory, %zd bytes, left for interim heap", interim_size); + vr->type = VREGION_MEM_TYPE_INVALID; + return; + } + + LOG_INF("creating interim heap: lifetime used %zu, interim available %zu at %p", + vr->lifetime.used, interim_size, (void *)interim_base); + + /* cap lifetime so no more lifetime allocs can grow into interim */ + vr->lifetime.size = interim_base - vr->base; + + vr->interim.heap.heap.init_mem = interim_base; + vr->interim.heap.heap.init_bytes = interim_size; + + k_heap_init(&vr->interim.heap, interim_base, interim_size); + vr->type = VREGION_MEM_TYPE_INTERIM; + + /* Update lifetime heap with the interim heap usage. */ + vr->lifetime.ptr = (void *)(interim_base + interim_size); + vr->lifetime.used = (uint8_t *)vr->lifetime.ptr - (uint8_t *)vr->lifetime.base; +} + +void vregion_set_interim(struct vregion *vr) +{ + if (!vr) + return; + + k_mutex_lock(&vr->lock, K_FOREVER); + + interim_heap_init(vr); + + k_mutex_unlock(&vr->lock); +} + /** * @brief Allocate memory with alignment from the virtual region dynamic heap. * @@ -220,8 +279,7 @@ struct vregion *vregion_put(struct vregion *vr) * @param[in] align Alignment of the allocation. * @return void* Pointer to the allocated memory, or NULL on failure. */ -static void *interim_alloc(struct interim_heap *heap, - size_t size, size_t align) +static void *interim_alloc(struct interim_heap *heap, size_t size, size_t align) { void *ptr; @@ -247,14 +305,16 @@ static void interim_free(struct interim_heap *heap, void *ptr) /** * @brief Allocate memory from the virtual region lifetime allocator. * + * If the interim heap has already been created (i.e., an interim allocation + * was made), log a warning and fall back to interim allocation. + * * @param[in] heap Pointer to the linear heap to use. * @param[in] size Size of the allocation. * @param[in] align Alignment of the allocation. * * @return void* Pointer to the allocated memory, or NULL on failure. */ -static void *lifetime_alloc(struct vlinear_heap *heap, - size_t size, size_t align) +static void *lifetime_alloc(struct vlinear_heap *heap, size_t size, size_t align) { void *ptr; uint8_t *aligned_ptr; @@ -315,14 +375,15 @@ void vregion_free(struct vregion *vr, void *ptr) if (sys_cache_is_ptr_uncached(ptr)) ptr = sys_cache_cached_ptr_get(ptr); - if (ptr >= (void *)vr->interim.heap.heap.init_mem && + /* Check if pointer is in interim heap (if initialized) */ + if (vr->type == VREGION_MEM_TYPE_INTERIM && + ptr >= (void *)vr->interim.heap.heap.init_mem && ptr < (void *)((uint8_t *)vr->interim.heap.heap.init_mem + vr->interim.heap.heap.init_bytes)) - /* pointer is in interim heap */ interim_free(&vr->interim, ptr); else if (ptr >= (void *)vr->lifetime.base && ptr < (void *)(vr->lifetime.base + vr->lifetime.size)) - /* pointer is in lifetime heap */ + /* pointer is in lifetime area - no-op free */ lifetime_free(&vr->lifetime, ptr); else LOG_ERR("error: vregion free invalid pointer %p", ptr); @@ -332,17 +393,15 @@ void vregion_free(struct vregion *vr, void *ptr) EXPORT_SYMBOL(vregion_free); /** - * @brief Allocate memory type from the virtual region. + * @brief Allocate memory from the virtual region. * * @param[in] vr Pointer to the virtual region instance. - * @param[in] type Memory type to allocate. * @param[in] size Size of the allocation. * @param[in] alignment Alignment of the allocation. * * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment) +void *vregion_alloc_align(struct vregion *vr, size_t size, size_t alignment) { void *p; @@ -354,7 +413,7 @@ void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, k_mutex_lock(&vr->lock, K_FOREVER); - switch (type) { + switch (vr->type) { case VREGION_MEM_TYPE_INTERIM: p = interim_alloc(&vr->interim, size, alignment); break; @@ -362,7 +421,7 @@ void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, p = lifetime_alloc(&vr->lifetime, size, alignment); break; default: - LOG_ERR("error: invalid memory type %d", type); + LOG_ERR("error: invalid memory type %d", vr->type); p = NULL; } @@ -375,21 +434,20 @@ EXPORT_SYMBOL(vregion_alloc_align); /** * @brief Allocate memory from the virtual region. * @param[in] vr Pointer to the virtual region instance. - * @param[in] type Memory type to allocate. * @param[in] size Size of the allocation. * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size) +void *vregion_alloc(struct vregion *vr, size_t size) { - return vregion_alloc_align(vr, type, size, 0); + return vregion_alloc_align(vr, size, 0); } EXPORT_SYMBOL(vregion_alloc); -void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, size_t size) +void *vregion_alloc_coherent(struct vregion *vr, size_t size) { size = ALIGN_UP(size, CONFIG_DCACHE_LINE_SIZE); - void *p = vregion_alloc_align(vr, type, size, CONFIG_DCACHE_LINE_SIZE); + void *p = vregion_alloc_align(vr, size, CONFIG_DCACHE_LINE_SIZE); if (!p) return NULL; @@ -399,14 +457,13 @@ void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, siz return sys_cache_uncached_ptr_get(p); } -void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment) +void *vregion_alloc_coherent_align(struct vregion *vr, size_t size, size_t alignment) { if (alignment < CONFIG_DCACHE_LINE_SIZE) alignment = CONFIG_DCACHE_LINE_SIZE; size = ALIGN_UP(size, CONFIG_DCACHE_LINE_SIZE); - void *p = vregion_alloc_align(vr, type, size, alignment); + void *p = vregion_alloc_align(vr, size, alignment); if (!p) return NULL; diff --git a/zephyr/test/vregion.c b/zephyr/test/vregion.c index eb59f68a14d7..eb36721d582e 100644 --- a/zephyr/test/vregion.c +++ b/zephyr/test/vregion.c @@ -17,8 +17,11 @@ LOG_MODULE_DECLARE(sof_boot_test, CONFIG_SOF_LOG_LEVEL); static struct vregion *test_vreg_create(void) { - struct vregion *vreg = vregion_create(CONFIG_MM_DRV_PAGE_SIZE - 100, - CONFIG_MM_DRV_PAGE_SIZE); + /* + * Use a 3-page vregion so that after two lifetime allocations there is still + * enough remaining space to initialize and exercise the interim heap. + */ + struct vregion *vreg = vregion_create(3 * CONFIG_MM_DRV_PAGE_SIZE); zassert_not_null(vreg); @@ -27,40 +30,46 @@ static struct vregion *test_vreg_create(void) static void test_vreg_alloc_lifet(struct vregion *vreg) { - void *ptr = vregion_alloc(vreg, VREGION_MEM_TYPE_LIFETIME, 2000); + void *ptr = vregion_alloc(vreg, 6000); zassert_not_null(ptr); - void *ptr_align = vregion_alloc_align(vreg, VREGION_MEM_TYPE_LIFETIME, 1600, 16); + void *ptr_align = vregion_alloc_align(vreg, 5000, 16); zassert_not_null(ptr_align); zassert_equal((uintptr_t)ptr_align & 15, 0); - void *ptr_nomem = vregion_alloc(vreg, VREGION_MEM_TYPE_LIFETIME, 2000); + /* + * Seal lifetime, switch to interim. The interim heap is created + * lazily from the remaining ~1 page, so a 6000-byte alloc won't fit. + */ + vregion_set_interim(vreg); + + void *ptr_nomem = vregion_alloc(vreg, 6000); zassert_is_null(ptr_nomem); + /* Lifetime frees are no-ops; re-alloc from interim still fails */ vregion_free(vreg, ptr_align); vregion_free(vreg, ptr); - /* Freeing isn't possible with LIFETIME */ - ptr_nomem = vregion_alloc(vreg, VREGION_MEM_TYPE_LIFETIME, 2000); + ptr_nomem = vregion_alloc(vreg, 6000); zassert_is_null(ptr_nomem); } static void test_vreg_alloc_tmp(struct vregion *vreg) { - void *ptr = vregion_alloc(vreg, VREGION_MEM_TYPE_INTERIM, 20); + void *ptr = vregion_alloc(vreg, 20); zassert_not_null(ptr); - void *ptr_align = vregion_alloc_align(vreg, VREGION_MEM_TYPE_INTERIM, 2000, 16); + void *ptr_align = vregion_alloc_align(vreg, 1000, 16); zassert_not_null(ptr_align); zassert_equal((uintptr_t)ptr_align & 15, 0); - void *ptr_nomem = vregion_alloc(vreg, VREGION_MEM_TYPE_INTERIM, 2000); + void *ptr_nomem = vregion_alloc(vreg, 2000); zassert_is_null(ptr_nomem); @@ -68,7 +77,7 @@ static void test_vreg_alloc_tmp(struct vregion *vreg) vregion_free(vreg, ptr); /* Should be possible to allocate again */ - ptr = vregion_alloc(vreg, VREGION_MEM_TYPE_INTERIM, 2000); + ptr = vregion_alloc(vreg, 1000); zassert_not_null(ptr); } @@ -83,10 +92,10 @@ ZTEST(sof_boot, vregion) { struct vregion *vreg = test_vreg_create(); - /* Test interim allocations */ - test_vreg_alloc_tmp(vreg); - /* Test lifetime allocations */ + /* Test lifetime allocations (initial mode), then seal */ test_vreg_alloc_lifet(vreg); + /* Test interim allocations (already switched by lifet test) */ + test_vreg_alloc_tmp(vreg); test_vreg_destroy(vreg); } From 26b1379e09f87c7c8a950d2f00b7525e251bdacc Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 21 May 2026 12:36:06 +0300 Subject: [PATCH 162/303] audio: host-zephyr: add HOST_DMA_IPC_POSITION_UPDATES Kconfig Add a built option HOST_DMA_IPC_POSITION_UPDATES to control whether functionality to send IPC stream position updates is enabled or not. Most platforms provide more efficient means for host to monitor DMA state, so this code is in most cases unncessary. The current IPC sending code (from audio context) also assume kernel context, so making this functionality user-space compatible will require extra work. Enable DMA IPC position updates by default for IPC3 as the feature can be controlled by host with sof_ipc_stream_params.no_stream_position IPC interface. Disable the feature by default for IPC4, as there is no host IPC interface to control this and copier_update_params() unconditionally disables IPC updates for IPC4 now, so this code is never used. Signed-off-by: Kai Vehmanen --- src/audio/Kconfig | 9 +++++++++ src/audio/copier/host_copier.h | 4 +++- src/audio/host-legacy.c | 12 ++++++++++++ src/audio/host-zephyr.c | 12 ++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 1f7d362ffdc2..2f2036c5a57f 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -42,6 +42,15 @@ config HOST_DMA_STREAM_SYNCHRONIZATION for each group, different than the default one determined by the system tick frequency. This feature will allow host lower power consumption in scenarios with deep buffering. +config HOST_DMA_IPC_POSITION_UPDATES + bool "Support for stream position updates via IPC messages" + depends on IPC_MAJOR_3 + default y + help + Support firmware functionality to report stream position updates + by sending an IPC message whenever one period of audio is transferred. + Most platforms provide more efficient ways to query the DMA status. + config COMP_CHAIN_DMA bool "Chain DMA component" depends on IPC_MAJOR_4 diff --git a/src/audio/copier/host_copier.h b/src/audio/copier/host_copier.h index 71ce89cf315b..d00907959e8f 100644 --- a/src/audio/copier/host_copier.h +++ b/src/audio/copier/host_copier.h @@ -70,7 +70,6 @@ struct host_data { /* host position reporting related */ uint32_t host_size; /**< Host buffer size (in bytes) */ - uint32_t report_pos; /**< Position in current report period */ uint32_t local_pos; /**< Local position in host buffer */ uint32_t host_period_bytes; uint16_t stream_tag; @@ -106,7 +105,10 @@ struct host_data { /* stream info */ struct sof_ipc_stream_posn posn; /* TODO: update this */ +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES + uint32_t report_pos; /**< Position in current report period */ struct ipc_msg *msg; /**< host notification */ +#endif #if CONFIG_XRUN_NOTIFICATIONS_ENABLE bool xrun_notification_sent; #endif diff --git a/src/audio/host-legacy.c b/src/audio/host-legacy.c index 033c4cf2a09e..c383c9d4e7d3 100644 --- a/src/audio/host-legacy.c +++ b/src/audio/host-legacy.c @@ -227,7 +227,9 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt struct comp_buffer *sink; int ret; bool update_mailbox = false; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES bool send_ipc = false; +#endif if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { source = hd->dma_buffer; @@ -272,6 +274,7 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt if (hd->cont_update_posn) update_mailbox = true; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES /* Don't send stream position if no_stream_position == 1 */ if (!hd->no_stream_position) { hd->report_pos += bytes; @@ -291,13 +294,16 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt send_ipc = true; } } +#endif if (update_mailbox) { pipeline_get_timestamp(dev->pipeline, dev, &hd->posn); mailbox_stream_write(dev->pipeline->posn_offset, &hd->posn, sizeof(hd->posn)); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES if (send_ipc) ipc_msg_send(hd->msg, &hd->posn, false); +#endif } } @@ -553,12 +559,14 @@ int host_common_new(struct host_data *hd, struct comp_dev *dev, ipc_build_stream_posn(&hd->posn, SOF_IPC_STREAM_POSITION, config_id); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES hd->msg = ipc_msg_init(hd->posn.rhdr.hdr.cmd, hd->posn.rhdr.hdr.size); if (!hd->msg) { comp_err(dev, "ipc_msg_init failed"); dma_put(hd->dma); return -ENOMEM; } +#endif hd->chan = NULL; hd->copy_type = COMP_COPY_NORMAL; @@ -614,7 +622,9 @@ void host_common_free(struct host_data *hd) dma_put(hd->dma); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES ipc_msg_free(hd->msg); +#endif dma_sg_free(NULL, &hd->config.elem_array); } @@ -952,7 +962,9 @@ void host_common_reset(struct host_data *hd, uint16_t state) /* reset buffer pointers */ hd->local_pos = 0; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES hd->report_pos = 0; +#endif hd->total_data_processed = 0; hd->copy_type = COMP_COPY_NORMAL; diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index e3f79419b389..c182d033c23a 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -246,7 +246,9 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt struct comp_buffer *sink; int ret; bool update_mailbox = false; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES bool send_ipc = false; +#endif if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { source = hd->dma_buffer; @@ -285,6 +287,7 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt if (hd->cont_update_posn) update_mailbox = true; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES /* Don't send stream position if no_stream_position == 1 */ if (!hd->no_stream_position) { hd->report_pos += bytes; @@ -304,13 +307,16 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt send_ipc = true; } } +#endif if (update_mailbox) { pipeline_get_timestamp(dev->pipeline, dev, &hd->posn); mailbox_stream_write(dev->pipeline->posn_offset, &hd->posn, sizeof(hd->posn)); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES if (send_ipc) ipc_msg_send(hd->msg, &hd->posn, false); +#endif } } @@ -721,12 +727,14 @@ __cold int host_common_new(struct host_data *hd, struct comp_dev *dev, ipc_build_stream_posn(&hd->posn, SOF_IPC_STREAM_POSITION, config_id); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES hd->msg = ipc_msg_init(hd->posn.rhdr.hdr.cmd, sizeof(hd->posn)); if (!hd->msg) { comp_err(dev, "ipc_msg_init failed"); sof_dma_put(hd->dma); return -ENOMEM; } +#endif hd->chan_index = -EINVAL; hd->copy_type = COMP_COPY_NORMAL; @@ -808,7 +816,9 @@ __cold void host_common_free(struct host_data *hd) sof_dma_put(hd->dma); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES ipc_msg_free(hd->msg); +#endif dma_sg_free(hd->alloc_ctx.heap, &hd->config.elem_array); } @@ -1212,7 +1222,9 @@ void host_common_reset(struct host_data *hd, uint16_t state) /* reset buffer pointers */ hd->local_pos = 0; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES hd->report_pos = 0; +#endif hd->total_data_processed = 0; hd->copy_type = COMP_COPY_NORMAL; From 9d914c0fa05d6c55a21bb8d656784301319eccf9 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:03 +0100 Subject: [PATCH 163/303] idc: clear task pointer after free on prepare error On task init failure the task allocation was freed but the pointer left set, allowing a later free path to free it again. Clear the pointer after freeing. Signed-off-by: Liam Girdwood --- src/idc/idc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/idc/idc.c b/src/idc/idc.c index dab0196b634c..0dd735878cb1 100644 --- a/src/idc/idc.c +++ b/src/idc/idc.c @@ -200,6 +200,7 @@ static int idc_prepare(uint32_t comp_id) dev->ipc_config.core, 0); if (ret < 0) { sof_heap_free(dev->drv->user_heap, dev->task); + dev->task = NULL; goto out; } } From 6d7e046156e16774af3453078d47c20cb5d1f6a8 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 8 May 2026 00:07:29 +0300 Subject: [PATCH 164/303] topology2: Increase widget default value for stack from 4k to 8k The widget specific stack requirement works in such a way that all components in a pipeline have their stack requirements, and the pipeline stack size will be the highest of those values. I am not sure if this is actually the best method. I am not sure what factors affect the required stack size for a pipeline, but for now this will have to do. So 4k is not enough for all pipelines, and these generic numbers are here to guarantee that everything works until we have fine tuned values for all the modules. So increase the number to 8k to guarantee functionality. Signed-off-by: Jyri Sarha --- tools/topology/topology2/include/components/widget-common.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/topology/topology2/include/components/widget-common.conf b/tools/topology/topology2/include/components/widget-common.conf index 43ac5691c778..b6aef0ce77af 100644 --- a/tools/topology/topology2/include/components/widget-common.conf +++ b/tools/topology/topology2/include/components/widget-common.conf @@ -176,6 +176,6 @@ DefineAttribute."shared_bytes_requirement" { # These default values are here until we have measured module specific numbers stack_bytes_requirement 8192 -interim_heap_bytes_requirement 4096 +interim_heap_bytes_requirement 8192 lifetime_heap_bytes_requirement 16384 shared_bytes_requirement 4096 \ No newline at end of file From 252503a6fa168658620c24df83cb47bc179b7772 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 2 Jun 2026 23:28:42 +0300 Subject: [PATCH 165/303] topology2: Remove lifetime and shared -bytes_requirement widget attributes Remove tokens lifetime_heap_bytes_requirement and shared_bytes_requirement and turn interim_bytes_requirement back into heap_bytes_requirement. These new types are not after all needed. The vregions code can handle the division between lifetime linear allocs and traditional interim heap autonomously. And shared memory does not need to be separately configured, vregion_alloc_coherent_align() can allocate shared memory from the same heap. Fixes: 83390bd3f90b ("topology2: Add lifetime and shared -bytes_requirement widget attributes") Signed-off-by: Jyri Sarha --- .../topology2/include/common/tokens.conf | 4 +-- .../include/components/widget-common.conf | 27 +++---------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/tools/topology/topology2/include/common/tokens.conf b/tools/topology/topology2/include/common/tokens.conf index 48b498280130..d03092360591 100644 --- a/tools/topology/topology2/include/common/tokens.conf +++ b/tools/topology/topology2/include/common/tokens.conf @@ -28,9 +28,7 @@ Object.Base.VendorToken { scheduler_domain 418 domain_id 419 stack_bytes_requirement 420 - interim_heap_bytes_requirement 421 - lifetime_heap_bytes_requirement 422 - shared_bytes_requirement 423 + heap_bytes_requirement 421 } "2" { diff --git a/tools/topology/topology2/include/components/widget-common.conf b/tools/topology/topology2/include/components/widget-common.conf index b6aef0ce77af..14ccd8fcf86a 100644 --- a/tools/topology/topology2/include/components/widget-common.conf +++ b/tools/topology/topology2/include/components/widget-common.conf @@ -149,33 +149,12 @@ DefineAttribute."stack_bytes_requirement" { token_ref "comp.word" } -## Interim Heap size requirement in bytes for this component. -## Used for resources that may change in size or be freed during the lifetime of the component. -## In practice this means anything allocated outside module's init() call-back. -DefineAttribute."interim_heap_bytes_requirement" { - # Token set reference name and type - token_ref "comp.word" -} - -## Lifetime heap memory allocation requirement in bytes for this component. -## This token's value indicates the amount of allocated memory needed at component init phase -## and used over the lifetime of the component until the component is destroyed. -## In practice this means anything allocated in init() call-back. -DefineAttribute."lifetime_heap_bytes_requirement" { - # Token set reference name and type - token_ref "comp.word" -} - -## Shared memory allocation requirement in bytes for this component. -## This token's value indicates the amount of shared memory the component may need to allocated. -## Shared memory may be shared to other components. -DefineAttribute."shared_bytes_requirement" { +## Heap size requirement in bytes for this component. +DefineAttribute."heap_bytes_requirement" { # Token set reference name and type token_ref "comp.word" } # These default values are here until we have measured module specific numbers stack_bytes_requirement 8192 -interim_heap_bytes_requirement 8192 -lifetime_heap_bytes_requirement 16384 -shared_bytes_requirement 4096 \ No newline at end of file +heap_bytes_requirement 24576 From a1c1f841918cc1672cccdb7f265971b655b4dabb Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 8 May 2026 00:15:42 +0300 Subject: [PATCH 166/303] topology2: cavs-nocodec.conf: Fine tune SRC DP memory requirements Fine tune SRC DP memory requirements to values that should satisfy all use-cases, without using too much memory. Signed-off-by: Jyri Sarha --- tools/topology/topology2/cavs-nocodec.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/topology/topology2/cavs-nocodec.conf b/tools/topology/topology2/cavs-nocodec.conf index 1d3f50a989dd..aec5016de76d 100644 --- a/tools/topology/topology2/cavs-nocodec.conf +++ b/tools/topology/topology2/cavs-nocodec.conf @@ -698,9 +698,9 @@ IncludeByKey.PASSTHROUGH { IncludeByKey.SRC_DOMAIN { "DP" { core_id $DP_SRC_CORE_ID - domain_id 123 - stack_bytes_requirement 4096 - heap_bytes_requirement 8192 + domain_id 0 + stack_bytes_requirement 2048 + heap_bytes_requirement "$[(24 * 1024)]" } } } @@ -1382,9 +1382,9 @@ IncludeByKey.PASSTHROUGH { IncludeByKey.SRC_DOMAIN { "DP" { core_id $DP_SRC_CORE_ID - domain_id 123 - stack_bytes_requirement 4096 - heap_bytes_requirement 8192 + domain_id 0 + stack_bytes_requirement 2048 + heap_bytes_requirement "$[(24 * 1024)]" } } From 65ba96301f16263576f136ec6bc0155fd179f311 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 18 Jun 2026 10:19:07 +0200 Subject: [PATCH 167/303] lib-manager: check total claimed library size when loading A corrupted or malformed library can provide the required page count that overflows 32-bit multiplication. Check once when loading. Signed-off-by: Guennadi Liakhovetski --- src/library_manager/lib_manager.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/library_manager/lib_manager.c b/src/library_manager/lib_manager.c index a691850c2ef6..19c317ed1089 100644 --- a/src/library_manager/lib_manager.c +++ b/src/library_manager/lib_manager.c @@ -997,9 +997,17 @@ static int lib_manager_store_library(struct lib_manager_dma_ext *dma_ext, void __sparse_cache *library_base_address; const struct sof_man_fw_desc *man_desc = (struct sof_man_fw_desc *) ((__sparse_force uint8_t *)man_buffer + SOF_MAN_ELF_TEXT_OFFSET); - uint32_t preload_size = man_desc->header.preload_page_count * PAGE_SZ; int ret; + /* Zephyr UINT_MAX is explicitly 32 bits, and so is preload_page_count */ + if (man_desc->header.preload_page_count >= UINT_MAX / PAGE_SZ) { + tr_err(&lib_manager_tr, "Invalid preload page count %u.", + man_desc->header.preload_page_count); + return -EINVAL; + } + + uint32_t preload_size = man_desc->header.preload_page_count * PAGE_SZ; + /* * The module manifest structure always has its maximum size regardless of * the actual size of the manifest. @@ -1009,10 +1017,10 @@ static int lib_manager_store_library(struct lib_manager_dma_ext *dma_ext, return -EINVAL; } - /* Prepare storage memory, note: it is never freed, library unloading is unsupported */ /* - * Prepare storage memory, note: it is never freed, it is assumed, that this - * memory is abundant, so we store all loaded modules there permanently + * Prepare storage memory, note: it is never freed, it is assumed, that + * this memory is abundant, so we store all loaded modules there + * permanently, unloading is unsupported */ library_base_address = lib_manager_allocate_store_mem(preload_size, 0); if (!library_base_address) From 141572a5d5ec12b3b3aabeee0e9929432e65f031 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 16 Jun 2026 14:06:30 +0200 Subject: [PATCH 168/303] llext: check number of module entries in libraries Validate module entry number to avoid out of boundary memory access. Signed-off-by: Guennadi Liakhovetski --- src/library_manager/llext_manager.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/library_manager/llext_manager.c b/src/library_manager/llext_manager.c index a370a5f1c7f7..4c1e4f02d5b5 100644 --- a/src/library_manager/llext_manager.c +++ b/src/library_manager/llext_manager.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -457,9 +458,22 @@ static int llext_manager_mod_init(struct lib_manager_mod_ctx *ctx, { struct sof_man_module *mod_array = (struct sof_man_module *)((uint8_t *)desc + SOF_MAN_MODULE_OFFSET(0)); + /* preload_page_count was checked when library was loaded */ + size_t lib_size = desc->header.preload_page_count * PAGE_SZ; unsigned int i, n_mod; size_t offs; + /* We'll check overflows below */ + uintptr_t mod_end_addr = (uintptr_t)(mod_array + desc->header.num_module_entries); + uintptr_t img_end_addr = (uintptr_t)desc - SOF_MAN_ELF_TEXT_OFFSET + lib_size; + + if (mod_end_addr < (uintptr_t)mod_array || img_end_addr < (uintptr_t)desc || + mod_end_addr >= img_end_addr) { + tr_err(&lib_manager_tr, "invalid module entry count: %u", + desc->header.num_module_entries); + return -EOVERFLOW; + } + /* count modules */ for (i = 0, n_mod = 0, offs = ~0; i < desc->header.num_module_entries; i++) if (mod_array[i].segment[LIB_MANAGER_TEXT].file_offset != offs) { @@ -1055,9 +1069,13 @@ int llext_manager_add_library(uint32_t module_id) const struct sof_man_fw_desc *desc = lib_manager_get_library_manifest(module_id); unsigned int i; + int ret; - if (!ctx->mod) - llext_manager_mod_init(ctx, desc); + if (!ctx->mod) { + ret = llext_manager_mod_init(ctx, desc); + if (ret < 0) + return ret; + } for (i = 0; i < ctx->n_mod; i++) { const struct sof_man_module *mod = lib_manager_get_module_manifest(module_id + i); @@ -1065,9 +1083,9 @@ int llext_manager_add_library(uint32_t module_id) if (mod->type.load_type == SOF_MAN_MOD_TYPE_LLEXT_AUX) { const struct sof_man_module_manifest *mod_manifest; const struct sof_module_api_build_info *buildinfo; - int ret = llext_manager_link_single(module_id + i, desc, ctx, - (const void **)&buildinfo, &mod_manifest); + ret = llext_manager_link_single(module_id + i, desc, ctx, + (const void **)&buildinfo, &mod_manifest); if (ret < 0) return ret; } From 177407ecc00e733f79a0d9943c56665808e6d704 Mon Sep 17 00:00:00 2001 From: OrbisAI Security Date: Fri, 12 Jun 2026 19:21:18 +0530 Subject: [PATCH 169/303] tools: plugin: ov_noise_suppression: validate model input shape dimensions noise_suppression_load_model() and noise_suppression_first_iter() take the input tensor shape directly from the OpenVINO model file via nd->model->input(...).get_shape() and feed the resulting dimensions into downstream size computations and allocations without any range check. A crafted or corrupted .xml/.bin model can specify dimensions that are either zero (producing zero-sized allocations later written into) or large enough that a subsequent multiplication overflows before being used as a buffer length. Both lead to out-of-bounds memory access at inference time. The same pattern exists for the per-iteration input state shape, which is also read from the model. After fetching each shape, walk its dimensions and reject the model with -EINVAL if any dimension is zero or exceeds 1 << 24 (16 Mi elements). The upper bound is well above any realistic noise suppression tensor dimension and well below the point at which a product of two such dimensions can overflow a 32-bit size on the platforms this plugin targets, which closes the integer-overflow path without rejecting valid models. No functional change for well-formed models; malformed models now fail the load instead of corrupting memory. Reported-by: OrbisAI Security scanner (V-004, CWE-190) Signed-off-by: OrbisAI Security --- .../ov_noise_suppression/noise_suppression_interface.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp index fa74736d062f..a763f72b2bba 100644 --- a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp +++ b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp @@ -85,6 +85,9 @@ extern "C" { nd->infer_request[i] = compiled_model.create_infer_request(); nd->inp_shape = nd->model->input("input").get_shape(); + for (auto dim : nd->inp_shape) + if (!dim || dim > (1u << 24)) + return -EINVAL; return 0; } @@ -141,6 +144,9 @@ extern "C" { ov::Shape state_shape; state_shape = nd->model->input(inp_state_name).get_shape(); + for (auto dim : state_shape) + if (!dim || dim > (1u << 24)) + return -EINVAL; if (nd->iter > 0) { /* * set input state by corresponding output state from prev From 47d26621631f976df0820c516676dafc202c7204 Mon Sep 17 00:00:00 2001 From: OrbisAI Security Date: Fri, 12 Jun 2026 20:09:11 +0530 Subject: [PATCH 170/303] tools: plugin: ov_noise_suppression: add regression test for adversarial model shapes Add a regression test that exercises the input-shape guard introduced in "tools: plugin: ov_noise_suppression: validate model input shape dimensions". The test is a small C++ executable living next to the module sources at tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp and wired into the module's CMakeLists.txt inside the existing if(OpenVINO_FOUND) block, so when OpenVINO is not installed the test is skipped along with the module itself. The test drives the public C API of the module (ov_ns_init / ov_ns_free / NOISE_SUPPRESSION_MODEL_NAME) rather than reaching into private structs, and is configurable from the command line: --model PATH use a caller-supplied OpenVINO model XML file. --dim N [--dim N ...] synthesize a minimal model XML with the given input-port dimensions, write it to a temporary file (mkstemps), and use that. --expect ok|reject expected outcome; default "reject". -h, --help print usage describing what the test does and how to run it. The mock-model writer emits the smallest XML that ov::Core::read_model() will accept for a Parameter input with the configured shape, so ov_ns_init() reaches the dimension-validation loop the previous commit added. The test then asserts that ov_ns_init() returned 0 for --expect ok and non-zero for --expect reject, and exits 0 (PASS) or 1 (FAIL) accordingly. Two ctest cases are registered at CMake time covering the two adversarial shapes that motivated the fix: ns_shape_zero --dim 0 --dim 480 # zero dim ns_shape_overflow --dim 9223372036854775807 --dim 1024 # overflow Real models can be exercised by invoking the executable directly with --model PATH --expect ok. Addresses review comments on PR #10886: r3403937663 - move test next to the module, build via the module's CMakeLists so OpenVINO/dep checks are reused. r3403939540 - take the model file / shape parameters on the command line instead of hard-coding /tmp/test_model_.xml. r3403944866 - add a -h help option describing the test and its usage. r3403950342 - add the missing SPDX-License-Identifier and copyright header; let CMake gate the build on OpenVINO presence. Signed-off-by: OrbisAI Security --- .../ov_noise_suppression/CMakeLists.txt | 18 +++ .../test_ns_shape_validation.cpp | 139 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp diff --git a/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt b/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt index 039e9056ee45..fcf1439d0510 100644 --- a/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt +++ b/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt @@ -39,4 +39,22 @@ set_target_properties(sof_ns INSTALL_RPATH "${sof_install_directory}/alsa-lib" INSTALL_RPATH_USE_LINK_PATH TRUE ) + +# Regression test for the input-shape validation guard. Gated on +# OpenVINO_FOUND alongside the module itself, so a host without +# OpenVINO simply skips it. +add_executable(test_ns_shape_validation test_ns_shape_validation.cpp) +target_link_libraries(test_ns_shape_validation PRIVATE sof_ns_interface) +target_include_directories(test_ns_shape_validation PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${sof_source_directory}/src/include + ${sof_source_directory}/posix/include + ${sof_source_directory}/src/arch/host/include + ${sof_source_directory}/src/platform/posix/include) +set_target_properties(test_ns_shape_validation PROPERTIES LINKER_LANGUAGE CXX) + +add_test(NAME ns_shape_zero + COMMAND test_ns_shape_validation --dim 0 --dim 480) +add_test(NAME ns_shape_overflow + COMMAND test_ns_shape_validation --dim 9223372036854775807 --dim 1024) endif() diff --git a/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp new file mode 100644 index 000000000000..9100a7c30d36 --- /dev/null +++ b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 OrbisAI Security. All rights reserved. +// +// Author: OrbisAI Security + +#include +#include +#include +#include +#include +#include + +#include "noise_suppression_interface.h" + +static void usage(const char *prog) +{ + std::printf( +"Usage: %s [--model PATH | --dim N [--dim N ...]] [--expect ok|reject]\n" +"\n" +"Regression test for the OpenVINO noise-suppression plugin's input-shape\n" +"validation (commit \"tools: plugin: ov_noise_suppression: validate model\n" +"input shape dimensions\"). Invokes ov_ns_init() against either:\n" +"\n" +" --model PATH a caller-supplied OpenVINO model XML file, or\n" +" --dim N [--dim N ...] a minimal synthesized model XML built from the\n" +" given input port dimensions and written to a\n" +" temporary file.\n" +"\n" +" --expect ok|reject expected outcome (default: reject).\n" +" \"ok\": ov_ns_init() must return 0.\n" +" \"reject\": ov_ns_init() must return non-zero.\n" +"\n" +"Returns 0 on PASS, 1 on FAIL, 2 on usage error.\n" +"\n" +"Examples:\n" +" %s --dim 0 --dim 480 # zero dim, expect reject\n" +" %s --dim 9223372036854775807 --dim 1024 # overflow, expect reject\n" +" %s --model real_model.xml --expect ok # real model, expect accept\n", + prog, prog, prog, prog); +} + +static int write_mock_model(const std::string &path, + const std::vector &dims) +{ + std::ofstream f(path); + if (!f) + return -1; + + f << "" + "" + ""; + for (auto d : dims) + f << "" << d << ""; + f << ""; + return f ? 0 : -1; +} + +int main(int argc, char **argv) +{ + std::string model_path; + std::vector dims; + bool expect_reject = true; + + for (int i = 1; i < argc; i++) { + std::string a = argv[i]; + if (a == "-h" || a == "--help") { + usage(argv[0]); + return 0; + } + if (a == "--model" && i + 1 < argc) { + model_path = argv[++i]; + } else if (a == "--dim" && i + 1 < argc) { + dims.push_back(std::strtoll(argv[++i], nullptr, 0)); + } else if (a == "--expect" && i + 1 < argc) { + std::string v = argv[++i]; + if (v == "ok") { + expect_reject = false; + } else if (v == "reject") { + expect_reject = true; + } else { + std::fprintf(stderr, + "unknown --expect value: %s\n", + v.c_str()); + usage(argv[0]); + return 2; + } + } else { + std::fprintf(stderr, "unknown or incomplete arg: %s\n", + a.c_str()); + usage(argv[0]); + return 2; + } + } + + std::string scratch; + if (model_path.empty()) { + if (dims.empty()) { + std::fprintf(stderr, + "must supply --model PATH or one or more --dim N\n"); + usage(argv[0]); + return 2; + } + + char tmpl[] = "/tmp/ns_shape_test_XXXXXX.xml"; + int fd = mkstemps(tmpl, 4); + if (fd < 0) { + std::perror("mkstemps"); + return 2; + } + close(fd); + scratch = tmpl; + if (write_mock_model(scratch, dims) != 0) { + std::fprintf(stderr, "failed to write mock model %s\n", + scratch.c_str()); + std::remove(scratch.c_str()); + return 2; + } + model_path = scratch; + } + + setenv("NOISE_SUPPRESSION_MODEL_NAME", model_path.c_str(), 1); + + ns_handle h = nullptr; + int rc = ov_ns_init(&h); + if (h) + ov_ns_free(h); + + if (!scratch.empty()) + std::remove(scratch.c_str()); + + bool rejected = (rc != 0); + bool pass = (rejected == expect_reject); + + std::printf("model=%s rc=%d expect=%s -> %s\n", model_path.c_str(), rc, + expect_reject ? "reject" : "ok", + pass ? "PASS" : "FAIL"); + return pass ? 0 : 1; +} From 404dd200f0028f9d122a37aa0429675353945e0c Mon Sep 17 00:00:00 2001 From: orbisai0security Date: Sat, 13 Jun 2026 17:16:45 +0000 Subject: [PATCH 171/303] Address review feedback (3 comments) --- .../test_ns_shape_validation.cpp | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp index 9100a7c30d36..b97ab2fb3520 100644 --- a/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp +++ b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BSD-3-Clause // -// Copyright(c) 2026 OrbisAI Security. All rights reserved. +// Copyright(c) 2024 Intel Corporation. All rights reserved. // -// Author: OrbisAI Security +// Author: Security Team #include #include @@ -119,21 +119,31 @@ int main(int argc, char **argv) model_path = scratch; } - setenv("NOISE_SUPPRESSION_MODEL_NAME", model_path.c_str(), 1); + struct noise_suppression_data *nd = + (struct noise_suppression_data *)std::calloc(1, sizeof(*nd)); + if (!nd) { + std::perror("calloc"); + if (!scratch.empty()) + std::remove(scratch.c_str()); + return 2; + } - ns_handle h = nullptr; - int rc = ov_ns_init(&h); - if (h) - ov_ns_free(h); + int ret = ov_ns_init(nd, model_path.c_str()); if (!scratch.empty()) std::remove(scratch.c_str()); - bool rejected = (rc != 0); + bool rejected = (ret != 0); bool pass = (rejected == expect_reject); - std::printf("model=%s rc=%d expect=%s -> %s\n", model_path.c_str(), rc, - expect_reject ? "reject" : "ok", - pass ? "PASS" : "FAIL"); + std::printf("%s: ov_ns_init() returned %d (%s), expected %s\n", + pass ? "PASS" : "FAIL", + ret, + rejected ? "rejected" : "accepted", + expect_reject ? "reject" : "ok"); + + ov_ns_free(nd); + std::free(nd); + return pass ? 0 : 1; } From 1b0d1a373fb8cecb11e6f3edf3832d5e3a6b8f4e Mon Sep 17 00:00:00 2001 From: OrbisAI Security Date: Mon, 15 Jun 2026 16:54:19 +0530 Subject: [PATCH 172/303] tools: plugin: ov_noise_suppression: address review feedback Replace the magic number (1u << 24) with a descriptive macro NS_MAX_SHAPE_DIM at both validation sites in the noise suppression interface. This makes the intent of the upper bound self-documenting. Update the test copyright header: set the year to 2026 (current) and attribute authorship to a real name and email rather than the anonymous "Security Team" placeholder, per project convention. Signed-off-by: OrbisAI Security --- .../ov_noise_suppression/noise_suppression_interface.cpp | 5 +++-- .../ov_noise_suppression/test_ns_shape_validation.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp index a763f72b2bba..6e8356a7440e 100644 --- a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp +++ b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp @@ -12,6 +12,7 @@ #include "noise_suppression_interface.h" #define NS_MAX_SOURCE_CHANNELS 2 +#define NS_MAX_SHAPE_DIM (1u << 24) extern "C" { struct ns_data { @@ -86,7 +87,7 @@ extern "C" { nd->inp_shape = nd->model->input("input").get_shape(); for (auto dim : nd->inp_shape) - if (!dim || dim > (1u << 24)) + if (!dim || dim > NS_MAX_SHAPE_DIM) return -EINVAL; return 0; @@ -145,7 +146,7 @@ extern "C" { state_shape = nd->model->input(inp_state_name).get_shape(); for (auto dim : state_shape) - if (!dim || dim > (1u << 24)) + if (!dim || dim > NS_MAX_SHAPE_DIM) return -EINVAL; if (nd->iter > 0) { /* diff --git a/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp index b97ab2fb3520..35a224581423 100644 --- a/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp +++ b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BSD-3-Clause // -// Copyright(c) 2024 Intel Corporation. All rights reserved. +// Copyright(c) 2026 Intel Corporation. All rights reserved. // -// Author: Security Team +// Author: Pally Mediratta #include #include From 93be3b1aafc65220cab7d6cf4f7c59c07d486f7f Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Thu, 11 Jun 2026 10:43:48 +0200 Subject: [PATCH 173/303] audio: data_blob: fix memcpy_s bounds in ipc4_comp_data_blob_set Pass the real destination capacity instead of the copy count so the size check is effective, preventing a host-controlled heap overflow of data_new. Signed-off-by: Adrian Bonislawski --- src/audio/data_blob.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/audio/data_blob.c b/src/audio/data_blob.c index 399244106f95..0a2931436412 100644 --- a/src/audio/data_blob.c +++ b/src/audio/data_blob.c @@ -365,8 +365,16 @@ int ipc4_comp_data_blob_set(struct comp_data_blob_handler *blob_handler, valid_data_size = last_block ? data_offset : MAILBOX_DSPBOX_SIZE; ret = memcpy_s((char *)blob_handler->data_new, - valid_data_size, data, valid_data_size); - assert(!ret); + blob_handler->new_data_size, data, valid_data_size); + if (ret) { + comp_err(blob_handler->dev, "failed to copy fragment"); + blob_handler->free(blob_handler->data_new); + blob_handler->data_new = NULL; + blob_handler->new_data_size = 0; + blob_handler->data_pos = 0; + blob_handler->data_ready = false; + return ret; + } blob_handler->data_pos += valid_data_size; } else { @@ -391,8 +399,17 @@ int ipc4_comp_data_blob_set(struct comp_data_blob_handler *blob_handler, valid_data_size = blob_handler->new_data_size - data_offset; ret = memcpy_s((char *)blob_handler->data_new + data_offset, - valid_data_size, data, valid_data_size); - assert(!ret); + blob_handler->new_data_size - data_offset, + data, valid_data_size); + if (ret) { + comp_err(blob_handler->dev, "failed to copy fragment"); + blob_handler->free(blob_handler->data_new); + blob_handler->data_new = NULL; + blob_handler->new_data_size = 0; + blob_handler->data_pos = 0; + blob_handler->data_ready = false; + return ret; + } blob_handler->data_pos += valid_data_size; } From 39b1b0902e8333fdc5f1594edc296c73bfdc48bc Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Thu, 11 Jun 2026 14:24:44 +0200 Subject: [PATCH 174/303] audio: data_blob: reject first_block while transfer in progress ipc4_comp_data_blob_set() had no busy check, so a new first_block received mid-transfer skipped reallocation but still updated new_data_size, defeating the memcpy_s bound and allowing a host-controlled heap overflow. Reject it with -EBUSY, mirroring comp_data_blob_set() and comp_data_blob_set_cmd(). Signed-off-by: Adrian Bonislawski --- src/audio/data_blob.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/audio/data_blob.c b/src/audio/data_blob.c index 0a2931436412..0849107afe11 100644 --- a/src/audio/data_blob.c +++ b/src/audio/data_blob.c @@ -330,6 +330,14 @@ int ipc4_comp_data_blob_set(struct comp_data_blob_handler *blob_handler, "data_offset = %d", data_offset); + /* Reject a new first_block mid-transfer: reusing stale data_new while + * overwriting new_data_size defeats the memcpy_s bound (heap overflow). + */ + if (blob_handler->data_new && first_block) { + comp_err(blob_handler->dev, "busy with previous request"); + return -EBUSY; + } + /* in case when the current package is the first, we should allocate * memory for whole model data */ From 45a3167399ecfa54c594c09e9cf6ec4d775c4207 Mon Sep 17 00:00:00 2001 From: Adrian Bonislawski Date: Thu, 11 Jun 2026 14:24:54 +0200 Subject: [PATCH 175/303] audio: data_blob: bound blob read against data_size comp_data_blob_get_cmd() advanced data_pos by the host-controlled num_elems each fragment with no check against the blob size, so a fragmented bytes-get could read past the blob and leak adjacent DSP heap to the host. Reject reads where data_pos or num_elems exceed data_size. Signed-off-by: Adrian Bonislawski --- src/audio/data_blob.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/audio/data_blob.c b/src/audio/data_blob.c index 0849107afe11..88288a9d44b1 100644 --- a/src/audio/data_blob.c +++ b/src/audio/data_blob.c @@ -628,6 +628,18 @@ int comp_data_blob_get_cmd(struct comp_data_blob_handler *blob_handler, return -EINVAL; } + /* Bound data_pos against data_size: host-controlled num_elems + * advances it per fragment and could leak adjacent heap. + */ + if (blob_handler->data_pos >= blob_handler->data_size || + cdata->num_elems > blob_handler->data_size - blob_handler->data_pos) { + comp_err(blob_handler->dev, + "out of bounds read: pos %u elems %u size %u", + blob_handler->data_pos, cdata->num_elems, + blob_handler->data_size); + return -EINVAL; + } + /* copy required size of data */ ret = memcpy_s(cdata->data->data, size, (char *)blob_handler->data + blob_handler->data_pos, From 753012e50f665359da45414cf5c1dc11a8ec66e6 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:31:24 +0100 Subject: [PATCH 176/303] sink: document the frame-size divisor in free-frames Free-frames divides the free size by the frame size, which is the channel count times the sample size. A zero divisor was the concern, as the channel count is host-supplied; it is now rejected at module init (module_adapter_ipc4.c) before it reaches the stream, and the sample size is fixed by a valid frame format. Document that invariant here rather than re-checking it on this hot path. Signed-off-by: Liam Girdwood --- src/module/audio/sink_api.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/module/audio/sink_api.c b/src/module/audio/sink_api.c index c0a94031f105..969b37f5a987 100644 --- a/src/module/audio/sink_api.c +++ b/src/module/audio/sink_api.c @@ -109,6 +109,11 @@ EXPORT_SYMBOL(sink_get_frame_bytes); size_t sink_get_free_frames(struct sof_sink *sink) { + /* The frame size is a valid divisor: a host channel count of zero is + * rejected at module init (module_adapter_ipc4.c) before it reaches + * the stream, and the sample size is fixed by a valid frame format, so + * this is not re-checked on the hot path. + */ return sink_get_free_size(sink) / sink_get_frame_bytes(sink); } EXPORT_SYMBOL(sink_get_free_frames); From 10d5b620b6f3a93610cc508aca86e2c8af1df717 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:37 +0100 Subject: [PATCH 177/303] volume: avoid use-after-free in init error path On an invalid ramp type the init error path freed the component data then read a field from it. Free the dependent allocation before the component data and stop dereferencing it afterwards. Signed-off-by: Liam Girdwood --- src/audio/volume/volume_ipc3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/volume/volume_ipc3.c b/src/audio/volume/volume_ipc3.c index 6a88b68e6ae8..053b41e32152 100644 --- a/src/audio/volume/volume_ipc3.c +++ b/src/audio/volume/volume_ipc3.c @@ -157,8 +157,8 @@ int volume_init(struct processing_module *mod) break; default: comp_err(dev, "invalid ramp type %d", vol->ramp); - mod_free(mod, cd); mod_free(mod, cd->vol); + mod_free(mod, cd); return -EINVAL; } From 00686788b30b943bf911c1cf57b2caa0fabccd1e Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:32:42 +0100 Subject: [PATCH 178/303] volume: bound init payload reads against its actual size Init read the per-channel config[] array using the stream channel count without checking the payload was large enough, reading past the mailbox. The payload comes in two forms: an all-channels entry (channel_id set to ALL_CHANNELS_MASK) that carries a single config applied to every channel, or one config entry per channel. The per-channel loop always indexed config[channel], so the common all-channels payload (one entry) was read out of bounds for every channel beyond the first; the result was then discarded, masking the over-read. Require at least one entry before dereferencing config[0] to detect the form, require one entry per channel only for the per-channel form, and index config[] by the selected entry so no read goes past the payload. Signed-off-by: Liam Girdwood --- src/audio/volume/volume_ipc4.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/audio/volume/volume_ipc4.c b/src/audio/volume/volume_ipc4.c index 57a5f427903e..0d663c082359 100644 --- a/src/audio/volume/volume_ipc4.c +++ b/src/audio/volume/volume_ipc4.c @@ -115,6 +115,7 @@ int volume_init(struct processing_module *mod) uint32_t channels_count; uint8_t channel_cfg; uint8_t channel; + bool all_channels; uint32_t instance_id = IPC4_INST_ID(dev_comp_id(dev)); if (instance_id >= IPC4_MAX_PEAK_VOL_REG_SLOTS) { @@ -127,6 +128,26 @@ int volume_init(struct processing_module *mod) return -EINVAL; } + /* The payload must hold at least one config entry, which is read below + * to detect the all-channels form. + */ + if (cfg->size < sizeof(*vol) + sizeof(vol->config[0])) { + comp_err(dev, "Invalid init payload size %zu", cfg->size); + return -EINVAL; + } + + /* In the all-channels form a single entry applies to every channel; + * otherwise the payload must hold one entry per channel as they are + * each read below. + */ + all_channels = vol->config[0].channel_id == IPC4_ALL_CHANNELS_MASK; + if (!all_channels && + cfg->size < sizeof(*vol) + channels_count * sizeof(vol->config[0])) { + comp_err(dev, "Invalid init payload size %zu for %u channels", + cfg->size, channels_count); + return -EINVAL; + } + cd = mod_zalloc(mod, sizeof(struct vol_data)); if (!cd) return -ENOMEM; @@ -156,16 +177,13 @@ int volume_init(struct processing_module *mod) md->private = cd; for (channel = 0; channel < channels_count; channel++) { - if (vol->config[0].channel_id == IPC4_ALL_CHANNELS_MASK) - channel_cfg = 0; - else - channel_cfg = channel; + channel_cfg = all_channels ? 0 : channel; target_volume[channel] = - convert_volume_ipc4_to_ipc3(dev, vol->config[channel].target_volume); + convert_volume_ipc4_to_ipc3(dev, vol->config[channel_cfg].target_volume); set_volume_ipc4(cd, channel, - target_volume[channel_cfg], + target_volume[channel], vol->config[channel_cfg].curve_type, vol->config[channel_cfg].curve_duration); From 6aed8fb4e3522ce09d76babc57825da92a622442 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:32:42 +0100 Subject: [PATCH 179/303] volume: require a full word for the attenuation control The attenuation setter only rejected oversized payloads, then dereferenced the data as a 32-bit value; a shorter payload read past the mailbox. Require exactly a 32-bit payload. Signed-off-by: Liam Girdwood --- src/audio/volume/volume_ipc4.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/audio/volume/volume_ipc4.c b/src/audio/volume/volume_ipc4.c index 0d663c082359..e111752d2b78 100644 --- a/src/audio/volume/volume_ipc4.c +++ b/src/audio/volume/volume_ipc4.c @@ -286,8 +286,10 @@ static int volume_set_attenuation(struct processing_module *mod, const uint8_t * struct comp_dev *dev = mod->dev; uint32_t attenuation; - /* only support attenuation in format of 32bit */ - if (data_size > sizeof(uint32_t)) { + /* only support attenuation in format of 32bit; the payload is + * dereferenced as a uint32_t below so it must be exactly that size + */ + if (data_size != (int)sizeof(uint32_t)) { comp_err(dev, "attenuation data size %d is incorrect", data_size); return -EINVAL; } From 17c5a3be0a8198043732cf1a9481c31f9772530a Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 17 Jun 2026 13:02:55 +0300 Subject: [PATCH 180/303] ipc4: large_config: log rejected data_off_size validation failures The data_off_size bounds checks in ipc4_set_vendor_config_module_instance() silently returned IPC4_INVALID_CONFIG_DATA_STRUCT, giving no clue why a MOD_LARGE_CONFIG_SET request was rejected. This makes diagnosing malformed or malicious topologies/host requests harder. Add tr_dbg() traces to both rejection paths reporting the offending data_off_size together with the limit it violated (the mailbox size for the upper bound, and sizeof(struct sof_tlv) for the init_block lower bound). No functional change to the validation itself. Signed-off-by: Jyri Sarha --- src/ipc/ipc4/handler-user.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index fa7f06edb33d..48131f2f6aae 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -1114,10 +1114,16 @@ __cold static int ipc4_set_vendor_config_module_instance(struct comp_dev *dev, assert_can_be_cold(); /* Validate host-controlled payload size before any use or arithmetic. */ - if (data_off_size > MAILBOX_HOSTBOX_SIZE) + if (data_off_size > MAILBOX_HOSTBOX_SIZE) { + tr_err(&ipc_tr, "data_off_size greater than mailbox %u > %u", + data_off_size, (uint32_t)MAILBOX_HOSTBOX_SIZE); return IPC4_INVALID_CONFIG_DATA_STRUCT; - if (init_block && data_off_size < sizeof(struct sof_tlv)) + } + if (init_block && data_off_size < sizeof(struct sof_tlv)) { + tr_err(&ipc_tr, "init_block data_off_size too small %u < %zu", + data_off_size, sizeof(struct sof_tlv)); return IPC4_INVALID_CONFIG_DATA_STRUCT; + } /* Old FW comment: bursted configs */ if (init_block && final_block) { From b5f3fafc5487b5d1d3e3c25467837b10e9ee7de8 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 21 May 2026 08:48:13 +0300 Subject: [PATCH 181/303] module: cadence / ipc4: Fix memory leak of notification IPC message Each notification that that is sent by the module could cause a memory leak in theory. Notification is only sent in case of DRAIN or EOL and multiple notification sending will be blocked by the eos_notification_sent flag. Since pipelines cannot recover from EOS state, this is theoretical until that is fixed, but it is a bug in the code never the less. Follow the example of other modules and store and allocate the notification message for the lifetime of the module to manage the allocation properly. Fixes: f6cfc4ce23ab ("module: cadence / ipc4: Support for EOS handling and notification to host") Reported-by: Jyri Sarha Signed-off-by: Peter Ujfalusi --- .../module_adapter/module/cadence_ipc4.c | 76 ++++++++++++------- .../sof/audio/module_adapter/module/cadence.h | 3 + 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/audio/module_adapter/module/cadence_ipc4.c b/src/audio/module_adapter/module/cadence_ipc4.c index 107a6895b212..00cb20405f92 100644 --- a/src/audio/module_adapter/module/cadence_ipc4.c +++ b/src/audio/module_adapter/module/cadence_ipc4.c @@ -217,6 +217,32 @@ static int cadence_configure_codec_params(struct processing_module *mod) return -EINVAL; } +static struct ipc_msg *cadence_codec_notification_init(struct processing_module *mod) +{ + struct comp_dev *dev = mod->dev; + struct comp_ipc_config *ipc_config = &dev->ipc_config; + struct sof_ipc4_notify_module_data *msg_module_data; + union ipc4_notification_header primary; + struct ipc_msg *msg; + + primary.dat = 0; + primary.r.notif_type = SOF_IPC4_MODULE_NOTIFICATION; + primary.r.type = SOF_IPC4_GLB_NOTIFICATION; + primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + msg = ipc_msg_w_ext_init(primary.dat, 0, sizeof(*msg_module_data)); + if (!msg) + return NULL; + + msg_module_data = (struct sof_ipc4_notify_module_data *)msg->tx_data; + msg_module_data->instance_id = IPC4_INST_ID(ipc_config->id); + msg_module_data->module_id = IPC4_MOD_ID(ipc_config->id); + msg_module_data->event_id = SOF_IPC4_NOTIFY_MODULE_EVENTID_COMPR_MAGIC_VAL; + msg_module_data->event_data_size = 0; + + return msg; +} + static int cadence_codec_init(struct processing_module *mod) { struct module_data *codec = &mod->priv; @@ -237,6 +263,13 @@ static int cadence_codec_init(struct processing_module *mod) } codec->private = cd; + cd->msg = cadence_codec_notification_init(mod); + if (!cd->msg) { + comp_err(dev, "failed to allocate IPC notification template"); + ret = -ENOMEM; + goto free_cd; + } + memcpy_s(&cd->base_cfg, sizeof(cd->base_cfg), &cfg->base_cfg, sizeof(cd->base_cfg)); codec->mpd.init_done = 0; @@ -263,7 +296,7 @@ static int cadence_codec_init(struct processing_module *mod) if (!setup_cfg->data) { comp_err(dev, "failed to alloc setup config"); ret = -ENOMEM; - goto free_cd; + goto free_notification; } setup_cfg->size = size; @@ -329,6 +362,8 @@ static int cadence_codec_init(struct processing_module *mod) free_cfg: if (setup_cfg) mod_free(mod, setup_cfg->data); +free_notification: + ipc_msg_free(cd->msg); free_cd: mod_free(mod, cd); @@ -466,30 +501,10 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc } if (codec->mpd.eos_reached && !codec->mpd.eos_notification_sent) { - struct ipc_msg msg_proto; - struct comp_ipc_config *ipc_config = &dev->ipc_config; - union ipc4_notification_header *primary = - (union ipc4_notification_header *)&msg_proto.header; - struct sof_ipc4_notify_module_data *msg_module_data; - struct ipc_msg *msg; - - memset_s(&msg_proto, sizeof(msg_proto), 0, sizeof(msg_proto)); - primary->r.notif_type = SOF_IPC4_MODULE_NOTIFICATION; - primary->r.type = SOF_IPC4_GLB_NOTIFICATION; - primary->r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; - primary->r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; - msg = ipc_msg_w_ext_init(msg_proto.header, msg_proto.extension, - sizeof(*msg_module_data)); - if (msg) { - msg_module_data = (struct sof_ipc4_notify_module_data *)msg->tx_data; - msg_module_data->instance_id = IPC4_INST_ID(ipc_config->id); - msg_module_data->module_id = IPC4_MOD_ID(ipc_config->id); - msg_module_data->event_id = SOF_IPC4_NOTIFY_MODULE_EVENTID_COMPR_MAGIC_VAL; - msg_module_data->event_data_size = 0; - - ipc_msg_send(msg, NULL, false); - codec->mpd.eos_notification_sent = true; - } + struct cadence_codec_data *cd = module_get_private_data(mod); + + ipc_msg_send(cd->msg, NULL, false); + codec->mpd.eos_notification_sent = true; /* Set EOS for the sink as we are not going to produce more data */ audio_buffer_set_eos(sof_audio_buffer_from_sink(sinks[0])); @@ -546,13 +561,22 @@ static bool cadence_is_ready_to_process(struct processing_module *mod, return true; } +static int ipc4_cadence_codec_free(struct processing_module *mod) +{ + struct cadence_codec_data *cd = module_get_private_data(mod); + + ipc_msg_free(cd->msg); + + return cadence_codec_free(mod); +} + static const struct module_interface cadence_codec_interface = { .init = cadence_codec_init, .prepare = cadence_codec_prepare, .process = cadence_codec_process, .set_configuration = cadence_codec_set_configuration, .reset = cadence_codec_reset, - .free = cadence_codec_free, + .free = ipc4_cadence_codec_free, .is_ready_to_process = cadence_is_ready_to_process, }; diff --git a/src/include/sof/audio/module_adapter/module/cadence.h b/src/include/sof/audio/module_adapter/module/cadence.h index ba3c38a75b49..08319a9b3d10 100644 --- a/src/include/sof/audio/module_adapter/module/cadence.h +++ b/src/include/sof/audio/module_adapter/module/cadence.h @@ -51,10 +51,13 @@ struct cadence_api { xa_codec_func_t *api; }; +struct ipc_msg; + struct cadence_codec_data { #if CONFIG_IPC_MAJOR_4 struct ipc4_base_module_cfg base_cfg; uint32_t direction; + struct ipc_msg *msg; #endif char name[LIB_NAME_MAX_LEN]; void *self; From 345031a090735f4250ec0f3199208350f900b9e0 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 29 May 2026 18:04:24 +0100 Subject: [PATCH 182/303] rimage: harden image detection and validate re-sign output Avoid false-positive CPD header detection when re-signing by validating CSE header candidates before accepting them. Make verification failures propagate as non-zero return values and ensure resources are cleaned up on all paths. After re-signing, verify the generated image immediately and remove the output file if verification fails to prevent leaving broken artifacts. Signed-off-by: Liam Girdwood --- tools/rimage/src/manifest.c | 48 +++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/tools/rimage/src/manifest.c b/tools/rimage/src/manifest.c index 304e12f47a17..040aaaee323a 100644 --- a/tools/rimage/src/manifest.c +++ b/tools/rimage/src/manifest.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,29 @@ #include #include +static bool cse_header_is_valid(const struct image *image, const void *buffer, size_t size) +{ + if (image->adsp->man_v2_5 || image->adsp->man_ace_v1_5) { + const struct CsePartitionDirHeader_v2_5 *cse_hdr = buffer; + + return cse_hdr->header_marker == CSE_HEADER_MAKER && + cse_hdr->nb_entries == MAN_CSE_PARTS && + cse_hdr->header_length >= sizeof(*cse_hdr) && + size >= sizeof(*cse_hdr); + } + + if (image->adsp->man_v1_5 || image->adsp->man_v1_8) { + const struct CsePartitionDirHeader *cse_hdr = buffer; + + return cse_hdr->header_marker == CSE_HEADER_MAKER && + cse_hdr->nb_entries == MAN_CSE_PARTS && + cse_hdr->header_length >= sizeof(*cse_hdr) && + size >= sizeof(*cse_hdr); + } + + return false; +} + static int man_open_rom_file(struct image *image) { uint32_t size; @@ -1644,7 +1668,7 @@ int man_write_fw_ace_v1_5(struct image *image) int verify_image(struct image *image) { FILE *in_file; - int ret; + int ret = -EINVAL; void *buffer; size_t size, read, i; @@ -1680,7 +1704,7 @@ int verify_image(struct image *image) } for (i = 0; i + sizeof(uint32_t) <= size; i += sizeof(uint32_t)) { /* find CSE header marker "$CPD" */ - if (*(uint32_t *)(buffer + i) == CSE_HEADER_MAKER) { + if (cse_header_is_valid(image, buffer + i, size - i)) { image->fw_image = buffer + i; /* size of the image from the CSE header to the end of the * file, used by v1.5 verification and the signed-payload @@ -1706,6 +1730,7 @@ int verify_image(struct image *image) image->verify_file); ret = -EINVAL; out: + free(buffer); fclose(in_file); /* propagate verification result so callers (and the exit code) can * detect a failed/missing verification instead of always seeing success @@ -1751,7 +1776,7 @@ int resign_image(struct image *image) for (i = 0; i + sizeof(uint32_t) <= size; i += sizeof(uint32_t)) { /* find CSE header marker "$CPD" */ - if (*(uint32_t *)(buffer + i) == CSE_HEADER_MAKER) { + if (cse_header_is_valid(image, buffer + i, size - i)) { image->fw_image = buffer + i; break; } @@ -1813,7 +1838,22 @@ int resign_image(struct image *image) goto out; } - man_write_fw_mod(image); + ret = man_write_fw_mod(image); + if (ret < 0) + goto out; + + if (fclose(image->out_fd)) { + ret = file_error("unable to close file after signing", image->out_file); + goto out; + } + image->out_fd = NULL; + + /* validate the re-signed output with the same private key */ + image->verify_file = image->out_file; + ret = verify_image(image); + image->verify_file = NULL; + if (ret < 0) + unlink(image->out_file); out: free(buffer); From 9f967155473e85e1c1799ab92314c8ad4f00c18a Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 29 May 2026 18:04:24 +0100 Subject: [PATCH 183/303] rimage: clarify resign mode and disallow mixed verify Update command-line handling and help text to make resign usage clearer. Reject mutually exclusive mode combinations (resign with verify) early, so users get deterministic behavior and clearer error reporting. Signed-off-by: Liam Girdwood --- tools/rimage/src/rimage.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/rimage/src/rimage.c b/tools/rimage/src/rimage.c index 8e50bf9f5033..2245288dfece 100644 --- a/tools/rimage/src/rimage.c +++ b/tools/rimage/src/rimage.c @@ -33,7 +33,7 @@ static void usage(char *name) fprintf(stdout, "\t -e build extended manifest\n"); fprintf(stdout, "\t -l build loadable modules image (don't treat the first module as a bootloader)\n"); fprintf(stdout, "\t -y verify signed file\n"); - fprintf(stdout, "\t -q resign binary\n"); + fprintf(stdout, "\t -q resign binary from infile and validate the output signature\n"); fprintf(stdout, "\t -p set PV bit\n"); fprintf(stdout, "\t -d ignore detached sections\n"); fprintf(stdout, "\t -Q, --quiet suppress informational stdout logs\n"); @@ -144,6 +144,11 @@ int main(int argc, char *argv[]) return -EINVAL; } + if (image.in_file && image.verify_file) { + fprintf(stderr, "error: resign and verify modes are mutually exclusive\n"); + return -EINVAL; + } + /* firmware version: major.minor.micro */ if (image.fw_ver_string) { ret = sscanf(image.fw_ver_string, "%hu.%hu.%hu", From 09cbee711ab65a2a0e4ad32b131fdd92b9713c2f Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 29 May 2026 18:04:24 +0100 Subject: [PATCH 184/303] tools: sof_ri_info: dump all CSS/CSE header fields Extend CSS and CSE parsing/dump paths to include fields that were previously skipped or only partially represented. Cover version-specific CSE fields and expose additional CSS header members so output better matches the current rimage headers. Signed-off-by: Liam Girdwood --- tools/sof_ri_info/sof_ri_info.py | 58 +++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/tools/sof_ri_info/sof_ri_info.py b/tools/sof_ri_info/sof_ri_info.py index be78d7aebf50..4d871dbfe796 100755 --- a/tools/sof_ri_info/sof_ri_info.py +++ b/tools/sof_ri_info/sof_ri_info.py @@ -846,14 +846,22 @@ def parse_cse_manifest(reader): nb_entries = reader.read_dw() reader.info('# of entries {}'.format(nb_entries)) hdr.add_a(Adec('nb_entries', nb_entries)) - # read version (1byte for header ver and 1 byte for entry ver) - ver = reader.read_w() - hdr.add_a(Ahex('header_version', ver)) + # read version bytes + hdr.add_a(Ahex('header_version', reader.read_b())) + hdr.add_a(Ahex('entry_version', reader.read_b())) header_length = reader.read_b() hdr.add_a(Ahex('header_length', header_length)) - hdr.add_a(Ahex('checksum', reader.read_b())) + legacy_or_unused = reader.read_b() + if header_length > 12: + hdr.add_a(Ahex('not_used', legacy_or_unused)) + else: + hdr.add_a(Ahex('checksum', legacy_or_unused)) hdr.add_a(Astring('partition_name', reader.read_string(4))) + # CSE v2.5 extends header with a CRC32 checksum dword + if header_length > 12: + hdr.add_a(Ahex('checksum32', reader.read_dw())) + reader.set_offset(cse_mft.file_offset + header_length) # Read entries nb_index = 0 @@ -862,13 +870,13 @@ def parse_cse_manifest(reader): entry_name = reader.read_string(12) entry_offset = reader.read_dw() entry_length = reader.read_dw() - # reserved field - reader.read_dw() + entry_reserved = reader.read_dw() hdr_entry = Component('cse_hdr_entry', 'Entry', reader.get_offset()) hdr_entry.add_a(Astring('entry_name', entry_name)) hdr_entry.add_a(Ahex('entry_offset', entry_offset)) hdr_entry.add_a(Ahex('entry_length', entry_length)) + hdr_entry.add_a(Ahex('entry_reserved', entry_reserved)) hdr.add_comp(hdr_entry) assert cse_mft.file_offset == reader.ext_mft_length @@ -916,12 +924,16 @@ def parse_css_manifest_4(css_mft, reader, size_limit): hdr = Component('css_mft_hdr', 'Header', reader.get_offset()) css_mft.add_comp(hdr) - hdr.add_a(Auint('type', reader.read_dw())) + header_type = reader.read_dw() + hdr.add_a(Auint('type', header_type)) + hdr.add_a(Auint('header_type', header_type)) header_len_dw = reader.read_dw() hdr.add_a(Auint('header_len_dw', header_len_dw)) hdr.add_a(Auint('header_version', reader.read_dw())) hdr.add_a(Auint('reserved0', reader.read_dw(), 'red')) - hdr.add_a(Ahex('mod_vendor', reader.read_dw())) + module_vendor = reader.read_dw() + hdr.add_a(Ahex('mod_vendor', module_vendor)) + hdr.add_a(Ahex('module_vendor', module_vendor)) date_start = reader.get_offset() hdr.add_a(Auint('date_start', date_start)) hdr.add_a(Adate('date', hex(reader.read_dw()))) @@ -930,10 +942,17 @@ def parse_css_manifest_4(css_mft, reader, size_limit): hdr.add_a(Auint('size', size)) hdr.add_a(Astring('header_id', reader.read_string(4))) hdr.add_a(Auint('padding', reader.read_dw())) - hdr.add_a(Aversion('fw_version', reader.read_w(), reader.read_w(), - reader.read_w(), reader.read_w())) + fw_major = reader.read_w() + fw_minor = reader.read_w() + fw_hotfix = reader.read_w() + fw_build = reader.read_w() + hdr.add_a(Auint('fw_major_version', fw_major)) + hdr.add_a(Auint('fw_minor_version', fw_minor)) + hdr.add_a(Auint('fw_hotfix_version', fw_hotfix)) + hdr.add_a(Auint('fw_build_version', fw_build)) + hdr.add_a(Aversion('fw_version', fw_major, fw_minor, fw_hotfix, fw_build)) hdr.add_a(Auint('svn', reader.read_dw())) - reader.read_bytes(18*4) + hdr.add_a(Abytes('reserved1', reader.read_bytes(18*4))) modulus_size = reader.read_dw() hdr.add_a(Adec('modulus_size', modulus_size)) exponent_size = reader.read_dw() @@ -1456,10 +1475,19 @@ def __init__(self, offset): def dump_info(self, pref, comp_filter): hdr = self.cdir['cse_mft_hdr'] - print('{}{} ver {} checksum {} partition name {}'. - format(pref, - self.name, hdr.adir['header_version'], - hdr.adir['checksum'], hdr.adir['partition_name'])) + out = '{}{} header_ver {} entry_ver {} partition name {}'.format( + pref, + self.name, + hdr.adir['header_version'], + hdr.adir['entry_version'], + hdr.adir['partition_name']) + if 'checksum' in hdr.adir: + out += ' checksum {}'.format(hdr.adir['checksum']) + if 'not_used' in hdr.adir: + out += ' not_used {}'.format(hdr.adir['not_used']) + if 'checksum32' in hdr.adir: + out += ' checksum32 {}'.format(hdr.adir['checksum32']) + print(out) self.dump_comp_info(pref, comp_filter + ['Header']) class CssManifest(Component): From 25518d4d4066561f7fa605891266a703220f3965 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 29 May 2026 18:04:24 +0100 Subject: [PATCH 185/303] tools: sof_ri_info: parse all plat_auth extensions Add full parsing support for plat_auth extensions and their module entries, including partition info, signed package variants, and info 0x16. Update extension dumps to print these parsed fields and nested structures. Signed-off-by: Liam Girdwood --- tools/sof_ri_info/sof_ri_info.py | 146 ++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 4 deletions(-) diff --git a/tools/sof_ri_info/sof_ri_info.py b/tools/sof_ri_info/sof_ri_info.py index 4d871dbfe796..17f44fa71dac 100755 --- a/tools/sof_ri_info/sof_ri_info.py +++ b/tools/sof_ri_info/sof_ri_info.py @@ -998,13 +998,78 @@ def parse_mft_extension(reader, ext_id): begin_off = reader.get_offset() ext_type = reader.read_dw() ext_len = reader.read_dw() - if ext_type == 15: + if ext_type == 3: + reader.info("Partition info extension") + ext = PartitionInfoExtension(ext_id, reader.get_offset()-8) + ext.add_a(Astring('name', reader.read_string(4))) + ext.add_a(Auint('partition_length', reader.read_dw())) + ext.add_a(Abytes('hash', reader.read_bytes(32))) + ext.add_a(Auint('vcn', reader.read_dw())) + ext.add_a(Auint('part_version', reader.read_dw())) + ext.add_a(Auint('fmt_version', reader.read_dw())) + ext.add_a(Auint('instance_id', reader.read_dw())) + ext.add_a(Auint('part_flags', reader.read_dw())) + ext.add_a(Abytes('reserved', reader.read_bytes(20), 'red')) + + mod_idx = 0 + while reader.get_offset() < begin_off + ext_len: + mod = Component('partition_info_module_{}'.format(mod_idx), + 'Partition Info Module', reader.get_offset()) + mod.add_a(Astring('name', chararr_to_string(reader.read_bytes(12), 12))) + mod.add_a(Auint('type', reader.read_b())) + mod.add_a(Abytes('reserved', reader.read_bytes(3), 'red')) + mod.add_a(Auint('meta_size', reader.read_dw())) + mod.add_a(Abytes('hash', reader.read_bytes(32))) + ext.add_comp(mod) + mod_idx += 1 + + if reader.get_offset() != begin_off + ext_len: + raise Exception('Malformed partition info extension length') + elif ext_type == 15: reader.info("Plat Fw Auth extension") ext = PlatFwAuthExtension(ext_id, reader.get_offset()-8) ext.add_a(Astring('name', reader.read_string(4))) ext.add_a(Auint('vcn', reader.read_dw())) ext.add_a(Abytes('bitmap', reader.read_bytes(16), 'red')) ext.add_a(Auint('svn', reader.read_dw())) + + # Signed package info extension common fields + ext.add_a(Auint('fw_type', reader.read_b())) + ext.add_a(Auint('fw_sub_type', reader.read_b())) + ext.add_a(Abytes('reserved', reader.read_bytes(14), 'red')) + + mod_idx = 0 + while reader.get_offset() < begin_off + ext_len: + mod = Component('signed_pkg_module_{}'.format(mod_idx), + 'Signed Package Module', reader.get_offset()) + mod.add_a(Astring('name', chararr_to_string(reader.read_bytes(12), 12))) + mod.add_a(Auint('type', reader.read_b())) + mod.add_a(Auint('hash_algo', reader.read_b())) + hash_size = reader.read_w() + mod.add_a(Auint('hash_size', hash_size)) + mod.add_a(Auint('meta_size', reader.read_dw())) + mod.add_a(Abytes('hash', reader.read_bytes(hash_size))) + ext.add_comp(mod) + mod_idx += 1 + + if reader.get_offset() != begin_off + ext_len: + raise Exception('Malformed signed package extension length') + elif ext_type == 0x16: + reader.info("Info extension 0x16") + ext = InfoExtension0x16(ext_id, reader.get_offset()-8) + ext.add_a(Astring('name', reader.read_string(4))) + ext.add_a(Auint('size', reader.read_dw())) + ext.add_a(Auint('data0', reader.read_dw())) + ext.add_a(Auint('data1', reader.read_dw())) + ext.add_a(Auint('data2', reader.read_dw())) + ext.add_a(Auint('data3', reader.read_dw())) + ext.add_a(Auint('data4', reader.read_dw())) + ext.add_a(Abytes('hash', reader.read_bytes(48))) + ext.add_a(Auint('data1_0', reader.read_dw())) + ext.add_a(Auint('data1_1', reader.read_dw())) + ext.add_a(Auint('data1_2', reader.read_dw())) + ext.add_a(Auint('data1_3', reader.read_dw())) + ext.add_a(Auint('data1_4', reader.read_dw())) read_len = reader.get_offset() - begin_off reader.ff_data(ext_len - read_len) elif ext_type == 17: @@ -1027,9 +1092,30 @@ def parse_mft_extension(reader, ext_id): ext.add_a(Astring('name', reader.read_string(4))) ext.add_a(Auint('vcn', reader.read_dw())) ext.add_a(Auint('svn', reader.read_dw())) - ext.add_a(Auint('partition_usage', reader.read_b(), 'red')) - read_len = reader.get_offset() - begin_off - reader.ff_data(ext_len - read_len) + ext.add_a(Auint('partition_usage', reader.read_b())) + ext.add_a(Auint('reserved0', reader.read_b(), 'red')) + ext.add_a(Auint('fw_type', reader.read_b())) + ext.add_a(Auint('fw_sub_type', reader.read_b())) + number_of_modules = reader.read_b() + ext.add_a(Auint('number_of_modules', number_of_modules)) + ext.add_a(Auint('boot_strap_svn', reader.read_b())) + ext.add_a(Abytes('reserved', reader.read_bytes(14), 'red')) + + mod_idx = 0 + while reader.get_offset() < begin_off + ext_len: + mod = Component('signed_pkg_ace_module_{}'.format(mod_idx), + 'Signed Package Module', reader.get_offset()) + mod.add_a(Astring('name', chararr_to_string(reader.read_bytes(12), 12))) + mod.add_a(Auint('type', reader.read_b())) + mod.add_a(Auint('hash_algo', reader.read_b())) + mod.add_a(Abytes('reserved', reader.read_bytes(2), 'red')) + mod.add_a(Auint('meta_size', reader.read_dw())) + mod.add_a(Abytes('hash', reader.read_bytes(48))) + ext.add_comp(mod) + mod_idx += 1 + + if reader.get_offset() != begin_off + ext_len: + raise Exception('Malformed signed package ACE extension length') else: reader.info("Other extension") ext = MftExtension(ext_id, 'Other Extension', reader.get_offset()-8) @@ -1532,6 +1618,7 @@ def dump_info(self, pref, comp_filter): print('{}{} type {} file offset 0x{:x} length {}'. format(pref, self.name, self.adir['type'], self.file_offset, self.adir['length'])) + self.dump_comp_info(pref, comp_filter) class PlatFwAuthExtension(MftExtension): """ Platform FW Auth Extension @@ -1547,6 +1634,9 @@ def dump_info(self, pref, comp_filter): out += ' vcn {}'.format(self.adir['vcn']) out += ' bitmap {}'.format(self.adir['bitmap']) out += ' svn {}'.format(self.adir['svn']) + out += ' fw_type {}'.format(self.adir['fw_type']) + out += ' fw_sub_type {}'.format(self.adir['fw_sub_type']) + out += ' reserved {}'.format(self.adir['reserved']) print(out) class SignedPkgInfoExtension(MftExtension): @@ -1563,6 +1653,54 @@ def dump_info(self, pref, comp_filter): out += ' vcn {}'.format(self.adir['vcn']) out += ' svn {}'.format(self.adir['svn']) out += ' partition_usage {}'.format(self.adir['partition_usage']) + out += ' reserved0 {}'.format(self.adir['reserved0']) + out += ' fw_type {}'.format(self.adir['fw_type']) + out += ' fw_sub_type {}'.format(self.adir['fw_sub_type']) + out += ' number_of_modules {}'.format(self.adir['number_of_modules']) + out += ' boot_strap_svn {}'.format(self.adir['boot_strap_svn']) + out += ' reserved {}'.format(self.adir['reserved']) + print(out) + +class PartitionInfoExtension(MftExtension): + """ Partition info Extension + """ + def __init__(self, ext_id, offset): + super(PartitionInfoExtension, + self).__init__(ext_id, 'Partition info Extension', offset) + + def dump_info(self, pref, comp_filter): + super().dump_info(pref, comp_filter) + out = '{}'.format(pref) + out += ' name {}'.format(self.adir['name']) + out += ' partition_length {}'.format(self.adir['partition_length']) + out += ' hash {}'.format(self.adir['hash']) + out += ' vcn {}'.format(self.adir['vcn']) + out += ' part_version {}'.format(self.adir['part_version']) + out += ' fmt_version {}'.format(self.adir['fmt_version']) + out += ' instance_id {}'.format(self.adir['instance_id']) + out += ' part_flags {}'.format(self.adir['part_flags']) + out += ' reserved {}'.format(self.adir['reserved']) + print(out) + +class InfoExtension0x16(MftExtension): + """ info_ext_0x16 Extension + """ + def __init__(self, ext_id, offset): + super(InfoExtension0x16, + self).__init__(ext_id, 'Info Extension 0x16', offset) + + def dump_info(self, pref, comp_filter): + super().dump_info(pref, comp_filter) + out = '{}'.format(pref) + out += ' name {}'.format(self.adir['name']) + out += ' size {}'.format(self.adir['size']) + out += ' data [{}, {}, {}, {}, {}]'.format( + self.adir['data0'], self.adir['data1'], self.adir['data2'], + self.adir['data3'], self.adir['data4']) + out += ' hash {}'.format(self.adir['hash']) + out += ' data1 [{}, {}, {}, {}, {}]'.format( + self.adir['data1_0'], self.adir['data1_1'], self.adir['data1_2'], + self.adir['data1_3'], self.adir['data1_4']) print(out) class AdspMetadataFileExt(MftExtension): From 840f27a61c6d405cf78e55a7cbb4eaa1d292a719 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 29 May 2026 18:04:24 +0100 Subject: [PATCH 186/303] tools: sof_ri_info: parse remaining rimage manifest structs Parse additional ADSP metadata and manifest structures from the user manifest definitions, including module config records. Improve field naming/alignment with rimage structures while keeping compatibility aliases where needed. Signed-off-by: Liam Girdwood --- tools/sof_ri_info/sof_ri_info.py | 142 +++++++++++++++++++++++++------ 1 file changed, 117 insertions(+), 25 deletions(-) diff --git a/tools/sof_ri_info/sof_ri_info.py b/tools/sof_ri_info/sof_ri_info.py index 17f44fa71dac..94dd6c690844 100755 --- a/tools/sof_ri_info/sof_ri_info.py +++ b/tools/sof_ri_info/sof_ri_info.py @@ -984,20 +984,27 @@ def parse_css_manifest_4(css_mft, reader, size_limit): if ext_type == 0xffffffff: continue reader.set_offset(reader.get_offset() - 4) - css_mft.add_comp(parse_mft_extension(reader, ext_idx)) + css_mft.add_comp(parse_mft_extension(reader, ext_idx, size_limit)) ext_idx += 1 - assert reader.get_offset() == size_limit # wrong extension length + if reader.get_offset() != size_limit: + reader.info('warning: CSS extension parser finished at 0x{:x}, expected 0x{:x}; clamping'.format( + reader.get_offset(), size_limit)) + reader.set_offset(size_limit) css_mft.length = reader.get_offset() - css_mft.file_offset return css_mft -def parse_mft_extension(reader, ext_id): +def parse_mft_extension(reader, ext_id, max_end=None): """ Parses mft extension from sof binary """ begin_off = reader.get_offset() ext_type = reader.read_dw() ext_len = reader.read_dw() + ext_end = begin_off + ext_len + if max_end is not None and ext_end > max_end: + reader.info('warning: extension 0x{:x} length 0x{:x} exceeds CSS entry end; truncating'.format(ext_type, ext_len)) + ext_end = max_end if ext_type == 3: reader.info("Partition info extension") ext = PartitionInfoExtension(ext_id, reader.get_offset()-8) @@ -1012,7 +1019,7 @@ def parse_mft_extension(reader, ext_id): ext.add_a(Abytes('reserved', reader.read_bytes(20), 'red')) mod_idx = 0 - while reader.get_offset() < begin_off + ext_len: + while reader.get_offset() < ext_end: mod = Component('partition_info_module_{}'.format(mod_idx), 'Partition Info Module', reader.get_offset()) mod.add_a(Astring('name', chararr_to_string(reader.read_bytes(12), 12))) @@ -1023,8 +1030,10 @@ def parse_mft_extension(reader, ext_id): ext.add_comp(mod) mod_idx += 1 - if reader.get_offset() != begin_off + ext_len: + if reader.get_offset() > ext_end: raise Exception('Malformed partition info extension length') + if reader.get_offset() < ext_end: + reader.ff_data(ext_end - reader.get_offset()) elif ext_type == 15: reader.info("Plat Fw Auth extension") ext = PlatFwAuthExtension(ext_id, reader.get_offset()-8) @@ -1039,7 +1048,7 @@ def parse_mft_extension(reader, ext_id): ext.add_a(Abytes('reserved', reader.read_bytes(14), 'red')) mod_idx = 0 - while reader.get_offset() < begin_off + ext_len: + while reader.get_offset() < ext_end: mod = Component('signed_pkg_module_{}'.format(mod_idx), 'Signed Package Module', reader.get_offset()) mod.add_a(Astring('name', chararr_to_string(reader.read_bytes(12), 12))) @@ -1052,8 +1061,10 @@ def parse_mft_extension(reader, ext_id): ext.add_comp(mod) mod_idx += 1 - if reader.get_offset() != begin_off + ext_len: + if reader.get_offset() > ext_end: raise Exception('Malformed signed package extension length') + if reader.get_offset() < ext_end: + reader.ff_data(ext_end - reader.get_offset()) elif ext_type == 0x16: reader.info("Info extension 0x16") ext = InfoExtension0x16(ext_id, reader.get_offset()-8) @@ -1071,21 +1082,62 @@ def parse_mft_extension(reader, ext_id): ext.add_a(Auint('data1_3', reader.read_dw())) ext.add_a(Auint('data1_4', reader.read_dw())) read_len = reader.get_offset() - begin_off - reader.ff_data(ext_len - read_len) + effective_len = ext_end - begin_off + if read_len < effective_len: + reader.ff_data(effective_len - read_len) elif ext_type == 17: reader.info("ADSP metadata file extension") ext = AdspMetadataFileExt(ext_id, reader.get_offset()-8) ext.add_a(Auint('adsp_imr_type', reader.read_dw(), 'red')) - # skip reserved part - reader.read_bytes(16) - reader.read_dw() - reader.read_dw() - # - ext.add_a(Auint('version', reader.read_dw())) - ext.add_a(Abytes('sha_hash', reader.read_bytes(32))) - ext.add_a(Auint('base_offset', reader.read_dw())) - ext.add_a(Auint('limit_offset', reader.read_dw())) - ext.add_a(Abytes('attributes', reader.read_bytes(16))) + ext.add_a(Abytes('reserved', reader.read_bytes(16), 'red')) + + comp_desc = Component('adsp_comp_desc', 'Component Descriptor', + reader.get_offset()) + comp_desc_res0 = reader.read_dw() + comp_desc_res1 = reader.read_dw() + comp_desc_ver = reader.read_dw() + comp_desc.add_a(Auint('reserved0', comp_desc_res0)) + comp_desc.add_a(Auint('reserved1', comp_desc_res1)) + comp_desc.add_a(Auint('version', comp_desc_ver)) + ext.add_a(Auint('comp_desc_reserved0', comp_desc_res0)) + ext.add_a(Auint('comp_desc_reserved1', comp_desc_res1)) + ext.add_a(Auint('version', comp_desc_ver)) + + remaining = ext_end - reader.get_offset() + if remaining >= 72: + hash_len = 48 + else: + hash_len = 32 + + comp_hash = reader.read_bytes(hash_len) + comp_desc.add_a(Abytes('hash', comp_hash)) + ext.add_a(Abytes('sha_hash', comp_hash)) + + base_offset = reader.read_dw() + limit_offset = reader.read_dw() + comp_desc.add_a(Auint('base_offset', base_offset)) + comp_desc.add_a(Auint('limit_offset', limit_offset)) + ext.add_a(Auint('base_offset', base_offset)) + ext.add_a(Auint('limit_offset', limit_offset)) + + attr0 = reader.read_dw() + attr1 = reader.read_dw() + attr2 = reader.read_dw() + attr3 = reader.read_dw() + comp_desc.add_a(Auint('attribute0', attr0)) + comp_desc.add_a(Auint('attribute1', attr1)) + comp_desc.add_a(Auint('attribute2', attr2)) + comp_desc.add_a(Auint('attribute3', attr3)) + ext.add_a(Abytes('attributes', struct.pack('IIII', attr0, attr1, + attr2, attr3))) + ext.add_comp(comp_desc) + + read_len = reader.get_offset() - begin_off + effective_len = ext_end - begin_off + if read_len < effective_len: + reader.ff_data(effective_len - read_len) + elif read_len > effective_len: + raise Exception('Malformed ADSP metadata file extension length') elif ext_type == 35: reader.info("Signed package info extension") ext = SignedPkgInfoExtension(ext_id, reader.get_offset()-8) @@ -1102,7 +1154,7 @@ def parse_mft_extension(reader, ext_id): ext.add_a(Abytes('reserved', reader.read_bytes(14), 'red')) mod_idx = 0 - while reader.get_offset() < begin_off + ext_len: + while reader.get_offset() < ext_end: mod = Component('signed_pkg_ace_module_{}'.format(mod_idx), 'Signed Package Module', reader.get_offset()) mod.add_a(Astring('name', chararr_to_string(reader.read_bytes(12), 12))) @@ -1114,12 +1166,14 @@ def parse_mft_extension(reader, ext_id): ext.add_comp(mod) mod_idx += 1 - if reader.get_offset() != begin_off + ext_len: + if reader.get_offset() > ext_end: raise Exception('Malformed signed package ACE extension length') + if reader.get_offset() < ext_end: + reader.ff_data(ext_end - reader.get_offset()) else: reader.info("Other extension") ext = MftExtension(ext_id, 'Other Extension', reader.get_offset()-8) - reader.ff_data(ext_len-8) + reader.ff_data(max(0, ext_end - reader.get_offset())) ext.add_a(Auint('type', ext_type)) ext.add_a(Auint('length', ext_len)) reader.info("... end of extension") @@ -1142,16 +1196,22 @@ def parse_adsp_manifest_hdr(reader): reader.get_offset() -4) hdr.add_a(Astring('sig', sig)) - hdr.add_a(Auint('size', reader.read_dw())) + header_len = reader.read_dw() + hdr.add_a(Auint('header_len', header_len)) + hdr.add_a(Auint('size', header_len)) hdr.add_a(Astring('name', chararr_to_string(reader.read_bytes(8), 8))) - hdr.add_a(Auint('preload', reader.read_dw())) + preload_page_count = reader.read_dw() + hdr.add_a(Auint('preload_page_count', preload_page_count)) + hdr.add_a(Auint('preload', preload_page_count)) hdr.add_a(Auint('fw_image_flags', reader.read_dw())) hdr.add_a(Auint('feature_mask', reader.read_dw())) hdr.add_a(Aversion('build_version', reader.read_w(), reader.read_w(), reader.read_w(), reader.read_w())) hdr.add_a(Adec('num_module_entries', reader.read_dw())) - hdr.add_a(Ahex('hw_buf_base_addr', reader.read_dw())) + fw_compat = reader.read_dw() + hdr.add_a(Ahex('fw_compat', fw_compat)) + hdr.add_a(Ahex('hw_buf_base_addr', fw_compat)) hdr.add_a(Auint('hw_buf_length', reader.read_dw())) hdr.add_a(Ahex('load_offset', reader.read_dw())) @@ -1186,7 +1246,9 @@ def parse_adsp_manifest_mod_entry(index, reader): mod.add_a(Adec('cfg_count', reader.read_w())) mod.add_a(Auint('affinity_mask', reader.read_dw())) mod.add_a(Adec('instance_max_count', reader.read_w())) - mod.add_a(Auint('instance_stack_size', reader.read_w())) + instance_bss_size = reader.read_w() + mod.add_a(Auint('instance_bss_size', instance_bss_size)) + mod.add_a(Auint('instance_stack_size', instance_bss_size)) for i in range(0, 3): seg_flags = reader.read_dw() mod.add_a(Astring('seg_'+repr(i)+'_flags', @@ -1197,6 +1259,24 @@ def parse_adsp_manifest_mod_entry(index, reader): return mod +def parse_adsp_manifest_mod_config(index, reader): + """ Parses ADSP manifest module config from sof binary + """ + cfg = Component('mod_cfg_'+repr(index), 'Module Config', + reader.get_offset()) + cfg.add_a(Auint('par0', reader.read_dw())) + cfg.add_a(Auint('par1', reader.read_dw())) + cfg.add_a(Auint('par2', reader.read_dw())) + cfg.add_a(Auint('par3', reader.read_dw())) + cfg.add_a(Auint('is_pages', reader.read_dw())) + cfg.add_a(Auint('cps', reader.read_dw())) + cfg.add_a(Auint('ibs', reader.read_dw())) + cfg.add_a(Auint('obs', reader.read_dw())) + cfg.add_a(Auint('module_flags', reader.read_dw())) + cfg.add_a(Auint('cpc', reader.read_dw())) + cfg.add_a(Auint('obls', reader.read_dw())) + return cfg + def parse_adsp_manifest(reader, name): """ Parses ADSP manifest from sof binary """ @@ -1207,6 +1287,10 @@ def parse_adsp_manifest(reader, name): mod_entry = parse_adsp_manifest_mod_entry(i, reader) adsp_mft.add_comp(mod_entry) + for i in range(0, num_module_entries): + mod_cfg = parse_adsp_manifest_mod_config(i, reader) + adsp_mft.add_comp(mod_cfg) + return adsp_mft def parse_fw_bin(path, no_cse, verbose): @@ -1719,6 +1803,12 @@ def dump_info(self, pref, comp_filter): out += ' limit offset {}'.format(self.adir['limit_offset']) print(out) print('{} IMR type {}'.format(pref, self.adir['adsp_imr_type'])) + print('{} Reserved {}'.format(pref, self.adir['reserved'])) + print('{} Component desc reserved0 {}'.format(pref, + self.adir['comp_desc_reserved0'])) + print('{} Component desc reserved1 {}'.format(pref, + self.adir['comp_desc_reserved1'])) + print('{} SHA hash {}'.format(pref, self.adir['sha_hash'])) print('{} Attributes'.format(pref)) print('{} {}'.format(pref, self.adir['attributes'])) @@ -1735,6 +1825,7 @@ def dump_info(self, pref, comp_filter): out += ' build ver {}'.format(hdr.adir['build_version']) out += ' feature mask {}'.format(hdr.adir['feature_mask']) out += ' image flags {}'.format(hdr.adir['fw_image_flags']) + out += ' fw compat {}'.format(hdr.adir['fw_compat']) print(out) print('{} HW buffers base address {} length {}'. format(pref, @@ -2089,6 +2180,7 @@ def main(args): comp_filter = [] if args.headers or args.no_modules: comp_filter.append('Module Entry') + comp_filter.append('Module Config') if args.no_headers: comp_filter.append('CSE Manifest') fw_bin.dump_info('', comp_filter) From de9b00c5ff8a72c3028b87cb27f86157c0d76707 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 29 May 2026 18:04:24 +0100 Subject: [PATCH 187/303] tools: sof_ri_info: add memory map aliases for new platforms Teach memory-map lookup about newer platform aliases used in firmware paths so known layouts are selected instead of the unknown fallback. Signed-off-by: Liam Girdwood --- tools/sof_ri_info/sof_ri_info.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/sof_ri_info/sof_ri_info.py b/tools/sof_ri_info/sof_ri_info.py index 94dd6c690844..6108203ade93 100755 --- a/tools/sof_ri_info/sof_ri_info.py +++ b/tools/sof_ri_info/sof_ri_info.py @@ -2054,10 +2054,15 @@ def dump_info(self): 'ehl' : TGL_LP_MEMORY_SPACE, 'adl' : TGL_LP_MEMORY_SPACE, 'rpl' : TGL_LP_MEMORY_SPACE, + 'mtl' : TGL_LP_MEMORY_SPACE, + 'lnl' : TGL_LP_MEMORY_SPACE, + 'arl' : TGL_LP_MEMORY_SPACE, 'tgl-h' : TGL_H_MEMORY_SPACE, 'adl-s' : TGL_H_MEMORY_SPACE, 'rpl-s' : TGL_H_MEMORY_SPACE, + 'arl-s' : TGL_H_MEMORY_SPACE, + 'ptl' : TGL_H_MEMORY_SPACE, } From c4108bc8a35e63564c063058b5f894393ef9117b Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 29 May 2026 18:04:24 +0100 Subject: [PATCH 188/303] tools: sof_ri_info: report PTL memory layout as Pantherlake Introduce a PTL-specific memory map label and map PTL/NVL platform identifiers to it. This keeps displayed platform naming accurate while preserving the current address/size values. Signed-off-by: Liam Girdwood --- tools/sof_ri_info/sof_ri_info.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/sof_ri_info/sof_ri_info.py b/tools/sof_ri_info/sof_ri_info.py index 6108203ade93..c97e49398d2f 100755 --- a/tools/sof_ri_info/sof_ri_info.py +++ b/tools/sof_ri_info/sof_ri_info.py @@ -2038,6 +2038,13 @@ def dump_info(self): DspMemorySegment('l2 lpsram', 0xbe800000, 1*64*1024) ]) +PTL_MEMORY_SPACE = DspMemory('Intel Pantherlake', + [ + DspMemorySegment('imr', 0xb0000000, 16*1024*1024), + DspMemorySegment('l2 hpsram', 0xbe000000, 30*64*1024), + DspMemorySegment('l2 lpsram', 0xbe800000, 1*64*1024) + ]) + DSP_MEM_SPACE_EXT = { 'apl' : APL_MEMORY_SPACE, 'glk' : APL_MEMORY_SPACE, @@ -2062,7 +2069,8 @@ def dump_info(self): 'adl-s' : TGL_H_MEMORY_SPACE, 'rpl-s' : TGL_H_MEMORY_SPACE, 'arl-s' : TGL_H_MEMORY_SPACE, - 'ptl' : TGL_H_MEMORY_SPACE, + 'ptl' : PTL_MEMORY_SPACE, + 'nvl' : PTL_MEMORY_SPACE, } From b7a72a2a3f1b3a78b874532070dbc214e5b38981 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 19 Jun 2026 16:56:41 +0100 Subject: [PATCH 189/303] tools: rimage: harden CSE header scan and re-sign cleanup Address review feedback on the verify/re-sign paths: - cse_header_is_valid() dereferenced the candidate header before confirming the remaining buffer was large enough, so scanning near the end of a short or truncated file could read past the allocation. Check the size first, then inspect the fields. Also include man_v1_5_sue in the v1.5 verifier set so SUE verify/re-sign scans accept valid headers. - verify_image() left buffer uninitialised, so an early get_file_size() failure reached the free(buffer) cleanup with a garbage pointer. Initialise it to NULL. - resign_image() left image->out_fd non-NULL when fclose() reported an error, even though the stream is already closed, leading the caller cleanup to close the same FILE * twice. Clear the handle before testing the result. Signed-off-by: Liam Girdwood --- tools/rimage/src/manifest.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tools/rimage/src/manifest.c b/tools/rimage/src/manifest.c index 040aaaee323a..22eddfa00c74 100644 --- a/tools/rimage/src/manifest.c +++ b/tools/rimage/src/manifest.c @@ -32,19 +32,23 @@ static bool cse_header_is_valid(const struct image *image, const void *buffer, s if (image->adsp->man_v2_5 || image->adsp->man_ace_v1_5) { const struct CsePartitionDirHeader_v2_5 *cse_hdr = buffer; + if (size < sizeof(*cse_hdr)) + return false; + return cse_hdr->header_marker == CSE_HEADER_MAKER && cse_hdr->nb_entries == MAN_CSE_PARTS && - cse_hdr->header_length >= sizeof(*cse_hdr) && - size >= sizeof(*cse_hdr); + cse_hdr->header_length >= sizeof(*cse_hdr); } - if (image->adsp->man_v1_5 || image->adsp->man_v1_8) { + if (image->adsp->man_v1_5 || image->adsp->man_v1_5_sue || image->adsp->man_v1_8) { const struct CsePartitionDirHeader *cse_hdr = buffer; + if (size < sizeof(*cse_hdr)) + return false; + return cse_hdr->header_marker == CSE_HEADER_MAKER && cse_hdr->nb_entries == MAN_CSE_PARTS && - cse_hdr->header_length >= sizeof(*cse_hdr) && - size >= sizeof(*cse_hdr); + cse_hdr->header_length >= sizeof(*cse_hdr); } return false; @@ -1669,7 +1673,7 @@ int verify_image(struct image *image) { FILE *in_file; int ret = -EINVAL; - void *buffer; + void *buffer = NULL; size_t size, read, i; /* is verify supported for target ? */ @@ -1842,11 +1846,15 @@ int resign_image(struct image *image) if (ret < 0) goto out; - if (fclose(image->out_fd)) { + /* fclose() closes the stream even when it reports an error, so clear the + * handle first to stop the caller's cleanup from closing it a second time. + */ + ret = fclose(image->out_fd); + image->out_fd = NULL; + if (ret) { ret = file_error("unable to close file after signing", image->out_file); goto out; } - image->out_fd = NULL; /* validate the re-signed output with the same private key */ image->verify_file = image->out_file; From 8bb382339f631310e362032e32e5b90f65349f46 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 19 Jun 2026 16:56:41 +0100 Subject: [PATCH 190/303] tools: sof_ri_info: fix CSE v2.5 detection and module config count Address review feedback on the manifest dumper: - The legacy v1.x CsePartitionDirHeader is 16 bytes while the v2.5 header is 20 bytes, so a "header_length > 12" test sent legacy images down the v2.5 path, mislabelling their checksum byte as not_used and consuming the first entry dword as checksum32. Discriminate on the v2.5 header length (>= 20) so 16-byte headers stay on the legacy path. - Module configs are not one-per-module-entry: each module advertises its own cfg_count and the configs are packed contiguously, so the total written can exceed num_module_entries. Sum every module's cfg_count and dump that many configs instead of dropping the extras. Signed-off-by: Liam Girdwood --- tools/sof_ri_info/sof_ri_info.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/sof_ri_info/sof_ri_info.py b/tools/sof_ri_info/sof_ri_info.py index c97e49398d2f..5fca3a42a3be 100755 --- a/tools/sof_ri_info/sof_ri_info.py +++ b/tools/sof_ri_info/sof_ri_info.py @@ -852,14 +852,17 @@ def parse_cse_manifest(reader): header_length = reader.read_b() hdr.add_a(Ahex('header_length', header_length)) legacy_or_unused = reader.read_b() - if header_length > 12: + # The v2.5 header (length 20) moves the checksum to a trailing CRC32 dword + # and leaves this byte unused. The legacy v1.x header (length 16) keeps the + # single-byte BSD checksum here, so discriminate on the v2.5 header length. + if header_length >= 20: hdr.add_a(Ahex('not_used', legacy_or_unused)) else: hdr.add_a(Ahex('checksum', legacy_or_unused)) hdr.add_a(Astring('partition_name', reader.read_string(4))) # CSE v2.5 extends header with a CRC32 checksum dword - if header_length > 12: + if header_length >= 20: hdr.add_a(Ahex('checksum32', reader.read_dw())) reader.set_offset(cse_mft.file_offset + header_length) @@ -1283,11 +1286,16 @@ def parse_adsp_manifest(reader, name): adsp_mft = AdspManifest(name, reader.get_offset()) adsp_mft.add_comp(parse_adsp_manifest_hdr(reader)) num_module_entries = adsp_mft.cdir['adsp_mft_hdr'].adir['num_module_entries'].val + # Each module advertises its own cfg_count; the configs are packed + # contiguously after the module entries, so the total number written is the + # sum of every module's cfg_count (which can exceed num_module_entries). + num_mod_configs = 0 for i in range(0, num_module_entries): mod_entry = parse_adsp_manifest_mod_entry(i, reader) adsp_mft.add_comp(mod_entry) + num_mod_configs += mod_entry.adir['cfg_count'].val - for i in range(0, num_module_entries): + for i in range(0, num_mod_configs): mod_cfg = parse_adsp_manifest_mod_config(i, reader) adsp_mft.add_comp(mod_cfg) From ee00287c86d4f512c345d772eea7a96bca5df1aa Mon Sep 17 00:00:00 2001 From: jmestwa-coder Date: Sun, 21 Jun 2026 13:24:58 +0530 Subject: [PATCH 191/303] tools: testbench: bound control name and value copies in tb_parse_amixer tb_parse_amixer() copies the control name and value parsed from a control-script line into two fixed 128-byte stack buffers (control_name, control_params) via memcpy. The copy length is derived from the quote delimiter pointers with no upper bound: - control_name: len = end_str - name_str - find_len, taken from the cset name="..." quotes and never capped to TB_MAX_CTL_NAME_CHARS - control_params: same unchecked length for the value after the closing quote A script line whose name or value exceeds the buffer overflows the stack. The sibling tb_parse_sofctl() parses the same shape safely with strndup(). Reject over-length fields before each memcpy. Signed-off-by: jmestwa-coder --- tools/testbench/utils.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/testbench/utils.c b/tools/testbench/utils.c index 597e3aaa043d..91c08c62aab1 100644 --- a/tools/testbench/utils.c +++ b/tools/testbench/utils.c @@ -368,10 +368,18 @@ static int tb_parse_amixer(struct testbench_prm *tp, char *line) } len = end_str - name_str - find_len; + if (len < 0 || len >= TB_MAX_CTL_NAME_CHARS) { + fprintf(stderr, "error: control name too long in script line: %s\n", line); + return -EINVAL; + } memcpy(control_name, name_str + find_len, len); line_end = line + strlen(line); len = line_end - end_str - find_end_len; + if (len < 0 || len >= TB_MAX_CTL_NAME_CHARS) { + fprintf(stderr, "error: control value too long in script line: %s\n", line); + return -EINVAL; + } memcpy(control_params, &end_str[find_end_len], len); printf("Info: Setting control name '%s' to value (%s)\n", control_name, control_params); From 883e5bc1ab262e73022d3858c747d9a2d2b89449 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:08:52 +0100 Subject: [PATCH 192/303] igo_nr: reject out-of-range control switch element count The switch get handler looped over a host-supplied element count while writing the reply channel array and reading per-channel state. Reject counts larger than the maximum channel count before the loop. Signed-off-by: Liam Girdwood --- src/audio/igo_nr/igo_nr.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/audio/igo_nr/igo_nr.c b/src/audio/igo_nr/igo_nr.c index 54980d977f33..ebf1fef0394b 100644 --- a/src/audio/igo_nr/igo_nr.c +++ b/src/audio/igo_nr/igo_nr.c @@ -566,6 +566,10 @@ static int igo_nr_get_config(struct processing_module *mod, comp_info(dev, "SOF_CTRL_CMD_BINARY"); return comp_data_blob_get_cmd(cd->model_handler, cdata, fragment_size); case SOF_CTRL_CMD_SWITCH: + if (cdata->num_elems > SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "num_elems %u out of range", cdata->num_elems); + return -EINVAL; + } for (j = 0; j < cdata->num_elems; j++) { cdata->chanv[j].channel = j; cdata->chanv[j].value = cd->process_enable[j]; From 454a273f5f3ff8e4f12d3d40d0e74d1bcec87d9e Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:08:52 +0100 Subject: [PATCH 193/303] igo_nr: validate config blob size before copy A new configuration blob was copied into the component state as a fixed-size structure without checking the blob was large enough, reading past the allocation for a short blob. Request the blob size and reject one smaller than the configuration structure. Signed-off-by: Liam Girdwood --- src/audio/igo_nr/igo_nr.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/audio/igo_nr/igo_nr.c b/src/audio/igo_nr/igo_nr.c index ebf1fef0394b..b58da4fa838e 100644 --- a/src/audio/igo_nr/igo_nr.c +++ b/src/audio/igo_nr/igo_nr.c @@ -719,17 +719,32 @@ static void igo_nr_print_config(struct processing_module *mod) static void igo_nr_set_igo_params(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); - struct sof_igo_nr_config *p_config = comp_get_data_blob(cd->model_handler, NULL, NULL); + size_t config_size; + struct sof_igo_nr_config *p_config = comp_get_data_blob(cd->model_handler, + &config_size, NULL); struct comp_dev *dev = mod->dev; comp_info(dev, "entry"); - /* Adopt the host blob only when new config is valid */ - if (p_config && igo_nr_check_config_validity(dev, cd) == 0) { - comp_info(dev, "New config detected."); - cd->config = *p_config; - igo_nr_print_config(mod); + if (!p_config) + return; + + /* the whole config struct is copied below, so the blob must be at + * least that large + */ + if (config_size < sizeof(cd->config)) { + comp_err(dev, "New config too small: %zu < %zu", + config_size, sizeof(cd->config)); + return; } + + /* Adopt the host blob only when the new config is valid */ + if (igo_nr_check_config_validity(dev, cd) != 0) + return; + + comp_info(dev, "New config detected."); + cd->config = *p_config; + igo_nr_print_config(mod); } /* copy and process stream data from source to sink buffers */ From 7802f2e8d1abfc02cf8e9911eddc27b9fb385f43 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:37 +0100 Subject: [PATCH 194/303] igo_nr: compute circular buffer ends in samples not bytes The processing buffers' end pointers were computed by adding a byte count to sample-typed pointers, placing the end past the real buffer and letting the wrap check pass too late. Convert the byte sizes to element counts when computing the ends. Signed-off-by: Liam Girdwood --- src/audio/igo_nr/igo_nr.c | 57 ++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/audio/igo_nr/igo_nr.c b/src/audio/igo_nr/igo_nr.c index b58da4fa838e..f762cf86b49d 100644 --- a/src/audio/igo_nr/igo_nr.c +++ b/src/audio/igo_nr/igo_nr.c @@ -69,10 +69,10 @@ static int igo_nr_capture_s16(struct comp_data *cd, struct sof_sink *sink, int32_t frames) { - int16_t *x, *y1, *y2; - int16_t *x_start, *y_start, *x_end, *y_end; + int16_t const *x, *x_start, *x_end; + int16_t *y1, *y2, *y_start, *y_end; int16_t sample; - size_t x_size, y_size; + int x_size, y_size; size_t request_size = frames * source_get_frame_bytes(source); int sink_samples_without_wrap; int samples_without_wrap; @@ -93,12 +93,14 @@ static int igo_nr_capture_s16(struct comp_data *cd, #endif - ret = source_get_data(source, request_size, (void const **)&x, - (void const **)&x_start, &x_size); + /* use the sample-count source/sink accessors so the returned sizes are + * already in samples and need no byte-to-element conversion + */ + ret = source_get_data_s16(source, request_size, &x, &x_start, &x_size); if (ret) return ret; - ret = sink_get_buffer(sink, request_size, (void **)&y1, (void **)&y_start, &y_size); + ret = sink_get_buffer_s16(sink, request_size, &y1, &y_start, &y_size); if (ret) { source_release_data(source, 0); return ret; @@ -130,7 +132,10 @@ static int igo_nr_capture_s16(struct comp_data *cd, i++; } - x = cir_buf_wrap(x, x_start, x_end); + /* x is const (read-only source), so wrap manually instead of + * cir_buf_wrap() which takes a non-const void * + */ + x = (x >= x_end) ? x - x_size : x; y1 = cir_buf_wrap(y1, y_start, y_end); samples_left -= samples_without_wrap; } @@ -172,9 +177,9 @@ static int igo_nr_capture_s24(struct comp_data *cd, struct sof_sink *sink, int32_t frames) { - int32_t *x, *y1, *y2; - int32_t *x_start, *y_start, *x_end, *y_end; - size_t x_size, y_size; + int32_t const *x, *x_start, *x_end; + int32_t *y1, *y2, *y_start, *y_end; + int x_size, y_size; size_t request_size = frames * source_get_frame_bytes(source); int sink_samples_without_wrap; int samples_without_wrap; @@ -195,12 +200,14 @@ static int igo_nr_capture_s24(struct comp_data *cd, #endif - ret = source_get_data(source, request_size, (void const **)&x, - (void const **)&x_start, &x_size); + /* use the sample-count source/sink accessors so the returned sizes are + * already in samples and need no byte-to-element conversion + */ + ret = source_get_data_s32(source, request_size, &x, &x_start, &x_size); if (ret) return ret; - ret = sink_get_buffer(sink, request_size, (void **)&y1, (void **)&y_start, &y_size); + ret = sink_get_buffer_s32(sink, request_size, &y1, &y_start, &y_size); if (ret) { source_release_data(source, 0); return ret; @@ -231,7 +238,10 @@ static int igo_nr_capture_s24(struct comp_data *cd, i++; } - x = cir_buf_wrap(x, x_start, x_end); + /* x is const (read-only source), so wrap manually instead of + * cir_buf_wrap() which takes a non-const void * + */ + x = (x >= x_end) ? x - x_size : x; y1 = cir_buf_wrap(y1, y_start, y_end); samples_left -= samples_without_wrap; } @@ -273,9 +283,9 @@ static int igo_nr_capture_s32(struct comp_data *cd, struct sof_sink *sink, int32_t frames) { - int32_t *x, *y1, *y2; - int32_t *x_start, *y_start, *x_end, *y_end; - size_t x_size, y_size; + int32_t const *x, *x_start, *x_end; + int32_t *y1, *y2, *y_start, *y_end; + int x_size, y_size; size_t request_size = frames * source_get_frame_bytes(source); int sink_samples_without_wrap; int samples_without_wrap; @@ -296,12 +306,14 @@ static int igo_nr_capture_s32(struct comp_data *cd, #endif - ret = source_get_data(source, request_size, (void const **)&x, - (void const **)&x_start, &x_size); + /* use the sample-count source/sink accessors so the returned sizes are + * already in samples and need no byte-to-element conversion + */ + ret = source_get_data_s32(source, request_size, &x, &x_start, &x_size); if (ret) return ret; - ret = sink_get_buffer(sink, request_size, (void **)&y1, (void **)&y_start, &y_size); + ret = sink_get_buffer_s32(sink, request_size, &y1, &y_start, &y_size); if (ret) { source_release_data(source, 0); return ret; @@ -332,7 +344,10 @@ static int igo_nr_capture_s32(struct comp_data *cd, i++; } - x = cir_buf_wrap(x, x_start, x_end); + /* x is const (read-only source), so wrap manually instead of + * cir_buf_wrap() which takes a non-const void * + */ + x = (x >= x_end) ? x - x_size : x; y1 = cir_buf_wrap(y1, y_start, y_end); samples_left -= samples_without_wrap; } From 8caf1753f9674c476c005501d6a95ae9f9ed304a Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Mon, 22 Jun 2026 14:04:26 +0800 Subject: [PATCH 195/303] topology2: add SDW jack and amp compress feature topologies add SDW jack and amp compress feature topologies. Signed-off-by: Bard Liao --- tools/topology/topology2/development/tplg-targets.cmake | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/topology/topology2/development/tplg-targets.cmake b/tools/topology/topology2/development/tplg-targets.cmake index 155176c16347..d6285209dce0 100644 --- a/tools/topology/topology2/development/tplg-targets.cmake +++ b/tools/topology/topology2/development/tplg-targets.cmake @@ -508,4 +508,12 @@ SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture- SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ SDW_JACK_COMPR_AUDIO_FEATURE_CAPTURE=true,SDW_DMIC_COMPR_AUDIO_FEATURE_CAPTURE=true,\ MFCC_FRAME_BYTES=76,MFCC_BLOB=ceps" + +# Soundwire compressed feature topologies +"cavs-sdw\;sof-sdca-jack-compr\;SDW_JACK=false,NUM_HDMIS=0,COMPRESSED=true,\ +COMPRESSED_1=true,COMPRESSED_2=false" + +"cavs-sdw\;sof-sdca-amp-compr\;SDW_JACK=false,NUM_HDMIS=0,COMPRESSED=true,\ +COMPRESSED_1=false,COMPRESSED_2=true" + ) From 1a0f791b88670d2024c557b213b4b3eb7b5a0fbb Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:13 +0100 Subject: [PATCH 196/303] crash-decode: do not eval() linker script memory expressions The crash-decode helper evaluated arithmetic from a user-supplied linker script with eval(), so a crafted crash bundle could run arbitrary Python on the analyst's machine. Parse the expressions with a restricted evaluator that only accepts integer literals and basic arithmetic operators. Signed-off-by: Liam Girdwood --- scripts/sof-crash-decode.py | 74 ++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/scripts/sof-crash-decode.py b/scripts/sof-crash-decode.py index 8f76921431be..2db0222f620d 100755 --- a/scripts/sof-crash-decode.py +++ b/scripts/sof-crash-decode.py @@ -35,6 +35,76 @@ import os import json import shlex +import ast +import operator + + +# Largest shift we accept: linker addresses/sizes fit well under 2**64, so a +# bigger shift is nonsensical and only serves to build a huge integer. +_MAX_SHIFT = 64 +# Reject any literal or intermediate result beyond 64 bits, and cap the source +# length, so a crafted expression can't build a huge integer or stall the parse. +_MAX_VALUE = 1 << 64 +_MAX_EXPR_LEN = 256 + + +def _trunc_div(a, b): + # truncate toward zero (C semantics); Python's // floors instead + q = abs(a) // abs(b) + return -q if (a < 0) != (b < 0) else q + + +def _lshift(a, b): + # reject absurd shift counts to avoid building a massive integer + if b < 0 or b > _MAX_SHIFT: + raise ValueError("shift count out of range") + return a << b + + +# Operators allowed when evaluating arithmetic from an untrusted linker script. +_SAFE_OPS = { + ast.Add: operator.add, ast.Sub: operator.sub, + ast.Mult: operator.mul, ast.Div: _trunc_div, + ast.Mod: operator.mod, ast.LShift: _lshift, + ast.RShift: operator.rshift, ast.BitOr: operator.or_, + ast.BitAnd: operator.and_, ast.BitXor: operator.xor, + ast.USub: operator.neg, ast.UAdd: operator.pos, +} + + +def safe_eval_int(expr): + """Evaluate an integer arithmetic expression without executing code. + + A crash bundle's linker.cmd is attacker-controllable, so its MEMORY + expressions must never be passed to eval(). Only integer literals and + basic arithmetic operators are accepted, and values are bounded to 64 + bits. Anything unsupported or out of range raises an exception + (ValueError, or ZeroDivisionError on '/ 0'); the caller treats any such + failure as 'skip this entry'. + """ + def _check(value): + if abs(value) >= _MAX_VALUE: + raise ValueError("value out of range") + return value + + def _eval(node): + if isinstance(node, ast.Expression): + return _eval(node.body) + if isinstance(node, ast.Constant): + # reject bool (a subclass of int) and everything non-integer + if type(node.value) is int: + return _check(node.value) + raise ValueError("non-integer constant") + if isinstance(node, ast.BinOp) and type(node.op) in _SAFE_OPS: + return _check(_SAFE_OPS[type(node.op)](_eval(node.left), + _eval(node.right))) + if isinstance(node, ast.UnaryOp) and type(node.op) in _SAFE_OPS: + return _check(_SAFE_OPS[type(node.op)](_eval(node.operand))) + raise ValueError("unsupported expression") + + if len(expr) > _MAX_EXPR_LEN: + raise ValueError("expression too long") + return _eval(ast.parse(expr, mode='eval')) XTENSA_EXCCAUSE = { 0: "No Error (or IllegalInstruction)", @@ -151,8 +221,8 @@ def parse_linker_cmd(filepath): org_expr = m_org.group(1).strip() len_expr = m_len.group(1).strip() try: - org_val = eval(org_expr) - len_val = eval(len_expr) + org_val = safe_eval_int(org_expr) + len_val = safe_eval_int(len_expr) # Ignore debug regions if not (name.startswith('.debug') or name.startswith('.stab')): regions.append({'name': name, 'start': org_val, 'end': org_val + len_val}) From 4b92311ef2a7c17773e612245364698e9e4bced5 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 10 Jun 2026 12:58:00 +0200 Subject: [PATCH 197/303] ipc: disable memory zones for IPC4 Memory zones are only used with IPC3, mark them as such. Signed-off-by: Guennadi Liakhovetski --- src/ipc/ipc-helper.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index b7ef418e0339..737c3b73370c 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -67,6 +67,7 @@ __cold struct comp_buffer *buffer_new(struct mod_alloc_ctx *alloc, return NULL; } +#if CONFIG_IPC_MAJOR_3 /* memory zones and caps are deprecated - convert to flags */ if (desc->caps & SOF_MEM_CAPS_DMA) flags |= SOF_MEM_FLAG_DMA; @@ -77,6 +78,7 @@ __cold struct comp_buffer *buffer_new(struct mod_alloc_ctx *alloc, if (desc->caps) tr_warn(&buffer_tr, "Deprecated buffer caps 0x%x used, convert to flags 0x%x", desc->caps, flags); +#endif /* allocate buffer */ buffer = buffer_alloc(alloc, desc->size, flags, PLATFORM_DCACHE_ALIGN, From 65bb7ade191988561d8d0b36a99b494494e8bc0c Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 10 Jun 2026 15:17:01 +0200 Subject: [PATCH 198/303] dma: allocate on userspace heap when running in userspace When initialising in userspace use the userspace heap for channel memory allocations. Signed-off-by: Guennadi Liakhovetski --- src/lib/dma.c | 13 ++++++++----- zephyr/include/sof/lib/dma.h | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/dma.c b/src/lib/dma.c index 70ac5c975058..137dfb4e0fea 100644 --- a/src/lib/dma.c +++ b/src/lib/dma.c @@ -5,6 +5,7 @@ // Author: Ranjani Sridharan #include +#include #include #include #include @@ -134,7 +135,7 @@ void z_impl_sof_dma_put(struct sof_dma *dma) key = k_spin_lock(&dma->lock); if (--dma->sref == 0) { - rfree(dma->chan); + sof_heap_free(dma->heap, dma->chan); dma->chan = NULL; } @@ -146,18 +147,20 @@ void z_impl_sof_dma_put(struct sof_dma *dma) static int dma_init(struct sof_dma *dma) { struct dma_chan_data *chan; + struct k_heap *heap = sof_sys_user_heap_get(); int i; /* allocate dma channels */ - dma->chan = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, - sizeof(struct dma_chan_data) * dma->plat_data.channels); - + dma->chan = sof_heap_alloc(heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct dma_chan_data) * dma->plat_data.channels, 0); if (!dma->chan) { - tr_err(&dma_tr, "dma_probe_sof(): dma %d allocaction of channels failed", + tr_err(&dma_tr, "dma %d allocation of channels failed", dma->plat_data.id); return -ENOMEM; } + dma->heap = heap; + memset(dma->chan, 0, sizeof(struct dma_chan_data) * dma->plat_data.channels); /* init work */ for (i = 0, chan = dma->chan; i < dma->plat_data.channels; i++, chan++) { diff --git a/zephyr/include/sof/lib/dma.h b/zephyr/include/sof/lib/dma.h index 509495634097..e11899d07a4c 100644 --- a/zephyr/include/sof/lib/dma.h +++ b/zephyr/include/sof/lib/dma.h @@ -200,7 +200,9 @@ struct dma_plat_data { }; #ifdef CONFIG_ZEPHYR_NATIVE_DRIVERS +struct k_heap; struct sof_dma { + struct k_heap *heap; #else struct dma { #endif From 2fd74bd8e525ad4c5decced92b8a27082b14727d Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 10 Jun 2026 15:24:02 +0200 Subject: [PATCH 199/303] schedule: ll: allocate semaphores only for used cores Instead of allocating semaphores during global initialisation, do that later when initialising the domain for specific cores. This also automatically grants access rights to the allocating thread. Signed-off-by: Guennadi Liakhovetski --- src/schedule/zephyr_domain.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index ef649f975be8..3837f0b135c6 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -319,6 +319,12 @@ static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, dt->handler = NULL; dt->arg = NULL; + dt->sem = k_object_alloc(K_OBJ_SEM); + if (!dt->sem) { + tr_err(&ll_tr, "Failed to allocate semaphore for core %d", core); + return -ENOMEM; + } + /* 10 is rather random, we better not accumulate 10 missed timer interrupts */ k_sem_init(dt->sem, 0, 10); @@ -328,6 +334,8 @@ static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, dt->ll_thread = k_object_alloc(K_OBJ_THREAD); if (!dt->ll_thread) { tr_err(&ll_tr, "Failed to allocate thread object for core %d", core); + k_object_free(dt->sem); + dt->sem = NULL; return -ENOMEM; } @@ -473,6 +481,11 @@ static void zephyr_domain_thread_free(struct ll_schedule_domain *domain, dt->ll_thread = NULL; } + if (dt->sem) { + k_object_free(dt->sem); + dt->sem = NULL; + } + tr_info(&ll_tr, "thread_free done, core %d", core); } @@ -530,11 +543,11 @@ APP_TASK_DATA static const struct ll_schedule_domain_ops zephyr_domain_ops = { #endif }; +/* Core 0 only */ struct ll_schedule_domain *zephyr_domain_init(int clk) { struct ll_schedule_domain *domain; struct zephyr_domain *zephyr_domain; - struct zephyr_domain_thread *dt; int core; domain = domain_init(SOF_SCHEDULE_LL_TIMER, clk, false, @@ -584,14 +597,9 @@ struct ll_schedule_domain *zephyr_domain_init(int clk) ll_sch_domain_set_pdata(domain, zephyr_domain); for (core = 0; core < CONFIG_CORE_COUNT; core++) { - dt = zephyr_domain->domain_thread + core; -#ifdef CONFIG_SOF_USERSPACE_LL - dt->sem = k_object_alloc(K_OBJ_SEM); - if (!dt->sem) { - tr_err(&ll_tr, "Failed to allocate semaphore for core %d", core); - k_panic(); - } -#else +#ifndef CONFIG_SOF_USERSPACE_LL + struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + /* not allocated dynamically when LL in kernel space */ dt->sem = &dt->sem_obj; #endif From 393d5fc462b07b0f2c8fd7030f983e699a82cf6b Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 10 Jun 2026 15:29:59 +0200 Subject: [PATCH 200/303] schedule: ll: initialise task object to 0 This is needed at least to set the .priv_data pointer to NULL. Signed-off-by: Guennadi Liakhovetski --- src/schedule/zephyr_ll.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 36bacda0c9ad..2c27daadc910 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -552,8 +552,14 @@ static const struct scheduler_ops zephyr_ll_ops = { #if CONFIG_SOF_USERSPACE_LL struct task *zephyr_ll_task_alloc(void) { - return sof_heap_alloc(zephyr_ll_user_heap(), SOF_MEM_FLAG_USER, - sizeof(struct task), sizeof(void *)); + struct task *task = sof_heap_alloc(zephyr_ll_user_heap(), SOF_MEM_FLAG_USER, + sizeof(*task), sizeof(void *)); + + if (task) + /* At least .priv_data must be NULL for zephyr_ll_task_init() */ + memset(task, 0, sizeof(*task)); + + return task; } #endif /* CONFIG_SOF_USERSPACE_LL */ From 59a3f0db93b1fbbd70b8bd6289ecec39474d267b Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 10 Jun 2026 15:34:30 +0200 Subject: [PATCH 201/303] schedule: ll: allocate coherent memory in userspace mode too Also when userspace is used scheduler instances have to be allocated uncached. Signed-off-by: Guennadi Liakhovetski --- src/schedule/zephyr_ll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 2c27daadc910..ddae93eee8b7 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -622,7 +622,7 @@ __cold int zephyr_ll_scheduler_init(struct ll_schedule_domain *domain) #if CONFIG_SOF_USERSPACE_LL heap = zephyr_ll_user_heap(); - flags = SOF_MEM_FLAG_USER; + flags = SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT; #endif tr_dbg(&ll_tr, "init on core %d", core); From 6d0963480f4375ecbc0c618086167664d6f087e3 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 23 Jun 2026 18:57:17 +0300 Subject: [PATCH 202/303] zephyr: lib: alloc: fine-tune logic in z_impl_sof_heap_alloc() Modify the special handling of SOF_MEM_FLAG_USER_SHARED_BUFFER and SOF_MEM_FLAG_LARGE_BUFFER in z_impl_sof_heap_alloc(). First make the shared user heap special handling conditional on CONFIG_SOF_USERSPACE_USE_SHARED_HEAP. This flag is a special case as it is not a hint to the allocator but rather an explicit selector for a specific heap (similar to SOF_MEM_FLAG_L3). If defined in build, and the flag is passed to sof_heap_alloc(), the heap argument is ignored, and instead the shared user heap instance is always used. No functional change to current code. Unlike the shared heap flag, SOF_MEM_FLAG_LARGE_BUFFER is a more traditional flag, which serves as a hint to the allocator. Modify the implementation to ignore this flag if the caller has passed a non-NULL heap. Assumption here is that the heap user has passed is more suitable for large buffers than use of default rballoc_align(), so the hint can be ignored. This is especially important for cases where e.g. the caller is allocating memory to a different memory domain and using default rballoc_align() would result in access errors. If no heap is passed, the existing logic to use rballoc_align() is used. Functionality is modified only for cases where a custom heap is passed to alloc. Signed-off-by: Kai Vehmanen --- zephyr/lib/alloc.c | 19 +++++++++++++++++-- zephyr/syscall/alloc.c | 3 ++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index 6fcae7505f96..0ae0b23add1c 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -631,11 +631,26 @@ EXPORT_SYMBOL(rfree); void *z_impl_sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, size_t alignment) { - if (flags & (SOF_MEM_FLAG_LARGE_BUFFER | SOF_MEM_FLAG_USER_SHARED_BUFFER)) +#if CONFIG_SOF_USERSPACE_USE_SHARED_HEAP + /* + * FLAG_USER_SHARED_BUFFER maps to a specific heap, so + * the passed 'heap' can be ignored + */ + if (flags & SOF_MEM_FLAG_USER_SHARED_BUFFER) return rballoc_align(flags, bytes, alignment); +#endif + + if (!heap) { + /* + * Ensure virtual heap is utilized for large buffers + * if no heap is explicitly passed. rballoc_align() + * has this logic. + */ + if (flags & SOF_MEM_FLAG_LARGE_BUFFER) + return rballoc_align(flags, bytes, alignment); - if (!heap) heap = &sof_heap; + } if (flags & SOF_MEM_FLAG_COHERENT) return heap_alloc_aligned(heap, alignment, bytes); diff --git a/zephyr/syscall/alloc.c b/zephyr/syscall/alloc.c index 3fc7001fa3aa..04f402f37d09 100644 --- a/zephyr/syscall/alloc.c +++ b/zephyr/syscall/alloc.c @@ -12,7 +12,8 @@ static inline void *z_vrfy_sof_heap_alloc(struct k_heap *heap, uint32_t flags, { /* only allow flags that are safe for user-space heap isolation */ static const uint32_t allowed_flags = - SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA; + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT | SOF_MEM_FLAG_DMA | + SOF_MEM_FLAG_LARGE_BUFFER; K_OOPS(flags & ~allowed_flags); From 96833ac3b358ab2c431b3c8de34b621e8c25debf Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 14 Apr 2026 20:26:28 +0300 Subject: [PATCH 203/303] audio: module_adapter: make data_blob compatible with user-space Use user-space friendy sof_heap_alloc() for dynamic allocations in data_blob handler. Signed-off-by: Kai Vehmanen --- src/audio/data_blob.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/audio/data_blob.c b/src/audio/data_blob.c index 88288a9d44b1..94ea19e829a7 100644 --- a/src/audio/data_blob.c +++ b/src/audio/data_blob.c @@ -661,12 +661,13 @@ EXPORT_SYMBOL(comp_data_blob_get_cmd); static void *default_alloc(size_t size) { - return rballoc(SOF_MEM_FLAG_USER, size); + return sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, size, 0); } static void default_free(void *buf) { - rfree(buf); + sof_heap_free(sof_sys_user_heap_get(), buf); } struct comp_data_blob_handler * @@ -678,10 +679,11 @@ comp_data_blob_handler_new_ext(struct comp_dev *dev, bool single_blob, comp_dbg(dev, "entry"); - handler = rzalloc(SOF_MEM_FLAG_USER, - sizeof(struct comp_data_blob_handler)); + handler = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER, + sizeof(struct comp_data_blob_handler), 0); if (handler) { + memset(handler, 0, sizeof(*handler)); handler->dev = dev; handler->single_blob = single_blob; handler->alloc = alloc ? alloc : default_alloc; @@ -699,6 +701,6 @@ void comp_data_blob_handler_free(struct comp_data_blob_handler *blob_handler) comp_free_data_blob(blob_handler); - rfree(blob_handler); + sof_heap_free(sof_sys_user_heap_get(), blob_handler); } EXPORT_SYMBOL(comp_data_blob_handler_free); From ef1bcdb0e9ba92889eb648a20dcd31f04cf9dc25 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 16 Apr 2026 14:28:54 +0300 Subject: [PATCH 204/303] audio: module-adapter: make generic.c user-space compatible Replace direct rballoc() and rfree() calls with sof_heap_alloc() and sof_heap_free(), and use sof_sys_user_heap_get() as the heap. This makes the code ready to be used in user-space, including module prepare and free stages. Signed-off-by: Kai Vehmanen --- src/audio/module_adapter/module/generic.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index b62ae38f415f..95a5bb000d6a 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -57,13 +57,15 @@ int module_load_config(struct comp_dev *dev, const void *cfg, size_t size) if (!dst->data) { /* No space for config available yet, allocate now */ - dst->data = rballoc(SOF_MEM_FLAG_USER, size); + dst->data = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, size, 0); } else if (dst->size != size) { /* The size allocated for previous config doesn't match the new one. * Free old container and allocate new one. */ - rfree(dst->data); - dst->data = rballoc(SOF_MEM_FLAG_USER, size); + sof_heap_free(sof_sys_user_heap_get(), dst->data); + dst->data = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, size, 0); } if (!dst->data) { comp_err(dev, "failed to allocate space for setup config."); @@ -538,7 +540,7 @@ int module_prepare(struct processing_module *mod, * as it has been applied during the procedure - it is safe to * free it. */ - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.avail = false; md->cfg.data = NULL; @@ -673,7 +675,7 @@ int module_reset(struct processing_module *mod) md->cfg.avail = false; md->cfg.size = 0; - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.data = NULL; #if CONFIG_IPC_MAJOR_3 @@ -724,10 +726,10 @@ int module_free(struct processing_module *mod) /* Free all memory shared by module_adapter & module */ md->cfg.avail = false; md->cfg.size = 0; - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.data = NULL; if (md->runtime_params) { - rfree(md->runtime_params); + sof_heap_free(sof_sys_user_heap_get(), md->runtime_params); md->runtime_params = NULL; } #if CONFIG_IPC_MAJOR_3 @@ -794,7 +796,9 @@ int module_set_configuration(struct processing_module *mod, } /* Allocate buffer for new params */ - md->runtime_params = rballoc(SOF_MEM_FLAG_USER, md->new_cfg_size); + md->runtime_params = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_LARGE_BUFFER, + md->new_cfg_size, 0); if (!md->runtime_params) { comp_err(dev, "space allocation for new params failed"); return -ENOMEM; @@ -835,7 +839,7 @@ int module_set_configuration(struct processing_module *mod, md->new_cfg_size = 0; if (md->runtime_params) - rfree(md->runtime_params); + sof_heap_free(sof_sys_user_heap_get(), md->runtime_params); md->runtime_params = NULL; return ret; From 9011deae4c4c09f21c5bfdd9f46a6a3bb61427b6 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 16 Jun 2026 20:24:14 +0300 Subject: [PATCH 205/303] schedule: add support for LL userspace tasks Add support for registering user-space LL tasks, and ability to use the task scheduling functions from user-space. The implementation splits scheduler list into kernel and user portions if SOF is built with CONFIG_SOF_USERSPACE_LL. A scheduler type can be either maintained in kernel or user, never both. With this patch, the SOF_SCHEDULE_LL_TIMER is moved to user managed if CONFIG_SOF_USERSPACE_LL is used. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/schedule.h | 16 ++++----- src/schedule/schedule.c | 21 ++++++++++- zephyr/schedule.c | 54 +++++++++++++++++++++++++++-- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/src/include/sof/schedule/schedule.h b/src/include/sof/schedule/schedule.h index 9b16a3eff6b1..1e7d6b3842e9 100644 --- a/src/include/sof/schedule/schedule.h +++ b/src/include/sof/schedule/schedule.h @@ -180,6 +180,10 @@ struct schedulers { */ struct schedulers **arch_schedulers_get(void); +struct schedulers **arch_user_schedulers_get(void); + +struct schedulers **arch_user_schedulers_get_for_core(int core); + /** * Retrieves scheduler's data. * @param type SOF_SCHEDULE_ type. @@ -322,17 +326,13 @@ static inline void schedule_free(uint32_t flags) /** See scheduler_ops::scheduler_init_context */ static inline struct k_thread *scheduler_init_context(struct task *task) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - assert(schedulers); + assert(task && task->sch); + sch = task->sch; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type && sch->ops->scheduler_init_context) - return sch->ops->scheduler_init_context(sch->data, task); - } + if (sch->ops->scheduler_init_context) + return sch->ops->scheduler_init_context(sch->data, task); return NULL; } diff --git a/src/schedule/schedule.c b/src/schedule/schedule.c index 1848b603145e..f3963eab22ad 100644 --- a/src/schedule/schedule.c +++ b/src/schedule/schedule.c @@ -22,12 +22,23 @@ SOF_DEFINE_REG_UUID(schedule); DECLARE_TR_CTX(sch_tr, SOF_UUID(schedule_uuid), LOG_LEVEL_INFO); +static inline bool scheduler_is_user(int type) +{ + /* + * currently only LL managed in user-space, but longterm + * goal is to move all audio application level scheduling + * to user-space and only keep Zephyr scheduler logic in + * kernel + */ + return type == SOF_SCHEDULE_LL_TIMER; +} + int schedule_task_init(struct task *task, const struct sof_uuid_entry *uid, uint16_t type, uint16_t priority, enum task_state (*run)(void *data), void *data, uint16_t core, uint32_t flags) { - struct schedulers *schedulers = *arch_schedulers_get(); + struct schedulers *schedulers; struct schedule_data *sch = NULL; struct list_item *slist; @@ -36,6 +47,11 @@ int schedule_task_init(struct task *task, return -EINVAL; } + if (IS_ENABLED(CONFIG_SOF_USERSPACE_LL) && scheduler_is_user(type)) + schedulers = *arch_user_schedulers_get_for_core(core); + else + schedulers = *arch_schedulers_get(); + if (!schedulers) return -ENODEV; @@ -69,6 +85,9 @@ static void scheduler_register(struct schedule_data *scheduler) { struct schedulers **sch = arch_schedulers_get(); + if (IS_ENABLED(CONFIG_SOF_USERSPACE_LL) && scheduler_is_user(scheduler->type)) + sch = arch_user_schedulers_get(); + if (!*sch) { /* init schedulers list */ *sch = rzalloc(SOF_MEM_FLAG_KERNEL, diff --git a/zephyr/schedule.c b/zephyr/schedule.c index 1e3971a33682..afc53995610f 100644 --- a/zephyr/schedule.c +++ b/zephyr/schedule.c @@ -14,7 +14,18 @@ #include #include -static APP_SYSUSER_BSS struct schedulers *_schedulers[CONFIG_CORE_COUNT]; +/* Kernel-only scheduler list — depending on how SOF is built, + * either holds all or subset of scheduler types. + * Not accessible from user-space threads. + */ +static struct schedulers *_k_schedulers[CONFIG_CORE_COUNT]; + +#if CONFIG_SOF_USERSPACE_LL +/* User-accessible scheduler list — holds the subset of scheduler + * types that are managed in user-space + */ +static APP_SYSUSER_BSS struct schedulers *_u_schedulers[CONFIG_CORE_COUNT]; +#endif /** * Retrieves registered schedulers. @@ -22,6 +33,45 @@ static APP_SYSUSER_BSS struct schedulers *_schedulers[CONFIG_CORE_COUNT]; */ struct schedulers **arch_schedulers_get(void) { - return _schedulers + cpu_get_id(); +#if CONFIG_SOF_USERSPACE_LL + /* user-space callers must use arch_user_schedulers_get() */ + assert(!k_is_user_context()); +#endif + return _k_schedulers + cpu_get_id(); } EXPORT_SYMBOL(arch_schedulers_get); + +/** + * Retrieves registered user schedulers for the current core. + * + * Relies on cpu_get_id(), so it may invoke privileged functions and + * must not be called from a user-space context. User-space callers + * should use arch_user_schedulers_get_for_core() instead. + * + * @return List of registered schedulers. + */ +struct schedulers **arch_user_schedulers_get(void) +{ +#ifdef CONFIG_SOF_USERSPACE_LL + return _u_schedulers + cpu_get_id(); +#else + return NULL; +#endif +} + +/** + * Retrieves registered user schedulers for the given core. + * + * Unlike arch_user_schedulers_get(), this takes the core explicitly and + * is therefore safe to call from a user-space context. + * + * @return List of registered schedulers. + */ +struct schedulers **arch_user_schedulers_get_for_core(int core) +{ +#ifdef CONFIG_SOF_USERSPACE_LL + return _u_schedulers + core; +#else + return NULL; +#endif +} From 22e3f125d5e3ec1a45f21da121f627c99ebcbc43 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 24 Jun 2026 13:45:27 +0300 Subject: [PATCH 206/303] schedule: allocate the scheduler objects with sof_heap_alloc() Ensure the scheduler objects and lists of schedulers are allocated such that they can be used with both kernel and user-space LL scheduler implementations. The SOF_MEM_FLAG_KERNEL flag is removed. This flag has been a no-op for a while, and given scheduler list is not always in kernel anymore, it would be highly confusing to keep it. When CONFIG_SOF_USERSPACE_LL is set, the context of all schedulers is managed in the LL user-space domain. Signed-off-by: Kai Vehmanen --- src/schedule/schedule.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/schedule/schedule.c b/src/schedule/schedule.c index f3963eab22ad..231bba864856 100644 --- a/src/schedule/schedule.c +++ b/src/schedule/schedule.c @@ -84,18 +84,21 @@ int schedule_task_init(struct task *task, static void scheduler_register(struct schedule_data *scheduler) { struct schedulers **sch = arch_schedulers_get(); + struct k_heap *heap = NULL; - if (IS_ENABLED(CONFIG_SOF_USERSPACE_LL) && scheduler_is_user(scheduler->type)) + if (IS_ENABLED(CONFIG_SOF_USERSPACE_LL) && scheduler_is_user(scheduler->type)) { sch = arch_user_schedulers_get(); + heap = sof_sys_user_heap_get(); + } if (!*sch) { /* init schedulers list */ - *sch = rzalloc(SOF_MEM_FLAG_KERNEL, - sizeof(**sch)); + *sch = sof_heap_alloc(heap, 0, sizeof(**sch), 0); if (!*sch) { tr_err(&sch_tr, "allocation failed"); return; } + memset(*sch, 0, sizeof(**sch)); list_init(&(*sch)->list); } @@ -105,16 +108,21 @@ static void scheduler_register(struct schedule_data *scheduler) void scheduler_init(int type, const struct scheduler_ops *ops, void *data) { struct schedule_data *sch; + struct k_heap *heap = NULL; + + if (IS_ENABLED(CONFIG_SOF_USERSPACE_LL) && scheduler_is_user(type)) + heap = sof_sys_user_heap_get(); if (!ops || !ops->schedule_task || !ops->schedule_task_cancel || !ops->schedule_task_free) return; - sch = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(*sch)); + sch = sof_heap_alloc(heap, SOF_MEM_FLAG_KERNEL, sizeof(*sch), 0); if (!sch) { tr_err(&sch_tr, "allocation failed"); sof_panic(SOF_IPC_PANIC_IPC); } + memset(sch, 0, sizeof(*sch)); list_init(&sch->list); sch->type = type; sch->ops = ops; From 7e26c80255a13f97ba7cc86ae7c22266e4a8b3c6 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 31 Mar 2026 14:45:40 +0300 Subject: [PATCH 207/303] ipc: ipc4: helper: drop redundant locking in ipc4_search_for_drv() Drop the IRQ disable/enable in ipc4_search_for_drv(). The driver list is only modified at FW boot and when a new driver is registered at runtime via SOF_IPC4_GLB_LOAD_LIBRARY IPC. ipc4_search_for_drv() is only used when processing IPC messages. As IPC processing is serialized, it is not possible for the driver list to be modified concurrently with a call to ipc4_search_for_drv(). Signed-off-by: Kai Vehmanen --- src/ipc/ipc4/helper.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 9e0846f6bc02..25ba2a920532 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -1142,12 +1142,9 @@ __cold static const struct comp_driver *ipc4_search_for_drv(const void *uuid) struct list_item *clist; const struct comp_driver *drv = NULL; struct comp_driver_info *info; - uint32_t flags; assert_can_be_cold(); - irq_local_disable(flags); - /* search driver list with UUID */ list_for_item(clist, &drivers->list) { info = container_of(clist, struct comp_driver_info, @@ -1162,7 +1159,6 @@ __cold static const struct comp_driver *ipc4_search_for_drv(const void *uuid) } } - irq_local_enable(flags); return drv; } From 6e63a688f75a1a93f815a47a5d61837f033dacca Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 15 Jun 2026 18:28:49 +0300 Subject: [PATCH 208/303] audio: component: drop redundant locking in driver list The component driver list is only modified at FW boot and at runtime when a library is loaded. At boot, module init runs serially on the primary core (Zephyr SYS_INIT at APPLICATION level, before secondary cores are started; .initcall walked on a single core for XTOS). At runtime, registration happens from the IPC thread, which is serialized with only one command processed at a time. These two phases never overlap, as IPC message processing only begins after boot completes, so the list can never be modified concurrently. The lock was also already incoherent: comp_set_adapter_ops() iterate the list without holding the lock, so it provided no real mutual exclusion. Drop the spinlock from comp_register() and comp_unregister(), and from the UUID search in the IPC3 get_drv() reader. Remove the now-unused lock field from struct comp_driver_list and its initialization. Signed-off-by: Kai Vehmanen --- src/audio/component.c | 16 +++++++--------- src/include/sof/audio/component_ext.h | 1 - src/ipc/ipc3/helper.c | 10 ++++------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/audio/component.c b/src/audio/component.c index da597023cf73..d63ab71725c0 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -45,23 +45,22 @@ DECLARE_TR_CTX(comp_tr, SOF_UUID(component_uuid), LOG_LEVEL_INFO); int comp_register(struct comp_driver_info *drv) { struct comp_driver_list *drivers = comp_drivers_get(); - k_spinlock_key_t key; - key = k_spin_lock(&drivers->lock); + /* + * No locking needed: the driver list is only modified at FW boot, + * where module init runs serially on the primary core, and at + * runtime from the serialized IPC thread (library load). These + * never overlap, so concurrent modification is not possible. + */ list_item_prepend(&drv->list, &drivers->list); - k_spin_unlock(&drivers->lock, key); return 0; } void comp_unregister(struct comp_driver_info *drv) { - struct comp_driver_list *drivers = comp_drivers_get(); - k_spinlock_key_t key; - - key = k_spin_lock(&drivers->lock); + /* see comp_register() on why no locking is needed */ list_item_del(&drv->list); - k_spin_unlock(&drivers->lock, key); } int comp_set_adapter_ops(const struct comp_driver *drv, const struct module_interface *ops) @@ -190,7 +189,6 @@ void sys_comp_init(struct sof *sof) sof->comp_drivers = platform_shared_get(&cd, sizeof(cd)); list_init(&sof->comp_drivers->list); - k_spinlock_init(&sof->comp_drivers->lock); } void comp_get_copy_limits(struct comp_buffer *source, diff --git a/src/include/sof/audio/component_ext.h b/src/include/sof/audio/component_ext.h index d2bbf87a7764..70324175fea8 100644 --- a/src/include/sof/audio/component_ext.h +++ b/src/include/sof/audio/component_ext.h @@ -26,7 +26,6 @@ /** \brief Holds list of registered components' drivers */ struct comp_driver_list { struct list_item list; /**< list of component drivers */ - struct k_spinlock lock; /**< list lock */ }; /** \brief Retrieves the component device buffer list. */ diff --git a/src/ipc/ipc3/helper.c b/src/ipc/ipc3/helper.c index 901ba4d06a3f..e962a3670a86 100644 --- a/src/ipc/ipc3/helper.c +++ b/src/ipc/ipc3/helper.c @@ -81,7 +81,6 @@ static const struct comp_driver *get_drv(struct sof_ipc_comp *comp) struct comp_driver_info *info; struct sof_ipc_comp_ext *comp_ext; uintptr_t offset; - k_spinlock_key_t key; /* do we have extended data ? */ if (!comp->ext_data_length) { @@ -127,9 +126,10 @@ static const struct comp_driver *get_drv(struct sof_ipc_comp *comp) goto out; } - /* search driver list with UUID */ - key = k_spin_lock(&drivers->lock); - + /* + * search driver list with UUID; no locking needed as the driver + * list is only modified at boot and from the serialized IPC thread + */ list_for_item(clist, &drivers->list) { info = container_of(clist, struct comp_driver_info, list); @@ -148,8 +148,6 @@ static const struct comp_driver *get_drv(struct sof_ipc_comp *comp) *(uint32_t *)(&comp_ext->uuid[8]), *(uint32_t *)(&comp_ext->uuid[12])); - k_spin_unlock(&drivers->lock, key); - out: if (drv) tr_dbg(&comp_tr, "get_drv(), found driver type %d, uuid %pU", From a5179f62455bc888488e01387884a6d1c89705cc Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 16 Jun 2026 20:45:41 +0300 Subject: [PATCH 209/303] schedule: zephyr_dp_sched_app: fix build error in release builds Release builds fail when building with DP user-space implementation. Build fails as CONFIG_THREAD_MAX_NAME_LEN is not defined. Problem not affecting when building with debug enabled, and/or configurations where the feature is disabled. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_dp_schedule_application.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/schedule/zephyr_dp_schedule_application.c b/src/schedule/zephyr_dp_schedule_application.c index ac03bb59fe5d..791e16ab190d 100644 --- a/src/schedule/zephyr_dp_schedule_application.c +++ b/src/schedule/zephyr_dp_schedule_application.c @@ -411,6 +411,7 @@ void scheduler_dp_internal_free(struct task *task) mod_free(pdata->mod, container_of(task, struct scheduler_dp_task_memory, task)); } +#ifdef CONFIG_THREAD_NAME static void scheduler_dp_thread_name_set(k_tid_t thread_id, struct processing_module *mod) { char name[CONFIG_THREAD_MAX_NAME_LEN]; @@ -419,6 +420,10 @@ static void scheduler_dp_thread_name_set(k_tid_t thread_id, struct processing_mo k_thread_name_set(thread_id, name); } +#else +/* k_thread_name_set() is a no-op so skip constructing a thread name */ +#define scheduler_dp_thread_name_set(x, y) +#endif /* Called only in IPC context */ int scheduler_dp_task_init(struct task **task, const struct sof_uuid_entry *uid, From be106745289ba479868fc4b59c1dae52e94fa788 Mon Sep 17 00:00:00 2001 From: Serhiy Katsyuba Date: Mon, 22 Jun 2026 15:11:26 +0200 Subject: [PATCH 210/303] audio: comp_buffer: make allocations from the passed memory context To ensure the buffer is not left outside the module's memory domain, the payload allocation must be made from the supplied context instead of always calling rballoc_align(). This fixes a crash during bind processing when running a test with a userspace DP module on a secondary core. Signed-off-by: Serhiy Katsyuba --- src/audio/buffers/comp_buffer.c | 118 ++++++++++++++++---------------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/src/audio/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index 9aa5c0f0ab10..8a3d44133d4b 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -156,12 +156,12 @@ static void comp_buffer_free(struct sof_audio_buffer *audio_buffer) struct mod_alloc_ctx *alloc = buffer->audio_buffer.alloc; -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, buffer->stream.addr); -#else - rfree(buffer->stream.addr); -#endif + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + + if (alloc) + sof_ctx_free(alloc, buffer->stream.addr); + else + sof_heap_free(sof_sys_user_heap_get(), buffer->stream.addr); if (alloc && alloc->vreg) { vregion_free(alloc->vreg, buffer); @@ -255,12 +255,14 @@ struct comp_buffer *buffer_alloc(struct mod_alloc_ctx *alloc, size_t size, uint3 return NULL; } -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - stream_addr = sof_ctx_alloc(alloc, flags, size, align); -#else - stream_addr = rballoc_align(flags, size, align); -#endif + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + + if (alloc) + stream_addr = sof_ctx_alloc(alloc, flags, size, align); + else + stream_addr = sof_heap_alloc(sof_sys_user_heap_get(), + flags | SOF_MEM_FLAG_LARGE_BUFFER, size, align); + if (!stream_addr) { tr_err(&buffer_tr, "could not alloc size = %zu bytes of flags = 0x%x", size, flags); @@ -270,12 +272,11 @@ struct comp_buffer *buffer_alloc(struct mod_alloc_ctx *alloc, size_t size, uint3 buffer = buffer_alloc_struct(alloc, stream_addr, size, flags, is_shared); if (!buffer) { tr_err(&buffer_tr, "could not alloc buffer structure"); -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, stream_addr); -#else - rfree(stream_addr); -#endif + + if (alloc) + sof_ctx_free(alloc, stream_addr); + else + sof_heap_free(sof_sys_user_heap_get(), stream_addr); } return buffer; @@ -302,13 +303,16 @@ struct comp_buffer *buffer_alloc_range(struct mod_alloc_ctx *alloc, size_t prefe if (preferred_size % minimum_size) preferred_size += minimum_size - preferred_size % minimum_size; + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + for (size = preferred_size; size >= minimum_size; size -= minimum_size) { -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - stream_addr = sof_ctx_alloc(alloc, flags, size, align); -#else - stream_addr = rballoc_align(flags, size, align); -#endif + if (alloc) + stream_addr = sof_ctx_alloc(alloc, flags, size, align); + else + stream_addr = sof_heap_alloc(sof_sys_user_heap_get(), + flags | SOF_MEM_FLAG_LARGE_BUFFER, size, + align); + if (stream_addr) break; } @@ -324,12 +328,11 @@ struct comp_buffer *buffer_alloc_range(struct mod_alloc_ctx *alloc, size_t prefe buffer = buffer_alloc_struct(alloc, stream_addr, size, flags, is_shared); if (!buffer) { tr_err(&buffer_tr, "could not alloc buffer structure"); -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, stream_addr); -#else - rfree(stream_addr); -#endif + + if (alloc) + sof_ctx_free(alloc, stream_addr); + else + sof_heap_free(sof_sys_user_heap_get(), stream_addr); } return buffer; @@ -350,9 +353,7 @@ void buffer_zero(struct comp_buffer *buffer) int buffer_set_size(struct comp_buffer *buffer, uint32_t size, uint32_t alignment) { void *new_ptr = NULL; -#ifdef CONFIG_SOF_USERSPACE_LL struct mod_alloc_ctx *alloc = buffer->audio_buffer.alloc; -#endif CORE_CHECK_STRUCT(&buffer->audio_buffer); @@ -365,12 +366,14 @@ int buffer_set_size(struct comp_buffer *buffer, uint32_t size, uint32_t alignmen if (size == audio_stream_get_size(&buffer->stream)) return 0; -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - new_ptr = sof_ctx_alloc(alloc, buffer->flags, size, alignment); -#else - new_ptr = rballoc_align(buffer->flags, size, alignment); -#endif + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + + if (alloc) + new_ptr = sof_ctx_alloc(alloc, buffer->flags, size, alignment); + else + new_ptr = sof_heap_alloc(sof_sys_user_heap_get(), + buffer->flags | SOF_MEM_FLAG_LARGE_BUFFER, size, + alignment); /* we couldn't allocate bigger chunk */ if (!new_ptr && size > audio_stream_get_size(&buffer->stream)) { @@ -381,12 +384,11 @@ int buffer_set_size(struct comp_buffer *buffer, uint32_t size, uint32_t alignmen /* use bigger chunk, else just use the old chunk but set smaller */ if (new_ptr) { -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, audio_stream_get_addr(&buffer->stream)); -#else - rfree(audio_stream_get_addr(&buffer->stream)); -#endif + if (alloc) + sof_ctx_free(alloc, audio_stream_get_addr(&buffer->stream)); + else + sof_heap_free(sof_sys_user_heap_get(), audio_stream_get_addr(&buffer->stream)); + audio_stream_set_addr(&buffer->stream, new_ptr); } @@ -401,9 +403,7 @@ int buffer_set_size_range(struct comp_buffer *buffer, size_t preferred_size, siz const size_t actual_size = audio_stream_get_size(&buffer->stream); void *new_ptr = NULL; size_t new_size; -#ifdef CONFIG_SOF_USERSPACE_LL struct mod_alloc_ctx *alloc = buffer->audio_buffer.alloc; -#endif CORE_CHECK_STRUCT(&buffer->audio_buffer); @@ -421,14 +421,17 @@ int buffer_set_size_range(struct comp_buffer *buffer, size_t preferred_size, siz if (preferred_size == actual_size) return 0; + assert(!IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || alloc); + for (new_size = preferred_size; new_size >= minimum_size; new_size -= minimum_size) { -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - new_ptr = sof_ctx_alloc(alloc, buffer->flags, new_size, alignment); -#else - new_ptr = rballoc_align(buffer->flags, new_size, alignment); -#endif + if (alloc) + new_ptr = sof_ctx_alloc(alloc, buffer->flags, new_size, alignment); + else + new_ptr = sof_heap_alloc(sof_sys_user_heap_get(), + buffer->flags | SOF_MEM_FLAG_LARGE_BUFFER, + new_size, alignment); + if (new_ptr) break; } @@ -442,12 +445,11 @@ int buffer_set_size_range(struct comp_buffer *buffer, size_t preferred_size, siz /* use bigger chunk, else just use the old chunk but set smaller */ if (new_ptr) { -#ifdef CONFIG_SOF_USERSPACE_LL - assert(alloc); - sof_ctx_free(alloc, audio_stream_get_addr(&buffer->stream)); -#else - rfree(audio_stream_get_addr(&buffer->stream)); -#endif + if (alloc) + sof_ctx_free(alloc, audio_stream_get_addr(&buffer->stream)); + else + sof_heap_free(sof_sys_user_heap_get(), audio_stream_get_addr(&buffer->stream)); + audio_stream_set_addr(&buffer->stream, new_ptr); } From 306770279595b389d1598f95d1ffc7b387c7e281 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Wed, 27 May 2026 22:58:06 +0200 Subject: [PATCH 211/303] fuzz: posix: widen fuzz framing length to 2 bytes The fuzz harness in posix/ipc.c frames each IPC message using a length prefix at the start of the libFuzzer input buffer. Until now this prefix was a single byte multiplied by 2, encoding a maximum per-message payload of 510 bytes. That fits IPC3 (SOF_IPC_MSG_MAX_SIZE = 384) but caps IPC4 at ~12% of its 4096-byte envelope, hiding the entire large_config / vendor_config / pipeline-state-data path family from coverage feedback. Replace the prefix with two little-endian bytes, encoding lengths up to 65535 (still clamped to SOF_IPC_MSG_MAX_SIZE - 4 in code). The remainder of the framing logic is mechanically updated to consume two header bytes per message. Introduce POSIX_FUZZ_HDR_LEN to keep header arithmetic self-documenting. Existing IPC3 and IPC4 corpora are reinterpreted under the new header: what was previously a length byte becomes the low byte of a 16-bit length, with the next byte becoming the high byte. libFuzzer's mutation engine re-discovers productive frame shapes within minutes of running, so the corpora are kept rather than regenerated. Signed-off-by: Tomasz Leman --- src/platform/posix/ipc.c | 44 +++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/platform/posix/ipc.c b/src/platform/posix/ipc.c index feb4bb03873c..c8012a697847 100644 --- a/src/platform/posix/ipc.c +++ b/src/platform/posix/ipc.c @@ -58,16 +58,26 @@ void posix_fuzz_case_abort(void) fuzz_in_sz = 0; } -// The protocol here is super simple: the first byte is a message size -// in units of 16 bits (the buffer maximum defaults to 384 bytes, and -// I didn't want to waste space early in the buffer lest I confuse the -// fuzzing heuristics). We then copy that much of the input buffer -// (subject to clamping obviously) into the incoming IPC message -// buffer and invoke the ISR. Any remainder will be delivered -// synchronously as another message after receipt of "complete_cmd()" -// from the SOF engine, etc... Eventually we'll receive another fuzz -// input after some amount of simulated time has passed (c.f. -// CONFIG_ZEPHYR_POSIX_FUZZ_TICKS) +// The framing protocol is super simple: the first two bytes are a +// little-endian message size in bytes (capped below at +// SOF_IPC_MSG_MAX_SIZE - 4 so it always fits in the IPC message +// buffer). We then copy that much of the input buffer (subject to +// clamping obviously) into the incoming IPC message buffer and invoke +// the ISR. Any remainder will be delivered synchronously as another +// message after receipt of "complete_cmd()" from the SOF engine, +// etc... Eventually we'll receive another fuzz input after some +// amount of simulated time has passed (c.f. +// CONFIG_ZEPHYR_POSIX_FUZZ_TICKS). +// +// The historical encoding used a single byte multiplied by 2, which +// capped each message at 510 bytes. That was fine for IPC3 +// (SOF_IPC_MSG_MAX_SIZE = 384) but limited IPC4 (max 4096) to ~12% +// of its envelope, hiding all large_config / vendor_config / +// pipeline-state-data paths from coverage feedback. Widening to two +// bytes lets the fuzzer reach the full IPC4 message range; existing +// corpora are reinterpreted but libFuzzer recovers within minutes. +#define POSIX_FUZZ_HDR_LEN 2 + static void fuzz_isr(const void *arg) { size_t rem, i, n = MIN(posix_fuzz_sz, sizeof(fuzz_in) - fuzz_in_sz); @@ -75,20 +85,22 @@ static void fuzz_isr(const void *arg) for (i = 0; i < n; i++) fuzz_in[fuzz_in_sz++] = posix_fuzz_buf[i]; - if (fuzz_in_sz == 0) + if (fuzz_in_sz < POSIX_FUZZ_HDR_LEN) return; if (!global_ipc->comp_data) return; - size_t maxsz = SOF_IPC_MSG_MAX_SIZE - 4, msgsz = fuzz_in[0] * 2; + size_t maxsz = SOF_IPC_MSG_MAX_SIZE - 4; + size_t msgsz = (size_t)fuzz_in[0] | ((size_t)fuzz_in[1] << 8); - n = MIN(msgsz, MIN(fuzz_in_sz - 1, maxsz)); - rem = fuzz_in_sz - (n + 1); + msgsz = MIN(msgsz, maxsz); + n = MIN(msgsz, fuzz_in_sz - POSIX_FUZZ_HDR_LEN); + rem = fuzz_in_sz - (n + POSIX_FUZZ_HDR_LEN); memset(global_ipc->comp_data, 0, maxsz); - memcpy(global_ipc->comp_data, &fuzz_in[1], n); - memmove(&fuzz_in[0], &fuzz_in[n + 1], rem); + memcpy(global_ipc->comp_data, &fuzz_in[POSIX_FUZZ_HDR_LEN], n); + memmove(&fuzz_in[0], &fuzz_in[n + POSIX_FUZZ_HDR_LEN], rem); fuzz_in_sz = rem; #ifdef CONFIG_IPC_MAJOR_3 From 053b3503f39b108cd253ce55714d13475a5b40e1 Mon Sep 17 00:00:00 2001 From: DineshKumar Kalva Date: Thu, 25 Jun 2026 13:24:29 +0530 Subject: [PATCH 212/303] ipc: dai: add SOF_DAI_AMD_TDM type and DMA capability bitmasks Add SOF_DAI_AMD_TDM to the sof_ipc_dai_type enum. Add format field to sof_ipc_dai_acp_params to carry the sample format (16/24/32-bit) from topology to the DAI driver. Add SOF_DMA_CAP_TDM (BIT(16)) and SOF_DMA_DEV_TDM (BIT(16)) capability/device bitmasks for the ACP TDM DMA controller. Signed-off-by: Sneha Voona Signed-off-by: DineshKumar Kalva --- src/include/ipc/dai-amd.h | 1 + zephyr/include/sof/lib/dma.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/include/ipc/dai-amd.h b/src/include/ipc/dai-amd.h index b167f03eed7e..4bfa10b150e8 100644 --- a/src/include/ipc/dai-amd.h +++ b/src/include/ipc/dai-amd.h @@ -24,6 +24,7 @@ struct sof_ipc_dai_acp_params { uint32_t fsync_rate; uint32_t tdm_slots; uint32_t tdm_mode; + uint32_t format; } __attribute__((packed, aligned(4))); /* ACP Configuration Request - SOF_IPC_DAI_AMD_SDW_CONFIG */ diff --git a/zephyr/include/sof/lib/dma.h b/zephyr/include/sof/lib/dma.h index e11899d07a4c..4408d265804f 100644 --- a/zephyr/include/sof/lib/dma.h +++ b/zephyr/include/sof/lib/dma.h @@ -81,6 +81,7 @@ struct k_heap; #define SOF_DMA_CAP_HS_VIRTUAL BIT(7) /**< HS VIRTUAL DMA */ #define SOF_DMA_CAP_HS BIT(8) /**< HS DMA */ #define SOF_DMA_CAP_SW BIT(9) /**< SW DMA */ +#define SOF_DMA_CAP_TDM BIT(10) /**< connectable to ACP TDM I2S */ /* DMA dev type bitmasks used to define the type of DMA */ @@ -100,6 +101,7 @@ struct k_heap; #define SOF_DMA_DEV_HS BIT(13) /**< connectable to ACP HS I2S */ #define SOF_DMA_DEV_MICFIL BIT(14) /**< connectable to MICFIL fifo */ #define SOF_DMA_DEV_SW BIT(15) /**< connectable to ACP SW */ +#define SOF_DMA_DEV_TDM BIT(16) /**< connectable to ACP TDM I2S */ /* DMA access privilege flag */ #define SOF_DMA_ACCESS_EXCLUSIVE 1 From f3a34b69a3a80888047c45681da77d5ce8419bf9 Mon Sep 17 00:00:00 2001 From: DineshKumar Kalva Date: Thu, 25 Jun 2026 13:25:41 +0530 Subject: [PATCH 213/303] dai: acp: add SOF_DAI_AMD_TDM to DAI and DMA dispatch tables Route SOF_DAI_AMD_TDM through the DAI and DMA layer: - dai-zephyr.c: add SOF_DAI_AMD_TDM -> DAI_AMD_TDM / acptdm params - lib/dai.c: map SOF_DAI_AMD_TDM -> DAI_AMD_TDM in type conversion and set SOF_DMA_DEV_TDM/SOF_DMA_CAP_TDM in dai_set_device_params() - ipc3/dai.c: add SOF_DAI_AMD_TDM to dai_config_dma_channel(); handle TDM context allocation with struct tdm_context for CONFIG_SOC_ACP_7_X, propagating frame_fmt, pin_dir and dma_channel - zephyr/lib/dma.c: add amd_acp_tdm_dma DMA array entry guarded by DT_HAS_COMPAT_STATUS_OKAY(amd_acp_tdm_dma) Signed-off-by: Sneha Voona Signed-off-by: DineshKumar Kalva --- src/audio/dai-zephyr.c | 1 + src/ipc/ipc3/dai.c | 11 +++++++++++ src/lib/dai.c | 5 +++++ zephyr/lib/dma.c | 5 +++++ 4 files changed, 22 insertions(+) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index e65457d34383..91e3f6021662 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -201,6 +201,7 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, case SOF_DAI_AMD_SP: case SOF_DAI_AMD_SP_VIRTUAL: case SOF_DAI_AMD_BT: + case SOF_DAI_AMD_TDM: cfg.type = DAI_AMD_TDM; cfg_params = &sof_cfg->acptdm; break; diff --git a/src/ipc/ipc3/dai.c b/src/ipc/ipc3/dai.c index d41fd355847d..af06e21955cd 100644 --- a/src/ipc/ipc3/dai.c +++ b/src/ipc/ipc3/dai.c @@ -95,6 +95,7 @@ int dai_config_dma_channel(struct dai_data *dd, struct comp_dev *dev, const void break; case SOF_DAI_AMD_HS: case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_TDM: case SOF_DAI_AMD_SDW: { struct dai_config *params = (struct dai_config *)dd->dai->dev->config; @@ -195,6 +196,7 @@ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev) case SOF_DAI_AMD_SP_VIRTUAL: case SOF_DAI_AMD_HS: case SOF_DAI_AMD_HS_VIRTUAL: + case SOF_DAI_AMD_TDM: #if defined(CONFIG_AMD) && !defined(CONFIG_SOC_ACP_6_0) { struct acp_dma_dev_data *tdm_data = dd->dma->z_dev->data; @@ -213,6 +215,15 @@ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev) tdm_ctx = (struct tdm_context *)tdm_data->dai_index_ptr; } tdm_ctx->index = dd->dai->index; + tdm_ctx->frame_fmt = dev->ipc_config.frame_fmt; + /* AMD HW needs 24-bit data MSB-aligned in 32-bit word */ + if (dev->ipc_config.frame_fmt == SOF_IPC_FRAME_S24_4LE) { + dev->ipc_config.frame_fmt = SOF_IPC_FRAME_S24_4LE_MSB; + if (dd->dma_buffer) { + audio_stream_set_frm_fmt(&dd->dma_buffer->stream, + dev->ipc_config.frame_fmt); + } + } } #endif break; diff --git a/src/lib/dai.c b/src/lib/dai.c index 585218104e61..68163282adc1 100644 --- a/src/lib/dai.c +++ b/src/lib/dai.c @@ -239,6 +239,7 @@ static int sof_dai_type_to_zephyr(uint32_t type) case SOF_DAI_AMD_SP: case SOF_DAI_AMD_SP_VIRTUAL: case SOF_DAI_AMD_BT: + case SOF_DAI_AMD_TDM: return DAI_AMD_TDM; default: return -EINVAL; @@ -316,6 +317,10 @@ static void dai_set_device_params(struct dai *d) d->dma_dev = SOF_DMA_DEV_HS | SOF_DMA_DEV_SP | SOF_DMA_DEV_BT; d->dma_caps = SOF_DMA_CAP_HS | SOF_DMA_CAP_SP | SOF_DMA_CAP_BT; break; + case SOF_DAI_AMD_TDM: + d->dma_dev = SOF_DMA_DEV_TDM; + d->dma_caps = SOF_DMA_CAP_TDM; + break; case SOF_DAI_MEDIATEK_AFE: d->dma_dev = SOF_DMA_DEV_AFE_MEMIF; break; diff --git a/zephyr/lib/dma.c b/zephyr/lib/dma.c index 9a2fe540addf..1e28d7215026 100644 --- a/zephyr/lib/dma.c +++ b/zephyr/lib/dma.c @@ -243,8 +243,13 @@ APP_SYSUSER_DATA SHARED_DATA struct sof_dma dma[] = { .plat_data = { .dir = SOF_DMA_DIR_MEM_TO_DEV | SOF_DMA_DIR_DEV_TO_MEM, +#if defined(CONFIG_SOC_ACP_7_X) + .devs = SOF_DMA_DEV_TDM, + .caps = SOF_DMA_CAP_TDM, +#elif defined(CONFIG_SOC_ACP_7_0) .devs = SOF_DMA_DEV_HS | SOF_DMA_DEV_SP | SOF_DMA_DEV_BT, .caps = SOF_DMA_CAP_HS | SOF_DMA_CAP_SP | SOF_DMA_CAP_BT, +#endif .base = DMA0_BASE, .chan_size = DMA0_SIZE, .channels = 6, From 69781eec226534b98ae7b0496f16eabb3d8c0d80 Mon Sep 17 00:00:00 2001 From: DineshKumar Kalva Date: Thu, 25 Jun 2026 13:26:04 +0530 Subject: [PATCH 214/303] acp_7_x: board: enable TDM DAI and DMA drivers Enable CONFIG_DMA_AMD_ACP_TDM and CONFIG_DAI_AMD_TDM for the ACP 7.X ADSP board to activate TDM audio support. Signed-off-by: Sneha Voona Signed-off-by: DineshKumar Kalva --- app/boards/acp_7_x_adsp.conf | 3 +++ src/platform/amd/acp_7_0/include/platform/platform_misc.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/boards/acp_7_x_adsp.conf b/app/boards/acp_7_x_adsp.conf index 0ec10fff2bf7..04c63271c9ac 100644 --- a/app/boards/acp_7_x_adsp.conf +++ b/app/boards/acp_7_x_adsp.conf @@ -14,7 +14,9 @@ CONFIG_INTC_AMD_ACP=y CONFIG_DMA_AMD_ACP_HOST=y CONFIG_DMA_AMD_ACP_SDW=y CONFIG_DMA_AMD_ACP_SDW_CHANNEL_COUNT=64 +CONFIG_DMA_AMD_ACP_TDM=y CONFIG_DAI_AMD_SDW=y +CONFIG_DAI_AMD_TDM=y CONFIG_AMS=n CONFIG_WRAP_ACTUAL_POSITION=y CONFIG_TRACE=n @@ -52,3 +54,4 @@ CONFIG_ASRC_SUPPORT_CONVERSION_48000_TO_44100=n CONFIG_CORE_COUNT=1 CONFIG_FORMAT_CONVERT_HIFI3=n CONFIG_PCM_CONVERTER_FORMAT_S24_4LE_MSB=y +CONFIG_XTENSA_INTERRUPT_NONPREEMPTABLE=y diff --git a/src/platform/amd/acp_7_0/include/platform/platform_misc.h b/src/platform/amd/acp_7_0/include/platform/platform_misc.h index 2338a91317e0..83eb206ee2e9 100644 --- a/src/platform/amd/acp_7_0/include/platform/platform_misc.h +++ b/src/platform/amd/acp_7_0/include/platform/platform_misc.h @@ -422,10 +422,13 @@ struct sdw_pin_data { }; struct tdm_context { + uint64_t prev_pos; + uint32_t buff_size; uint32_t tdm_instance; uint32_t pin_dir; uint32_t dma_channel; uint32_t index; + uint32_t frame_fmt; }; struct dmic_context { From 5263b6e93eee5469c996047a2a8bb3eeb639f381 Mon Sep 17 00:00:00 2001 From: DineshKumar Kalva Date: Thu, 25 Jun 2026 13:32:34 +0530 Subject: [PATCH 215/303] tools: topology: add ACP 7.X TDM I2S topology support Add TDM I2S topology templates for ACP 7.X: - platform/common/acp-tdm.m4: TDM DAI and pipeline macros - sof-acp_7_x_i2s.m4: default 16-bit TDM topology - sof-acp_7_x_i2s_24bit.m4: 24-bit TDM topology - dai.m4: add SOF_DAI_AMD_TDM token - tokens.m4: add SOF_TKN_AMD_ACP_I2S_BITDEPTH token Signed-off-by: Sneha Voona Signed-off-by: DineshKumar Kalva --- tools/topology/topology1/CMakeLists.txt | 2 + tools/topology/topology1/m4/dai.m4 | 2 +- .../topology1/platform/common/acp-tdm.m4 | 74 +++++++ tools/topology/topology1/sof-acp_7_x_i2s.m4 | 187 ++++++++++++++++++ .../topology1/sof-acp_7_x_i2s_24bit.m4 | 165 ++++++++++++++++ tools/topology/topology1/sof/tokens.m4 | 1 + 6 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 tools/topology/topology1/platform/common/acp-tdm.m4 create mode 100644 tools/topology/topology1/sof-acp_7_x_i2s.m4 create mode 100644 tools/topology/topology1/sof-acp_7_x_i2s_24bit.m4 diff --git a/tools/topology/topology1/CMakeLists.txt b/tools/topology/topology1/CMakeLists.txt index f00903368c6a..95d81a01629f 100644 --- a/tools/topology/topology1/CMakeLists.txt +++ b/tools/topology/topology1/CMakeLists.txt @@ -123,6 +123,8 @@ set(TPLGS "sof-acp_7_0\;sof-acp_7_0" "sof-acp_7_0_sdw\;sof-acp_7_0-rt722-l0" "sof-acp_7_x_sdw\;sof-acp7x-rt721-l0" + "sof-acp_7_x_i2s\;sof-acp7x" + "sof-acp_7_x_i2s_24bit\;sof-acp7x-24bit" ) # This empty 'production/' source subdirectory exists only to create the diff --git a/tools/topology/topology1/m4/dai.m4 b/tools/topology/topology1/m4/dai.m4 index cd968c6c61fd..4dc5d5ec5135 100644 --- a/tools/topology/topology1/m4/dai.m4 +++ b/tools/topology/topology1/m4/dai.m4 @@ -155,7 +155,7 @@ define(`DO_DAI_CONFIG', `' ` id "'$3`"' `' -` ifelse($1, `SSP', $5, $1, `HDA', $5, $1, `ALH', $5, $1, `ESAI', $5, $1, `SAI', $5, $1, `MICFIL', $5, $1, `AFE', $5, $1, `ACP', $5, $1, `ACPSP', $5, $1,`ACPSP_VIRTUAL', $5, $1, `ACPHS', $5, $1, `ACPHS_VIRTUAL', $5, $1, `ACP_SDW', $5, $1, `ACPDMIC', $5, `}')' +` ifelse($1, `SSP', $5, $1, `HDA', $5, $1, `ALH', $5, $1, `ESAI', $5, $1, `SAI', $5, $1, `MICFIL', $5, $1, `AFE', $5, $1, `ACP', $5, $1, `ACPSP', $5, $1,`ACPSP_VIRTUAL', $5, $1, `ACPHS', $5, $1, `ACPHS_VIRTUAL', $5, $1, `ACP_SDW', $5, $1, `ACPDMIC', $5, $1, `ACPTDM', $5, `}')' `ifelse($1, `DMIC', $5, `')' `SectionVendorTuples."'N_DAI_CONFIG($1$2)`_tuples_common" {' ` tokens "sof_dai_tokens"' diff --git a/tools/topology/topology1/platform/common/acp-tdm.m4 b/tools/topology/topology1/platform/common/acp-tdm.m4 new file mode 100644 index 000000000000..22e72b0d5ea4 --- /dev/null +++ b/tools/topology/topology1/platform/common/acp-tdm.m4 @@ -0,0 +1,74 @@ +divert(-1) + +dnl ACPTDM related macros + +dnl ACP_CLOCK(clock, freq, codec_provider, polarity) +dnl polarity is optional +define(`ACP_CLOCK', + $1 STR($3) + $1_freq STR($2)) + `ifelse($4, `inverted', `$1_invert "true"',`')') + +dnl ACP_TDM(slots, width, tx_mask, rx_mask) +define(`ACP_TDM', +` tdm_slots 'STR($1) +` tdm_slot_width 'STR($2) +` tx_slots 'STR($3) +` rx_slots 'STR($4) +) + +dnl ACP_CONFIG(format, mclk, bclk, fsync, tdm, tdm_config_data) +define(`ACPTDM_CONFIG', +` format "'$1`"' +` '$2 +` '$3 +` '$4 +` '$5 +`}' +$6 +) + +dnl ACPTDM_VIRTUAL_CONFIG(format, mclk, bclk, fsync, tdm, tdm_config_data) +define(`ACPTDM_VIRTUAL_CONFIG', +` format "'$1`"' +` '$2 +` '$3 +` '$4 +` '$5 +`}' +$6 +) + +dnl ACPTDM_CONFIG_DATA(type, idx, rate, channel,i2s_tdm_mode) +#i2s_tdm_mode 1-> tdm mode, 0->i2s mode +define(`ACPTDM_CONFIG_DATA', +`SectionVendorTuples."'N_DAI_CONFIG($1$2)`_tuples" {' +` tokens "sof_acp_tokens"' +` tuples."word" {' +` SOF_TKN_AMD_ACP_RATE' STR($3) +` SOF_TKN_AMD_ACP_CH' STR($4) +` SOF_TKN_AMD_ACP_I2S_TDM_MODE' STR($5) +` SOF_TKN_AMD_ACP_I2S_BITDEPTH' STR($6) +` }' +`}' +`SectionData."'N_DAI_CONFIG($1$2)`_data" {' +` tuples "'N_DAI_CONFIG($1$2)`_tuples"' +`}' +) + +dnl ACPTDM_VIRTUAL_CONFIG_DATA(type, idx, rate, channel,i2s_tdm_mode) +#i2s_tdm_mode 1-> tdm mode, 0->i2s mode +define(`ACPTDM_VIRTUAL_CONFIG_DATA', +`SectionVendorTuples."'N_DAI_CONFIG($1$2)`_tuples" {' +` tokens "sof_acp_tokens"' +` tuples."word" {' +` SOF_TKN_AMD_ACP_RATE' STR($3) +` SOF_TKN_AMD_ACP_CH' STR($4) +` SOF_TKN_AMD_ACP_I2S_TDM_MODE' STR($5) +` }' +`}' +`SectionData."'N_DAI_CONFIG($1$2)`_data" {' +` tuples "'N_DAI_CONFIG($1$2)`_tuples"' +`}' +) +divert(0)dnl diff --git a/tools/topology/topology1/sof-acp_7_x_i2s.m4 b/tools/topology/topology1/sof-acp_7_x_i2s.m4 new file mode 100644 index 000000000000..6f633f700a56 --- /dev/null +++ b/tools/topology/topology1/sof-acp_7_x_i2s.m4 @@ -0,0 +1,187 @@ +# +# Topology for ACP_7_X with TDM. +# +# Include topology builder +include(`utils.m4') +include(`dai.m4') +include(`pipeline.m4') +include(`acp-tdm.m4') + +# Include TLV library +include(`common/tlv.m4') + +# Include Token library +include(`sof/tokens.m4') + +# Include ACP DSP configuration +include(`platform/amd/acp.m4') + +# +# Pipeline Graph (16-bit / s16le): +# +# PLAYBACK: +# [Host PCM ] -> [Passthrough Pipeline ] -> [ACPTDM DAI ] +# s16le 2ch s16le 48kHz s16le 2ch +# | +# [acp-i2s0-codec] +# I2S bclk=3.072MHz +# fsync=48kHz, 2ch +# | +# CAPTURE: +# [ACPTDM DAI ] -> [Passthrough Pipeline ] -> [Host PCM ] +# s16le 2ch s16le 48kHz s16le 2ch +# + +DEBUG_START +#====================================================================== +# Playback pipeline 1 on PCM 0 using max 2 channels of s16le. + +dnl PIPELINE_PCM_ADD(pipeline, +dnl pipe id, pcm, max channels, format, +dnl period, priority, core, +dnl pcm_min_rate, pcm_max_rate, pipeline_rate) +# Playback pipeline 0 on PCM 0, dai index 0, link id 0 using max 2 channels of s16le. + +# Schedule 96 frames per 2000us deadline on core 0 with priority 0 +PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, + 0, 0, 2, s16le, + 2000, 0, 0, + 48000, 48000, 48000) + +# Capture pipeline 3 on PCM 0, dai index 0 using max 2 channels of s16le. +PIPELINE_PCM_ADD(sof/pipe-passthrough-capture.m4, + 3, 0, 2, s16le, + 2000, 0, 0, + 48000, 48000, 48000) +#=========================================================================== +dnl DAI_ADD(pipeline, +dnl pipe id, dai type, dai_index, dai_be, +dnl buffer, periods, format, +dnl deadline, priority, core, time_domain) + +# Schedule 96 frames per 2000us deadline on core 0 with priority 0 + +# playback DAI is ACPTDM using 2 periods +DAI_ADD(sof/pipe-dai-playback.m4, + 0, ACPTDM, 0, acp-i2s0-codec, + PIPELINE_SOURCE_0, 2, s16le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +# Capture DAI is ACPTDM using 2 periods +DAI_ADD(sof/pipe-dai-capture.m4, + 3, ACPTDM, 0, acp-i2s0-codec, + PIPELINE_SINK_3, 2, s16le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) +#=========================================================================== +# playback DAI is ACPTDM using 2 periods + +dnl DAI_CONFIG(type, dai_index, link_id, name, ACPTDM_config/acpdmic_config) +dnl ACPTDM_CONFIG(format, mclk, bclk, fsync, tdm, ACPTDM_config_data) +dnl ACP_CLOCK(clock, freq, codec_provider, polarity) +dnl ACPTDM_CONFIG_DATA(type, idx, valid bits, mclk_id) +dnl mclk_id is optional + +DAI_CONFIG(ACPTDM, 0, 0, acp-i2s0-codec, + ACPTDM_CONFIG(I2S, ACP_CLOCK(mclk, 49152000, codec_mclk_in), + ACP_CLOCK(bclk, 3072000, codec_consumer), + ACP_CLOCK(fsync, 48000, codec_consumer), + ACP_TDM(2, 32, 3, 3),ACPTDM_CONFIG_DATA(ACPTDM, 0, 48000, 2, 0, 0)) +) +dnl PCM_DUPLEX_ADD(name, pcm_id, playback_pipeline, capture_pipeline) +PCM_DUPLEX_ADD(I2STDM0, 0, PIPELINE_PCM_0, PIPELINE_PCM_3) + +#==================================================================================================================== +# TDM instance 1 + +dnl PIPELINE_PCM_ADD(pipeline, +dnl pipe id, pcm, max channels, format, +dnl period, priority, core, +dnl pcm_min_rate, pcm_max_rate, pipeline_rate) + +PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, + 1, 1, 2, s16le, + 2000, 0, 0, + 48000, 48000, 48000) +PIPELINE_PCM_ADD(sof/pipe-passthrough-capture.m4, + 4, 1, 2, s16le, + 2000, 0, 0, + 48000, 48000, 48000) + +dnl DAI_ADD(pipeline, +dnl pipe id, dai type, dai_index, dai_be, +dnl buffer, periods, format, +dnl deadline, priority, core, time_domain) + +DAI_ADD(sof/pipe-dai-playback.m4, + 1, ACPTDM, 1, acp-i2s1-codec, + PIPELINE_SOURCE_1, 2, s16le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +DAI_ADD(sof/pipe-dai-capture.m4, + 4, ACPTDM, 1, acp-i2s1-codec, + PIPELINE_SINK_4, 2, s16le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) +dnl DAI_CONFIG(type, dai_index, link_id, name, ACPTDM_config/acpdmic_config) +dnl ACPTDM_CONFIG(format, mclk, bclk, fsync, tdm, ACPTDM_config_data) +dnl ACP_CLOCK(clock, freq, codec_provider, polarity) +dnl ACPTDM_CONFIG_DATA(type, idx, valid bits, mclk_id) +dnl mclk_id is optional + +DAI_CONFIG(ACPTDM, 1, 1, acp-i2s1-codec, + ACPTDM_CONFIG(I2S, ACP_CLOCK(mclk, 49152000, codec_mclk_in), + ACP_CLOCK(bclk, 3072000, codec_consumer), + ACP_CLOCK(fsync, 48000, codec_consumer), + ACP_TDM(2, 32, 3, 3), ACPTDM_CONFIG_DATA(ACPTDM, 1, 48000, 2, 0))) + +dnl PCM_DUPLEX_ADD(name, pcm_id, playback_pipeline, capture_pipeline) +PCM_DUPLEX_ADD(I2STDM1, 1, PIPELINE_PCM_1, PIPELINE_PCM_4) + +#==================================================================================================================== +# TDM instance 2 + +dnl PIPELINE_PCM_ADD(pipeline, +dnl pipe id, pcm, max channels, format, +dnl period, priority, core, +dnl pcm_min_rate, pcm_max_rate, pipeline_rate) + +PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, + 2, 2, 2, s16le, + 2000, 0, 0, + 48000, 48000, 48000) +PIPELINE_PCM_ADD(sof/pipe-passthrough-capture.m4, + 5, 2, 2, s16le, + 2000, 0, 0, + 48000, 48000, 48000) + +dnl DAI_ADD(pipeline, +dnl pipe id, dai type, dai_index, dai_be, +dnl buffer, periods, format, +dnl deadline, priority, core, time_domain) + +DAI_ADD(sof/pipe-dai-playback.m4, + 2, ACPTDM, 2, acp-i2s2-codec, + PIPELINE_SOURCE_2, 2, s16le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +DAI_ADD(sof/pipe-dai-capture.m4, + 5, ACPTDM, 2, acp-i2s2-codec, + PIPELINE_SINK_5, 2, s16le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +dnl DAI_CONFIG(type, dai_index, link_id, name, ACPTDM_config/acpdmic_config) +dnl ACPTDM_CONFIG(format, mclk, bclk, fsync, tdm, ACPTDM_config_data) +dnl ACP_CLOCK(clock, freq, codec_provider, polarity) +dnl ACPTDM_CONFIG_DATA(type, idx, valid bits, mclk_id) +dnl mclk_id is optional + +DAI_CONFIG(ACPTDM, 2, 2, acp-i2s2-codec, + ACPTDM_CONFIG(I2S, ACP_CLOCK(mclk, 49152000, codec_mclk_in), + ACP_CLOCK(bclk, 3072000, codec_consumer), + ACP_CLOCK(fsync, 48000, codec_consumer), + ACP_TDM(2, 32, 3, 3), ACPTDM_CONFIG_DATA(ACPTDM, 2, 48000, 2, 0))) + +dnl PCM_DUPLEX_ADD(name, pcm_id, playback_pipeline, capture_pipeline) +PCM_DUPLEX_ADD(I2STDM2, 2, PIPELINE_PCM_2, PIPELINE_PCM_5) +#==================================================================================================================== + +DEBUG_END diff --git a/tools/topology/topology1/sof-acp_7_x_i2s_24bit.m4 b/tools/topology/topology1/sof-acp_7_x_i2s_24bit.m4 new file mode 100644 index 000000000000..dabf7927d90c --- /dev/null +++ b/tools/topology/topology1/sof-acp_7_x_i2s_24bit.m4 @@ -0,0 +1,165 @@ +# +# Topology for ACP_7_x with TDM (24-bit). +# +# Include topology builder +include(`utils.m4') +include(`dai.m4') +include(`pipeline.m4') +include(`acp-tdm.m4') + +# Include TLV library +include(`common/tlv.m4') + +# Include Token library +include(`sof/tokens.m4') + +# Include ACP DSP configuration +include(`platform/amd/acp.m4') + +# +# Pipeline Graph (24-bit / s24le): +# +# PLAYBACK/CAPTURE: +# TDM0, TDM1, TDM2 each expose a duplex PCM. +# Host side uses s32le; DAI side uses s24le (frame_fmt=1). +# + +DEBUG_START +#====================================================================== +# Playback/Capture pipelines for three TDM instances (24-bit). + +# TDM instance 0 + +dnl PIPELINE_PCM_ADD(pipeline, +dnl pipe id, pcm, max channels, format, +dnl period, priority, core, +dnl pcm_min_rate, pcm_max_rate, pipeline_rate) +PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, + 0, 0, 2, s32le, + 2000, 0, 0, + 48000, 48000, 48000) + +PIPELINE_PCM_ADD(sof/pipe-passthrough-capture.m4, + 3, 0, 2, s32le, + 2000, 0, 0, + 48000, 48000, 48000) + +dnl DAI_ADD(pipeline, +dnl pipe id, dai type, dai_index, dai_be, +dnl buffer, periods, format, +dnl deadline, priority, core, time_domain) +DAI_ADD(sof/pipe-dai-playback.m4, + 0, ACPTDM, 0, acp-i2s0-codec, + PIPELINE_SOURCE_0, 2, s24le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +DAI_ADD(sof/pipe-dai-capture.m4, + 3, ACPTDM, 0, acp-i2s0-codec, + PIPELINE_SINK_3, 2, s24le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +dnl DAI_CONFIG(type, dai_index, link_id, name, ACPTDM_config/acpdmic_config) +dnl ACPTDM_CONFIG(format, mclk, bclk, fsync, tdm, ACPTDM_config_data) +dnl ACP_CLOCK(clock, freq, codec_provider, polarity) +dnl ACPTDM_CONFIG_DATA(type, idx, rate, tdm_mode, frame_fmt(0 for s16le, 1 for s24le, 2 for s32le)) +dnl mclk_id is optional +DAI_CONFIG(ACPTDM, 0, 0, acp-i2s0-codec, + ACPTDM_CONFIG(I2S, ACP_CLOCK(mclk, 49152000, codec_mclk_in), + ACP_CLOCK(bclk, 3072000, codec_consumer), + ACP_CLOCK(fsync, 48000, codec_consumer), + ACP_TDM(2, 32, 3, 3), ACPTDM_CONFIG_DATA(ACPTDM, 0, 48000, 2, 0, 1))) + +dnl PCM_DUPLEX_ADD(name, pcm_id, playback_pipeline, capture_pipeline) +PCM_DUPLEX_ADD(I2STDM0, 0, PIPELINE_PCM_0, PIPELINE_PCM_3) + +#============================ +# TDM instance 1 + +dnl PIPELINE_PCM_ADD(pipeline, +dnl pipe id, pcm, max channels, format, +dnl period, priority, core, +dnl pcm_min_rate, pcm_max_rate, pipeline_rate) +PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, + 1, 1, 2, s32le, + 2000, 0, 0, + 48000, 48000, 48000) + +PIPELINE_PCM_ADD(sof/pipe-passthrough-capture.m4, + 4, 1, 2, s32le, + 2000, 0, 0, + 48000, 48000, 48000) + +dnl DAI_ADD(pipeline, +dnl pipe id, dai type, dai_index, dai_be, +dnl buffer, periods, format, +dnl deadline, priority, core, time_domain) +DAI_ADD(sof/pipe-dai-playback.m4, + 1, ACPTDM, 1, acp-i2s1-codec, + PIPELINE_SOURCE_1, 2, s24le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +DAI_ADD(sof/pipe-dai-capture.m4, + 4, ACPTDM, 1, acp-i2s1-codec, + PIPELINE_SINK_4, 2, s24le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +dnl DAI_CONFIG(type, dai_index, link_id, name, ACPTDM_config/acpdmic_config) +dnl ACPTDM_CONFIG(format, mclk, bclk, fsync, tdm, ACPTDM_config_data) +dnl ACP_CLOCK(clock, freq, codec_provider, polarity) +dnl ACPTDM_CONFIG_DATA(type, idx, rate, tdm_mode, frame_fmt(0 for s16le, 1 for s24le, 2 for s32le)) +dnl mclk_id is optional +DAI_CONFIG(ACPTDM, 1, 1, acp-i2s1-codec, + ACPTDM_CONFIG(I2S, ACP_CLOCK(mclk, 49152000, codec_mclk_in), + ACP_CLOCK(bclk, 3072000, codec_consumer), + ACP_CLOCK(fsync, 48000, codec_consumer), + ACP_TDM(2, 32, 3, 3), ACPTDM_CONFIG_DATA(ACPTDM, 1, 48000, 2, 0, 1))) + +dnl PCM_DUPLEX_ADD(name, pcm_id, playback_pipeline, capture_pipeline) +PCM_DUPLEX_ADD(I2STDM1, 1, PIPELINE_PCM_1, PIPELINE_PCM_4) + +#============================ +# TDM instance 2 + +dnl PIPELINE_PCM_ADD(pipeline, +dnl pipe id, pcm, max channels, format, +dnl period, priority, core, +dnl pcm_min_rate, pcm_max_rate, pipeline_rate) +PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, + 2, 2, 2, s32le, + 2000, 0, 0, + 48000, 48000, 48000) + +PIPELINE_PCM_ADD(sof/pipe-passthrough-capture.m4, + 5, 2, 2, s32le, + 2000, 0, 0, + 48000, 48000, 48000) + +dnl DAI_ADD(pipeline, +dnl pipe id, dai type, dai_index, dai_be, +dnl buffer, periods, format, +dnl deadline, priority, core, time_domain) +DAI_ADD(sof/pipe-dai-playback.m4, + 2, ACPTDM, 2, acp-i2s2-codec, + PIPELINE_SOURCE_2, 2, s24le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +DAI_ADD(sof/pipe-dai-capture.m4, + 5, ACPTDM, 2, acp-i2s2-codec, + PIPELINE_SINK_5, 2, s24le, + 2000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +dnl DAI_CONFIG(type, dai_index, link_id, name, ACPTDM_config/acpdmic_config) +dnl ACPTDM_CONFIG(format, mclk, bclk, fsync, tdm, ACPTDM_config_data) +dnl ACP_CLOCK(clock, freq, codec_provider, polarity) +dnl ACPTDM_CONFIG_DATA(type, idx, rate, tdm_mode, frame_fmt(0 for s16le, 1 for s24le, 2 for s32le)) +dnl mclk_id is optional +DAI_CONFIG(ACPTDM, 2, 2, acp-i2s2-codec, + ACPTDM_CONFIG(I2S, ACP_CLOCK(mclk, 49152000, codec_mclk_in), + ACP_CLOCK(bclk, 3072000, codec_consumer), + ACP_CLOCK(fsync, 48000, codec_consumer), + ACP_TDM(2, 32, 3, 3), ACPTDM_CONFIG_DATA(ACPTDM, 2, 48000, 2, 0, 1))) + +dnl PCM_DUPLEX_ADD(name, pcm_id, playback_pipeline, capture_pipeline) +PCM_DUPLEX_ADD(I2STDM2, 2, PIPELINE_PCM_2, PIPELINE_PCM_5) + +DEBUG_END diff --git a/tools/topology/topology1/sof/tokens.m4 b/tools/topology/topology1/sof/tokens.m4 index b9748bb52fbc..a9a12f5264b6 100644 --- a/tools/topology/topology1/sof/tokens.m4 +++ b/tools/topology/topology1/sof/tokens.m4 @@ -141,6 +141,7 @@ SectionVendorTokens."sof_acp_tokens" { SOF_TKN_AMD_ACP_RATE "1700" SOF_TKN_AMD_ACP_CH "1701" SOF_TKN_AMD_ACP_I2S_TDM_MODE "1702" + SOF_TKN_AMD_ACP_I2S_BITDEPTH "1703" } SectionVendorTokens."sof_acpdmic_tokens" { From 5bbd3c09dfdbbed719c98b522e52106a0a41e849 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 22:14:43 +0300 Subject: [PATCH 216/303] module: generic: turn mod_data_blob_handler_new() into a syscall Convert mod_data_blob_handler_new() to a Zephyr syscall following the same pattern as mod_alloc_ext(), mod_free(), and mod_fast_get(). This allows modules running in user mode to call the function through the syscall interface. The z_vrfy_ handler validates the processing_module pointer and its heap region before dispatching to the implementation. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module/generic.c | 20 +++++++++++++++++-- .../sof/audio/module_adapter/module/generic.h | 7 ++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 95a5bb000d6a..3f280082c6a3 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -283,7 +283,7 @@ EXPORT_SYMBOL(z_impl_mod_alloc_ext); * Like comp_data_blob_handler_new() but the handler is automatically freed. */ #if CONFIG_COMP_BLOB -struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod) +struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processing_module *mod) { struct module_resources * __maybe_unused res = &mod->priv.resources; struct comp_data_blob_handler *bhp; @@ -307,7 +307,7 @@ struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_modul return bhp; } -EXPORT_SYMBOL(mod_data_blob_handler_new); +EXPORT_SYMBOL(z_impl_mod_data_blob_handler_new); #endif /** @@ -476,6 +476,22 @@ int z_vrfy_mod_free(struct processing_module *mod, const void *ptr) return z_impl_mod_free(mod, ptr); } #include + +#if CONFIG_COMP_BLOB +struct comp_data_blob_handler *z_vrfy_mod_data_blob_handler_new(struct processing_module *mod) +{ + size_t h_size = 0; + uintptr_t h_start; + + K_OOPS(K_SYSCALL_MEMORY_WRITE(mod, sizeof(*mod))); + mod_heap_info(mod, &h_size, &h_start); + if (h_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE(h_start, h_size)); + + return z_impl_mod_data_blob_handler_new(mod); +} +#include +#endif #endif #if CONFIG_COMP_BLOB diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 94827d86cd9f..f4f4d6d381bc 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -241,7 +241,12 @@ static inline void *mod_zalloc(struct processing_module *mod, size_t size) } #if CONFIG_COMP_BLOB -struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod); +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +__syscall struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod); +#else +struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processing_module *mod); +#define mod_data_blob_handler_new z_impl_mod_data_blob_handler_new +#endif void mod_data_blob_handler_free(struct processing_module *mod, struct comp_data_blob_handler *dbh); #endif #if CONFIG_FAST_GET From 88241fed12edc4c67a75800bc1dcb3c040b7b9c3 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 26 Jun 2026 11:30:10 +0300 Subject: [PATCH 217/303] module: generic: turn mod_balloc_align() into a syscall Convert mod_balloc_align() to a Zephyr syscall following the same pattern as mod_alloc_ext(), mod_free(), mod_fast_get(), and mod_data_blob_handler_new(). This allows modules running in user mode to allocate aligned buffer memory blocks through the syscall interface. The z_vrfy_ handler validates the processing_module pointer and its heap region before dispatching to the implementation. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module/generic.c | 18 ++++++++++++++++-- .../sof/audio/module_adapter/module/generic.h | 7 ++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 3f280082c6a3..e1bd0813b097 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -185,7 +185,7 @@ void mod_heap_info(struct processing_module *mod, size_t *size, uintptr_t *start * unloaded. The back-end, rballoc(), always aligns the memory to * PLATFORM_DCACHE_ALIGN at the minimum. */ -void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment) +void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment) { struct module_resources *res = &mod->priv.resources; struct module_resource *container; @@ -223,7 +223,7 @@ void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignm return ptr; } -EXPORT_SYMBOL(mod_balloc_align); +EXPORT_SYMBOL(z_impl_mod_balloc_align); /** * Allocates aligned memory block with flags for module. @@ -463,6 +463,20 @@ void *z_vrfy_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t } #include +void *z_vrfy_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment) +{ + size_t h_size = 0; + uintptr_t h_start; + + K_OOPS(K_SYSCALL_MEMORY_WRITE(mod, sizeof(*mod))); + mod_heap_info(mod, &h_size, &h_start); + if (h_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE(h_start, h_size)); + + return z_impl_mod_balloc_align(mod, size, alignment); +} +#include + int z_vrfy_mod_free(struct processing_module *mod, const void *ptr) { size_t h_size = 0; diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index f4f4d6d381bc..1d9602223563 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -191,7 +191,12 @@ struct module_processing_data { /*****************************************************************************/ int module_load_config(struct comp_dev *dev, const void *cfg, size_t size); int module_init(struct processing_module *mod); -void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +__syscall void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); +#else +void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); +#define mod_balloc_align z_impl_mod_balloc_align +#endif void mod_resource_init(struct processing_module *mod); void mod_heap_info(struct processing_module *mod, size_t *size, uintptr_t *start); #if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) From 7bb70f5bfff9620c9ae11eed1945902d34009f1c Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 22:26:20 +0300 Subject: [PATCH 218/303] module: generic: add mutex to protect module_resources Add a k_mutex to struct module_resources and take it around every objpool access in z_impl_mod_balloc_align(), z_impl_mod_alloc_ext(), z_impl_mod_data_blob_handler_new(), z_impl_mod_fast_get(), z_impl_mod_free(), and mod_free_all(). This prevents concurrent access to the resource tracking pool when a module's IPC thread and processing thread operate in parallel (e.g. set_large_config arriving while the module is running). In mod_free_all() the mutex is released before mod_resource_init() re-initializes it, so the lock is not held across re-initialization. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module/generic.c | 44 ++++++++++++++++--- .../sof/audio/module_adapter/module/generic.h | 2 + 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index e1bd0813b097..83ac6eb46cae 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -12,6 +12,7 @@ */ #include +#include #include #include #include @@ -88,6 +89,7 @@ void mod_resource_init(struct processing_module *mod) struct module_resources *res = &mod->priv.resources; /* Init memory list */ + k_mutex_init(&res->lock); list_init(&res->objpool.list); res->objpool.heap = res->alloc->heap; res->objpool.vreg = res->alloc->vreg; @@ -192,13 +194,18 @@ void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); + container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } if (!size) { comp_err(mod->dev, "requested allocation of 0 bytes."); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -210,6 +217,7 @@ void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t comp_err(mod->dev, "Failed to alloc %zu bytes %zu alignment for comp %#x.", size, alignment, dev_comp_id(mod->dev)); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } /* Store reference to allocated memory */ @@ -221,6 +229,7 @@ void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t if (res->heap_usage > res->heap_high_water_mark) res->heap_high_water_mark = res->heap_usage; + k_mutex_unlock(&res->lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_balloc_align); @@ -243,13 +252,18 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); + container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } if (!size) { comp_err(mod->dev, "requested allocation of 0 bytes."); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -260,6 +274,7 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t comp_err(mod->dev, "Failed to alloc %zu bytes %zu alignment for comp %#x.", size, alignment, dev_comp_id(mod->dev)); container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } /* Store reference to allocated memory */ @@ -271,6 +286,7 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t if (res->heap_usage > res->heap_high_water_mark) res->heap_high_water_mark = res->heap_usage; + k_mutex_unlock(&res->lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_alloc_ext); @@ -285,19 +301,24 @@ EXPORT_SYMBOL(z_impl_mod_alloc_ext); #if CONFIG_COMP_BLOB struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processing_module *mod) { - struct module_resources * __maybe_unused res = &mod->priv.resources; + struct module_resources *res = &mod->priv.resources; struct comp_data_blob_handler *bhp; struct module_resource *container; MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); + container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } bhp = comp_data_blob_handler_new_ext(mod->dev, false, NULL, NULL); if (!bhp) { container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -305,6 +326,7 @@ struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processin container->size = 0; container->type = MOD_RES_BLOB_HANDLER; + k_mutex_unlock(&res->lock); return bhp; } EXPORT_SYMBOL(z_impl_mod_data_blob_handler_new); @@ -327,13 +349,18 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons MEM_API_CHECK_THREAD(res); + k_mutex_lock(&res->lock, K_FOREVER); + container = container_get(mod); - if (!container) + if (!container) { + k_mutex_unlock(&res->lock); return NULL; + } ptr = fast_get(res->alloc, dram_ptr, size); if (!ptr) { container_put(mod, container); + k_mutex_unlock(&res->lock); return NULL; } @@ -341,6 +368,7 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons container->size = 0; container->type = MOD_RES_FAST_GET; + k_mutex_unlock(&res->lock); return ptr; } EXPORT_SYMBOL(z_impl_mod_fast_get); @@ -418,8 +446,12 @@ int z_impl_mod_free(struct processing_module *mod, const void *ptr) /* Find which container holds this memory */ struct mod_res_cb_arg cb_arg = {mod, ptr}; + + k_mutex_lock(&res->lock, K_FOREVER); int ret = objpool_iterate(&res->objpool, mod_res_free, &cb_arg); + k_mutex_unlock(&res->lock); + if (ret < 0) comp_err(mod->dev, "error: could not find memory pointed by %p", ptr); @@ -733,8 +765,10 @@ void mod_free_all(struct processing_module *mod) /* Free all contents found in used containers */ struct mod_res_cb_arg cb_arg = {mod, NULL}; + k_mutex_lock(&res->lock, K_FOREVER); objpool_iterate(&res->objpool, mod_res_free, &cb_arg); objpool_prune(&res->objpool); + k_mutex_unlock(&res->lock); /* Make sure resource lists and accounting are reset */ mod_resource_init(mod); diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 1d9602223563..5c16c4c3669a 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -13,6 +13,7 @@ #ifndef __SOF_AUDIO_MODULE_GENERIC__ #define __SOF_AUDIO_MODULE_GENERIC__ +#include #include #include #include @@ -129,6 +130,7 @@ struct module_param { * when the module unloads. */ struct module_resources { + struct k_mutex lock; struct objpool_head objpool; size_t heap_usage; size_t heap_high_water_mark; From 768faad56199b52585c42f181011013102b1e164 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 25 Jun 2026 22:43:58 +0300 Subject: [PATCH 219/303] module: generic: remove MEM_API_CHECK_THREAD debug mechanism Remove the MEM_API_CHECK_THREAD() macro, CONFIG_MODULE_MEMORY_API_DEBUG Kconfig option, and the rsrc_mngr field from struct module_resources. This single-thread-owner check is now redundant: the previous commit added a spinlock that properly serializes all accesses to the resource pool, making the debug-only thread identity warning unnecessary. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/Kconfig | 11 --------- src/audio/module_adapter/module/generic.c | 24 ------------------- .../sof/audio/module_adapter/module/generic.h | 7 ------ 3 files changed, 42 deletions(-) diff --git a/src/audio/module_adapter/Kconfig b/src/audio/module_adapter/Kconfig index a9f88dd1f2ad..9bea2a5dd175 100644 --- a/src/audio/module_adapter/Kconfig +++ b/src/audio/module_adapter/Kconfig @@ -13,17 +13,6 @@ menu "Processing modules" containers to allocate at once is selected by this config option. - config MODULE_MEMORY_API_DEBUG - bool "Turn on memory API thread safety checks" - default y if DEBUG - help - The Module Memory API structures are not protected - by locks. This is because the initialization, - allocation, and freeing of resources should always - be done in the same thread. This option adds an - assert to make sure no other thread makes such - operations. - config CADENCE_CODEC bool "Cadence codec" help diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index 83ac6eb46cae..ee1b2df92829 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -26,16 +26,6 @@ #include #endif -/* The __ZEPHYR__ condition is to keep cmocka tests working */ -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) -#define MEM_API_CHECK_THREAD(res) do { \ - if ((res)->rsrc_mngr != k_current_get()) \ - LOG_WRN("mngr %p != cur %p", (res)->rsrc_mngr, k_current_get()); \ -} while (0) -#else -#define MEM_API_CHECK_THREAD(res) -#endif - LOG_MODULE_DECLARE(module_adapter, CONFIG_SOF_LOG_LEVEL); int module_load_config(struct comp_dev *dev, const void *cfg, size_t size) @@ -124,9 +114,6 @@ int module_init(struct processing_module *mod) return -EIO; } -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) - mod->priv.resources.rsrc_mngr = k_current_get(); -#endif /* Now we can proceed with module specific initialization */ #if CONFIG_SOF_USERSPACE_APPLICATION if (mod->dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) @@ -192,8 +179,6 @@ void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t struct module_resources *res = &mod->priv.resources; struct module_resource *container; - MEM_API_CHECK_THREAD(res); - k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); @@ -250,8 +235,6 @@ void *z_impl_mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t struct module_resources *res = &mod->priv.resources; struct module_resource *container; - MEM_API_CHECK_THREAD(res); - k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); @@ -305,8 +288,6 @@ struct comp_data_blob_handler *z_impl_mod_data_blob_handler_new(struct processin struct comp_data_blob_handler *bhp; struct module_resource *container; - MEM_API_CHECK_THREAD(res); - k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); @@ -347,8 +328,6 @@ const void *z_impl_mod_fast_get(struct processing_module *mod, const void * cons struct module_resource *container; const void *ptr; - MEM_API_CHECK_THREAD(res); - k_mutex_lock(&res->lock, K_FOREVER); container = container_get(mod); @@ -440,7 +419,6 @@ int z_impl_mod_free(struct processing_module *mod, const void *ptr) { struct module_resources *res = &mod->priv.resources; - MEM_API_CHECK_THREAD(res); if (!ptr) return 0; @@ -760,8 +738,6 @@ void mod_free_all(struct processing_module *mod) { struct module_resources *res = &mod->priv.resources; - MEM_API_CHECK_THREAD(res); - /* Free all contents found in used containers */ struct mod_res_cb_arg cb_arg = {mod, NULL}; diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 5c16c4c3669a..c44b7a5a6c18 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -21,10 +21,6 @@ #include #include "module_interface.h" -/* The __ZEPHYR__ condition is to keep cmocka tests working */ -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) -#include -#endif #include /* @@ -135,9 +131,6 @@ struct module_resources { size_t heap_usage; size_t heap_high_water_mark; struct mod_alloc_ctx *alloc; -#if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) - k_tid_t rsrc_mngr; -#endif }; enum mod_resource_type { From c0037d9435a822104fdb9063a01996674eff05ab Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 13:08:12 +0100 Subject: [PATCH 220/303] selector: re-validate configuration on control set A new configuration accepted at runtime through the control set path was copied in without the validation done when parameters are applied, so a later out-of-range channel count could overflow the channel table. Validate the blob size and channel fields before accepting it. Signed-off-by: Liam Girdwood --- src/audio/selector/selector.c | 70 ++++++++++++++++++++++++++++++++ src/include/sof/audio/selector.h | 1 + 2 files changed, 71 insertions(+) diff --git a/src/audio/selector/selector.c b/src/audio/selector/selector.c index 6afc92d8bdfc..95cef6c72bb8 100644 --- a/src/audio/selector/selector.c +++ b/src/audio/selector/selector.c @@ -231,16 +231,86 @@ static int selector_ctrl_set_data(struct comp_dev *dev, struct sof_ipc_ctrl_data *cdata) { struct comp_data *cd = comp_get_drvdata(dev); + struct comp_buffer *src; struct sof_sel_config *cfg; + uint32_t src_channels; int ret = 0; switch (cdata->cmd) { case SOF_CTRL_CMD_BINARY: comp_dbg(dev, "SOF_CTRL_CMD_BINARY"); + if (cdata->data->size < sizeof(struct sof_sel_config)) { + comp_err(dev, "invalid config blob size %u", cdata->data->size); + return -EINVAL; + } + cfg = (struct sof_sel_config *) ASSUME_ALIGNED(&cdata->data->data, 4); + /* + * The config validated at .params() time can be replaced here at + * runtime, so re-validate the new channel counts and selected + * channel before accepting them; otherwise an out-of-range value + * later indexes past the source channels in the copy routine. + */ + switch (cfg->in_channels_count) { + case 0: + case SEL_SOURCE_1CH: + case SEL_SOURCE_2CH: + case SEL_SOURCE_4CH: + break; + default: + comp_err(dev, "invalid in_channels_count %u", + cfg->in_channels_count); + return -EINVAL; + } + + switch (cfg->out_channels_count) { + case 0: + case SEL_SINK_1CH: + case SEL_SINK_2CH: + case SEL_SINK_4CH: + break; + default: + comp_err(dev, "invalid out_channels_count %u", + cfg->out_channels_count); + return -EINVAL; + } + + /* sel_channel indexes the source channels, so it must be below + * the source channel count, otherwise the copy routine reads + * past the end of each source frame. The copy routine strides by + * the live producer stream channel count, so when the source is + * already connected that live count is the authority for both the + * selected channel and any fixed input count; before connection + * fall back to the configured input count. Always cap by the + * maximum supported source width. + */ + src = comp_dev_get_first_data_producer(dev); + if (src) + src_channels = audio_stream_get_channels(&src->stream); + else + src_channels = cfg->in_channels_count; + + /* A fixed (non-zero) input count must not exceed the live source + * width, otherwise sel_channel could be accepted below the + * requested count yet still index past the actual stream. + */ + if (src && cfg->in_channels_count && + cfg->in_channels_count > src_channels) { + comp_err(dev, "in_channels_count %u exceeds source channels %u", + cfg->in_channels_count, src_channels); + return -EINVAL; + } + + if (cfg->sel_channel >= SEL_SOURCE_4CH || + (src_channels && cfg->sel_channel >= src_channels)) { + comp_err(dev, "invalid sel_channel %u (source channels %u)", + cfg->sel_channel, src_channels); + return -EINVAL; + } + /* Just set the configuration */ cd->config.in_channels_count = cfg->in_channels_count; cd->config.out_channels_count = cfg->out_channels_count; diff --git a/src/include/sof/audio/selector.h b/src/include/sof/audio/selector.h index e977a47af9a9..fd413badab7f 100644 --- a/src/include/sof/audio/selector.h +++ b/src/include/sof/audio/selector.h @@ -32,6 +32,7 @@ struct comp_dev; #if CONFIG_IPC_MAJOR_3 /** \brief Supported channel count on input. */ +#define SEL_SOURCE_1CH 1 #define SEL_SOURCE_2CH 2 #define SEL_SOURCE_4CH 4 From 2e32e3e9fbdca3c1520fc39416d34f64a84465e9 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 30 Jun 2026 12:42:22 +0200 Subject: [PATCH 221/303] cmocka: Remove scalar power tests This patch removes the scalar power test that has been successfully converted from cmocka to ztest. The ztest equivalent is in test/ztest/unit/math/advanced/functions/ test_scalar_power_ztest.c and was merged in PR #10364. Ztest coverage for power.c: - lines: 92.9% (13/14) - funcs: 100% (1/1) Code coverage testing did not reveal any regression. Signed-off-by: Tomasz Leman --- .../cmocka/src/math/arithmetic/CMakeLists.txt | 5 -- .../cmocka/src/math/arithmetic/scalar_power.c | 55 ------------------- 2 files changed, 60 deletions(-) delete mode 100644 test/cmocka/src/math/arithmetic/scalar_power.c diff --git a/test/cmocka/src/math/arithmetic/CMakeLists.txt b/test/cmocka/src/math/arithmetic/CMakeLists.txt index 721c50020ba5..410991e06cb0 100644 --- a/test/cmocka/src/math/arithmetic/CMakeLists.txt +++ b/test/cmocka/src/math/arithmetic/CMakeLists.txt @@ -1,10 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause -cmocka_test(scalar_power - scalar_power.c - ${PROJECT_SOURCE_DIR}/src/math/power.c -) - cmocka_test(base2_logarithm base2_logarithm.c ${PROJECT_SOURCE_DIR}/src/math/base2log.c diff --git a/test/cmocka/src/math/arithmetic/scalar_power.c b/test/cmocka/src/math/arithmetic/scalar_power.c deleted file mode 100644 index 8b23791e979e..000000000000 --- a/test/cmocka/src/math/arithmetic/scalar_power.c +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include "power_tables.h" -/* 'Error (max = 0.000034912111005), THD+N = -96.457180359025074' */ -#define CMP_TOLERANCE 0.0000150363575813 - -static void test_math_arithmetic_power_fixed(void **state) -{ - (void)state; - - double p; - int i; - int j; - - for (i = 0; i < ARRAY_SIZE(b); i++) { - for (j = 0; j < ARRAY_SIZE(e); j++) { - p = power_int32(b[i], e[j]); - float diff = fabsf(power_table[i][j] - (double)p / (1 << 15)); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: base = %.16f", - __func__, diff, (double)b[i] / (1 << 25)); - printf(", exponent = %.16f, power = %.16f\n", - (double)e[j] / (1 << 29), (double)p / (1 << 15)); - assert_true(diff <= CMP_TOLERANCE); - } - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_power_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} From 39eb0dc23a35fd76a727f2c3f10618658fcc73f5 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 30 Jun 2026 12:45:07 +0200 Subject: [PATCH 222/303] cmocka: Remove base-2 logarithm tests This patch removes the base-2 logarithm test that has been successfully converted from cmocka to ztest. The ztest equivalent is in test/ztest/unit/math/advanced/functions/ test_base2_logarithm_ztest.c and was merged in PR #10590. Ztest coverage for base2log.c: - lines: 100% (21/21) - funcs: 100% (1/1) Note: base2log.c remains covered by the cmocka auditory test as well, which uses it as a dependency. Code coverage testing did not reveal any regression. Signed-off-by: Tomasz Leman --- .../cmocka/src/math/arithmetic/CMakeLists.txt | 5 -- .../src/math/arithmetic/base2_logarithm.c | 73 ------------------- 2 files changed, 78 deletions(-) delete mode 100644 test/cmocka/src/math/arithmetic/base2_logarithm.c diff --git a/test/cmocka/src/math/arithmetic/CMakeLists.txt b/test/cmocka/src/math/arithmetic/CMakeLists.txt index 410991e06cb0..9dab35b4bb97 100644 --- a/test/cmocka/src/math/arithmetic/CMakeLists.txt +++ b/test/cmocka/src/math/arithmetic/CMakeLists.txt @@ -1,10 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause -cmocka_test(base2_logarithm - base2_logarithm.c - ${PROJECT_SOURCE_DIR}/src/math/base2log.c -) - cmocka_test(exponential exponential.c ${PROJECT_SOURCE_DIR}/src/math/exp_fcn.c diff --git a/test/cmocka/src/math/arithmetic/base2_logarithm.c b/test/cmocka/src/math/arithmetic/base2_logarithm.c deleted file mode 100644 index a06e44d09b78..000000000000 --- a/test/cmocka/src/math/arithmetic/base2_logarithm.c +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include "log2_tables.h" -/* 'Error[max] = 0.0000236785999981,THD(-dBc) = -92.5128795787487235' */ -#define CMP_TOLERANCE 0.0000236785691029 - -/* testvector in Q32.0 */ -static const uint32_t uv[100] = { - 1ULL, 43383509ULL, 86767017ULL, 130150525ULL, 173534033ULL, 216917541ULL, - 260301049ULL, 303684557ULL, 347068065ULL, 390451573ULL, 433835081ULL, 477218589ULL, - 520602097ULL, 563985605ULL, 607369113ULL, 650752621ULL, 694136129ULL, 737519638ULL, - 780903146ULL, 824286654ULL, 867670162ULL, 911053670ULL, 954437178ULL, 997820686ULL, - 1041204194ULL, 1084587702ULL, 1127971210ULL, 1171354718ULL, 1214738226ULL, 1258121734ULL, - 1301505242ULL, 1344888750ULL, 1388272258ULL, 1431655766ULL, 1475039274ULL, 1518422782ULL, - 1561806290ULL, 1605189798ULL, 1648573306ULL, 1691956814ULL, 1735340322ULL, 1778723830ULL, - 1822107338ULL, 1865490846ULL, 1908874354ULL, 1952257862ULL, 1995641370ULL, 2039024878ULL, - 2082408386ULL, 2125791894ULL, 2169175403ULL, 2212558911ULL, 2255942419ULL, 2299325927ULL, - 2342709435ULL, 2386092943ULL, 2429476451ULL, 2472859959ULL, 2516243467ULL, 2559626975ULL, - 2603010483ULL, 2646393991ULL, 2689777499ULL, 2733161007ULL, 2776544515ULL, 2819928023ULL, - 2863311531ULL, 2906695039ULL, 2950078547ULL, 2993462055ULL, 3036845563ULL, 3080229071ULL, - 3123612579ULL, 3166996087ULL, 3210379595ULL, 3253763103ULL, 3297146611ULL, 3340530119ULL, - 3383913627ULL, 3427297135ULL, 3470680643ULL, 3514064151ULL, 3557447659ULL, 3600831168ULL, - 3644214676ULL, 3687598184ULL, 3730981692ULL, 3774365200ULL, 3817748708ULL, 3861132216ULL, - 3904515724ULL, 3947899232ULL, 3991282740ULL, 4034666248ULL, 4078049756ULL, 4121433264ULL, - 4164816772ULL, 4208200280ULL, 4251583788ULL, 4294967295ULL}; - -static void test_math_arithmetic_base2log_fixed(void **state) -{ - (void)state; - - uint32_t u[100]; - int i; - - memcpy_s((void *)&u[0], sizeof(u), (void *)&uv[0], 100U * sizeof(uint32_t)); - for (i = 0; i < ARRAY_SIZE(log2_lookup_table); i++) { - float y = Q_CONVERT_QTOF(base2_logarithm(u[i]), 0); - float diff = fabs(log2_lookup_table[i] - (double)y / (1 << 16)); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: value = %.16f, log2 = %.16f\n", __func__, diff, - (double)u[i], (double)y / (1 << 16)); - assert_true(diff <= CMP_TOLERANCE); - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_base2log_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} From 81386c610f964d85c66434064b9a9d44723cbcf9 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 30 Jun 2026 12:54:05 +0200 Subject: [PATCH 223/303] cmocka: Remove exponential tests This patch removes the exponential test that has been successfully converted from cmocka to ztest. The ztest equivalent is in test/ztest/unit/math/advanced/functions/ test_exponential_ztest.c and was merged in PR #10590. Ztest coverage for exp_fcn.c: - lines: 100% (59/59) - funcs: 100% (3/3) The ztest version tests sofm_exp_approx(), sofm_exp_fixed(), and sofm_db2lin_fixed() with full line coverage. Code coverage testing did not reveal any regression. Signed-off-by: Tomasz Leman --- .../cmocka/src/math/arithmetic/CMakeLists.txt | 5 - test/cmocka/src/math/arithmetic/exponential.c | 291 ------------------ 2 files changed, 296 deletions(-) delete mode 100644 test/cmocka/src/math/arithmetic/exponential.c diff --git a/test/cmocka/src/math/arithmetic/CMakeLists.txt b/test/cmocka/src/math/arithmetic/CMakeLists.txt index 9dab35b4bb97..d566e2cdbf51 100644 --- a/test/cmocka/src/math/arithmetic/CMakeLists.txt +++ b/test/cmocka/src/math/arithmetic/CMakeLists.txt @@ -1,10 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause -cmocka_test(exponential - exponential.c - ${PROJECT_SOURCE_DIR}/src/math/exp_fcn.c - ${PROJECT_SOURCE_DIR}/src/math/exp_fcn_hifi.c -) cmocka_test(square_root square_root.c ${PROJECT_SOURCE_DIR}/src/math/sqrt_int16.c diff --git a/test/cmocka/src/math/arithmetic/exponential.c b/test/cmocka/src/math/arithmetic/exponential.c deleted file mode 100644 index 3e44bf0c3743..000000000000 --- a/test/cmocka/src/math/arithmetic/exponential.c +++ /dev/null @@ -1,291 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -/* - * Copyright(c) 2022-2025 Intel Corporation. - * - * Author: Shriram Shastry - * Seppo Ingalsuo - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#define ULP_TOLERANCE 1.0 -#define ULP_SCALE 1.9073e-06 /* For exp() output Q13.19, 1 / 2^19 */ -#define NUMTESTSAMPLES 256 - -#define NUMTESTSAMPLES_TEST2 100 -#define ABS_DELTA_TOLERANCE_TEST2 2.0e-6 -#define REL_DELTA_TOLERANCE_TEST2 1000.0 /* rel. error is large with values near zero */ -#define NUMTESTSAMPLES_TEST3 100 -#define ABS_DELTA_TOLERANCE_TEST3 2.0e-6 -#define REL_DELTA_TOLERANCE_TEST3 10.0e-2 -#define SOFM_EXP_FIXED_ARG_MIN -11.5 -#define SOFM_EXP_FIXED_ARG_MAX 7.6245 - -#define NUMTESTSAMPLES_TEST4 100 -#define ABS_DELTA_TOLERANCE_TEST4 2.5e-5 -#define REL_DELTA_TOLERANCE_TEST4 1000.0 /* rel. error is large with values near zero */ - -/** - * Saturates input to 32 bits - * @param x Input value - * @return Saturated output value - */ -static int32_t saturate32(int64_t x) -{ - if (x < INT32_MIN) - return INT32_MIN; - else if (x > INT32_MAX) - return INT32_MAX; - - return x; -} - -/** - * Generates linearly spaced values for a vector with end points and number points in - * desired fractional Q-format for 32 bit integer. If the test values exceed int32_t - * range, the values are saturated to INT32_MIN to INT32_MAX range. - * - * @param a First value of test vector - * @param b Last value of test vector - * @param step_count Number of values in vector - * @param point Calculate n-th point of vector 0 .. step_count - 1 - * @param qformat Number of fractional bits y in Qx.y format - * @param fout Pointer to calculated test vector value, double - * @param iout Pointer to calculated test vector value, int32_t - */ -static void gen_testvector_linspace_int32(double a, double b, int step_count, int point, - int qformat, double *fout, int32_t *iout) -{ - double fstep = (b - a) / (step_count - 1); - double fvalue = a + fstep * point; - int64_t itmp; - - itmp = (int64_t)round(fvalue * (double)(1 << qformat)); - *iout = saturate32(itmp); - *fout = (double)*iout / (1 << qformat); - return; -} - -/** - * Test for sofm_exp_approx() - * @param state Cmocka internal state - */ -static void test_function_sofm_exp_approx(void **state) -{ - (void)state; - - int32_t accum; - int i; - double a_i; - double max_ulp = 0; - double ulp; - double a_tmp = -8; - double b_tmp = 8; - int32_t b_i; - - for (i = 0; i < NUMTESTSAMPLES; i++) { - gen_testvector_linspace_int32(a_tmp, b_tmp, NUMTESTSAMPLES, i, 28, &a_i, &b_i); - accum = sofm_exp_approx(b_i); - ulp = fabs(exp(a_i) - (double)accum / (1 << 19)) / ULP_SCALE; - if (ulp > max_ulp) - max_ulp = ulp; - - if (ulp > ULP_TOLERANCE) { - printf("%s: ULP for %.16f: value = %.16f, Exp = %.16f\n", __func__, - ulp, (double)b_i / (1 << 28), (double)accum / (1 << 19)); - assert_true(false); - } - } - printf("%s: Worst-case ULP: %g ULP_SCALE %g\n", __func__, max_ulp, ULP_SCALE); -} - - -/** - * Calculate reference exponent value - * @param x Input value - * @param qformat Fractional bits y in Qx.y format - * @return Saturated exponent value to match fractional format - */ -static double ref_exp(double x, int qformat) -{ - double yf; - int64_t yi; - - yf = exp(x); - yi = yf * (1 << qformat); - if (yi > INT32_MAX) - yi = INT32_MAX; - else if (yi < INT32_MIN) - yi = INT32_MIN; - - yf = (double)yi / (1 << qformat); - return yf; -} - -/** - * Calculates test exponent function and compares result to reference exponent. - * @param ivalue Fractional format input value Q5.27 - * @param iexp_value Fractional format output value Q12.20 - * @param abs_delta_max Calculated absolute error - * @param rel_delta_max Calculated relative error - * @param abs_delta_tolerance Tolerance for absolute error - * @param rel_delta_tolerance Tolerance for relative error - */ -static void test_exp_with_input_value(int32_t ivalue, int32_t *iexp_value, - double *abs_delta_max, double *rel_delta_max, - double abs_delta_tolerance, double rel_delta_tolerance) -{ - double fvalue, fexp_value, ref_exp_value; - double rel_delta, abs_delta; - double eps = 1e-9; - - *iexp_value = sofm_exp_fixed(ivalue); - fvalue = (double)ivalue / (1 << 27); /* Q5.27 */ - fexp_value = (double)*iexp_value / (1 << 20); /* Q12.20 */ - ref_exp_value = ref_exp(fvalue, 20); - abs_delta = fabs(ref_exp_value - fexp_value); - rel_delta = abs_delta / (ref_exp_value + eps); - if (abs_delta > *abs_delta_max) - *abs_delta_max = abs_delta; - - if (rel_delta > *rel_delta_max) - *rel_delta_max = rel_delta; - - if (abs_delta > abs_delta_tolerance) { - printf("%s: Absolute error %g exceeds limit %g, input %g output %g.\n", - __func__, abs_delta, abs_delta_tolerance, fvalue, fexp_value); - assert_true(false); - } - - if (rel_delta > rel_delta_tolerance) { - printf("%s: Relative error %g exceeds limit %g, input %g output %g.\n", - __func__, rel_delta, rel_delta_tolerance, fvalue, fexp_value); - assert_true(false); - } -} - -/** - * Test for sofm_exp_fixed() - * @param state Cmocka internal state - */ -static void test_function_sofm_exp_fixed(void **state) -{ - (void)state; - double rel_delta_max, abs_delta_max; - double tmp; - int32_t ivalue, iexp_value; - int i; - - /* Test max int32_t range with coarse grid */ - rel_delta_max = 0; - abs_delta_max = 0; - for (i = 0; i < NUMTESTSAMPLES_TEST2; i++) { - gen_testvector_linspace_int32(-16, 16, NUMTESTSAMPLES_TEST2, i, 27, &tmp, &ivalue); - test_exp_with_input_value(ivalue, &iexp_value, &abs_delta_max, &rel_delta_max, - ABS_DELTA_TOLERANCE_TEST2, REL_DELTA_TOLERANCE_TEST2); - } - - printf("%s: Absolute max error was %.6e (max range).\n", __func__, abs_delta_max); - printf("%s: Relative max error was %.6e (max range).\n", __func__, rel_delta_max); - - /* Test max int32_t middle range with fine grid */ - rel_delta_max = 0; - abs_delta_max = 0; - for (i = 0; i < NUMTESTSAMPLES_TEST3; i++) { - gen_testvector_linspace_int32(SOFM_EXP_FIXED_ARG_MIN, SOFM_EXP_FIXED_ARG_MAX, - NUMTESTSAMPLES_TEST3, i, 27, &tmp, &ivalue); - test_exp_with_input_value(ivalue, &iexp_value, &abs_delta_max, &rel_delta_max, - ABS_DELTA_TOLERANCE_TEST3, REL_DELTA_TOLERANCE_TEST3); - } - - printf("%s: Absolute max error was %.6e (middle).\n", __func__, abs_delta_max); - printf("%s: Relative max error was %.6e (middle).\n", __func__, rel_delta_max); -} - -/** - * Reference function for dB to linear conversion - * @param x Input value - * @param qformat Fractional bits y in Qx.y format for saturation - * @return Saturated linear value - */ -static double ref_db2lin(double x, int qformat) -{ - double fref; - int64_t iref; - - fref = pow(10, x / 20); - iref = fref * (1 << qformat); - return (double)saturate32(iref) / (1 << qformat); -} - -/** - * Test for sofm_db2lin_fixed() - * @param state Cmocka internal state - */ -static void test_function_sofm_db2lin_fixed(void **state) -{ - (void)state; - double abs_delta, rel_delta, abs_delta_max, rel_delta_max; - double fin, fout, fref; - double eps = 1e-9; - int32_t iin, iout; - int i; - - rel_delta_max = 0; - abs_delta_max = 0; - for (i = 0; i < NUMTESTSAMPLES_TEST2; i++) { - gen_testvector_linspace_int32(-128, 128, NUMTESTSAMPLES_TEST4, i, 24, &fin, &iin); - iout = sofm_db2lin_fixed(iin); - fout = (double)iout / (1 << 20); - fref = ref_db2lin(fin, 20); - abs_delta = fabs(fref - fout); - rel_delta = abs_delta / (fref + eps); - if (abs_delta > abs_delta_max) - abs_delta_max = abs_delta; - - if (rel_delta > rel_delta_max) - rel_delta_max = rel_delta; - - if (abs_delta > ABS_DELTA_TOLERANCE_TEST4) { - printf("%s: Absolute error %g exceeds limit %g, input %g output %g.\n", - __func__, abs_delta, ABS_DELTA_TOLERANCE_TEST4, fin, fout); - assert_true(false); - } - - if (rel_delta > REL_DELTA_TOLERANCE_TEST4) { - printf("%s: Relative error %g exceeds limit %g, input %g output %g.\n", - __func__, rel_delta, REL_DELTA_TOLERANCE_TEST4, fin, fout); - assert_true(false); - } - } - printf("%s: Absolute max error was %.6e.\n", __func__, abs_delta_max); - printf("%s: Relative max error was %.6e.\n", __func__, rel_delta_max); -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_function_sofm_exp_approx), - cmocka_unit_test(test_function_sofm_exp_fixed), - cmocka_unit_test(test_function_sofm_db2lin_fixed), - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} From 280fbebe4b2a96646255073da72d01586bc6a758 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 30 Jun 2026 12:54:45 +0200 Subject: [PATCH 224/303] cmocka: Remove square root tests This patch removes the square root test that has been successfully converted from cmocka to ztest. The ztest equivalent is in test/ztest/unit/math/advanced/functions/ test_square_root_ztest.c and was merged in PR #10764. Ztest coverage for sqrt_int16.c: - lines: 100% (27/27) - funcs: 100% (1/1) Code coverage testing did not reveal any regression. Signed-off-by: Tomasz Leman --- .../cmocka/src/math/arithmetic/CMakeLists.txt | 5 - test/cmocka/src/math/arithmetic/square_root.c | 162 ------------------ 2 files changed, 167 deletions(-) delete mode 100644 test/cmocka/src/math/arithmetic/square_root.c diff --git a/test/cmocka/src/math/arithmetic/CMakeLists.txt b/test/cmocka/src/math/arithmetic/CMakeLists.txt index d566e2cdbf51..68553cfc048a 100644 --- a/test/cmocka/src/math/arithmetic/CMakeLists.txt +++ b/test/cmocka/src/math/arithmetic/CMakeLists.txt @@ -1,10 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause -cmocka_test(square_root - square_root.c - ${PROJECT_SOURCE_DIR}/src/math/sqrt_int16.c -) - cmocka_test(base_10_logarithm base_10_logarithm.c ${PROJECT_SOURCE_DIR}/src/math/log_10.c diff --git a/test/cmocka/src/math/arithmetic/square_root.c b/test/cmocka/src/math/arithmetic/square_root.c deleted file mode 100644 index 6a31588c1235..000000000000 --- a/test/cmocka/src/math/arithmetic/square_root.c +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -/* 'Error[max] = 0.0003000860000000,THD(-dBc) = -87.1210823527511309' */ -#define CMP_TOLERANCE 0.0001689942 - -static const double sqrt_ref_table[] = { - 0.0000000000000000, 0.2529127196287289, 0.3580137261684250, 0.4383362543470480, - 0.5060667106469264, 0.5657458434447398, 0.6199010757774179, 0.6695089151758922, - 0.7156864056624241, 0.7590598625931949, 0.8002380017063674, 0.8392530626247365, - 0.8765332548597343, 0.9124250825410271, 0.9468285879714448, 0.9800251112854200, - 1.0121334212938529, 1.0433710015497843, 1.0735864616438677, 1.1029744939820685, - 1.1317074351394887, 1.1596234572049671, 1.1868830634270588, 1.2135304899342250, - 1.2397036881347898, 1.2652391387105444, 1.2902693214499832, 1.3148230929007141, - 1.3390178303517843, 1.3626935069009465, 1.3859648038460428, 1.4089384024417106, - 1.4314580907679415, 1.4536289448738284, 1.4754666899408471, 1.4970674458754356, - 1.5182805344369004, 1.5392012945030940, 1.5598414883410430, 1.5802893574042065, - 1.6003997303408295, 1.6202605162827983, 1.6399552204252408, 1.6593426315110451, - 1.6785061252494731, 1.6974532854396907, 1.7162624043615824, 1.7347972458979175, - 1.7531361407845656, 1.7712851751976586, 1.7893183496097054, 1.8071040368501201, - 1.8247163735084968, 1.8422265952170487, 1.8595062978852748, 1.8766268983537990, - 1.8935927121149891, 1.9104717594746068, 1.9271396388170734, 1.9436645881555799, - 1.9601125007572908, 1.9763617734046062, 1.9924785326635266, 2.0084659685628234, - 2.0243874459327196, 2.0401248429936829, 2.0557417684986605, 2.0712409474756917, - 2.0866835042418388, 2.1019545405705138, 2.1171154277400652, 2.1322257663648099, - 2.1471729232877355, 2.1620167451363552, 2.1767593459084997, 2.1914584719713490, - 2.2060043241401410, 2.2204548907543695, 2.2348120201987909, 2.2491317769308226, - 2.2633070038662453, 2.2773940013752560, 2.2914476694602910, 2.3053627188850347, - 2.3191942802134968, 2.3329438383992445, 2.3466648541067809, 2.3602543890966499, - 2.3737661268541177, 2.3872013883939496, 2.4006123079591588, 2.4138981537908761, - 2.4271112748749282, 2.4403028756693299, 2.4533737931163282, 2.4663754402969551, - 2.4793089069839604, 2.4922242356226696, 2.5050242482608827, 2.5177591878742098, - 2.5304782774210888, 2.5430857547967194, 2.5556310375326090, 2.5681150370943278, - 2.5805859466650203, 2.5929498012639969, 2.6052549809231724, 2.6175023131556161, - 2.6297390257875399, 2.6418728560436060, 2.6539512111660981, 2.6660206330081166, - 2.6779900782816579, 2.6899062628881700, 2.7017698915479462, 2.7136266381449752, - 2.7253870138018930, 2.7370968595849874, 2.7487568212739375, 2.7604117531402812, - 2.7719736453698474, 2.7834875128828940, 2.7949976241045360, 2.8064170328908711, - 2.8177901636300033, 2.8291175744390689, 2.8404427884354582, 2.8516802201728368, - 2.8628735427669523, 2.8740232715872360, 2.8851722218959477, 2.8962361080806240, - 2.9072578897476569, 2.9182798738083706, 2.9292187124939990, 2.9401168530136688, - 2.9509747462702896, 2.9618340496219568, 2.9726126187665289, 2.9833522462156559, - 2.9940941216626773, 3.0047569707257522, 3.0153821145710538, 3.0259699503836783, - 3.0365610688738007, 3.0470753139280951, 3.0575534030495688, 3.0679957066870220, - 3.0784422425351754, 3.0888139284157279, 3.0991509043809078, 3.1094927741514371, - 3.1197612338526808, 3.1299960063872287, 3.1401974211424988, 3.1504045499149789, - 3.1605400917999757, 3.1706432337342845, 3.1807142844611178, 3.1907918051402224, - 3.2007994606816590, 3.2107759235502562, 3.2207593849316032, 3.2306742112715421, - 3.2405587023112234, 3.2504131347991749, 3.2602752232365293, 3.2700702400713046, - 3.2798360048560355, 3.2895727781126838, 3.2993178153786578, 3.3089972636170311, - 3.3186484800856810, 3.3283083869662677, 3.3379037677111065, 3.3474716438306089, - 3.3570122504989461, 3.3665620793882591, 3.3760487375221642, 3.3855088128485207, - 3.3949784839156196, 3.4043859578490805, 3.4137675072784321, 3.4231233453528955, - 3.4324892457042018, 3.4417941928048226, 3.4510740515635128, 3.4603290238249023, - 3.4695944917958350, 3.4788001927748020, 3.4879815975718680, 3.4971738031409019, - 3.5063070962374359, 3.5154166604934614, 3.5245026799003858, 3.5335998818485379, - 3.5426392659640071, 3.5516556438511886, 3.5606491902811768, 3.5696542746637245, - 3.5786025882144274, 3.5875285822032135, 3.5964663647113397, 3.6053481324623839, - 3.6142080737002402, 3.6230463485511746, 3.6318967259718442, 3.6406920594682268, - 3.6494661959833250, 3.6582526566654741, 3.6669847755001657, 3.6756961500510350, - 3.6843869274616097, 3.6930903069956198, 3.7017402474207994, 3.7103700224000571, - 3.7189797723132347, 3.7276023837381045, 3.7361724230822109, 3.7447228493908598, - 3.7532863204297375, 3.7617978476886553, 3.7702901600042669, 3.7787633869263368, - 3.7872498886065071, 3.7956852559847478, 3.8041019184887777, 3.8125000000000000, - 3.8209115711273665, 3.8292727871131094, 3.8376157861196840, 3.8459724266107265, - 3.8542792776341468, 3.8625682639598748, 3.8708395003538962, 3.8791245690071618, - 3.8873605782876637, 3.8955791750874478, 3.9037804693815712, 3.9119957742180653, - 3.9201627238228260, 3.9283126944020128, 3.9364768015796816, 3.9445930655930783, - 3.9526926641056979, 3.9607756993580185, 3.9688730295891301, 3.9769231786332004, - 3.9849570653270532, 3.9930053589840071, 3.9999694823054588, 3.9999694823054588}; -/* testvector in Q4.12 */ -static const uint32_t uv[252] = { - 0U, 262U, 525U, 787U, 1049U, 1311U, - 1574U, 1836U, 2098U, 2360U, 2623U, 2885U, - 3147U, 3410U, 3672U, 3934U, 4196U, 4459U, - 4721U, 4983U, 5246U, 5508U, 5770U, 6032U, - 6295U, 6557U, 6819U, 7081U, 7344U, 7606U, - 7868U, 8131U, 8393U, 8655U, 8917U, 9180U, - 9442U, 9704U, 9966U, 10229U, 10491U, 10753U, - 11016U, 11278U, 11540U, 11802U, 12065U, 12327U, - 12589U, 12851U, 13114U, 13376U, 13638U, 13901U, - 14163U, 14425U, 14687U, 14950U, 15212U, 15474U, - 15737U, 15999U, 16261U, 16523U, 16786U, 17048U, - 17310U, 17572U, 17835U, 18097U, 18359U, 18622U, - 18884U, 19146U, 19408U, 19671U, 19933U, 20195U, - 20457U, 20720U, 20982U, 21244U, 21507U, 21769U, - 22031U, 22293U, 22556U, 22818U, 23080U, 23342U, - 23605U, 23867U, 24129U, 24392U, 24654U, 24916U, - 25178U, 25441U, 25703U, 25965U, 26228U, 26490U, - 26752U, 27014U, 27277U, 27539U, 27801U, 28063U, - 28326U, 28588U, 28850U, 29113U, 29375U, 29637U, - 29899U, 30162U, 30424U, 30686U, 30948U, 31211U, - 31473U, 31735U, 31998U, 32260U, 32522U, 32784U, - 33047U, 33309U, 33571U, 33833U, 34096U, 34358U, - 34620U, 34883U, 35145U, 35407U, 35669U, 35932U, - 36194U, 36456U, 36719U, 36981U, 37243U, 37505U, - 37768U, 38030U, 38292U, 38554U, 38817U, 39079U, - 39341U, 39604U, 39866U, 40128U, 40390U, 40653U, - 40915U, 41177U, 41439U, 41702U, 41964U, 42226U, - 42489U, 42751U, 43013U, 43275U, 43538U, 43800U, - 44062U, 44324U, 44587U, 44849U, 45111U, 45374U, - 45636U, 45898U, 46160U, 46423U, 46685U, 46947U, - 47210U, 47472U, 47734U, 47996U, 48259U, 48521U, - 48783U, 49045U, 49308U, 49570U, 49832U, 50095U, - 50357U, 50619U, 50881U, 51144U, 51406U, 51668U, - 51930U, 52193U, 52455U, 52717U, 52980U, 53242U, - 53504U, 53766U, 54029U, 54291U, 54553U, 54816U, - 55078U, 55340U, 55602U, 55865U, 56127U, 56389U, - 56651U, 56914U, 57176U, 57438U, 57701U, 57963U, - 58225U, 58487U, 58750U, 59012U, 59274U, 59536U, - 59799U, 60061U, 60323U, 60586U, 60848U, 61110U, - 61372U, 61635U, 61897U, 62159U, 62421U, 62684U, - 62946U, 63208U, 63471U, 63733U, 63995U, 64257U, - 64520U, 64782U, 65044U, 65307U, UINT16_MAX, UINT16_MAX}; -static void test_math_arithmetic_sqrt_fixed(void **state) -{ - (void)state; - - uint32_t u[252]; - int i; - double y; - double diff; - - memcpy_s((void *)&u[0], sizeof(u), (void *)&uv[0], 252U * sizeof(uint32_t)); - for (i = 0; i < ARRAY_SIZE(sqrt_ref_table); i++) { - y = Q_CONVERT_QTOF(sofm_sqrt_int16(u[i]), 12); - diff = fabs(sqrt_ref_table[i] - y); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: reftbl = %.16f, sqrt = %.16f\n", __func__, - diff, (double)sqrt_ref_table[i], y); - assert_true(diff <= CMP_TOLERANCE); - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_sqrt_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} From cd6d2a146e85c175e3abf06ff88c46e53fe3d48e Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 30 Jun 2026 13:09:54 +0200 Subject: [PATCH 225/303] cmocka: Remove base-10 logarithm tests This patch removes the base-10 logarithm test that has been successfully converted from cmocka to ztest. The ztest equivalent is in test/ztest/unit/math/advanced/functions/ test_base10_logarithm_ztest.c and was merged in PR #10764. Ztest coverage for log_10.c: - lines: 100% (2/2) - funcs: 100% (1/1) Code coverage testing did not reveal any regression. Signed-off-by: Tomasz Leman --- .../cmocka/src/math/arithmetic/CMakeLists.txt | 6 - .../src/math/arithmetic/base_10_logarithm.c | 105 ------------------ 2 files changed, 111 deletions(-) delete mode 100644 test/cmocka/src/math/arithmetic/base_10_logarithm.c diff --git a/test/cmocka/src/math/arithmetic/CMakeLists.txt b/test/cmocka/src/math/arithmetic/CMakeLists.txt index 68553cfc048a..0a678c30b47a 100644 --- a/test/cmocka/src/math/arithmetic/CMakeLists.txt +++ b/test/cmocka/src/math/arithmetic/CMakeLists.txt @@ -1,11 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause -cmocka_test(base_10_logarithm - base_10_logarithm.c - ${PROJECT_SOURCE_DIR}/src/math/log_10.c - ${PROJECT_SOURCE_DIR}/src/math/base2log.c -) - cmocka_test(base_e_logarithm base_e_logarithm.c ${PROJECT_SOURCE_DIR}/src/math/log_e.c diff --git a/test/cmocka/src/math/arithmetic/base_10_logarithm.c b/test/cmocka/src/math/arithmetic/base_10_logarithm.c deleted file mode 100644 index 3973df31a515..000000000000 --- a/test/cmocka/src/math/arithmetic/base_10_logarithm.c +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry -// -// - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include "log2_tables.h" -/* 'Error[max] = 0.0000071279028671,THD = -102.9407645424143993 ' */ -/* 'Error[max] = rms(log10() - double(log10_int32()))' */ -/* 'THD = 20*log10(Error[max])' */ -#define CMP_TOLERANCE 0.0000071279028671 -/* Natural logarithm log10(X) reference table generated by matlab/Octave */ -/* UQ4.28 */ -static const double common_log10_ref_table[] = { - 0.0000000000000000, 7.6373246662453802, 7.9383546619093615, 8.1144459209650428, - 8.2393846575733427, 8.3362946705813989, 8.4154759166290241, 8.4824227062596371, - 8.5404146532373240, 8.5915671756847054, 8.6373246662453802, 8.6787173514036056, - 8.7165059122930053, 8.7512680193222625, 8.7834527026386606, 8.8134159259684335, - 8.8414446495269665, 8.8677735882125130, 8.8925971719048302, 8.9160782677250818, - 8.9383546624098908, 8.9595439614559940, 8.9797473475226131, 8.9990525026982162, - 9.0175359083740947, 9.0352646753178405, 9.0522980146012202, 9.0686884307751292, - 9.0844826979451199, 9.0997226644895282, 9.1144459212987297, 9.1286863604025754, - 9.1424746448781171, 9.1558386064266184, 9.1688035835820649, 9.1813927108816724, - 9.1936271672907388, 9.2055263908534872, 9.2171082633890631, 9.2283892737852433, - 9.2393846580738721, 9.2501085234534361, 9.2605739571199752, 9.2707931222905753, - 9.2807773431865943, 9.2905371804656394, 9.3000824983621975, 9.3094225246070792, - 9.3185659040380759, 9.3275207466824899, 9.3362946709818218, 9.3448948427358882, - 9.3533280102652014, 9.3616005362239267, 9.3697184264391105, 9.3776873561036460, - 9.3855126936091011, 9.3931995222691196, 9.4007526601535094, 9.4081766782268659, - 9.4154759169627091, 9.4226545015843630, 9.4297163562280168, 9.4366652161756566, - 9.4435046406985137, 9.4502380233502628, 9.4568686022422757, 9.4633994693944423, - 9.4698335793932600, 9.4761737574178788, 9.4824227066886628, 9.4885830153874373, - 9.4946571630937573, 9.5006475267772306, 9.5065563863821918, 9.5123859300375031, - 9.5181382589213257, 9.5238153918078847, 9.5294192693208828, 9.5349517579159713, - 9.5404146536127215, 9.5458096854947918, 9.5511385189953373, 9.5564027589832818, - 9.5616039526647825, 9.5667435923129869, 9.5718231178381536, 9.5768439193242560, - 9.5818073388505756, 9.5867146733402073, 9.5915671761296206, 9.5963660590064990, - 9.6011124940261787, 9.6058076152298781, 9.6104525202710605, 9.6150482719557271, - 9.6195958997020572, 9.6240964009244330, 9.6285507423464711, 9.6329598611462810}; - -/* testvector in Q32.0 */ -static const uint32_t uv[100] = { - 1ULL, 43383509ULL, 86767017ULL, 130150525ULL, 173534033ULL, 216917541ULL, - 260301049ULL, 303684557ULL, 347068065ULL, 390451573ULL, 433835081ULL, 477218589ULL, - 520602097ULL, 563985605ULL, 607369113ULL, 650752621ULL, 694136129ULL, 737519638ULL, - 780903146ULL, 824286654ULL, 867670162ULL, 911053670ULL, 954437178ULL, 997820686ULL, - 1041204194ULL, 1084587702ULL, 1127971210ULL, 1171354718ULL, 1214738226ULL, 1258121734ULL, - 1301505242ULL, 1344888750ULL, 1388272258ULL, 1431655766ULL, 1475039274ULL, 1518422782ULL, - 1561806290ULL, 1605189798ULL, 1648573306ULL, 1691956814ULL, 1735340322ULL, 1778723830ULL, - 1822107338ULL, 1865490846ULL, 1908874354ULL, 1952257862ULL, 1995641370ULL, 2039024878ULL, - 2082408386ULL, 2125791894ULL, 2169175403ULL, 2212558911ULL, 2255942419ULL, 2299325927ULL, - 2342709435ULL, 2386092943ULL, 2429476451ULL, 2472859959ULL, 2516243467ULL, 2559626975ULL, - 2603010483ULL, 2646393991ULL, 2689777499ULL, 2733161007ULL, 2776544515ULL, 2819928023ULL, - 2863311531ULL, 2906695039ULL, 2950078547ULL, 2993462055ULL, 3036845563ULL, 3080229071ULL, - 3123612579ULL, 3166996087ULL, 3210379595ULL, 3253763103ULL, 3297146611ULL, 3340530119ULL, - 3383913627ULL, 3427297135ULL, 3470680643ULL, 3514064151ULL, 3557447659ULL, 3600831168ULL, - 3644214676ULL, 3687598184ULL, 3730981692ULL, 3774365200ULL, 3817748708ULL, 3861132216ULL, - 3904515724ULL, 3947899232ULL, 3991282740ULL, 4034666248ULL, 4078049756ULL, 4121433264ULL, - 4164816772ULL, 4208200280ULL, 4251583788ULL, 4294967295ULL}; - -static void test_math_arithmetic_base10log_fixed(void **state) -{ - (void)state; - - double clogfxp; - double diff; - int i; - - for (i = 0; i < ARRAY_SIZE(common_log10_ref_table); i++) { - clogfxp = log10_int32(uv[i]); - diff = fabs(common_log10_ref_table[i] - (double)clogfxp / (1 << 28)); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: val = %16d, log10() = %.16f\n", __func__, diff, - uv[i], clogfxp / (1 << 28)); - assert_true(diff <= CMP_TOLERANCE); - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_base10log_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} From e936c83d928dd8652567864991d4c3740f33bc40 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 30 Jun 2026 13:10:19 +0200 Subject: [PATCH 226/303] cmocka: Remove base-e logarithm tests This patch removes the base-e logarithm test that has been successfully converted from cmocka to ztest. The ztest equivalent is in test/ztest/unit/math/advanced/functions/ test_base_e_logarithm_ztest.c and was merged in PR #10764. Ztest coverage for log_e.c: - lines: 100% (2/2) - funcs: 100% (1/1) Code coverage testing did not reveal any regression. Signed-off-by: Tomasz Leman --- .../cmocka/src/math/arithmetic/CMakeLists.txt | 6 - .../src/math/arithmetic/base_e_logarithm.c | 104 ------------------ 2 files changed, 110 deletions(-) delete mode 100644 test/cmocka/src/math/arithmetic/base_e_logarithm.c diff --git a/test/cmocka/src/math/arithmetic/CMakeLists.txt b/test/cmocka/src/math/arithmetic/CMakeLists.txt index 0a678c30b47a..6625aef444ef 100644 --- a/test/cmocka/src/math/arithmetic/CMakeLists.txt +++ b/test/cmocka/src/math/arithmetic/CMakeLists.txt @@ -1,11 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause -cmocka_test(base_e_logarithm - base_e_logarithm.c - ${PROJECT_SOURCE_DIR}/src/math/log_e.c - ${PROJECT_SOURCE_DIR}/src/math/base2log.c -) - cmocka_test(a_law_codec a_law_codec.c ${PROJECT_SOURCE_DIR}/src/math/a_law.c diff --git a/test/cmocka/src/math/arithmetic/base_e_logarithm.c b/test/cmocka/src/math/arithmetic/base_e_logarithm.c deleted file mode 100644 index c2c1d2083c8b..000000000000 --- a/test/cmocka/src/math/arithmetic/base_e_logarithm.c +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// -// Copyright(c) 2021 Intel Corporation. All rights reserved. -// -// Author: Shriram Shastry - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include "log2_tables.h" -/* 'Error[max] = 0.0000164133276926,THD(-dBc) = -95.6960671942683234' */ -/* 'Error[max] = rms(log() - double(log_int32()))' */ -/* 'THD = 20*log10(Error[max])' */ -#define CMP_TOLERANCE 0.0000164133276926 - -/* Natural logarithm loge(X) reference table generated by matlab/Octave */ -/* UQ5.27 */ -static const double natural_log_lookup_table[] = { - 0.0000000000000000, 17.5855899268523359, 18.2787371074122831, 18.6842022155204468, - 18.9718842879722267, 19.1950278392864391, 19.3773493960803940, 19.5315000759076511, - 19.6650314685321739, 19.7828145041885577, 19.8881750198463827, 19.9834851996507084, - 20.0704965766403376, 20.1505392860869676, 20.2246472581140395, 20.2936401294912301, - 20.3581786505327571, 20.4188032722644479, 20.4759616860290699, 20.5300289072319480, - 20.5813222015588408, 20.6301123656733907, 20.6766323812583899, 20.7210841437836706, - 20.7636437581607112, 20.8044657526425461, 20.8436864657603671, 20.8814267937103786, - 20.9177944378507625, 20.9528857576336485, 20.9867873092828354, 21.0195771320810394, - 21.0513258303723845, 21.0820974890173112, 21.1119504521464485, 21.1409379890003279, - 21.1691088659487328, 21.1965078407425196, 21.2231760877918916, 21.2491515741640455, - 21.2744693821187845, 21.2991619946810467, 21.3232595462333343, 21.3467900436180038, - 21.3697795618183370, 21.3922524176471107, 21.4142313243436178, 21.4357375295432568, - 21.4567909387206548, 21.4774102259037889, 21.4976129332024932, 21.5174155604805932, - 21.5368336463203107, 21.5558818412742781, 21.5745739742703257, 21.5929231129229997, - 21.6109416184107097, 21.6286411954956677, 21.6460329381935921, 21.6631273715394208, - 21.6799344898427790, 21.6964637917813938, 21.7127243130127638, 21.7287246543415016, - 21.7444730112924880, 21.7599771978118319, 21.7752446699265008, 21.7902825472754031, - 21.8050976330453672, 21.8196964324517815, 21.8340851698895619, 21.8482698048676056, - 21.8622560468288185, 21.8760493689479993, 21.8896550209909755, 21.9030780413106569, - 21.9163232680485471, 21.9293953496040821, 21.9422987544284780, 21.9550377801946830, - 21.9676165623906030, 21.9800390823784895, 21.9923091749598925, 22.0044305354820757, - 22.0164067265188734, 22.0282411841561903, 22.0399372239099236, 22.0514980465667030, - 22.0629267423782807, 22.0742262976204415, 22.0853995982070579, 22.0964494343823858, - 22.1073785049035614, 22.1181894209970018, 22.1288847101032040, 22.1394668194234150, - 22.1499381192805984, 22.1603009063062437, 22.1705574064637361, 22.1807097776854185}; - -/* testvector in Q32.0 */ -static const uint32_t uv[100] = { - 1ULL, 43383509ULL, 86767017ULL, 130150525ULL, 173534033ULL, 216917541ULL, - 260301049ULL, 303684557ULL, 347068065ULL, 390451573ULL, 433835081ULL, 477218589ULL, - 520602097ULL, 563985605ULL, 607369113ULL, 650752621ULL, 694136129ULL, 737519638ULL, - 780903146ULL, 824286654ULL, 867670162ULL, 911053670ULL, 954437178ULL, 997820686ULL, - 1041204194ULL, 1084587702ULL, 1127971210ULL, 1171354718ULL, 1214738226ULL, 1258121734ULL, - 1301505242ULL, 1344888750ULL, 1388272258ULL, 1431655766ULL, 1475039274ULL, 1518422782ULL, - 1561806290ULL, 1605189798ULL, 1648573306ULL, 1691956814ULL, 1735340322ULL, 1778723830ULL, - 1822107338ULL, 1865490846ULL, 1908874354ULL, 1952257862ULL, 1995641370ULL, 2039024878ULL, - 2082408386ULL, 2125791894ULL, 2169175403ULL, 2212558911ULL, 2255942419ULL, 2299325927ULL, - 2342709435ULL, 2386092943ULL, 2429476451ULL, 2472859959ULL, 2516243467ULL, 2559626975ULL, - 2603010483ULL, 2646393991ULL, 2689777499ULL, 2733161007ULL, 2776544515ULL, 2819928023ULL, - 2863311531ULL, 2906695039ULL, 2950078547ULL, 2993462055ULL, 3036845563ULL, 3080229071ULL, - 3123612579ULL, 3166996087ULL, 3210379595ULL, 3253763103ULL, 3297146611ULL, 3340530119ULL, - 3383913627ULL, 3427297135ULL, 3470680643ULL, 3514064151ULL, 3557447659ULL, 3600831168ULL, - 3644214676ULL, 3687598184ULL, 3730981692ULL, 3774365200ULL, 3817748708ULL, 3861132216ULL, - 3904515724ULL, 3947899232ULL, 3991282740ULL, 4034666248ULL, 4078049756ULL, 4121433264ULL, - 4164816772ULL, 4208200280ULL, 4251583788ULL, 4294967295ULL}; - -static void test_math_arithmetic_base_e_log_fixed(void **state) -{ - (void)state; - - double logefxp; - double diff; - int i; - - for (i = 0; i < ARRAY_SIZE(natural_log_lookup_table); i++) { - logefxp = ln_int32(uv[i]); - diff = fabs(natural_log_lookup_table[i] - (double)logefxp / (1 << 27)); - - if (diff > CMP_TOLERANCE) { - printf("%s: diff for %.16f: value = %16d, log() = %.16f\n", __func__, diff, - uv[i], logefxp / (1 << 27)); - assert_true(diff <= CMP_TOLERANCE); - } - } -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_math_arithmetic_base_e_log_fixed) - }; - - cmocka_set_message_output(CM_OUTPUT_TAP); - - return cmocka_run_group_tests(tests, NULL, NULL); -} From 915b414e5d94c63f9ea5516a9a9e1a52937effd8 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 26 Jun 2026 08:27:49 +0200 Subject: [PATCH 227/303] tree-wide: remove legacy platform_shared_get() platform_shared_get() was returning its first argument and not doing anything else in all int implementation but one - cAVS with multicore but with no Zephyr. And such builds haven't been supported in SOF for a long time. Remove platform_shared_get() completely. Signed-off-by: Guennadi Liakhovetski --- src/audio/base_fw.c | 3 +-- src/audio/chain_dma.c | 3 +-- src/audio/component.c | 2 +- src/audio/dai-legacy.c | 3 +-- src/audio/dai-zephyr.c | 2 +- src/audio/google/google_hotword_detect.c | 3 +-- src/audio/host-legacy.c | 3 +-- src/audio/host-zephyr.c | 3 +-- src/audio/kpb.c | 3 +-- src/audio/pipeline/pipeline-graph.c | 3 +-- src/audio/selector/selector.c | 3 +-- src/audio/tone/tone-ipc3.c | 3 +-- src/drivers/interrupt.c | 3 +-- src/idc/idc.c | 2 +- .../sof/audio/module_adapter/module/generic.h | 3 +-- src/lib/clk.c | 1 - src/lib/notifier.c | 3 +-- .../amd/acp_6_3/include/platform/lib/memory.h | 5 ----- .../amd/acp_7_0/include/platform/lib/memory.h | 5 ----- .../amd/acp_7_x/include/platform/lib/memory.h | 5 ----- .../rembrandt/include/platform/lib/memory.h | 5 ----- .../amd/renoir/include/platform/lib/memory.h | 5 ----- src/platform/amd/renoir/lib/clk.c | 2 +- src/platform/amd/renoir/lib/memory.c | 2 +- src/platform/amd/renoir/platform.c | 2 +- .../amd/vangogh/include/platform/lib/memory.h | 5 ----- .../imx8/include/platform/lib/memory.h | 5 ----- src/platform/imx8/lib/clk.c | 2 +- src/platform/imx8/lib/memory.c | 2 +- .../imx8m/include/platform/lib/memory.h | 5 ----- src/platform/imx8m/lib/clk.c | 2 +- src/platform/imx8m/lib/memory.c | 2 +- .../imx8m_cm7/include/platform/lib/memory.h | 5 ----- .../imx8ulp/include/platform/lib/memory.h | 5 ----- src/platform/imx8ulp/lib/clk.c | 2 +- src/platform/imx8ulp/lib/memory.c | 2 +- .../imx93_a55/include/platform/lib/memory.h | 5 ----- .../imx95/include/platform/lib/memory.h | 5 ----- .../intel/ace/include/ace/lib/memory.h | 15 -------------- .../intel/cavs/include/cavs/lib/memory.h | 20 ------------------- .../library/include/platform/lib/memory.h | 5 ----- .../mt8186/include/platform/lib/memory.h | 5 ----- src/platform/mt8186/lib/clk.c | 2 +- src/platform/mt8186/lib/memory.c | 2 +- src/platform/mt8186/platform.c | 2 +- .../mt8188/include/platform/lib/memory.h | 5 ----- src/platform/mt8188/lib/clk.c | 2 +- src/platform/mt8188/lib/memory.c | 2 +- src/platform/mt8188/platform.c | 2 +- .../mt8195/include/platform/lib/memory.h | 5 ----- src/platform/mt8195/lib/clk.c | 2 +- src/platform/mt8195/lib/memory.c | 2 +- src/platform/mt8195/platform.c | 2 +- .../mt8196/include/platform/lib/memory.h | 5 ----- src/platform/mt8196/lib/clk.c | 2 +- src/platform/mt8196/lib/memory.c | 2 +- src/platform/mt8196/platform.c | 2 +- .../mt8365/include/platform/lib/memory.h | 5 ----- src/platform/mt8365/lib/clk.c | 2 +- src/platform/mt8365/lib/memory.c | 2 +- src/platform/mt8365/platform.c | 2 +- .../mtk/include/platform/lib/memory.h | 5 ----- .../posix/include/platform/lib/memory.h | 5 ----- .../qemu_xtensa/include/platform/lib/memory.h | 5 ----- src/samples/audio/detect_test.c | 3 +-- src/samples/audio/smart_amp_test_ipc3.c | 3 +-- 66 files changed, 42 insertions(+), 198 deletions(-) diff --git a/src/audio/base_fw.c b/src/audio/base_fw.c index 6ae45f72c209..b03b97ad998e 100644 --- a/src/audio/base_fw.c +++ b/src/audio/base_fw.c @@ -859,8 +859,7 @@ static SHARED_DATA struct comp_driver_info comp_basefw_info = { UT_STATIC void sys_comp_basefw_init(void) { - comp_register(platform_shared_get(&comp_basefw_info, - sizeof(comp_basefw_info))); + comp_register(&comp_basefw_info); } DECLARE_MODULE(sys_comp_basefw_init); diff --git a/src/audio/chain_dma.c b/src/audio/chain_dma.c index 15929dd6941c..257dead39b52 100644 --- a/src/audio/chain_dma.c +++ b/src/audio/chain_dma.c @@ -694,8 +694,7 @@ static SHARED_DATA struct comp_driver_info comp_chain_dma_info = { UT_STATIC void sys_comp_chain_dma_init(void) { - comp_register(platform_shared_get(&comp_chain_dma_info, - sizeof(comp_chain_dma_info))); + comp_register(&comp_chain_dma_info); } DECLARE_MODULE(sys_comp_chain_dma_init); diff --git a/src/audio/component.c b/src/audio/component.c index d63ab71725c0..8a6d35776971 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -186,7 +186,7 @@ EXPORT_SYMBOL(comp_set_state); void sys_comp_init(struct sof *sof) { - sof->comp_drivers = platform_shared_get(&cd, sizeof(cd)); + sof->comp_drivers = &cd; list_init(&sof->comp_drivers->list); } diff --git a/src/audio/dai-legacy.c b/src/audio/dai-legacy.c index fb095a16a626..8a9496c49121 100644 --- a/src/audio/dai-legacy.c +++ b/src/audio/dai-legacy.c @@ -1134,8 +1134,7 @@ static SHARED_DATA struct comp_driver_info comp_dai_info = { UT_STATIC void sys_comp_dai_init(void) { - comp_register(platform_shared_get(&comp_dai_info, - sizeof(comp_dai_info))); + comp_register(&comp_dai_info); } DECLARE_MODULE(sys_comp_dai_init); diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 91e3f6021662..e0295526d054 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -2066,7 +2066,7 @@ static SHARED_DATA struct comp_driver_info comp_dai_info = { UT_STATIC void sys_comp_dai_init(void) { - comp_register(platform_shared_get(&comp_dai_info, sizeof(comp_dai_info))); + comp_register(&comp_dai_info); } DECLARE_MODULE(sys_comp_dai_init); diff --git a/src/audio/google/google_hotword_detect.c b/src/audio/google/google_hotword_detect.c index f7cf44f026fd..1055f7914e1e 100644 --- a/src/audio/google/google_hotword_detect.c +++ b/src/audio/google/google_hotword_detect.c @@ -472,8 +472,7 @@ static SHARED_DATA struct comp_driver_info ghd_driver_info = { UT_STATIC void sys_comp_ghd_init(void) { - comp_register(platform_shared_get(&ghd_driver_info, - sizeof(ghd_driver_info))); + comp_register(&ghd_driver_info); } DECLARE_MODULE(sys_comp_ghd_init); diff --git a/src/audio/host-legacy.c b/src/audio/host-legacy.c index c383c9d4e7d3..4560959bb044 100644 --- a/src/audio/host-legacy.c +++ b/src/audio/host-legacy.c @@ -1074,8 +1074,7 @@ static SHARED_DATA struct comp_driver_info comp_host_info = { UT_STATIC void sys_comp_host_init(void) { - comp_register(platform_shared_get(&comp_host_info, - sizeof(comp_host_info))); + comp_register(&comp_host_info); } DECLARE_MODULE(sys_comp_host_init); diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index c182d033c23a..95f8f35a3736 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -1338,8 +1338,7 @@ static SHARED_DATA struct comp_driver_info comp_host_info = { UT_STATIC void sys_comp_host_init(void) { - comp_register(platform_shared_get(&comp_host_info, - sizeof(comp_host_info))); + comp_register(&comp_host_info); } DECLARE_MODULE(sys_comp_host_init); diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 7dc874bf58d2..2cec91393517 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -2748,8 +2748,7 @@ static SHARED_DATA struct comp_driver_info comp_kpb_info = { UT_STATIC void sys_comp_kpb_init(void) { - comp_register(platform_shared_get(&comp_kpb_info, - sizeof(comp_kpb_info))); + comp_register(&comp_kpb_info); } DECLARE_MODULE(sys_comp_kpb_init); diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 915987051275..9ff70a218cf0 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -105,8 +105,7 @@ static inline void pipeline_posn_offset_put(uint32_t posn_offset) void pipeline_posn_init(struct sof *sof) { - sof->pipeline_posn = platform_shared_get(&pipeline_posn_shared, - sizeof(pipeline_posn_shared)); + sof->pipeline_posn = &pipeline_posn_shared; k_spinlock_init(&sof->pipeline_posn->lock); } diff --git a/src/audio/selector/selector.c b/src/audio/selector/selector.c index 95cef6c72bb8..faf21da0e228 100644 --- a/src/audio/selector/selector.c +++ b/src/audio/selector/selector.c @@ -609,8 +609,7 @@ static SHARED_DATA struct comp_driver_info comp_selector_info = { /** \brief Initializes selector component. */ UT_STATIC void sys_comp_selector_init(void) { - comp_register(platform_shared_get(&comp_selector_info, - sizeof(comp_selector_info))); + comp_register(&comp_selector_info); } DECLARE_MODULE(sys_comp_selector_init); diff --git a/src/audio/tone/tone-ipc3.c b/src/audio/tone/tone-ipc3.c index b2f153696c20..db36b9a88cbe 100644 --- a/src/audio/tone/tone-ipc3.c +++ b/src/audio/tone/tone-ipc3.c @@ -401,8 +401,7 @@ static SHARED_DATA struct comp_driver_info comp_tone_info = { UT_STATIC void sys_comp_tone_init(void) { - comp_register(platform_shared_get(&comp_tone_info, - sizeof(comp_tone_info))); + comp_register(&comp_tone_info); } DECLARE_MODULE(sys_comp_tone_init); diff --git a/src/drivers/interrupt.c b/src/drivers/interrupt.c index 5bc1a26e53f7..afa07c4d1d46 100644 --- a/src/drivers/interrupt.c +++ b/src/drivers/interrupt.c @@ -168,8 +168,7 @@ struct irq_cascade_desc *interrupt_get_parent(uint32_t irq) void interrupt_init(struct sof *sof) { - sof->cascade_root = platform_shared_get(&cascade_root, - sizeof(cascade_root)); + sof->cascade_root = &cascade_root; sof->cascade_root->last_irq = PLATFORM_IRQ_FIRST_CHILD - 1; k_spinlock_init(&sof->cascade_root->lock); diff --git a/src/idc/idc.c b/src/idc/idc.c index 0dd735878cb1..145a22abfa1c 100644 --- a/src/idc/idc.c +++ b/src/idc/idc.c @@ -430,7 +430,7 @@ __cold int idc_init(void) tr_dbg(&idc_tr, "entry"); /* initialize idc data */ - (*idc)->payload = platform_shared_get(static_payload, sizeof(static_payload)); + (*idc)->payload = static_payload; #ifdef CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS struct io_perf_data_item init_data = {IO_PERF_IDC_ID, diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index c44b7a5a6c18..6740593a7cf1 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -84,8 +84,7 @@ static SHARED_DATA struct comp_driver_info comp_module_##adapter##_info = { \ \ UT_STATIC void sys_comp_module_##adapter##_init(void) \ { \ - comp_register(platform_shared_get(&comp_module_##adapter##_info, \ - sizeof(comp_module_##adapter##_info))); \ + comp_register(&comp_module_##adapter##_info); \ } \ \ DECLARE_MODULE(sys_comp_module_##adapter##_init) diff --git a/src/lib/clk.c b/src/lib/clk.c index d93ab81c680b..1b5025b616cb 100644 --- a/src/lib/clk.c +++ b/src/lib/clk.c @@ -89,7 +89,6 @@ uint64_t clock_ticks_per_sample(int clock, uint32_t sample_rate) uint32_t ticks_per_msec; uint64_t ticks_per_sample; - platform_shared_get(clk_info, sizeof(*clk_info)); ticks_per_msec = clk_info->freqs[clk_info->current_freq_idx].ticks_per_msec; ticks_per_sample = sample_rate ? ticks_per_msec * 1000 / sample_rate : 0; diff --git a/src/lib/notifier.c b/src/lib/notifier.c index 3f4d3d07f535..102dc55fc7b3 100644 --- a/src/lib/notifier.c +++ b/src/lib/notifier.c @@ -209,8 +209,7 @@ __cold void init_system_notify(struct sof *sof) list_init(&(*notify)->list[i]); if (cpu_get_id() == PLATFORM_PRIMARY_CORE_ID) - sof->notify_data = platform_shared_get(notify_data_shared, - sizeof(notify_data_shared)); + sof->notify_data = notify_data_shared; } void free_system_notify(void) diff --git a/src/platform/amd/acp_6_3/include/platform/lib/memory.h b/src/platform/amd/acp_6_3/include/platform/lib/memory.h index 2597b0858715..2b4fc43e6322 100644 --- a/src/platform/amd/acp_6_3/include/platform/lib/memory.h +++ b/src/platform/amd/acp_6_3/include/platform/lib/memory.h @@ -165,11 +165,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/acp_7_0/include/platform/lib/memory.h b/src/platform/amd/acp_7_0/include/platform/lib/memory.h index de506811721d..60a72e9f38c1 100644 --- a/src/platform/amd/acp_7_0/include/platform/lib/memory.h +++ b/src/platform/amd/acp_7_0/include/platform/lib/memory.h @@ -176,11 +176,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/acp_7_x/include/platform/lib/memory.h b/src/platform/amd/acp_7_x/include/platform/lib/memory.h index dd407a18e086..df1007e50674 100644 --- a/src/platform/amd/acp_7_x/include/platform/lib/memory.h +++ b/src/platform/amd/acp_7_x/include/platform/lib/memory.h @@ -231,11 +231,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/rembrandt/include/platform/lib/memory.h b/src/platform/amd/rembrandt/include/platform/lib/memory.h index 92ac53b4c2ed..bd618b6d9bdb 100644 --- a/src/platform/amd/rembrandt/include/platform/lib/memory.h +++ b/src/platform/amd/rembrandt/include/platform/lib/memory.h @@ -156,11 +156,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/renoir/include/platform/lib/memory.h b/src/platform/amd/renoir/include/platform/lib/memory.h index 4caeb0e30d06..0d1df900e6fd 100644 --- a/src/platform/amd/renoir/include/platform/lib/memory.h +++ b/src/platform/amd/renoir/include/platform/lib/memory.h @@ -151,11 +151,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/amd/renoir/lib/clk.c b/src/platform/amd/renoir/lib/clk.c index dddc974f631a..4880daafac71 100644 --- a/src/platform/amd/renoir/lib/clk.c +++ b/src/platform/amd/renoir/lib/clk.c @@ -122,7 +122,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < PLATFORM_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info) { diff --git a/src/platform/amd/renoir/lib/memory.c b/src/platform/amd/renoir/lib/memory.c index 9f16b1977f05..a5b33cbb24d6 100644 --- a/src/platform/amd/renoir/lib/memory.c +++ b/src/platform/amd/renoir/lib/memory.c @@ -91,5 +91,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/amd/renoir/platform.c b/src/platform/amd/renoir/platform.c index ad2fa16cf0a0..419f1251bd84 100644 --- a/src/platform/amd/renoir/platform.c +++ b/src/platform/amd/renoir/platform.c @@ -133,7 +133,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; /* to view system memory */ diff --git a/src/platform/amd/vangogh/include/platform/lib/memory.h b/src/platform/amd/vangogh/include/platform/lib/memory.h index 6e0b768cc57a..49a12236833c 100644 --- a/src/platform/amd/vangogh/include/platform/lib/memory.h +++ b/src/platform/amd/vangogh/include/platform/lib/memory.h @@ -150,11 +150,6 @@ struct sof; #define SHARED_DATA void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - static inline void *platform_rfree_prepare(void *ptr) { return ptr; diff --git a/src/platform/imx8/include/platform/lib/memory.h b/src/platform/imx8/include/platform/lib/memory.h index 1c3d88fe4d4d..674d1af2611f 100644 --- a/src/platform/imx8/include/platform/lib/memory.h +++ b/src/platform/imx8/include/platform/lib/memory.h @@ -187,11 +187,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/imx8/lib/clk.c b/src/platform/imx8/lib/clk.c index 45edfe98461d..152cac1752d7 100644 --- a/src/platform/imx8/lib/clk.c +++ b/src/platform/imx8/lib/clk.c @@ -30,7 +30,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info) { diff --git a/src/platform/imx8/lib/memory.c b/src/platform/imx8/lib/memory.c index 78cb05c86cbb..d4527d756ac1 100644 --- a/src/platform/imx8/lib/memory.c +++ b/src/platform/imx8/lib/memory.c @@ -95,5 +95,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/imx8m/include/platform/lib/memory.h b/src/platform/imx8m/include/platform/lib/memory.h index 6b2c00f89bad..490e65a0052d 100644 --- a/src/platform/imx8m/include/platform/lib/memory.h +++ b/src/platform/imx8m/include/platform/lib/memory.h @@ -211,11 +211,6 @@ void platform_init_memmap(struct sof *sof); #define cache_to_uncache_init(address) address #define is_uncached(address) 0 -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - /** * \brief Function for keeping shared data synchronized. * It's used after usage of data shared by different cores. diff --git a/src/platform/imx8m/lib/clk.c b/src/platform/imx8m/lib/clk.c index 37b115e507a2..78aacfd05e28 100644 --- a/src/platform/imx8m/lib/clk.c +++ b/src/platform/imx8m/lib/clk.c @@ -25,7 +25,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info) { diff --git a/src/platform/imx8m/lib/memory.c b/src/platform/imx8m/lib/memory.c index 85596ca2b525..9f26cd3ebe80 100644 --- a/src/platform/imx8m/lib/memory.c +++ b/src/platform/imx8m/lib/memory.c @@ -97,5 +97,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/imx8m_cm7/include/platform/lib/memory.h b/src/platform/imx8m_cm7/include/platform/lib/memory.h index 413e77f142e1..af0b0b823e24 100644 --- a/src/platform/imx8m_cm7/include/platform/lib/memory.h +++ b/src/platform/imx8m_cm7/include/platform/lib/memory.h @@ -28,11 +28,6 @@ /* WAKEUP domain MU1 side B */ #define MU_BASE 0x30AB0000UL -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #endif /* __PLATFORM_LIB_MEMORY_H__ */ #else diff --git a/src/platform/imx8ulp/include/platform/lib/memory.h b/src/platform/imx8ulp/include/platform/lib/memory.h index 00aa9b8b2a41..ee74e6989e86 100644 --- a/src/platform/imx8ulp/include/platform/lib/memory.h +++ b/src/platform/imx8ulp/include/platform/lib/memory.h @@ -195,11 +195,6 @@ void platform_init_memmap(struct sof *sof); #define cache_to_uncache_init(address) address #define is_uncached(address) 0 -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - /** * \brief Function for keeping shared data synchronized. * It's used after usage of data shared by different cores. diff --git a/src/platform/imx8ulp/lib/clk.c b/src/platform/imx8ulp/lib/clk.c index 21d1f4166188..93a0a83ed54d 100644 --- a/src/platform/imx8ulp/lib/clk.c +++ b/src/platform/imx8ulp/lib/clk.c @@ -25,7 +25,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info) { diff --git a/src/platform/imx8ulp/lib/memory.c b/src/platform/imx8ulp/lib/memory.c index ac80bf55aae6..cafd4b5d3d0c 100644 --- a/src/platform/imx8ulp/lib/memory.c +++ b/src/platform/imx8ulp/lib/memory.c @@ -96,5 +96,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/imx93_a55/include/platform/lib/memory.h b/src/platform/imx93_a55/include/platform/lib/memory.h index 77ca3ec6103b..b9c84ff42a7f 100644 --- a/src/platform/imx93_a55/include/platform/lib/memory.h +++ b/src/platform/imx93_a55/include/platform/lib/memory.h @@ -58,11 +58,6 @@ /* WM8962 is connected to SAI3 */ #define SAI3_BASE 0x42660000 -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #endif /* __PLATFORM_LIB_MEMORY_H__ */ #else diff --git a/src/platform/imx95/include/platform/lib/memory.h b/src/platform/imx95/include/platform/lib/memory.h index bf3ff05a5c69..552c20e32530 100644 --- a/src/platform/imx95/include/platform/lib/memory.h +++ b/src/platform/imx95/include/platform/lib/memory.h @@ -28,11 +28,6 @@ /* WAKEUP domain MU7 side B */ #define MU_BASE 0x42440000UL -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #endif /* __PLATFORM_LIB_MEMORY_H__ */ #else diff --git a/src/platform/intel/ace/include/ace/lib/memory.h b/src/platform/intel/ace/include/ace/lib/memory.h index 357397b2affa..f5740a7d659d 100644 --- a/src/platform/intel/ace/include/ace/lib/memory.h +++ b/src/platform/intel/ace/include/ace/lib/memory.h @@ -33,21 +33,6 @@ #define cache_to_uncache(address) sys_cache_uncached_ptr_get(address) #define is_uncached(address) (!sys_cache_is_ptr_cached(address)) -/** - * \brief Returns pointer to the memory shared by multiple cores. - * \param[in,out] ptr Initial pointer to the allocated memory. - * \param[in] bytes Size of the allocated memory - * \return Appropriate pointer to the shared memory. - * - * This function is called only once right after allocation of shared memory. - * Platforms with uncached memory region should return aliased address. - * On platforms without such region simple invalidate is enough. - */ -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #endif /* __ACE_LIB_MEMORY_H__ */ #else diff --git a/src/platform/intel/cavs/include/cavs/lib/memory.h b/src/platform/intel/cavs/include/cavs/lib/memory.h index b53c76d64b6d..0473131da6c8 100644 --- a/src/platform/intel/cavs/include/cavs/lib/memory.h +++ b/src/platform/intel/cavs/include/cavs/lib/memory.h @@ -75,26 +75,6 @@ static inline void *cache_to_uncache(void __sparse_cache *address) #define cache_to_uncache_init(address) address #endif -/** - * \brief Returns pointer to the memory shared by multiple cores. - * \param[in,out] ptr Initial pointer to the allocated memory. - * \param[in] bytes Size of the allocated memory - * \return Appropriate pointer to the shared memory. - * - * This function is called only once right after allocation of shared memory. - * Platforms with uncached memory region should return aliased address. - * On platforms without such region simple invalidate is enough. - */ -static inline void *platform_shared_get(void *ptr, int bytes) -{ -#if CONFIG_CORE_COUNT > 1 && !defined __ZEPHYR__ - dcache_invalidate_region((__sparse_force void __sparse_cache *)ptr, bytes); - return cache_to_uncache(ptr); -#else - return ptr; -#endif -} - /** * \brief Transforms pointer if necessary before freeing the memory. * \param[in,out] ptr Pointer to the allocated memory. diff --git a/src/platform/library/include/platform/lib/memory.h b/src/platform/library/include/platform/lib/memory.h index fafb3df303f7..11f0114c1dde 100644 --- a/src/platform/library/include/platform/lib/memory.h +++ b/src/platform/library/include/platform/lib/memory.h @@ -37,11 +37,6 @@ uint8_t *get_library_mailbox(void); #define SHARED_DATA -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - void platform_init_memmap(struct sof *sof); static inline void *platform_rfree_prepare(void *ptr) diff --git a/src/platform/mt8186/include/platform/lib/memory.h b/src/platform/mt8186/include/platform/lib/memory.h index 65148c9c348d..305e4c917846 100644 --- a/src/platform/mt8186/include/platform/lib/memory.h +++ b/src/platform/mt8186/include/platform/lib/memory.h @@ -192,11 +192,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8186/lib/clk.c b/src/platform/mt8186/lib/clk.c index 033580a280bf..6a5b2910c506 100644 --- a/src/platform/mt8186/lib/clk.c +++ b/src/platform/mt8186/lib/clk.c @@ -135,7 +135,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info){ diff --git a/src/platform/mt8186/lib/memory.c b/src/platform/mt8186/lib/memory.c index 2a01e0333bab..1ffc41859713 100644 --- a/src/platform/mt8186/lib/memory.c +++ b/src/platform/mt8186/lib/memory.c @@ -98,5 +98,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8186/platform.c b/src/platform/mt8186/platform.c index f171e6cb7d0b..e08766eba70d 100644 --- a/src/platform/mt8186/platform.c +++ b/src/platform/mt8186/platform.c @@ -163,7 +163,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); diff --git a/src/platform/mt8188/include/platform/lib/memory.h b/src/platform/mt8188/include/platform/lib/memory.h index 65148c9c348d..305e4c917846 100644 --- a/src/platform/mt8188/include/platform/lib/memory.h +++ b/src/platform/mt8188/include/platform/lib/memory.h @@ -192,11 +192,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8188/lib/clk.c b/src/platform/mt8188/lib/clk.c index f39872fca845..bd97dad069a4 100644 --- a/src/platform/mt8188/lib/clk.c +++ b/src/platform/mt8188/lib/clk.c @@ -123,7 +123,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info){ diff --git a/src/platform/mt8188/lib/memory.c b/src/platform/mt8188/lib/memory.c index 73d3af0f4d43..5ef0828ffc6c 100644 --- a/src/platform/mt8188/lib/memory.c +++ b/src/platform/mt8188/lib/memory.c @@ -98,5 +98,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8188/platform.c b/src/platform/mt8188/platform.c index fc2e83dbed25..d3433a3b46c8 100644 --- a/src/platform/mt8188/platform.c +++ b/src/platform/mt8188/platform.c @@ -161,7 +161,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); diff --git a/src/platform/mt8195/include/platform/lib/memory.h b/src/platform/mt8195/include/platform/lib/memory.h index 42ee7f7990ab..7a93d36d1b2a 100644 --- a/src/platform/mt8195/include/platform/lib/memory.h +++ b/src/platform/mt8195/include/platform/lib/memory.h @@ -183,11 +183,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8195/lib/clk.c b/src/platform/mt8195/lib/clk.c index 1514b9d9307d..894396ab4855 100644 --- a/src/platform/mt8195/lib/clk.c +++ b/src/platform/mt8195/lib/clk.c @@ -177,7 +177,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info){ diff --git a/src/platform/mt8195/lib/memory.c b/src/platform/mt8195/lib/memory.c index b2d545ff6f1d..c452a0520cc7 100644 --- a/src/platform/mt8195/lib/memory.c +++ b/src/platform/mt8195/lib/memory.c @@ -97,5 +97,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8195/platform.c b/src/platform/mt8195/platform.c index 7e713290437d..6a08580ad043 100644 --- a/src/platform/mt8195/platform.c +++ b/src/platform/mt8195/platform.c @@ -182,7 +182,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); diff --git a/src/platform/mt8196/include/platform/lib/memory.h b/src/platform/mt8196/include/platform/lib/memory.h index f6d8bd521208..b849fe09cb4f 100644 --- a/src/platform/mt8196/include/platform/lib/memory.h +++ b/src/platform/mt8196/include/platform/lib/memory.h @@ -191,11 +191,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8196/lib/clk.c b/src/platform/mt8196/lib/clk.c index 69af725dec73..ebf109a0c9fc 100644 --- a/src/platform/mt8196/lib/clk.c +++ b/src/platform/mt8196/lib/clk.c @@ -37,7 +37,7 @@ void platform_clock_init(struct sof *sof) int i; tr_dbg(&clkdrv_tr, "clock init\n"); - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; /* When the system is in an active state, the DSP clock operates at 800MHz (0.75V). * In a low power scenario, the DSP enters WFI state, and the clock reduces to 26MHz. diff --git a/src/platform/mt8196/lib/memory.c b/src/platform/mt8196/lib/memory.c index dca28790d741..7445f1713830 100644 --- a/src/platform/mt8196/lib/memory.c +++ b/src/platform/mt8196/lib/memory.c @@ -97,5 +97,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8196/platform.c b/src/platform/mt8196/platform.c index fc7cd9a7eb34..c093e7b003ef 100644 --- a/src/platform/mt8196/platform.c +++ b/src/platform/mt8196/platform.c @@ -162,7 +162,7 @@ int platform_init(struct sof *sof) { int ret; - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); platform_clock_init(sof); diff --git a/src/platform/mt8365/include/platform/lib/memory.h b/src/platform/mt8365/include/platform/lib/memory.h index 3f0532d4d417..75965466ae95 100644 --- a/src/platform/mt8365/include/platform/lib/memory.h +++ b/src/platform/mt8365/include/platform/lib/memory.h @@ -206,11 +206,6 @@ struct sof; void platform_init_memmap(struct sof *sof); -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define uncache_to_cache(address) address #define cache_to_uncache(address) address #define cache_to_uncache_init(address) address diff --git a/src/platform/mt8365/lib/clk.c b/src/platform/mt8365/lib/clk.c index 8101d4fd0f2c..57bc821049fb 100644 --- a/src/platform/mt8365/lib/clk.c +++ b/src/platform/mt8365/lib/clk.c @@ -168,7 +168,7 @@ void platform_clock_init(struct sof *sof) { int i; - sof->clocks = platform_shared_get(platform_clocks_info, sizeof(platform_clocks_info)); + sof->clocks = platform_clocks_info; for (i = 0; i < CONFIG_CORE_COUNT; i++) { sof->clocks[i] = (struct clock_info){ diff --git a/src/platform/mt8365/lib/memory.c b/src/platform/mt8365/lib/memory.c index 13c7a9b2bdd4..7ed1417dc1f2 100644 --- a/src/platform/mt8365/lib/memory.c +++ b/src/platform/mt8365/lib/memory.c @@ -97,5 +97,5 @@ static SHARED_DATA struct mm memmap = { void platform_init_memmap(struct sof *sof) { /* memmap has been initialized statically as a part of .data */ - sof->memory_map = platform_shared_get(&memmap, sizeof(memmap)); + sof->memory_map = &memmap; } diff --git a/src/platform/mt8365/platform.c b/src/platform/mt8365/platform.c index d57edc2f8431..5ae6378c5eb5 100644 --- a/src/platform/mt8365/platform.c +++ b/src/platform/mt8365/platform.c @@ -164,7 +164,7 @@ int platform_init(struct sof *sof) mailbox_sw_reg_write(SRAM_REG_OP_CPU2DSP, 0); mailbox_sw_reg_write(SRAM_REG_OP_DSP2CPU, 0); - sof->platform_timer = platform_shared_get(&timer_shared, sizeof(timer_shared)); + sof->platform_timer = &timer_shared; sof->cpu_timers = sof->platform_timer; platform_interrupt_init(); diff --git a/src/platform/mtk/include/platform/lib/memory.h b/src/platform/mtk/include/platform/lib/memory.h index 26806ab394ba..1e3acbee6912 100644 --- a/src/platform/mtk/include/platform/lib/memory.h +++ b/src/platform/mtk/include/platform/lib/memory.h @@ -16,11 +16,6 @@ BUILD_ASSERT(PLATFORM_DCACHE_ALIGN == XCHAL_DCACHE_LINESIZE); #define uncache_to_cache(addr) (addr) #define cache_to_uncache(addr) (addr) -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define host_to_local(addr) (addr) #define PLATFORM_HEAP_SYSTEM 1 diff --git a/src/platform/posix/include/platform/lib/memory.h b/src/platform/posix/include/platform/lib/memory.h index 7782ab55048b..f2f6e567223b 100644 --- a/src/platform/posix/include/platform/lib/memory.h +++ b/src/platform/posix/include/platform/lib/memory.h @@ -47,11 +47,6 @@ extern uint32_t posix_trace[]; #define host_to_local(addr) (addr) -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define SHARED_DATA /**/ #endif /* PLATFORM_HOST_PLATFORM_MEMORY_H */ diff --git a/src/platform/qemu_xtensa/include/platform/lib/memory.h b/src/platform/qemu_xtensa/include/platform/lib/memory.h index d0843904f563..7c8f9da9c1c1 100644 --- a/src/platform/qemu_xtensa/include/platform/lib/memory.h +++ b/src/platform/qemu_xtensa/include/platform/lib/memory.h @@ -8,11 +8,6 @@ /* Dummy memory header for qemu_xtensa */ -static inline void *platform_shared_get(void *ptr, int bytes) -{ - return ptr; -} - #define PLATFORM_DCACHE_ALIGN sizeof(void *) #define HOST_PAGE_SIZE 4096 #define SHARED_DATA diff --git a/src/samples/audio/detect_test.c b/src/samples/audio/detect_test.c index 1e2b21ee9d9e..7979d7db3d6c 100644 --- a/src/samples/audio/detect_test.c +++ b/src/samples/audio/detect_test.c @@ -1113,8 +1113,7 @@ static SHARED_DATA struct comp_driver_info comp_keyword_info = { UT_STATIC void sys_comp_keyword_init(void) { - comp_register(platform_shared_get(&comp_keyword_info, - sizeof(comp_keyword_info))); + comp_register(&comp_keyword_info); } DECLARE_MODULE(sys_comp_keyword_init); diff --git a/src/samples/audio/smart_amp_test_ipc3.c b/src/samples/audio/smart_amp_test_ipc3.c index 3e69bb5f308e..5b2abf1ae03b 100644 --- a/src/samples/audio/smart_amp_test_ipc3.c +++ b/src/samples/audio/smart_amp_test_ipc3.c @@ -555,8 +555,7 @@ static SHARED_DATA struct comp_driver_info comp_smart_amp_info = { UT_STATIC void sys_comp_smart_amp_test_init(void) { - comp_register(platform_shared_get(&comp_smart_amp_info, - sizeof(comp_smart_amp_info))); + comp_register(&comp_smart_amp_info); } DECLARE_MODULE(sys_comp_smart_amp_test_init); From c6efc097a6b5e1a808842c22fda5dbafa06ff3b9 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 25 Jun 2026 18:55:27 +0300 Subject: [PATCH 228/303] schedule: zephyr_ll: add user_ll_lock/unlock_sched() Add new functions to lock/unlock the LL scheduler for a given core. This is intended for audio application code that needs to modify the audio pipelines and needs an interface to get exclusive access to the pipelines on a particular core. This interface is specific to SOF builds with CONFIG_SOF_USERSPACE_LL. If LL scheduler is running in kernel space, there is option to disable interrupts for similar effect. For now these code paths are kept separate. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/ll_schedule_domain.h | 2 ++ src/schedule/zephyr_ll.c | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index 1367c408899f..62d7edd2d1be 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -120,6 +120,8 @@ struct task *zephyr_ll_task_alloc(void); struct k_heap *zephyr_ll_user_heap(void); bool zephyr_ll_user_heap_verify(struct k_heap *heap); void zephyr_ll_user_resources_init(void); +void user_ll_lock_sched(int core); +void user_ll_unlock_sched(int core); #endif /* CONFIG_SOF_USERSPACE_LL */ static inline struct ll_schedule_domain *domain_init diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index ddae93eee8b7..ff5deb31aeef 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -44,6 +44,15 @@ struct zephyr_ll_pdata { struct k_sem sem; }; +#if CONFIG_SOF_USERSPACE_LL +/* + * Mutex pointer in user-accessible partition so user-space threads + * can read the pointer for syscalls. The actual lock object resides + * in kernel memory, there are just handles! + */ +static APP_SYSUSER_BSS struct k_mutex *zephyr_ll_locks[CONFIG_CORE_COUNT]; +#endif + static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) { #if CONFIG_SOF_USERSPACE_LL @@ -561,6 +570,31 @@ struct task *zephyr_ll_task_alloc(void) return task; } + +/** + * Lock the LL scheduler to prevent it from processing tasks. + * + * Uses the LL scheduler's own k_mutex which is re-entrant, so + * schedule_task() calls within the locked section will not deadlock. + * Must be paired with user_ll_unlock_sched(). + */ +void user_ll_lock_sched(int core) +{ + assert(core >= 0 && core < CONFIG_CORE_COUNT && zephyr_ll_locks[core] != NULL); + int __maybe_unused ret = k_mutex_lock(zephyr_ll_locks[core], K_FOREVER); + assert(!ret); +} + +/** + * Unlock the LL scheduler after a previous user_ll_lock_sched() call. + */ +void user_ll_unlock_sched(int core) +{ + assert(core >= 0 && core < CONFIG_CORE_COUNT && zephyr_ll_locks[core] != NULL); + int __maybe_unused ret = k_mutex_unlock(zephyr_ll_locks[core]); + assert(!ret); +} + #endif /* CONFIG_SOF_USERSPACE_LL */ int zephyr_ll_task_init(struct task *task, @@ -649,6 +683,8 @@ __cold int zephyr_ll_scheduler_init(struct ll_schedule_domain *domain) sof_heap_free(sch->heap, sch); return -ENOMEM; } + assert(core < CONFIG_CORE_COUNT && zephyr_ll_locks[core] == NULL); + zephyr_ll_locks[core] = sch->lock; k_mutex_init(sch->lock); tr_dbg(&ll_tr, "ll-scheduler init done, sch %p sch->lock %p", sch, sch->lock); From 9edd2553e9228e6cf4fccf3c64e107d170c970a8 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 16 Jun 2026 12:48:07 +0300 Subject: [PATCH 229/303] schedule: zephyr_ll: user_ll: keep lock while running tasks In user-space LL builds (CONFIG_SOF_USERSPACE_LL), the IPC user thread cannot block interrupts while making modifications to the audio graph. To workaround this limitation, one could either protect each pipeline object with locks, or keep the LL level lock held while executing LL tasks. This patch implements support for the latter approach. If building SOF for user LL, do not release the lock when running a task. This will help to reduce number of syscalls during a LL iteration, while still allowing to safely implement IPC handlers that need to modify the audio graph. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index ff5deb31aeef..9705a156b326 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -231,7 +231,15 @@ static void zephyr_ll_run(void *data) list_item_del(list); list_item_append(list, &task_head); + /* in user-space LL builds, the LL lock protects LL + * tasks against IPC thread, so the lock must be held + * while running the tasks. + * in kernel LL builds, IPC thread blocks interrupts for + * critical section, so lock can be freed here. + */ +#ifndef CONFIG_SOF_USERSPACE_LL zephyr_ll_unlock(sch, &flags); +#endif /* * task's .run() should only return either @@ -246,7 +254,9 @@ static void zephyr_ll_run(void *data) state = SOF_TASK_STATE_RESCHEDULE; } +#ifndef CONFIG_SOF_USERSPACE_LL zephyr_ll_lock(sch, &flags); +#endif if (pdata->freeing || state == SOF_TASK_STATE_COMPLETED) { zephyr_ll_task_done(sch, task); From dbd1327e28f6738f3752bee54b6b54fc478623e5 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 1 Jul 2026 00:52:28 +0300 Subject: [PATCH 230/303] audio: pipeline: adapt to changes in locking strategy for user LL builds Modify the locking approach for CONFIG_SOF_USERSPACE_LL builds. Kernel LL implementation heavily relies on ability to disable interrupts when IPC handler is modifying the graph. This ensures a new LL tick and execution of a new graph cycle does not start before the graph modifications done by IPC handler are complete. In user-space, this approach is not available as user-space thread cannot disable interrupts. In commit 1e59ce23dfa4 ("pipeline: protect component connections with a mutex"), a sys_mutex based locking was implemented to protect the component list and modifications to it. This approach does not scale in the end as this would require taking the mutex for each component of each pipeline, and take the locks on every LL cycle tick. This results in significant system call overhead. Additionally Zephyr sys_mutex does not work correctly if the lock object is put into dynamically allocated user memory. The LL scheduler logic was changed to keep the LL lock when building with CONFIG_SOF_USERSPACE_LL. A single lock is used to protect the whole LL graph, and the lock is taken at start of LL tick. Take benefit of this in audio pipeline code and remove redundant locking. The patch only changes behaviour for userspace LL SOF builds. If LL scheduling is kept in kernel, locking is done as before. Signed-off-by: Kai Vehmanen --- src/audio/pipeline/pipeline-graph.c | 30 +++++---- src/audio/pipeline/pipeline-stream.c | 19 ++++++ src/include/sof/audio/component.h | 7 -- src/include/sof/schedule/ll_schedule_domain.h | 6 ++ src/ipc/ipc-helper.c | 13 ++++ src/ipc/ipc4/helper.c | 67 ++++++++++++++----- src/schedule/zephyr_ll.c | 41 ++++++++++++ 7 files changed, 149 insertions(+), 34 deletions(-) diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 9ff70a218cf0..adcb00a80719 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -180,19 +181,24 @@ static void buffer_set_comp(struct comp_buffer *buffer, struct comp_dev *comp, } #ifdef CONFIG_SOF_USERSPACE_LL +/* + * User-space LL: callers (IPC handlers) already hold the per-core LL + * lock via user_ll_lock_sched()/ll_block() while modifying pipeline + * connections, which provides mutual exclusion with the LL thread. No + * additional lock is taken here; instead assert that the lock is held. + */ #define PPL_LOCK_DECLARE -#define PPL_LOCK() do { \ - int ret = sys_mutex_lock(&comp->list_mutex, K_FOREVER); \ - assert(ret == 0); \ - } while (0) -#define PPL_UNLOCK() do { \ - int ret = sys_mutex_unlock(&comp->list_mutex); \ - assert(ret == 0); \ - } while (0) +#define PPL_LOCK(x) user_ll_assert_locked(x) +#define PPL_UNLOCK() #else +/* + * Kernel-space LL. When modifying pipeline connections, block IRQs + * and prevent LL from running. No locking needed when iterating + * the pipeline in the LL thread. + */ #define PPL_LOCK_DECLARE uint32_t flags -#define PPL_LOCK() irq_local_disable(flags) -#define PPL_UNLOCK() irq_local_enable(flags) +#define PPL_LOCK(x) irq_local_disable(flags) +#define PPL_UNLOCK() irq_local_enable(flags) #endif int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, @@ -207,7 +213,7 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, else comp_info(comp, "connect buffer %d as source", buf_get_id(buffer)); - PPL_LOCK(); + PPL_LOCK(buffer->core); comp_list = comp_buffer_list(comp, dir); ret = buffer_attach(buffer, comp_list, dir); @@ -234,7 +240,7 @@ void pipeline_disconnect(struct comp_dev *comp, struct comp_buffer *buffer, int else comp_dbg(comp, "disconnect buffer %d as source", buf_get_id(buffer)); - PPL_LOCK(); + PPL_LOCK(buffer->core); comp_list = comp_buffer_list(comp, dir); buffer_detach(buffer, comp_list, dir); diff --git a/src/audio/pipeline/pipeline-stream.c b/src/audio/pipeline/pipeline-stream.c index e4a166554fa2..5fd795e818db 100644 --- a/src/audio/pipeline/pipeline-stream.c +++ b/src/audio/pipeline/pipeline-stream.c @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef CONFIG_IPC_MAJOR_4 #include @@ -145,6 +146,20 @@ static int pipeline_comp_copy(struct comp_dev *current, return err; } +#ifdef CONFIG_SOF_USERSPACE_LL +/* + * User-space LL: pipeline_copy() runs as an LL task, with the per-core + * LL lock already held by the LL thread for the whole tick. Taking the + * lock again here would only add syscall overhead on this hot path, so + * we just assert that the lock is in fact held. + */ +#define PPL_LOCK(x) user_ll_assert_locked(x) +#define PPL_UNLOCK() +#else +#define PPL_LOCK(x) +#define PPL_UNLOCK() +#endif + /* Copy data across all pipeline components. * For capture pipelines it always starts from source component * and continues downstream and for playback pipelines it first @@ -162,6 +177,8 @@ int pipeline_copy(struct pipeline *p) uint32_t dir; int ret; + PPL_LOCK(p->core); + if (p->source_comp->direction == SOF_IPC_STREAM_PLAYBACK) { dir = PPL_DIR_UPSTREAM; start = p->sink_comp; @@ -178,6 +195,8 @@ int pipeline_copy(struct pipeline *p) pipe_err(p, "ret = %d, start->comp.id = %u, dir = %u", ret, dev_comp_id(start), dir); + PPL_UNLOCK(); + return ret; } diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 13f2524ac909..b08ef224ae1e 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -680,10 +680,6 @@ struct comp_dev { struct list_item bsource_list; /**< list of source buffers */ struct list_item bsink_list; /**< list of sink buffers */ -#ifdef CONFIG_SOF_USERSPACE_LL - struct sys_mutex list_mutex; /**< protect lists of source/sinks */ -#endif - /* performance data*/ struct comp_perf_data perf_data; /* Input Buffer Size for pin 0, add array for other pins if needed */ @@ -868,9 +864,6 @@ static inline void comp_init(const struct comp_driver *drv, dev->state = COMP_STATE_INIT; list_init(&dev->bsink_list); list_init(&dev->bsource_list); -#ifdef CONFIG_SOF_USERSPACE_LL - sys_mutex_init(&dev->list_mutex); -#endif #ifndef __ZEPHYR__ memcpy_s(&dev->tctx, sizeof(dev->tctx), trace_comp_drv_get_tr_ctx(dev->drv), sizeof(struct tr_ctx)); diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index 62d7edd2d1be..9b0fd46b371e 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -122,6 +122,12 @@ bool zephyr_ll_user_heap_verify(struct k_heap *heap); void zephyr_ll_user_resources_init(void); void user_ll_lock_sched(int core); void user_ll_unlock_sched(int core); +#ifdef CONFIG_ASSERT +/* Assert that the calling context already holds the LL lock for 'core'. */ +void user_ll_assert_locked(int core); +#else +static inline void user_ll_assert_locked(int core) { ARG_UNUSED(core); } +#endif #endif /* CONFIG_SOF_USERSPACE_LL */ static inline struct ll_schedule_domain *domain_init diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index 737c3b73370c..1a2fcef4194e 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -336,7 +337,15 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) return -EINVAL; } + /* Lock buffer lists to prevent racing with the LL scheduler. + * In user-space builds, use the LL scheduler's lock + * (re-entrant, so safe if caller already holds it). + */ +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_lock_sched(icd->core); +#else irq_local_disable(flags); +#endif comp_dev_for_each_producer_safe(icd->cd, buffer, safe) { comp_buffer_set_sink_component(buffer, NULL); /* This breaks the list, but we anyway delete all buffers */ @@ -349,7 +358,11 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) comp_buffer_reset_source_list(buffer); } +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_unlock_sched(icd->core); +#else irq_local_enable(flags); +#endif /* * A completed pipeline stores raw comp_dev pointers in its diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 25ba2a920532..5881fbbfb0b5 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -448,10 +448,21 @@ __cold static int ipc_pipeline_module_free(uint32_t pipeline_id) struct ipc *ipc = ipc_get(); struct ipc_comp_dev *icd; int ret; +#ifdef CONFIG_SOF_USERSPACE_LL + int ppl_core; +#endif assert_can_be_cold(); icd = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_COMPONENT, pipeline_id, IPC_COMP_ALL); + if (!icd) + return IPC4_SUCCESS; + +#ifdef CONFIG_SOF_USERSPACE_LL + ppl_core = icd->core; + user_ll_lock_sched(ppl_core); +#endif + while (icd) { struct comp_buffer *buffer; struct comp_buffer *safe; @@ -481,12 +492,20 @@ __cold static int ipc_pipeline_module_free(uint32_t pipeline_id) else ret = ipc_comp_free(ipc, icd->id); - if (ret) + if (ret) { +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_unlock_sched(ppl_core); +#endif return IPC4_INVALID_RESOURCE_STATE; + } icd = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_COMPONENT, pipeline_id, IPC_COMP_ALL); } +#ifdef CONFIG_SOF_USERSPACE_LL + user_ll_unlock_sched(ppl_core); +#endif + return IPC4_SUCCESS; } @@ -560,21 +579,25 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool * disable any interrupts. */ -#define ll_block(cross_core_bind, flags) \ +#if CONFIG_SOF_USERSPACE_LL +#error "CONFIG_SOF_USERSPACE_LL not compatible with cross-core streams" +#else +#define ll_block(src_core, dst_core, flags) \ do { \ - if (cross_core_bind) \ + if (src_core != dst_core) \ domain_block(sof_get()->platform_timer_domain); \ else \ irq_local_disable(flags); \ } while (0) -#define ll_unblock(cross_core_bind, flags) \ +#define ll_unblock(src_core, dst_core, flags) \ do { \ - if (cross_core_bind) \ + if (src_core != dst_core) \ domain_unblock(sof_get()->platform_timer_domain); \ else \ irq_local_enable(flags); \ } while (0) +#endif /* Calling both ll_block() and ll_wait_finished_on_core() makes sure LL will not start its * next cycle and its current cycle on specified core has finished. @@ -606,8 +629,22 @@ static int ll_wait_finished_on_core(struct comp_dev *dev) #else -#define ll_block(cross_core_bind, flags) irq_local_disable(flags) -#define ll_unblock(cross_core_bind, flags) irq_local_enable(flags) +#if CONFIG_SOF_USERSPACE_LL +/* note: cross-core streams are disabled so src_core==dst_core */ +#define ll_block(src_core, dst_core, flags) \ + do { \ + user_ll_lock_sched(src_core); \ + ARG_UNUSED(flags); \ + } while(0) +#define ll_unblock(src_core, dst_core, flags) \ + do { \ + user_ll_unlock_sched(src_core); \ + ARG_UNUSED(flags); \ + } while(0) +#else +#define ll_block(src_core, dst_core, flags) irq_local_disable(flags) +#define ll_unblock(src_core, dst_core, flags) irq_local_enable(flags) +#endif #endif @@ -794,7 +831,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) * blocked on corresponding core(s) to prevent IPC or IDC task getting preempted which * could result in buffers being only half connected when a pipeline task gets executed. */ - ll_block(cross_core_bind, flags); + ll_block(source->ipc_config.core, sink->ipc_config.core, flags); if (cross_core_bind) { #if CONFIG_CROSS_CORE_STREAM @@ -851,7 +888,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) source->direction_set = true; } - ll_unblock(cross_core_bind, flags); + ll_unblock(source->ipc_config.core, sink->ipc_config.core, flags); return IPC4_SUCCESS; @@ -865,7 +902,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) e_sink_connect: pipeline_disconnect(source, buffer, PPL_CONN_DIR_COMP_TO_BUFFER); free: - ll_unblock(cross_core_bind, flags); + ll_unblock(source->ipc_config.core, sink->ipc_config.core, flags); buffer_free(buffer); return IPC4_INVALID_RESOURCE_STATE; } @@ -938,24 +975,24 @@ __cold int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) * IPC or IDC task getting preempted which could result in buffers being only half connected * when a pipeline task gets executed. */ - ll_block(cross_core_unbind, flags); + ll_block(src->ipc_config.core, sink->ipc_config.core, flags); if (cross_core_unbind) { #if CONFIG_CROSS_CORE_STREAM /* Make sure LL has finished on both cores */ if (!cpu_is_me(src->ipc_config.core)) if (ll_wait_finished_on_core(src) < 0) { - ll_unblock(cross_core_unbind, flags); + ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags); return IPC4_FAILURE; } if (!cpu_is_me(sink->ipc_config.core)) if (ll_wait_finished_on_core(sink) < 0) { - ll_unblock(cross_core_unbind, flags); + ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags); return IPC4_FAILURE; } #else tr_err(&ipc_tr, "Cross-core binding is disabled"); - ll_unblock(cross_core_unbind, flags); + ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags); return IPC4_FAILURE; #endif } @@ -972,7 +1009,7 @@ __cold int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) unbind_data.source = audio_buffer_get_source(&buffer->audio_buffer); ret1 = comp_unbind(sink, &unbind_data); - ll_unblock(cross_core_unbind, flags); + ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags); buffer_free(buffer); diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 9705a156b326..79b85118b5d3 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -51,12 +51,50 @@ struct zephyr_ll_pdata { * in kernel memory, there are just handles! */ static APP_SYSUSER_BSS struct k_mutex *zephyr_ll_locks[CONFIG_CORE_COUNT]; + +/* + * Shadow ownership tracking for the per-core LL mutex, used only to + * power user_ll_assert_locked(). The kernel k_mutex object is not + * readable from user-space threads, so its owner cannot be inspected + * directly; instead record the owner and recursion depth here, in a + * user-accessible partition. Updated on every lock/unlock of the LL + * mutex, by both the LL thread (zephyr_ll_lock()) and IPC handlers + * (user_ll_lock_sched()). Only ever mutated by the current lock holder + * while the lock is held, so no extra synchronization is required. + */ +#ifdef CONFIG_ASSERT +static APP_SYSUSER_BSS k_tid_t zephyr_ll_lock_owner[CONFIG_CORE_COUNT]; +static APP_SYSUSER_BSS uint32_t zephyr_ll_lock_depth[CONFIG_CORE_COUNT]; + +static inline void zephyr_ll_lock_acquired(int core) +{ + zephyr_ll_lock_owner[core] = k_current_get(); + zephyr_ll_lock_depth[core]++; +} + +static inline void zephyr_ll_lock_releasing(int core) +{ + assert(zephyr_ll_lock_owner[core] == k_current_get()); + if (--zephyr_ll_lock_depth[core] == 0) + zephyr_ll_lock_owner[core] = NULL; +} + +void user_ll_assert_locked(int core) +{ + assert(core < CONFIG_CORE_COUNT && + zephyr_ll_lock_owner[core] == k_current_get()); +} +#else +static inline void zephyr_ll_lock_acquired(int core) { (void)core; } +static inline void zephyr_ll_lock_releasing(int core) { (void)core; } +#endif /* CONFIG_ASSERT */ #endif static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) { #if CONFIG_SOF_USERSPACE_LL k_mutex_lock(sch->lock, K_FOREVER); + zephyr_ll_lock_acquired(sch->core); #else irq_local_disable(*flags); #endif @@ -65,6 +103,7 @@ static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) static void zephyr_ll_unlock(struct zephyr_ll *sch, uint32_t *flags) { #if CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_releasing(sch->core); k_mutex_unlock(sch->lock); #else irq_local_enable(*flags); @@ -593,6 +632,7 @@ void user_ll_lock_sched(int core) assert(core >= 0 && core < CONFIG_CORE_COUNT && zephyr_ll_locks[core] != NULL); int __maybe_unused ret = k_mutex_lock(zephyr_ll_locks[core], K_FOREVER); assert(!ret); + zephyr_ll_lock_acquired(core); } /** @@ -601,6 +641,7 @@ void user_ll_lock_sched(int core) void user_ll_unlock_sched(int core) { assert(core >= 0 && core < CONFIG_CORE_COUNT && zephyr_ll_locks[core] != NULL); + zephyr_ll_lock_releasing(core); int __maybe_unused ret = k_mutex_unlock(zephyr_ll_locks[core]); assert(!ret); } From 9b775d9e2396c8e5abe364e892a1b244d0159d1a Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 10 Feb 2026 20:14:26 +0200 Subject: [PATCH 231/303] Tools: Testbench: Increase copies timeout value The testbench quits after three file module copies without data written. The value is too low for components those accumulate more than one LL period of data before producing output or can't output at every copy. The empirically found value 16 should better ensure that testbench run is not ended too early. Currently testbench lacks the DP scheduler so, so the modules those are designed for DP can be run with this workaround. Signed-off-by: Seppo Ingalsuo --- tools/testbench/include/testbench/file.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testbench/include/testbench/file.h b/tools/testbench/include/testbench/file.h index c9552c5a4425..dd1735026564 100644 --- a/tools/testbench/include/testbench/file.h +++ b/tools/testbench/include/testbench/file.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: BSD-3-Clause * - * Copyright(c) 2018 Intel Corporation. All rights reserved. + * Copyright(c) 2018-2026 Intel Corporation. * * Author: Seppo Ingalsuo * Liam Girdwood @@ -13,7 +13,7 @@ #include -#define FILE_MAX_COPIES_TIMEOUT 3 +#define FILE_MAX_COPIES_TIMEOUT 16 /**< Convert with right shift a bytes count to samples count */ #define FILE_BYTES_TO_S16_SAMPLES(s) ((s) >> 1) From 26fca8bc6187007d7bf35b5905cc3e9e94208bf9 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Mon, 8 Jun 2026 18:07:41 +0300 Subject: [PATCH 232/303] Tools: Testbench: Fix file component to set correct valid format This patch fixes issue with testbench run and usage of source_get_valid_format() in downstream module. The added audio_stream_set_valid_fmt() to file_prepare() is needed to get similar operation is with real host or DAI copier. Signed-off-by: Seppo Ingalsuo --- tools/testbench/file.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/testbench/file.c b/tools/testbench/file.c index 18fb96481564..6e48cc184c38 100644 --- a/tools/testbench/file.c +++ b/tools/testbench/file.c @@ -819,6 +819,14 @@ static int file_prepare(struct processing_module *mod, /* set file function */ stream = &buffer->stream; + + /* + * Real copier/DAI components call ipc4_update_buffer_format() to populate + * valid_sample_fmt; the testbench file component is the host endpoint stub + * so propagate cd->frame_fmt to the buffer's valid format too. + */ + audio_stream_set_valid_fmt(stream, cd->frame_fmt); + switch (audio_stream_get_frm_fmt(stream)) { case SOF_IPC_FRAME_S16_LE: cd->file_func = file_s16; From 377296ca1f8612f332582d13caea68b909d917f7 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Fri, 30 Jan 2026 16:45:31 +0200 Subject: [PATCH 233/303] Tools: Topology: Phase Vocoder: Bench topology to test the component This patch adds the Phase Vocoder class to topology2 with a few configuration options for STFT parameters. The Hann window with 1024 size FFT and hop of 256 is providing good quality. Due to current high load (near 300 MCPS for stereo 48 kHz) of the module, there is a forced mono version of the configuration blob to enable testing of the feature to slow down or speed up the audio playback. The provided topology works in testbench. In a real device it can work in < 1.0 speed. The > 1.0 speed needs more patches. Signed-off-by: Seppo Ingalsuo --- .../topology2/cavs-benchmark-hda.conf | 17 +++ .../topology2/cavs-benchmark-sdw.conf | 17 +++ .../development/tplg-targets-bench.cmake | 2 + .../one_input_output_format_s32_dp128.conf | 19 +++ .../one_input_output_format_s32_dp192.conf | 19 +++ .../one_input_output_format_s32_dp256.conf | 19 +++ .../one_input_output_format_s32_dp_5ms.conf | 22 ++++ .../bench/phase_vocoder_controls_capture.conf | 17 +++ .../phase_vocoder_controls_playback.conf | 17 +++ .../include/bench/phase_vocoder_route.conf | 19 +++ .../include/bench/phase_vocoder_s16.conf | 13 ++ .../include/bench/phase_vocoder_s24.conf | 13 ++ .../include/bench/phase_vocoder_s32.conf | 21 +++ .../include/components/phase_vocoder.conf | 121 ++++++++++++++++++ .../phase_vocoder/hann_1024_256.conf | 17 +++ .../phase_vocoder/hann_1024_256_mono.conf | 17 +++ .../phase_vocoder/hann_256_128.conf | 17 +++ .../phase_vocoder/hann_512_128.conf | 17 +++ .../phase_vocoder/hann_512_256.conf | 17 +++ 19 files changed, 421 insertions(+) create mode 100644 tools/topology/topology2/include/bench/one_input_output_format_s32_dp128.conf create mode 100644 tools/topology/topology2/include/bench/one_input_output_format_s32_dp192.conf create mode 100644 tools/topology/topology2/include/bench/one_input_output_format_s32_dp256.conf create mode 100644 tools/topology/topology2/include/bench/one_input_output_format_s32_dp_5ms.conf create mode 100644 tools/topology/topology2/include/bench/phase_vocoder_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/phase_vocoder_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/phase_vocoder_route.conf create mode 100644 tools/topology/topology2/include/bench/phase_vocoder_s16.conf create mode 100644 tools/topology/topology2/include/bench/phase_vocoder_s24.conf create mode 100644 tools/topology/topology2/include/bench/phase_vocoder_s32.conf create mode 100644 tools/topology/topology2/include/components/phase_vocoder.conf create mode 100644 tools/topology/topology2/include/components/phase_vocoder/hann_1024_256.conf create mode 100644 tools/topology/topology2/include/components/phase_vocoder/hann_1024_256_mono.conf create mode 100644 tools/topology/topology2/include/components/phase_vocoder/hann_256_128.conf create mode 100644 tools/topology/topology2/include/components/phase_vocoder/hann_512_128.conf create mode 100644 tools/topology/topology2/include/components/phase_vocoder/hann_512_256.conf diff --git a/tools/topology/topology2/cavs-benchmark-hda.conf b/tools/topology/topology2/cavs-benchmark-hda.conf index e06f21ea9d49..d1357caa20ab 100644 --- a/tools/topology/topology2/cavs-benchmark-hda.conf +++ b/tools/topology/topology2/cavs-benchmark-hda.conf @@ -45,6 +45,7 @@ + @@ -864,6 +865,22 @@ IncludeByKey.BENCH_CONFIG { } + # + # Phase Vocoder component + # + + "phase_vocoder16" { + + } + + "phase_vocoder24" { + + } + + "phase_vocoder32" { + + } + # # RTNR component # diff --git a/tools/topology/topology2/cavs-benchmark-sdw.conf b/tools/topology/topology2/cavs-benchmark-sdw.conf index ee81baf23772..b19a26d74e7f 100644 --- a/tools/topology/topology2/cavs-benchmark-sdw.conf +++ b/tools/topology/topology2/cavs-benchmark-sdw.conf @@ -42,6 +42,7 @@ + @@ -467,6 +468,22 @@ IncludeByKey.BENCH_CONFIG { } + # + # Phase Vocoder component + # + + "phase_vocoder16" { + + } + + "phase_vocoder24" { + + } + + "phase_vocoder32" { + + } + # # RTNR component # diff --git a/tools/topology/topology2/development/tplg-targets-bench.cmake b/tools/topology/topology2/development/tplg-targets-bench.cmake index 5c0f82dc7dfc..2dd4f28bc07c 100644 --- a/tools/topology/topology2/development/tplg-targets-bench.cmake +++ b/tools/topology/topology2/development/tplg-targets-bench.cmake @@ -21,6 +21,7 @@ set(components "mfcc" "mfccmel" "micsel" + "phase_vocoder" "rtnr" "sound_dose" "src" @@ -48,6 +49,7 @@ set(component_parameters "BENCH_MFCC_PARAMS=default" "BENCH_MFCC_PARAMS=mel80" "BENCH_MICSEL_PARAMS=passthrough" + "BENCH_PHASE_VOCODER_PARAMS=default" "BENCH_RTNR_PARAMS=default" "BENCH_SOUND_DOSE_PARAMS=default" "BENCH_SRC_PARAMS=default" diff --git a/tools/topology/topology2/include/bench/one_input_output_format_s32_dp128.conf b/tools/topology/topology2/include/bench/one_input_output_format_s32_dp128.conf new file mode 100644 index 000000000000..379ee73f9eff --- /dev/null +++ b/tools/topology/topology2/include/bench/one_input_output_format_s32_dp128.conf @@ -0,0 +1,19 @@ + num_input_audio_formats 1 + num_output_audio_formats 1 + + # 32-bit 48KHz 2ch + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + ibs 1024 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + obs 1024 + } + ] + diff --git a/tools/topology/topology2/include/bench/one_input_output_format_s32_dp192.conf b/tools/topology/topology2/include/bench/one_input_output_format_s32_dp192.conf new file mode 100644 index 000000000000..b3989ce90f36 --- /dev/null +++ b/tools/topology/topology2/include/bench/one_input_output_format_s32_dp192.conf @@ -0,0 +1,19 @@ + num_input_audio_formats 1 + num_output_audio_formats 1 + + # 32-bit 48KHz 2ch + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + ibs 1536 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + obs 1536 + } + ] + diff --git a/tools/topology/topology2/include/bench/one_input_output_format_s32_dp256.conf b/tools/topology/topology2/include/bench/one_input_output_format_s32_dp256.conf new file mode 100644 index 000000000000..d753c1ea114f --- /dev/null +++ b/tools/topology/topology2/include/bench/one_input_output_format_s32_dp256.conf @@ -0,0 +1,19 @@ + num_input_audio_formats 1 + num_output_audio_formats 1 + + # 32-bit 48KHz 2ch + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + ibs 2048 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + obs 2048 + } + ] + diff --git a/tools/topology/topology2/include/bench/one_input_output_format_s32_dp_5ms.conf b/tools/topology/topology2/include/bench/one_input_output_format_s32_dp_5ms.conf new file mode 100644 index 000000000000..097f75fb121e --- /dev/null +++ b/tools/topology/topology2/include/bench/one_input_output_format_s32_dp_5ms.conf @@ -0,0 +1,22 @@ + num_input_audio_formats 1 + num_output_audio_formats 1 + + # 32-bit 48KHz 2ch, ibs/obs is set to match + # 5 ms or 240 stereo frames period. + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + in_channels 2 + ibs 1920 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + out_channels 2 + obs 1920 + } + ] + diff --git a/tools/topology/topology2/include/bench/phase_vocoder_controls_capture.conf b/tools/topology/topology2/include/bench/phase_vocoder_controls_capture.conf new file mode 100644 index 000000000000..c25aaebfcc58 --- /dev/null +++ b/tools/topology/topology2/include/bench/phase_vocoder_controls_capture.conf @@ -0,0 +1,17 @@ + # Created initially with script "./bench_comp_generate.sh phase_vocoder" + # may need edits to modify controls + Object.Control { + # Un-comment the supported controls in PHASE_VOCODER + bytes."1" { + name '$ANALOG_CAPTURE_PCM Phase Vocoder bytes' + IncludeByKey.BENCH_PHASE_VOCODER_PARAMS { + "default" "include/components/phase_vocoder/hann_1024_256.conf" + } + } + mixer."1" { + name '$ANALOG_CAPTURE_PCM Phase Vocoder enable' + } + enum."1" { + name '$ANALOG_CAPTURE_PCM Phase Vocoder speed' + } + } diff --git a/tools/topology/topology2/include/bench/phase_vocoder_controls_playback.conf b/tools/topology/topology2/include/bench/phase_vocoder_controls_playback.conf new file mode 100644 index 000000000000..fb6c3da0e369 --- /dev/null +++ b/tools/topology/topology2/include/bench/phase_vocoder_controls_playback.conf @@ -0,0 +1,17 @@ + # Created initially with script "./bench_comp_generate.sh phase_vocoder" + # may need edits to modify controls + Object.Control { + # Un-comment the supported controls in PHASE_VOCODER + bytes."1" { + name '$ANALOG_PLAYBACK_PCM Phase Vocoder bytes' + IncludeByKey.BENCH_PHASE_VOCODER_PARAMS { + "default" "include/components/phase_vocoder/hann_1024_256.conf" + } + } + mixer."1" { + name '$ANALOG_PLAYBACK_PCM Phase Vocoder enable' + } + enum."1" { + name '$ANALOG_PLAYBACK_PCM Phase Vocoder speed' + } + } diff --git a/tools/topology/topology2/include/bench/phase_vocoder_route.conf b/tools/topology/topology2/include/bench/phase_vocoder_route.conf new file mode 100644 index 000000000000..4213b01c87ed --- /dev/null +++ b/tools/topology/topology2/include/bench/phase_vocoder_route.conf @@ -0,0 +1,19 @@ + # Created with script "./bench_comp_generate.sh phase_vocoder" + Object.Base.route [ + { + sink '$BENCH_PLAYBACK_DAI_COPIER' + source 'phase_vocoder.$BENCH_PLAYBACK_HOST_PIPELINE.1' + } + { + sink 'phase_vocoder.$BENCH_PLAYBACK_HOST_PIPELINE.1' + source 'host-copier.0.playback' + } + { + source '$BENCH_CAPTURE_DAI_COPIER' + sink 'phase_vocoder.$BENCH_CAPTURE_HOST_PIPELINE.2' + } + { + source 'phase_vocoder.$BENCH_CAPTURE_HOST_PIPELINE.2' + sink 'host-copier.0.capture' + } + ] diff --git a/tools/topology/topology2/include/bench/phase_vocoder_s16.conf b/tools/topology/topology2/include/bench/phase_vocoder_s16.conf new file mode 100644 index 000000000000..c5fdb6aeef5c --- /dev/null +++ b/tools/topology/topology2/include/bench/phase_vocoder_s16.conf @@ -0,0 +1,13 @@ + # Created with script "./bench_comp_generate.sh phase_vocoder" + Object.Widget.phase_vocoder.1 { + index $BENCH_PLAYBACK_HOST_PIPELINE + + + } + Object.Widget.phase_vocoder.2 { + index $BENCH_CAPTURE_HOST_PIPELINE + + + } + + diff --git a/tools/topology/topology2/include/bench/phase_vocoder_s24.conf b/tools/topology/topology2/include/bench/phase_vocoder_s24.conf new file mode 100644 index 000000000000..9865e4e8f8b7 --- /dev/null +++ b/tools/topology/topology2/include/bench/phase_vocoder_s24.conf @@ -0,0 +1,13 @@ + # Created with script "./bench_comp_generate.sh phase_vocoder" + Object.Widget.phase_vocoder.1 { + index $BENCH_PLAYBACK_HOST_PIPELINE + + + } + Object.Widget.phase_vocoder.2 { + index $BENCH_CAPTURE_HOST_PIPELINE + + + } + + diff --git a/tools/topology/topology2/include/bench/phase_vocoder_s32.conf b/tools/topology/topology2/include/bench/phase_vocoder_s32.conf new file mode 100644 index 000000000000..46f9cad71989 --- /dev/null +++ b/tools/topology/topology2/include/bench/phase_vocoder_s32.conf @@ -0,0 +1,21 @@ + # Created with script "./bench_comp_generate.sh phase_vocoder" + Object.Widget.phase_vocoder.1 { + index $BENCH_PLAYBACK_HOST_PIPELINE + scheduler_domain DP + domain_id 123 + stack_bytes_requirement 4096 + heap_bytes_requirement 49152 + + + } + Object.Widget.phase_vocoder.2 { + index $BENCH_CAPTURE_HOST_PIPELINE + scheduler_domain DP + domain_id 123 + stack_bytes_requirement 4096 + heap_bytes_requirement 49152 + + + } + + diff --git a/tools/topology/topology2/include/components/phase_vocoder.conf b/tools/topology/topology2/include/components/phase_vocoder.conf new file mode 100644 index 000000000000..c0541c2ce69d --- /dev/null +++ b/tools/topology/topology2/include/components/phase_vocoder.conf @@ -0,0 +1,121 @@ +# +# +# A PHASE_VOCODER component for SOF. All attributes defined herein are namespaced +# by alsatplg to "Object.Widget.phase_vocoder.attribute_name" +# +# Usage: this component can be used by declaring in the parent object. e.g. +# +# Object.Widget.phase_vocoder."N" { +# index 1 +# } + +# +# Where N is a unique integer in the parent object. + +Class.Widget."phase_vocoder" { + # + # Pipeline ID + # + DefineAttribute."index" { + type "integer" + } + + # + # Unique instance for PHASE_VOCODER widget + # + DefineAttribute."instance" { + type "integer" + } + + # Include common widget attributes definition + + + attributes { + !constructor [ + "index" + "instance" + ] + !mandatory [ + "num_input_pins" + "num_output_pins" + "num_input_audio_formats" + "num_output_audio_formats" + ] + + !immutable [ + "uuid" + "type" + ] + !deprecated [ + "preload_count" + ] + unique "instance" + } + + Object.Control { + # Switch controls + mixer."1" { + Object.Base.channel.1 { + name "fc" + shift 0 + } + Object.Base.ops.1 { + name "ctl" + info "volsw" + #259 binds the mixer control to switch get/put handlers + get 259 + put 259 + } + max 1 + } + + # Enum controls + enum."1" { + Object.Base { + channel.1 { + name "fc" + reg 3 + shift 0 + } + text.0 { + name "phase_vocoder_speed_enum" + !values [ + "0.5" + "0.6" + "0.7" + "0.8" + "0.9" + "1.0" + "1.1" + "1.2" + "1.3" + "1.4" + "1.5" + "1.6" + "1.7" + "1.8" + "1.9" + "2.0" + ] + } + ops.1 { + name "ctl" + info "enum" + #257 binds the mixer control to enum get/put handlers + get 257 + put 257 + } + } + } + } + + # + # Default attributes for phase_vocoder + # + + uuid "7a:cb:fb:09:c5:a9:57:4a:84:34:44:40:e5:98:ab:24" + type "effect" + no_pm "true" + num_input_pins 1 + num_output_pins 1 +} diff --git a/tools/topology/topology2/include/components/phase_vocoder/hann_1024_256.conf b/tools/topology/topology2/include/components/phase_vocoder/hann_1024_256.conf new file mode 100644 index 000000000000..fbce252b333a --- /dev/null +++ b/tools/topology/topology2/include/components/phase_vocoder/hann_1024_256.conf @@ -0,0 +1,17 @@ +# Exported PHASE_VOCODER configuration 17-Jun-2026 +# cd src/audio/phase_vocoder/tune; octave setup_phase_vocoder.m +Object.Base.data."phase_vocoder_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0xbb,0x00,0x00, + 0x01,0xb0,0x6a,0x55,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x04,0x00,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/phase_vocoder/hann_1024_256_mono.conf b/tools/topology/topology2/include/components/phase_vocoder/hann_1024_256_mono.conf new file mode 100644 index 000000000000..9aa3a2bdc326 --- /dev/null +++ b/tools/topology/topology2/include/components/phase_vocoder/hann_1024_256_mono.conf @@ -0,0 +1,17 @@ +# Exported PHASE_VOCODER configuration 17-Jun-2026 +# cd src/audio/phase_vocoder/tune; octave setup_phase_vocoder.m +Object.Base.data."phase_vocoder_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0xbb,0x00,0x00, + 0x01,0xb0,0x6a,0x55,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x04,0x00,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/phase_vocoder/hann_256_128.conf b/tools/topology/topology2/include/components/phase_vocoder/hann_256_128.conf new file mode 100644 index 000000000000..839b9e7e54ed --- /dev/null +++ b/tools/topology/topology2/include/components/phase_vocoder/hann_256_128.conf @@ -0,0 +1,17 @@ +# Exported PHASE_VOCODER configuration 17-Jun-2026 +# cd src/audio/phase_vocoder/tune; octave setup_phase_vocoder.m +Object.Base.data."phase_vocoder_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0xbb,0x00,0x00, + 0xab,0x00,0x56,0xab,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01,0x80,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/phase_vocoder/hann_512_128.conf b/tools/topology/topology2/include/components/phase_vocoder/hann_512_128.conf new file mode 100644 index 000000000000..0cf41f15424d --- /dev/null +++ b/tools/topology/topology2/include/components/phase_vocoder/hann_512_128.conf @@ -0,0 +1,17 @@ +# Exported PHASE_VOCODER configuration 17-Jun-2026 +# cd src/audio/phase_vocoder/tune; octave setup_phase_vocoder.m +Object.Base.data."phase_vocoder_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0xbb,0x00,0x00, + 0x60,0x15,0x80,0x55,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x02,0x80,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/phase_vocoder/hann_512_256.conf b/tools/topology/topology2/include/components/phase_vocoder/hann_512_256.conf new file mode 100644 index 000000000000..1197148a532e --- /dev/null +++ b/tools/topology/topology2/include/components/phase_vocoder/hann_512_256.conf @@ -0,0 +1,17 @@ +# Exported PHASE_VOCODER configuration 17-Jun-2026 +# cd src/audio/phase_vocoder/tune; octave setup_phase_vocoder.m +Object.Base.data."phase_vocoder_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x80,0xbb,0x00,0x00, + 0xc0,0x2a,0x00,0xab,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00" +} From 14b3d2b6a8db19d83d2a968b86c979c67545ad5d Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Fri, 30 Jan 2026 15:21:05 +0200 Subject: [PATCH 234/303] Audio: Phase Vocoder: Add new component This patch adds the Phase Vocoder SOF module. It provides render speed control in range 0.5-2.0x. The pitch is preserved in audio waveform stretch or shorten. The module is using a frequency domain algorithm in STFT domain to interpolate magnitude and phase of output IFFT frames from input FFT frames. The render speed can be controlled via enable/disable switch and enum control with steps of 0.1, or with finer precision with bytes control. (WIP) The STFT parameters are configured with bytes control blob. The default is 1024 size FFT with hop of 256 and Hann window. Signed-off-by: Seppo Ingalsuo --- app/boards/intel_adsp_ace15_mtpm.conf | 1 + app/boards/intel_adsp_ace20_lnl.conf | 1 + src/arch/host/configs/library_defconfig | 1 + src/audio/CMakeLists.txt | 3 + src/audio/Kconfig | 1 + src/audio/phase_vocoder/CMakeLists.txt | 15 + src/audio/phase_vocoder/Kconfig | 14 + src/audio/phase_vocoder/llext/CMakeLists.txt | 11 + src/audio/phase_vocoder/llext/llext.toml.h | 6 + .../phase_vocoder/phase_vocoder-generic.c | 694 ++++++++++++++++++ src/audio/phase_vocoder/phase_vocoder-ipc4.c | 89 +++ src/audio/phase_vocoder/phase_vocoder.c | 284 +++++++ src/audio/phase_vocoder/phase_vocoder.h | 334 +++++++++ src/audio/phase_vocoder/phase_vocoder.toml | 21 + .../phase_vocoder/phase_vocoder_common.c | 588 +++++++++++++++ src/audio/phase_vocoder/phase_vocoder_setup.c | 264 +++++++ src/include/sof/audio/component.h | 1 + tools/rimage/config/lnl.toml.h | 4 + tools/rimage/config/mtl.toml.h | 4 + tools/rimage/config/ptl.toml.h | 4 + tools/rimage/config/wcl.toml.h | 4 + tools/testbench/utils_ipc4.c | 1 + uuid-registry.txt | 1 + 23 files changed, 2346 insertions(+) create mode 100644 src/audio/phase_vocoder/CMakeLists.txt create mode 100644 src/audio/phase_vocoder/Kconfig create mode 100644 src/audio/phase_vocoder/llext/CMakeLists.txt create mode 100644 src/audio/phase_vocoder/llext/llext.toml.h create mode 100644 src/audio/phase_vocoder/phase_vocoder-generic.c create mode 100644 src/audio/phase_vocoder/phase_vocoder-ipc4.c create mode 100644 src/audio/phase_vocoder/phase_vocoder.c create mode 100644 src/audio/phase_vocoder/phase_vocoder.h create mode 100644 src/audio/phase_vocoder/phase_vocoder.toml create mode 100644 src/audio/phase_vocoder/phase_vocoder_common.c create mode 100644 src/audio/phase_vocoder/phase_vocoder_setup.c diff --git a/app/boards/intel_adsp_ace15_mtpm.conf b/app/boards/intel_adsp_ace15_mtpm.conf index 9bbf01b9c61b..0b5472cd282a 100644 --- a/app/boards/intel_adsp_ace15_mtpm.conf +++ b/app/boards/intel_adsp_ace15_mtpm.conf @@ -15,6 +15,7 @@ CONFIG_COMP_MFCC=y CONFIG_COMP_MULTIBAND_DRC=y CONFIG_FORMAT_CONVERT_HIFI3=n CONFIG_SAMPLE_KEYPHRASE=y +CONFIG_COMP_PHASE_VOCODER=y CONFIG_COMP_STFT_PROCESS=y # SOF / audio modules / mocks diff --git a/app/boards/intel_adsp_ace20_lnl.conf b/app/boards/intel_adsp_ace20_lnl.conf index 695be421be20..c0b225650c01 100644 --- a/app/boards/intel_adsp_ace20_lnl.conf +++ b/app/boards/intel_adsp_ace20_lnl.conf @@ -11,6 +11,7 @@ CONFIG_COMP_TESTER=m CONFIG_COMP_SRC_IPC4_FULL_MATRIX=y CONFIG_FORMAT_CONVERT_HIFI3=n CONFIG_SAMPLE_KEYPHRASE=y +CONFIG_COMP_PHASE_VOCODER=y CONFIG_COMP_STFT_PROCESS=y # SOF / infrastructure diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig index 28c486bec58d..1279dd26c924 100644 --- a/src/arch/host/configs/library_defconfig +++ b/src/arch/host/configs/library_defconfig @@ -14,6 +14,7 @@ CONFIG_COMP_MFCC=y CONFIG_COMP_MODULE_ADAPTER=y CONFIG_COMP_MULTIBAND_DRC=y CONFIG_COMP_MUX=y +CONFIG_COMP_PHASE_VOCODER=y CONFIG_COMP_RTNR=y CONFIG_COMP_SEL=y CONFIG_COMP_SOUND_DOSE=y diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 4fc676dfae3d..92002c8b7c1c 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -71,6 +71,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_MUX) add_subdirectory(mux) endif() + if(CONFIG_COMP_PHASE_VOCODER) + add_subdirectory(phase_vocoder) + endif() if(CONFIG_COMP_RTNR) add_subdirectory(rtnr) endif() diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 2f2036c5a57f..8accb25738a2 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -148,6 +148,7 @@ rsource "module_adapter/Kconfig" rsource "multiband_drc/Kconfig" rsource "mux/Kconfig" rsource "nxp/Kconfig" +rsource "phase_vocoder/Kconfig" rsource "rtnr/Kconfig" rsource "selector/Kconfig" rsource "smart_amp/Kconfig" diff --git a/src/audio/phase_vocoder/CMakeLists.txt b/src/audio/phase_vocoder/CMakeLists.txt new file mode 100644 index 000000000000..b632c50af6cb --- /dev/null +++ b/src/audio/phase_vocoder/CMakeLists.txt @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause + +if(CONFIG_COMP_PHASE_VOCODER STREQUAL "m" AND DEFINED CONFIG_LLEXT) + add_subdirectory(llext ${PROJECT_BINARY_DIR}/phase_vocoder_llext) + add_dependencies(app phase_vocoder) +else() + add_local_sources(sof phase_vocoder.c) + add_local_sources(sof phase_vocoder_setup.c) + add_local_sources(sof phase_vocoder_common.c) + add_local_sources(sof phase_vocoder-generic.c) + + if(CONFIG_IPC_MAJOR_4) + add_local_sources(sof phase_vocoder-ipc4.c) + endif() +endif() diff --git a/src/audio/phase_vocoder/Kconfig b/src/audio/phase_vocoder/Kconfig new file mode 100644 index 000000000000..a3eecfc8023f --- /dev/null +++ b/src/audio/phase_vocoder/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause + +config COMP_PHASE_VOCODER + tristate "Phase Vocoder component" + default m if LIBRARY_DEFAULT_MODULAR + select MATH_FFT + select MATH_32BIT_FFT + help + Select for phase_vocoder component. The component provides + render speed control in range 0.5-2.0x. The pitch is + preserved in audio waveform stretch or shorten. The module + is using a frequency domain algorithm in STFT domain to + interpolate magnitude and phase of output IFFT frames from + input FFT frames. diff --git a/src/audio/phase_vocoder/llext/CMakeLists.txt b/src/audio/phase_vocoder/llext/CMakeLists.txt new file mode 100644 index 000000000000..ffefa29c7080 --- /dev/null +++ b/src/audio/phase_vocoder/llext/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Intel Corporation. +# SPDX-License-Identifier: Apache-2.0 + +sof_llext_build("phase_vocoder" + SOURCES ../phase_vocoder.c + ../phase_vocoder_setup.c + ../phase_vocoder_common.c + ../phase_vocoder-generic.c + ../phase_vocoder-ipc4.c + LIB openmodules +) diff --git a/src/audio/phase_vocoder/llext/llext.toml.h b/src/audio/phase_vocoder/llext/llext.toml.h new file mode 100644 index 000000000000..94cfd0d67d50 --- /dev/null +++ b/src/audio/phase_vocoder/llext/llext.toml.h @@ -0,0 +1,6 @@ +#include +#define LOAD_TYPE "2" +#include "../phase_vocoder.toml" + +[module] +count = __COUNTER__ diff --git a/src/audio/phase_vocoder/phase_vocoder-generic.c b/src/audio/phase_vocoder/phase_vocoder-generic.c new file mode 100644 index 000000000000..5cc382cb5b8b --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder-generic.c @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include "phase_vocoder.h" + +#if CONFIG_FORMAT_S32LE +/** + * phase_vocoder_source_s32() - Read S32_LE samples into the input ring buffer. + * @cd: Pointer to component private data. + * @source: Source for PCM samples data. + * @frames: Number of audio data frames to consume. + * + * Copies up to @frames frames of S32_LE PCM from @source into the per-channel + * input ring buffers. In mono-mix mode the channels are pre-summed using + * @cd->mono_mix_coef so that downstream STFT processing runs on a single + * channel. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_source_s32(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *ibuf; + int32_t const *x, *x_start, *x_end; + const int32_t mix_gain = cd->mono_mix_coef; + int64_t mix; + int frames_left; + int x_size; + int bytes; + int ret; + int n1; + int n2; + int n; + int i; + int j; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + ibuf = &state->ibuf[0]; + frames = MIN(frames, ibuf->s_free); + bytes = frames * cd->frame_bytes; + + /* Get pointer to source data in circular buffer */ + ret = source_get_data_s32(source, bytes, &x, &x_start, &x_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + + frames_left = frames; + while (frames_left) { + /* Find out samples to process before first wrap or end of data. */ + ibuf = &state->ibuf[0]; + n1 = (x_end - x) / stream_channels; + n2 = phase_vocoder_buffer_samples_without_wrap(ibuf, ibuf->w_ptr); + n = MIN(n1, n2); + n = MIN(n, frames_left); + if (process_mono) { + for (i = 0; i < n; i++) { + mix = 0; + for (j = 0; j < stream_channels; j++) + mix += Q_MULTSR_32X32((int64_t)mix_gain, *x++, 31, 31, 31); + + *ibuf->w_ptr++ = sat_int32(mix); + } + } else { + for (i = 0; i < n; i++) { + for (j = 0; j < stream_channels; j++) { + ibuf = &state->ibuf[j]; + *ibuf->w_ptr++ = *x++; + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->w_ptr = phase_vocoder_buffer_wrap(ibuf, ibuf->w_ptr); + } + + if (x >= x_end) + x -= x_size; + + /* Update processed samples count for next loop iteration. */ + frames_left -= n; + } + + /* Update the source for bytes consumed. Return success. */ + source_release_data(source, bytes); + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->s_avail += frames; + ibuf->s_free -= frames; + } + + return 0; +} + +/** + * phase_vocoder_sink_s32() - Write processed samples out as S32_LE. + * @cd: Pointer to component private data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to produce. + * + * Copies up to @frames frames from the per-channel output ring buffer to + * @sink in S32_LE format. In mono-mix mode the single processed channel is + * replicated to all output channels. The output ring buffer slots are + * cleared after read so the next overlap-add pass starts from zero. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_sink_s32(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *obuf; + int32_t *y, *y_start, *y_end; + int frames_remain; + int bytes; + int y_size; + int ret; + int ch, n1, n, i; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + obuf = &state->obuf[0]; + frames = MIN(frames, obuf->s_avail); + if (!frames) + return 0; + + /* Get pointer to sink data in circular buffer */ + bytes = frames * cd->frame_bytes; + ret = sink_get_buffer_s32(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + + frames_remain = frames; + while (frames_remain) { + /* Find out samples to process before first wrap or end of data. */ + obuf = &state->obuf[0]; + n1 = (y_end - y) / stream_channels; + n = phase_vocoder_buffer_samples_without_wrap(obuf, obuf->r_ptr); + n = MIN(n1, n); + n = MIN(n, frames_remain); + + if (process_mono) { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) + *y++ = *obuf->r_ptr; + + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } else { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) { + obuf = &state->obuf[ch]; + *y++ = *obuf->r_ptr; + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->r_ptr = phase_vocoder_buffer_wrap(obuf, obuf->r_ptr); + } + + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + frames_remain -= n; + } + + /* Update the sink for bytes produced. Return success. */ + sink_commit_buffer(sink, bytes); + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->s_avail -= frames; + obuf->s_free += frames; + } + + return 0; +} +#endif /* CONFIG_FORMAT_S32LE */ + +#if CONFIG_FORMAT_S24LE +/** + * phase_vocoder_source_s24() - Read S24_4LE samples into the input ring buffer. + * @cd: Pointer to component private data. + * @source: Source for PCM samples data. + * @frames: Number of audio data frames to consume. + * + * Copies up to @frames frames of S24_4LE PCM from @source into the per-channel + * input ring buffers, left-shifting each 24-bit sample by 8 to align to Q1.31 + * for STFT processing. In mono-mix mode the channels are pre-summed using + * @cd->mono_mix_coef so that downstream STFT processing runs on a single + * channel. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_source_s24(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *ibuf; + int32_t const *x, *x_start, *x_end; + const int32_t mix_gain = cd->mono_mix_coef; + int64_t mix; + int32_t tmp; + int frames_left; + int x_size; + int bytes; + int ret; + int n1; + int n2; + int n; + int i; + int j; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + ibuf = &state->ibuf[0]; + frames = MIN(frames, ibuf->s_free); + bytes = frames * cd->frame_bytes; + + /* Get pointer to source data in circular buffer */ + ret = source_get_data_s32(source, bytes, &x, &x_start, &x_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + + frames_left = frames; + while (frames_left) { + /* Find out samples to process before first wrap or end of data. */ + ibuf = &state->ibuf[0]; + n1 = (x_end - x) / stream_channels; + n2 = phase_vocoder_buffer_samples_without_wrap(ibuf, ibuf->w_ptr); + n = MIN(n1, n2); + n = MIN(n, frames_left); + if (process_mono) { + for (i = 0; i < n; i++) { + mix = 0; + for (j = 0; j < stream_channels; j++) { + tmp = (int32_t)(((uint32_t)*x++) << 8); + mix += Q_MULTSR_32X32((int64_t)mix_gain, tmp, 31, 31, 31); + } + + *ibuf->w_ptr++ = sat_int32(mix); + } + } else { + for (i = 0; i < n; i++) { + for (j = 0; j < stream_channels; j++) { + ibuf = &state->ibuf[j]; + tmp = (int32_t)(((uint32_t)*x++) << 8); + *ibuf->w_ptr++ = tmp; + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->w_ptr = phase_vocoder_buffer_wrap(ibuf, ibuf->w_ptr); + } + + if (x >= x_end) + x -= x_size; + + /* Update processed samples count for next loop iteration. */ + frames_left -= n; + } + + /* Update the source for bytes consumed. Return success. */ + source_release_data(source, bytes); + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->s_avail += frames; + ibuf->s_free -= frames; + } + + return 0; +} + +/** + * phase_vocoder_sink_s24() - Write processed samples out as S24_4LE. + * @cd: Pointer to component private data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to produce. + * + * Copies up to @frames frames from the per-channel output ring buffer to + * @sink in S24_4LE format, right-shifting each Q1.31 sample by 8 to produce + * the 24-bit output. In mono-mix mode the single processed channel is + * replicated to all output channels. The output ring buffer slots are + * cleared after read so the next overlap-add pass starts from zero. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_sink_s24(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *obuf; + int32_t *y, *y_start, *y_end; + int frames_remain; + int bytes; + int y_size; + int ret; + int ch, n1, n, i; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + obuf = &state->obuf[0]; + frames = MIN(frames, obuf->s_avail); + if (!frames) + return 0; + + /* Get pointer to sink data in circular buffer */ + bytes = frames * cd->frame_bytes; + ret = sink_get_buffer_s32(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + + frames_remain = frames; + while (frames_remain) { + /* Find out samples to process before first wrap or end of data. */ + obuf = &state->obuf[0]; + n1 = (y_end - y) / stream_channels; + n = phase_vocoder_buffer_samples_without_wrap(obuf, obuf->r_ptr); + n = MIN(n1, n); + n = MIN(n, frames_remain); + + if (process_mono) { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) + *y++ = *obuf->r_ptr >> 8; + + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } else { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) { + obuf = &state->obuf[ch]; + *y++ = *obuf->r_ptr >> 8; + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->r_ptr = phase_vocoder_buffer_wrap(obuf, obuf->r_ptr); + } + + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + frames_remain -= n; + } + + /* Update the sink for bytes produced. Return success. */ + sink_commit_buffer(sink, bytes); + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->s_avail -= frames; + obuf->s_free += frames; + } + + return 0; +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S16LE +/** + * phase_vocoder_source_s16() - Read S16_LE samples into the input ring buffer. + * @cd: Pointer to component private data. + * @source: Source for PCM samples data. + * @frames: Number of audio data frames to consume. + * + * Copies up to @frames frames of S16_LE PCM from @source into the per-channel + * input ring buffers, sign-extending each sample to S32 left-aligned. In + * mono-mix mode the channels are pre-summed using @cd->mono_mix_coef. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_source_s16(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *ibuf; + const int32_t mix_gain = cd->mono_mix_coef; + int64_t mix; + int16_t const *x, *x_start, *x_end; + int16_t in; + int frames_left; + int x_size; + int bytes; + int ret; + int n1; + int n2; + int n; + int i; + int j; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + ibuf = &state->ibuf[0]; + frames = MIN(frames, ibuf->s_free); + bytes = frames * cd->frame_bytes; + frames_left = frames; + + /* Get pointer to source data in circular buffer, get buffer start and size to + * check for wrap. The size in bytes is converted to number of s16 samples to + * control the samples process loop. If the number of bytes requested is not + * possible, an error is returned. + */ + ret = source_get_data_s16(source, bytes, &x, &x_start, &x_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + + while (frames_left) { + /* Find out samples to process before first wrap or end of data. */ + ibuf = &state->ibuf[0]; + n1 = (x_end - x) / stream_channels; + n2 = phase_vocoder_buffer_samples_without_wrap(ibuf, ibuf->w_ptr); + n = MIN(n1, n2); + n = MIN(n, frames_left); + if (process_mono) { + for (i = 0; i < n; i++) { + mix = 0; + for (j = 0; j < stream_channels; j++) + mix += Q_MULTSR_32X32((int64_t)mix_gain, *x++, 31, 15, 31); + + *ibuf->w_ptr++ = sat_int32(mix); + } + } else { + for (i = 0; i < n; i++) { + for (j = 0; j < stream_channels; j++) { + ibuf = &state->ibuf[j]; + in = *x++; + *ibuf->w_ptr++ = (int32_t)in << 16; + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->w_ptr = phase_vocoder_buffer_wrap(ibuf, ibuf->w_ptr); + } + + if (x >= x_end) + x -= x_size; + + /* Update processed samples count for next loop iteration. */ + frames_left -= n; + } + + /* Update the source for bytes consumed. Return success. */ + source_release_data(source, bytes); + for (j = 0; j < process_channels; j++) { + ibuf = &state->ibuf[j]; + ibuf->s_avail += frames; + ibuf->s_free -= frames; + } + return 0; +} + +/** + * phase_vocoder_sink_s16() - Write processed samples out as S16_LE. + * @cd: Pointer to component private data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to produce. + * + * Copies up to @frames frames from the per-channel output ring buffer to + * @sink in S16_LE format with rounding shift from Q1.31 down to Q1.15. In + * mono-mix mode the single processed channel is replicated to all output + * channels. The output ring buffer slots are cleared after read. + * + * Return: Value zero for success, otherwise an error code. + */ +int phase_vocoder_sink_s16(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames) +{ + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_buffer *obuf; + int16_t sample; + int16_t *y, *y_start, *y_end; + int frames_remain; + int y_size; + int bytes; + int ret; + int ch, n1, n, i; + int stream_channels = cd->stream_channels; + int process_channels = cd->process_channels; + bool process_mono = cd->config->mono; + + obuf = &state->obuf[0]; + frames = MIN(frames, obuf->s_avail); + if (!frames) + return 0; + + /* Get pointer to sink data in circular buffer */ + bytes = frames * cd->frame_bytes; + frames_remain = frames; + ret = sink_get_buffer_s16(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + while (frames_remain) { + /* Find out samples to process before first wrap or end of data. */ + obuf = &state->obuf[0]; + n1 = (y_end - y) / stream_channels; + n = phase_vocoder_buffer_samples_without_wrap(obuf, obuf->r_ptr); + n = MIN(n1, n); + n = MIN(n, frames_remain); + + if (process_mono) { + for (i = 0; i < n; i++) { + sample = sat_int16(Q_SHIFT_RND(*obuf->r_ptr, 31, 15)); + for (ch = 0; ch < stream_channels; ch++) + *y++ = sample; + + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } else { + for (i = 0; i < n; i++) { + for (ch = 0; ch < stream_channels; ch++) { + obuf = &state->obuf[ch]; + *y++ = sat_int16(Q_SHIFT_RND(*obuf->r_ptr, 31, 15)); + *obuf->r_ptr++ = 0; /* clear overlap add mix */ + } + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->r_ptr = phase_vocoder_buffer_wrap(obuf, obuf->r_ptr); + } + + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + frames_remain -= n; + } + + /* Update the sink for bytes produced. Return success. */ + sink_commit_buffer(sink, bytes); + for (ch = 0; ch < process_channels; ch++) { + obuf = &state->obuf[ch]; + obuf->s_avail -= frames; + obuf->s_free += frames; + } + + return 0; +} +#endif /* CONFIG_FORMAT_S16LE */ + +void phase_vocoder_fill_fft_buffer(struct phase_vocoder_state *state, int ch) +{ + struct phase_vocoder_buffer *ibuf = &state->ibuf[ch]; + struct phase_vocoder_fft *fft = &state->fft; + struct icomplex32 *fft_buf_ptr; + int32_t *prev_data = state->prev_data[ch]; + int32_t *r = ibuf->r_ptr; + const int prev_data_size = state->prev_data_size; + const int fft_hop_size = fft->fft_hop_size; + int samples_remain = fft_hop_size; + int j; + int n; + + /* Copy overlapped samples from state buffer. Imaginary part of input + * remains zero. + */ + fft_buf_ptr = fft->fft_buf; + for (j = 0; j < prev_data_size; j++) { + *fft_buf_ptr = (struct icomplex32){.real = prev_data[j], .imag = 0}; + fft_buf_ptr++; + } + + /* Copy hop size of new data from circular buffer */ + fft_buf_ptr = &fft->fft_buf[prev_data_size]; + while (samples_remain) { + n = phase_vocoder_buffer_samples_without_wrap(ibuf, r); + n = MIN(n, samples_remain); + for (j = 0; j < n; j++) { + *fft_buf_ptr = (struct icomplex32){.real = *r++, .imag = 0}; + fft_buf_ptr++; + } + r = phase_vocoder_buffer_wrap(ibuf, r); + samples_remain -= n; + } + + ibuf->r_ptr = r; + ibuf->s_avail -= fft_hop_size; + ibuf->s_free += fft_hop_size; + + /* Copy for next time data back to input data overlap buffer */ + fft_buf_ptr = &fft->fft_buf[fft_hop_size]; + for (j = 0; j < prev_data_size; j++) + *prev_data++ = fft_buf_ptr++->real; +} + +int phase_vocoder_overlap_add_ifft_buffer(struct phase_vocoder_state *state, int ch) +{ + struct phase_vocoder_buffer *obuf = &state->obuf[ch]; + struct phase_vocoder_fft *fft = &state->fft; + int32_t *w = obuf->w_ptr; + int32_t sample; + int i; + int n; + int samples_remain = fft->fft_size; + int idx = 0; + + if (obuf->s_free < samples_remain) + return -EINVAL; + + while (samples_remain) { + n = phase_vocoder_buffer_samples_without_wrap(obuf, w); + n = MIN(samples_remain, n); + for (i = 0; i < n; i++) { + sample = Q_MULTSR_32X32((int64_t)state->gain_comp, fft->fft_buf[idx].real, + 31, 31, 31); + *w = sat_int32((int64_t)*w + sample); + w++; + idx++; + } + w = phase_vocoder_buffer_wrap(obuf, w); + samples_remain -= n; + } + + w = obuf->w_ptr + fft->fft_hop_size; + obuf->w_ptr = phase_vocoder_buffer_wrap(obuf, w); + obuf->s_avail += fft->fft_hop_size; + obuf->s_free -= fft->fft_hop_size; + return 0; +} + +void phase_vocoder_apply_window(struct phase_vocoder_state *state) +{ + struct phase_vocoder_fft *fft = &state->fft; + struct icomplex32 *fft_buf_ptr = fft->fft_buf; + const int32_t *window = state->window; + const int fft_size = fft->fft_size; + int i; + + for (i = 0; i < fft_size; i++) { + fft_buf_ptr->real = sat_int32(Q_MULTSR_32X32((int64_t)fft_buf_ptr->real, + window[i], 31, 31, 31)); + fft_buf_ptr++; + } +} diff --git a/src/audio/phase_vocoder/phase_vocoder-ipc4.c b/src/audio/phase_vocoder/phase_vocoder-ipc4.c new file mode 100644 index 000000000000..f28e7d5db833 --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder-ipc4.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include "phase_vocoder.h" + +LOG_MODULE_DECLARE(phase_vocoder, CONFIG_SOF_LOG_LEVEL); + +/* IPC4 controls handler */ +__cold int phase_vocoder_set_config(struct processing_module *mod, uint32_t param_id, + enum module_cfg_fragment_position pos, + uint32_t data_offset_size, const uint8_t *fragment, + size_t fragment_size, uint8_t *response, size_t response_size) +{ + struct sof_ipc4_control_msg_payload *ctl = (struct sof_ipc4_control_msg_payload *)fragment; + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + + assert_can_be_cold(); + + switch (param_id) { + case SOF_IPC4_SWITCH_CONTROL_PARAM_ID: + if (ctl->id != 0) { + comp_err(dev, "Illegal switch control id = %d.", ctl->id); + return -EINVAL; + } + + if (ctl->num_elems != 1) { + comp_err(dev, "Illegal switch control num_elems = %d.", ctl->num_elems); + return -EINVAL; + } + + cd->enable = ctl->chanv[0].value; + comp_info(dev, "enable = %d", cd->enable); + return 0; + + case SOF_IPC4_ENUM_CONTROL_PARAM_ID: + if (ctl->id != 0) { + comp_err(dev, "Illegal enum control id = %d.", ctl->id); + return -EINVAL; + } + + if (ctl->num_elems != 1) { + comp_err(dev, "Illegal enum control num_elems = %d.", ctl->num_elems); + return -EINVAL; + } + + if (ctl->chanv[0].value > 15) { + comp_err(dev, "Illegal enum control value = %d.", ctl->chanv[0].value); + return -EINVAL; + } + + cd->speed_enum = ctl->chanv[0].value; + cd->speed_ctrl = PHASE_VOCODER_MIN_SPEED_Q29 + + Q_MULTSR_32X32((int64_t)cd->speed_enum, PHASE_VOCODER_SPEED_STEP_Q31, + 0, 31, 29); + + comp_info(dev, "speed_enum = %d, speed = %d", cd->speed_enum, cd->speed_ctrl); + return 0; + } + + if (fragment_size != sizeof(struct sof_phase_vocoder_config)) { + comp_err(dev, "Illegal fragment size %d, expect %d.", fragment_size, + sizeof(struct sof_phase_vocoder_config)); + return -EINVAL; + } + + if (!cd->config) { + cd->config = mod_alloc(mod, sizeof(struct sof_phase_vocoder_config)); + if (!cd->config) { + comp_err(dev, "Failed to allocate configuration."); + return -ENOMEM; + } + } + + memcpy_s(cd->config, sizeof(struct sof_phase_vocoder_config), fragment, fragment_size); + return 0; +} + +/* Not used in IPC4 systems, if IPC4 only component, omit .get_configuration set */ +__cold int phase_vocoder_get_config(struct processing_module *mod, uint32_t config_id, + uint32_t *data_offset_size, uint8_t *fragment, + size_t fragment_size) +{ + assert_can_be_cold(); + return 0; +} diff --git a/src/audio/phase_vocoder/phase_vocoder.c b/src/audio/phase_vocoder/phase_vocoder.c new file mode 100644 index 000000000000..ff07304aa0c5 --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include "phase_vocoder.h" + +/* UUID identifies the components. Use e.g. command uuidgen from package + * uuid-runtime, add it to uuid-registry.txt in SOF top level. + */ +SOF_DEFINE_REG_UUID(phase_vocoder); + +/* Creates logging data for the component */ +LOG_MODULE_REGISTER(phase_vocoder, CONFIG_SOF_LOG_LEVEL); + +/* Creates the component trace. Traces show in trace console the component + * info, warning, and error messages. + */ +DECLARE_TR_CTX(phase_vocoder_tr, SOF_UUID(phase_vocoder_uuid), LOG_LEVEL_INFO); + +#if STFT_DEBUG +FILE *stft_debug_fft_in_fh; +FILE *stft_debug_fft_out_fh; +FILE *stft_debug_ifft_out_fh; +#endif + +static void phase_vocoder_reset_parameters(struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct sof_phase_vocoder_config *config = cd->config; + + memset(cd, 0, sizeof(*cd)); + cd->config = config; + cd->enable = false; + cd->speed_ctrl = PHASE_VOCODER_SPEED_NORMAL; + + /* Sync state.speed */ + phase_vocoder_reset_for_new_speed(cd); +} + +/** + * phase_vocoder_init() - Initialize the phase_vocoder component. + * @mod: Pointer to module data. + * + * This function is called when the instance is created. The + * macro __cold informs that the code that is non-critical + * is loaded to slower but large DRAM. + * + * Return: Zero if success, otherwise error code. + */ +__cold static int phase_vocoder_init(struct processing_module *mod) +{ + struct module_data *md = &mod->priv; + struct comp_dev *dev = mod->dev; + struct phase_vocoder_comp_data *cd; + + assert_can_be_cold(); + + comp_info(dev, "phase_vocoder_init()"); + + cd = mod_zalloc(mod, sizeof(*cd)); + if (!cd) + return -ENOMEM; + + md->private = cd; + phase_vocoder_reset_parameters(mod); + +#if STFT_DEBUG + stft_debug_fft_in_fh = fopen("stft_debug_fft_in.txt", "w"); + if (!stft_debug_fft_in_fh) { + fprintf(stderr, "Debug file open failed.\n"); + return -EINVAL; + } + + stft_debug_fft_out_fh = fopen("stft_debug_fft_out.txt", "w"); + if (!stft_debug_fft_out_fh) { + fclose(stft_debug_fft_in_fh); + fprintf(stderr, "Debug file open failed.\n"); + return -EINVAL; + } + + stft_debug_ifft_out_fh = fopen("stft_debug_ifft_out.txt", "w"); + if (!stft_debug_ifft_out_fh) { + fprintf(stderr, "Debug file open failed.\n"); + fclose(stft_debug_fft_in_fh); + fclose(stft_debug_fft_out_fh); + return -EINVAL; + } +#endif + + return 0; +} + +/** + * phase_vocoder_process() - The audio data processing function. + * @mod: Pointer to module data. + * @sources: Pointer to audio samples data sources array. + * @num_of_sources: Number of sources in the array. + * @sinks: Pointer to audio samples data sinks array. + * @num_of_sinks: Number of sinks in the array. + * + * This is the processing function that is called for scheduled + * pipelines. The processing is controlled by the enable switch. + * + * Return: Zero if success, otherwise error code. + */ +static int phase_vocoder_process(struct processing_module *mod, struct sof_source **sources, + int num_of_sources, struct sof_sink **sinks, int num_of_sinks) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct sof_source *source = sources[0]; /* One input */ + struct sof_sink *sink = sinks[0]; /* One output */ + int source_frames = source_get_data_frames_available(source); + int sink_frames = sink_get_free_frames(sink); + int frames; + int ret; + + if (cd->speed_ctrl != cd->state.speed) + phase_vocoder_reset_for_new_speed(cd); + + if (cd->enable) { + ret = cd->phase_vocoder_func(mod, source, sink, source_frames, sink_frames); + if (ret) + comp_err(mod->dev, "Failure, check the setup parameters."); + + return ret; + } + + /* Just copy from source to sink. */ + frames = MIN(source_frames, sink_frames); + source_to_sink_copy(source, sink, true, frames * cd->frame_bytes); + return 0; +} + +/** + * phase_vocoder_prepare() - Prepare the component for processing. + * @mod: Pointer to module data. + * @sources: Pointer to audio samples data sources array. + * @num_of_sources: Number of sources in the array. + * @sinks: Pointer to audio samples data sinks array. + * @num_of_sinks: Number of sinks in the array. + * + * Function prepare is called just before the pipeline is started. In + * this case the audio format parameters are for better code performance + * saved to component data to avoid to find out them in process. The + * processing function pointer is set to process the current audio format. + * + * Return: Value zero if success, otherwise error code. + */ +static int phase_vocoder_prepare(struct processing_module *mod, struct sof_source **sources, + int num_of_sources, struct sof_sink **sinks, int num_of_sinks) +{ + struct module_data *mod_priv = &mod->priv; + struct ipc4_base_module_cfg *base_cfg = &mod_priv->cfg.base_cfg; + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + enum sof_ipc_frame source_format; + int ret; + + comp_dbg(dev, "prepare"); + + /* The processing example in this component supports one input and one + * output. Generally there can be more. + */ + if (num_of_sources != 1 || num_of_sinks != 1) { + comp_err(dev, "Only one source and one sink is supported."); + return -EINVAL; + } + + /* Initialize STFT, max_frames is set to dev->frames + 4 */ + if (!cd->config) { + comp_err(dev, "Can't prepare without bytes control configuration."); + return -EINVAL; + } + + /* get source data format */ + cd->frame_bytes = source_get_frame_bytes(sources[0]); + cd->stream_channels = source_get_channels(sources[0]); + cd->sample_rate = source_get_rate(sources[0]); + + /* Note: dev->frames is zero, use ibs */ + cd->max_input_frames = base_cfg->ibs / cd->frame_bytes + PHASE_VOCODER_MAX_FRAMES_MARGIN; + cd->max_output_frames = base_cfg->obs / cd->frame_bytes + PHASE_VOCODER_MAX_FRAMES_MARGIN; + source_format = source_get_valid_fmt(sources[0]); + comp_info(dev, "source_format %d channels %d max_input_frames %d max_output_frames %d", + source_format, cd->stream_channels, cd->max_input_frames, cd->max_output_frames); + + ret = phase_vocoder_setup(mod); + if (ret < 0) { + comp_err(dev, "setup failed."); + return ret; + } + + cd->phase_vocoder_func = phase_vocoder_find_proc_func(source_format); + if (!cd->phase_vocoder_func) { + comp_err(dev, "No processing function found for format %d.", source_format); + return -EINVAL; + } + + return 0; +} + +/** + * phase_vocoder_reset() - Reset the component. + * @mod: Pointer to module data. + * + * The component reset is called when pipeline is stopped. The reset + * should return the component to same state as init. + * + * Return: Value zero, always success. + */ +static int phase_vocoder_reset(struct processing_module *mod) +{ + comp_dbg(mod->dev, "reset"); + + phase_vocoder_free_buffers(mod); + phase_vocoder_reset_parameters(mod); + return 0; +} + +/** + * phase_vocoder_free() - Free dynamic allocations. + * @mod: Pointer to module data. + * + * Component free is called when the pipelines are deleted. All + * dynamic allocations need to be freed here. The macro __cold + * instructs the build to locate this performance wise non-critical + * function to large and slower DRAM. + * + * Return: Value zero, always success. + */ +__cold static int phase_vocoder_free(struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + + assert_can_be_cold(); + + comp_dbg(mod->dev, "free"); + mod_free(mod, cd->config); + mod_free(mod, cd); + +#if STFT_DEBUG + fclose(stft_debug_fft_in_fh); + fclose(stft_debug_fft_out_fh); + fclose(stft_debug_ifft_out_fh); +#endif + return 0; +} + +/* This defines the module operations */ +static const struct module_interface phase_vocoder_interface = { + .init = phase_vocoder_init, + .prepare = phase_vocoder_prepare, + .process = phase_vocoder_process, + .set_configuration = phase_vocoder_set_config, + .get_configuration = phase_vocoder_get_config, + .reset = phase_vocoder_reset, + .free = phase_vocoder_free}; + +/* This controls build of the module. If COMP_MODULE is selected in kconfig + * this is build as dynamically loadable module. + */ +#if CONFIG_COMP_PHASE_VOCODER_MODULE + +#include +#include +#include + +static const struct sof_man_module_manifest mod_manifest __section(".module") __used = + SOF_LLEXT_MODULE_MANIFEST("PHASE_VOCODER", &phase_vocoder_interface, 1, + SOF_REG_UUID(phase_vocoder), 40); + +SOF_LLEXT_BUILDINFO; + +#else + +DECLARE_MODULE_ADAPTER(phase_vocoder_interface, phase_vocoder_uuid, phase_vocoder_tr); +SOF_MODULE_INIT(phase_vocoder, sys_comp_module_phase_vocoder_interface_init); + +#endif diff --git a/src/audio/phase_vocoder/phase_vocoder.h b/src/audio/phase_vocoder/phase_vocoder.h new file mode 100644 index 000000000000..fbf95fdeeeec --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder.h @@ -0,0 +1,334 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. + * + */ +#ifndef __SOF_AUDIO_PHASE_VOCODER_H__ +#define __SOF_AUDIO_PHASE_VOCODER_H__ + +#include +#include +#include +#include + +#include +#include + +#if CONFIG_LIBRARY +#define STFT_DEBUG 0 /* Keep zero, produces large files with fprintf() */ +#else +#define STFT_DEBUG 0 +#endif + +#define PHASE_VOCODER_MAX_FRAMES_MARGIN 0 /* Adds to buffer size */ +#define PHASE_VOCODER_MIN_SPEED_Q29 Q_CONVERT_FLOAT(0.5, 29) /* Min. speed is 0.5 */ +#define PHASE_VOCODER_MAX_SPEED_Q29 Q_CONVERT_FLOAT(2.0, 29) /* Max. speed is 2.0 */ +#define PHASE_VOCODER_SPEED_STEP_Q31 Q_CONVERT_FLOAT((2 - 0.5) / 15, 31) /* Steps for enum ctrl */ +#define PHASE_VOCODER_SPEED_NORMAL Q_CONVERT_FLOAT(1.0, 29) /* Default to speed 1 */ +#define PHASE_VOCODER_ONE_Q29 Q_CONVERT_FLOAT(1.0, 29) /* One as Q29 */ +#define PHASE_VOCODER_HALF_Q29 Q_CONVERT_FLOAT(0.5, 29) /* 0.5 as Q29 */ + +#define PHASE_VOCODER_PI_Q28 843314857 /* int32(pi * 2^28), Q28 */ +#define PHASE_VOCODER_TWO_PI_Q28 1686629713 /* int32(2 * pi * 2^28), Q28 */ + +#define PHASE_VOCODER_PI_Q27 421657428 /* int32(pi * 2^27) */ +#define PHASE_VOCODER_TWO_PI_Q27 843314857 /* int32(2 * pi * 2^27) */ + +enum sof_phase_vocoder_fft_window_type { + STFT_RECTANGULAR_WINDOW = 0, + STFT_BLACKMAN_WINDOW = 1, + STFT_HAMMING_WINDOW = 2, + STFT_HANN_WINDOW = 3, +}; + +/** + * struct sof_phase_vocoder_config - IPC configuration blob for phase vocoder + * @size: Size of this struct in bytes + * @reserved: Reserved for future use + * @sample_frequency: Sample rate in Hz, e.g. 16000 + * @window_gain_comp: Q1.31 gain for IFFT + * @reserved_32: Reserved for future use + * @mono: Set to 1 for mono, zero for all channels + * @frame_length: Frame length in samples, e.g. 400 for 25 ms @ 16 kHz + * @frame_shift: Frame shift in samples, e.g. 160 for 10 ms @ 16 kHz + * @reserved_16: Reserved for future use + * @reserved_pad: Reserved for future use, kept for ABI compatibility + * @window: Window type, use RECTANGULAR_WINDOW, etc. + */ +struct sof_phase_vocoder_config { + uint32_t size; + uint32_t reserved[8]; + int32_t sample_frequency; + int32_t window_gain_comp; + int32_t reserved_32; + int16_t mono; + int16_t frame_length; + int16_t frame_shift; + int16_t reserved_16; + int32_t reserved_pad; + enum sof_phase_vocoder_fft_window_type window; +} __attribute__((packed)); + +/** + * struct phase_vocoder_buffer - Circular buffer for phase vocoder audio data + * @addr: Start address of the buffer + * @end_addr: End address of the buffer + * @r_ptr: Read pointer + * @w_ptr: Write pointer + * @s_avail: Available samples count + * @s_free: Free samples count + * @s_length: Buffer length in samples for wrap + */ +struct phase_vocoder_buffer { + int32_t *addr; + int32_t *end_addr; + int32_t *r_ptr; + int32_t *w_ptr; + int s_avail; + int s_free; + int s_length; +}; + +/** + * struct phase_vocoder_fft - FFT processing state + * @fft_buf: FFT input buffer, size is fft_size + * @fft_out: FFT output buffer, size is fft_size + * @fft_plan: FFT plan instance + * @ifft_plan: Inverse FFT plan instance + * @fft_size: FFT length in samples + * @fft_hop_size: FFT hop size in samples + * @half_fft_size: Half of the FFT size + * @fft_buffer_size: FFT buffer size in bytes + */ +struct phase_vocoder_fft { + struct icomplex32 *fft_buf; + struct icomplex32 *fft_out; + struct fft_plan *fft_plan; + struct fft_plan *ifft_plan; + int fft_size; + int fft_hop_size; + int half_fft_size; + size_t fft_buffer_size; +}; + +/** + * struct phase_vocoder_polar - Polar domain processing data + * @polar: Current polar representation per channel + * @polar_prev: Previous polar representation per channel + * @polar_tmp: Temporary polar buffer + * @polar_buffer: Base address of the joint polar/angle/output-phase allocation + * @angle_delta_prev: Previous angle delta per channel + * @angle_delta: Current angle delta per channel + * @output_phase: Output phase per channel + */ +struct phase_vocoder_polar { + struct ipolar32 *polar[PLATFORM_MAX_CHANNELS]; + struct ipolar32 *polar_prev[PLATFORM_MAX_CHANNELS]; + struct ipolar32 *polar_tmp; + int32_t *polar_buffer; + int32_t *angle_delta_prev[PLATFORM_MAX_CHANNELS]; + int32_t *angle_delta[PLATFORM_MAX_CHANNELS]; + int32_t *output_phase[PLATFORM_MAX_CHANNELS]; +}; + +/** + * struct phase_vocoder_state - Phase vocoder algorithm state + * @ibuf: Circular buffers for input data, per channel + * @obuf: Circular buffers for output data, per channel + * @fft: FFT instance, common for all channels + * @polar: Processing state in polar domain + * @phase_vocoder_polar_bytes: Size in bytes of the joint polar allocation + * @prev_data: Previous frame data per channel, size is prev_data_size + * @buffers: Pointer to allocated memory for all buffers + * @window: Window function coefficients, size is fft_size + * @num_input_fft_to_use: Number of input FFTs to use for processing + * @num_input_fft: Total input FFTs count + * @num_output_ifft: Total output IFFTs count + * @gain_comp: Gain to compensate window gain + * @interpolate_fraction: Q3.29 interpolation coefficient + * @speed: Q3.29 actual render speed + * @prev_data_size: Size of previous data buffer + * @first_output_ifft_done: True after first output IFFT is completed + */ +struct phase_vocoder_state { + struct phase_vocoder_buffer ibuf[PLATFORM_MAX_CHANNELS]; + struct phase_vocoder_buffer obuf[PLATFORM_MAX_CHANNELS]; + struct phase_vocoder_fft fft; + struct phase_vocoder_polar polar; + size_t phase_vocoder_polar_bytes; + int32_t *prev_data[PLATFORM_MAX_CHANNELS]; + int32_t *buffers; + int32_t *window; + int32_t num_input_fft_to_use; + int32_t num_input_fft; + int32_t num_output_ifft; + int32_t gain_comp; + int32_t interpolate_fraction; + int32_t speed; + int prev_data_size; + bool first_output_ifft_done; +}; + +/** + * typedef phase_vocoder_func - Function pointer for process function + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @source_frames: Number of source audio data frames to process. + * @sink_frames: Number of sink audio data frames to produce. + */ +typedef int (*phase_vocoder_func)(const struct processing_module *mod, struct sof_source *source, + struct sof_sink *sink, uint32_t source_frames, + uint32_t sink_frames); + +/** + * struct phase_vocoder_comp_data - Phase vocoder component data + * @phase_vocoder_func: Pointer to used processing function. + * @state: Phase vocoder algorithm state. + * @config: Configuration blob for the module. + * @mono_mix_coef: Gain for channel for mixing, Q1.15 + * @sample_rate: Audio sample rate in Hz + * @speed_ctrl: Speed Q3.29, allowed range 0.5 to 2.0 + * @speed_enum: Speed control value as enum 0-15 + * @frame_bytes: Number of bytes in an audio frame. + * @max_input_frames: Maximum number of input frames per processing call + * @max_output_frames: Maximum number of output frames per processing call + * @stream_channels: Channels count to consume/produce + * @process_channels: Channels count to process. + * @enable: Control processing on/off, off is pass-through + */ +struct phase_vocoder_comp_data { + phase_vocoder_func phase_vocoder_func; + struct phase_vocoder_state state; + struct sof_phase_vocoder_config *config; + int32_t mono_mix_coef; + int32_t sample_rate; + int32_t speed_ctrl; + int32_t speed_enum; + size_t frame_bytes; + int max_input_frames; + int max_output_frames; + int stream_channels; + int process_channels; + bool enable; +}; + +static inline int phase_vocoder_buffer_samples_without_wrap(struct phase_vocoder_buffer *buffer, + int32_t *ptr) +{ + return buffer->end_addr - ptr; +} + +static inline int32_t *phase_vocoder_buffer_wrap(struct phase_vocoder_buffer *buffer, int32_t *ptr) +{ + if (ptr >= buffer->end_addr) + ptr -= buffer->s_length; + + return ptr; +} + +/** + * struct phase_vocoder_proc_fnmap - processing functions for frame formats + * @frame_fmt: Current frame format + * @phase_vocoder_proc_func: Function pointer for the suitable processing function + */ +struct phase_vocoder_proc_fnmap { + enum sof_ipc_frame frame_fmt; + phase_vocoder_func phase_vocoder_function; +}; + +/** + * phase_vocoder_find_proc_func() - Find suitable processing function. + * @src_fmt: Enum value for PCM format. + * + * This function finds the suitable processing function to use for + * the used PCM format. If not found, return NULL. + * + * Return: Pointer to processing function for the requested PCM format. + */ +phase_vocoder_func phase_vocoder_find_proc_func(enum sof_ipc_frame src_fmt); + +#if CONFIG_IPC_MAJOR_4 +/** + * phase_vocoder_set_config() - Handle controls set + * @mod: Pointer to module data. + * @param_id: Id to know control type, used to know ALSA control type. + * @pos: Position of the fragment in the large message. + * @data_offset_size: Size of the whole configuration if it is the first or only + * fragment. Otherwise it is offset of the fragment. + * @fragment: Message payload data. + * @fragment_size: Size of this fragment. + * @response_size: Size of response. + * + * This function handles the real-time controls. The ALSA controls have the + * param_id set to indicate the control type. The control ID, from topology, + * is used to separate the controls instances of same type. In control payload + * the num_elems defines to how many channels the control is applied to. + * + * Return: Zero if success, otherwise error code. + */ +int phase_vocoder_set_config(struct processing_module *mod, uint32_t param_id, + enum module_cfg_fragment_position pos, uint32_t data_offset_size, + const uint8_t *fragment, size_t fragment_size, uint8_t *response, + size_t response_size); +/** + * phase_vocoder_get_config() - Handle controls get + * @mod: Pointer to module data. + * @config_id: Configuration ID. + * @data_offset_size: Size of the whole configuration if it is the first or only + * fragment. Otherwise it is offset of the fragment. + * @fragment: Message payload data. + * @fragment_size: Size of this fragment. + * + * This function is used for controls get. + * + * Return: Zero if success, otherwise error code. + */ +int phase_vocoder_get_config(struct processing_module *mod, uint32_t config_id, + uint32_t *data_offset_size, uint8_t *fragment, size_t fragment_size); +#else +static inline int phase_vocoder_set_config(struct processing_module *mod, uint32_t param_id, + enum module_cfg_fragment_position pos, + uint32_t data_offset_size, const uint8_t *fragment, + size_t fragment_size, uint8_t *response, + size_t response_size) +{ + return 0; +} + +static inline int phase_vocoder_get_config(struct processing_module *mod, uint32_t config_id, + uint32_t *data_offset_size, uint8_t *fragment, + size_t fragment_size) +{ + return 0; +} +#endif + +void phase_vocoder_apply_window(struct phase_vocoder_state *state); + +void phase_vocoder_fill_fft_buffer(struct phase_vocoder_state *state, int ch); + +void phase_vocoder_free_buffers(struct processing_module *mod); + +int phase_vocoder_overlap_add_ifft_buffer(struct phase_vocoder_state *state, int ch); + +void phase_vocoder_reset_for_new_speed(struct phase_vocoder_comp_data *cd); + +int phase_vocoder_setup(struct processing_module *mod); + +int phase_vocoder_sink_s16(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames); + +int phase_vocoder_sink_s24(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames); + +int phase_vocoder_sink_s32(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, int frames); + +int phase_vocoder_source_s16(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames); + +int phase_vocoder_source_s24(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames); + +int phase_vocoder_source_s32(struct phase_vocoder_comp_data *cd, struct sof_source *source, + int frames); + +#endif // __SOF_AUDIO_PHASE_VOCODER_H__ diff --git a/src/audio/phase_vocoder/phase_vocoder.toml b/src/audio/phase_vocoder/phase_vocoder.toml new file mode 100644 index 000000000000..9ec249fe01a5 --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder.toml @@ -0,0 +1,21 @@ +#ifndef LOAD_TYPE +#define LOAD_TYPE "0" +#endif + + REM # Template component module config + [[module.entry]] + name = "PHASEVOC" + uuid = UUIDREG_STR_PHASE_VOCODER + affinity_mask = "0x1" + instance_count = "40" + domain_types = "0" + load_type = LOAD_TYPE + module_type = "9" + 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, 0xf, 0x1ff] + 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, 4096, 1000000, 128, 128, 0, 0, 0] + + index = __COUNTER__ diff --git a/src/audio/phase_vocoder/phase_vocoder_common.c b/src/audio/phase_vocoder/phase_vocoder_common.c new file mode 100644 index 000000000000..d21f4e9e90e5 --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder_common.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phase_vocoder.h" + +#include +#include +#include + +#if STFT_DEBUG +extern FILE *stft_debug_fft_in_fh; +extern FILE *stft_debug_fft_out_fh; +extern FILE *stft_debug_ifft_out_fh; + +static void debug_print_to_file_real(FILE *fh, struct icomplex32 *c, int n) +{ + for (int i = 0; i < n; i++) + fprintf(fh, "%d\n", c[i].real); +} + +static void debug_print_to_file_complex(FILE *fh, struct icomplex32 *c, int n) +{ + for (int i = 0; i < n; i++) + fprintf(fh, "%d %d\n", c[i].real, c[i].imag); +} +#endif + +LOG_MODULE_REGISTER(phase_vocoder_common, CONFIG_SOF_LOG_LEVEL); + +static int stft_get_num_ffts_avail(struct phase_vocoder_state *state, int channel) +{ + struct phase_vocoder_buffer *ibuf = &state->ibuf[channel]; + struct phase_vocoder_fft *fft = &state->fft; + + /* Wait for FFT hop size of new data */ + return ibuf->s_avail / fft->fft_hop_size; +} + +static void stft_do_fft(struct phase_vocoder_state *state, int ch) +{ + struct phase_vocoder_fft *fft = &state->fft; + + /* Copy data to FFT input buffer from overlap buffer and from new samples buffer */ + phase_vocoder_fill_fft_buffer(state, ch); + + /* Window function */ + phase_vocoder_apply_window(state); + +#if STFT_DEBUG + debug_print_to_file_real(stft_debug_fft_in_fh, fft->fft_buf, fft->fft_size); +#endif + + /* Compute FFT. A full scale s16 sine input with 2^N samples period in low + * part of s32 real part and zero imaginary part gives to output about 0.5 + * full scale 32 bit output to real and imaginary. The scaling is same for + * all FFT sizes. + */ + fft_execute_32(fft->fft_plan, false); + +#if STFT_DEBUG + debug_print_to_file_complex(stft_debug_fft_out_fh, fft->fft_out, fft->fft_size); +#endif +} + +static int stft_do_ifft(struct phase_vocoder_state *state, int ch) +{ + struct phase_vocoder_fft *fft = &state->fft; + + /* Compute IFFT */ + fft_execute_32(fft->ifft_plan, true); + +#if STFT_DEBUG + debug_print_to_file_complex(stft_debug_ifft_out_fh, fft->fft_buf, fft->fft_size); +#endif + + /* Window function */ + phase_vocoder_apply_window(state); + + /* Copy to output buffer */ + return phase_vocoder_overlap_add_ifft_buffer(state, ch); +} + +/** + * stft_convert_to_polar() - Convert FFT output to polar format + * @fft: FFT state containing the FFT output buffer and parameters as Q1.31. + * @polar_data: Output buffer for polar data, size is half of FFT size. + * Magnitude is Q2.30 and phase is Q5.27. + */ +static void stft_convert_to_polar(struct phase_vocoder_fft *fft, struct ipolar32 *polar_data) +{ + int i; + + /* Get magnitude and phase, convert phase from Q3.29 to Q5.27. */ + for (i = 0; i < fft->half_fft_size; i++) { + sofm_icomplex32_to_polar(&fft->fft_out[i], &polar_data[i]); + polar_data[i].angle = Q_SHIFT_RND(polar_data[i].angle, 29, 27); + } +} + +/** + * stft_convert_to_complex() - Convert polar data back to complex format for IFFT + * @polar_data: Input polar data, size is half of FFT size. + * Magnitude is Q2.30 and phase is Q5.27. + * @fft: FFT state containing the output buffer where complex data will + * be stored and parameters as Q1.31. + */ +static void stft_convert_to_complex(struct ipolar32 *polar_data, struct phase_vocoder_fft *fft) +{ + int i; + + /* Convert phase from Q5.27 to Q3.29, and then to (re, im) complex format as Q1.31*/ + for (i = 0; i < fft->half_fft_size; i++) { + polar_data[i].angle = Q_SHIFT_LEFT(polar_data[i].angle, 27, 29); + sofm_ipolar32_to_complex(&polar_data[i], &fft->fft_out[i]); + } +} + +static void stft_apply_fft_symmetry(struct phase_vocoder_fft *fft) +{ + int i, j, k; + + j = 2 * fft->half_fft_size - 2; + for (i = fft->half_fft_size; i < fft->fft_size; i++) { + k = j - i; + fft->fft_out[i].real = fft->fft_out[k].real; + fft->fft_out[i].imag = -fft->fft_out[k].imag; + } +} + +/** + * phase_vocoder_normalize_counters - Prevent int32 overflow in frame counters + * + * The interpolation fraction depends on (num_output_ifft * speed) mod 2^29. + * By subtracting a multiple of the fraction cycle period from num_output_ifft + * (and the corresponding integer amount from num_input_fft), the fraction is + * preserved exactly while keeping both counters small. + */ +static void phase_vocoder_normalize_counters(struct phase_vocoder_state *state, int32_t speed) +{ + int32_t g; + int32_t output_period; + int32_t delta_output; + int64_t delta_input_scaled; + + if (state->num_output_ifft < (1 << 28)) + return; + + /* The fraction cycle repeats every (2^29 / gcd(speed, 2^29)) output IFFTs */ + g = gcd(speed, 1 << 29); + output_period = (1 << 29) / g; + + /* Subtract the largest multiple of output_period that fits */ + delta_output = (state->num_output_ifft / output_period) * output_period; + if (!delta_output) + return; + + /* delta_output * speed is a multiple of 2^29 by construction, + * so the interpolation fraction is exactly preserved. + */ + delta_input_scaled = (int64_t)delta_output * speed; + + state->num_output_ifft -= delta_output; + state->num_input_fft -= (int32_t)(delta_input_scaled >> 29); +} + +static void phase_vocoder_interpolation_parameters(struct phase_vocoder_comp_data *cd) +{ + struct phase_vocoder_state *state = &cd->state; + int64_t input_frame_num_frac; + int32_t input_frame_num_floor; + + phase_vocoder_normalize_counters(state, cd->state.speed); + + input_frame_num_frac = (int64_t)state->num_output_ifft * cd->state.speed; /* Q31.29 */ + input_frame_num_floor = (int32_t)(input_frame_num_frac >> 29); /* Use floor, not round */ + state->num_input_fft_to_use = input_frame_num_floor + 1; + state->interpolate_fraction = input_frame_num_frac - ((int64_t)input_frame_num_floor << 29); +} + +#if 0 +static int32_t unwrap_angle(int32_t angle) +{ + if (angle > PHASE_VOCODER_PI_Q28) + return angle - PHASE_VOCODER_TWO_PI_Q28; + else if (angle < -PHASE_VOCODER_PI_Q28) + return angle + PHASE_VOCODER_TWO_PI_Q28; + else + return angle; +} +#endif + +static int32_t unwrap_angle_q27(int32_t angle) +{ + while (angle > PHASE_VOCODER_PI_Q27) + angle -= PHASE_VOCODER_TWO_PI_Q27; + + while (angle < -PHASE_VOCODER_PI_Q27) + angle += PHASE_VOCODER_TWO_PI_Q27; + + return angle; +} + +void phase_vocoder_reset_for_new_speed(struct phase_vocoder_comp_data *cd) +{ + struct phase_vocoder_state *state = &cd->state; + + state->speed = cd->speed_ctrl; + + /* Keep num_input_fft at 1, not 0, to avoid re-entering the first + * analysis FFT cold-start path. That path sets angle_delta to the + * absolute phase angle (not a delta between frames). For non-unity + * speeds, multiple IFFTs are produced per input FFT, and the + * absolute angle gets repeatedly accumulated into output_phase + * causing random inter-bin phase incoherence and gain variation. + * + * By setting num_input_fft = 1 the existing polar, angle_delta, + * and output_phase state from the last processing step is preserved. + * These contain proper phase deltas, so interpolation continues + * correctly at the new speed. + */ + state->num_input_fft = 1; + state->num_output_ifft = 0; +} + +static void copy_polar_angles(int32_t *angle_delta_ch, struct ipolar32 *polar_data_ch, + int num_angles) +{ + int i; + + for (i = 0; i < num_angles; i++) + angle_delta_ch[i] = polar_data_ch[i].angle; +} + +// TODO: Civilized input and output counters reset to prevent int32_t wrap +static int stft_do_fft_ifft(const struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_polar *polar = &state->polar; + struct phase_vocoder_fft *fft = &state->fft; + struct ipolar32 *polar_data_prev_ch; + struct ipolar32 *polar_data_ch; + int32_t *angle_delta_prev_ch; + int32_t *angle_delta_ch; + int32_t *output_phase_ch; + int32_t one_minus_frac; + int32_t frac; + int32_t p1, p2; + int32_t a; + const size_t polar_fft_half_bytes = sizeof(struct ipolar32) * fft->half_fft_size; + const size_t int32_fft_half_bytes = sizeof(int32_t) * fft->half_fft_size; + int num_fft; + int ret = 0; + int ch; + int i; + + num_fft = stft_get_num_ffts_avail(state, 0); + if (!num_fft) + return 0; + + /* First analysis FFT */ + if (!state->num_input_fft) { + for (ch = 0; ch < cd->process_channels; ch++) { + stft_do_fft(state, ch); + + /* Convert half-FFT to polar. Magnitude is Q2.30 and phase + * angle is Q5.27. + */ + polar_data_ch = polar->polar[ch]; /* struct ipolar32 */ + stft_convert_to_polar(&state->fft, polar_data_ch); + + /* Initialize output phase directly to the absolute phase + * of the first frame. This avoids the accumulation path + * which would double-count the initial phase when it + * later moves from angle_delta to angle_delta_prev. + */ + output_phase_ch = polar->output_phase[ch]; + copy_polar_angles(output_phase_ch, polar_data_ch, fft->half_fft_size); + + /* Initialize prev polar to same data so magnitude + * interpolation works before second FFT is consumed. + * Leave angle_delta and angle_delta_prev at zero so the + * phase does not advance until real deltas are computed. + */ + memcpy(polar->polar_prev[ch], polar_data_ch, polar_fft_half_bytes); + } + state->num_input_fft++; + num_fft--; + } + + phase_vocoder_interpolation_parameters(cd); + while (state->num_input_fft < state->num_input_fft_to_use && num_fft > 0) { + for (ch = 0; ch < cd->process_channels; ch++) { + stft_do_fft(state, ch); + + /* Update previous polar data. + * Note: Not using memcpy_s() since this is hot algorithm code. + */ + polar_data_prev_ch = polar->polar_prev[ch]; + polar_data_ch = polar->polar[ch]; + memcpy(polar_data_prev_ch, polar_data_ch, polar_fft_half_bytes); + + /* Convert half-FFT to polar. Magnitude is Q2.30 and phase + * angle is Q5.27. + */ + stft_convert_to_polar(&state->fft, polar_data_ch); + + /* Update previous delta phase data */ + angle_delta_ch = polar->angle_delta[ch]; + angle_delta_prev_ch = polar->angle_delta_prev[ch]; + memcpy(angle_delta_prev_ch, angle_delta_ch, int32_fft_half_bytes); + + /* Calculate new delta phase */ + for (i = 0; i < fft->half_fft_size; i++) { + a = polar_data_ch[i].angle - polar_data_prev_ch[i].angle; + angle_delta_ch[i] = unwrap_angle_q27(a); + } + } + state->num_input_fft++; + num_fft--; + } + + if (state->num_input_fft < state->num_input_fft_to_use) + return 0; + + /* Interpolate IFFT frame */ + frac = state->interpolate_fraction; + one_minus_frac = PHASE_VOCODER_ONE_Q29 - frac; + + for (ch = 0; ch < cd->process_channels; ch++) { + polar_data_prev_ch = polar->polar_prev[ch]; + polar_data_ch = polar->polar[ch]; + angle_delta_ch = polar->angle_delta[ch]; + angle_delta_prev_ch = polar->angle_delta_prev[ch]; + output_phase_ch = polar->output_phase[ch]; + + for (i = 0; i < fft->half_fft_size; i++) { + p1 = Q_MULTSR_32X32((int64_t)one_minus_frac, + polar_data_prev_ch[i].magnitude, 29, 30, 30); + p2 = Q_MULTSR_32X32((int64_t)frac, polar_data_ch[i].magnitude, 29, 30, 30); + polar->polar_tmp[i].magnitude = p1 + p2; + + a = output_phase_ch[i]; + p1 = Q_MULTSR_32X32((int64_t)one_minus_frac, angle_delta_prev_ch[i], 29, 27, + 27); + p2 = Q_MULTSR_32X32((int64_t)frac, angle_delta_ch[i], 29, 27, 27); + a = output_phase_ch[i] + p1 + p2; + output_phase_ch[i] = unwrap_angle_q27(a); + polar->polar_tmp[i].angle = output_phase_ch[i]; + } + + /* Convert back to (re, im) complex, and fix upper part */ + stft_convert_to_complex(polar->polar_tmp, &state->fft); + stft_apply_fft_symmetry(&state->fft); + ret = stft_do_ifft(state, ch); + if (ret) { + comp_err(mod->dev, "IFFT failure, check output overlap-add buffer size"); + return ret; + } + } + + comp_dbg(mod->dev, "no = %d, ni = %d, frac = %d", state->num_output_ifft, + state->num_input_fft, frac); + state->num_output_ifft++; + state->first_output_ifft_done = true; + return 0; +} + +static int phase_vocoder_check_fft_run_need(struct phase_vocoder_comp_data *cd) +{ + return cd->state.obuf[0].s_avail < cd->state.fft.fft_hop_size; +} + +#if CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S24LE +static int phase_vocoder_output_zeros_s32(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, + int frames) +{ + int32_t *y, *y_start, *y_end; + int samples = frames * cd->stream_channels; + size_t bytes = samples * sizeof(int32_t); + int samples_without_wrap; + int y_size; + int ret; + + /* Get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s32(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + while (samples) { + /* Find out samples to process before first wrap or end of data. */ + samples_without_wrap = y_end - y; + samples_without_wrap = MIN(samples_without_wrap, samples); + memset(y, 0, samples_without_wrap * sizeof(int32_t)); + y += samples_without_wrap; + + /* Check for wrap */ + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + sink_commit_buffer(sink, bytes); + return 0; +} +#endif + +#if CONFIG_FORMAT_S32LE +static int phase_vocoder_s32(const struct processing_module *mod, struct sof_source *source, + struct sof_sink *sink, uint32_t source_frames, uint32_t sink_frames) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + int ret; + + if (phase_vocoder_check_fft_run_need(cd)) { + /* Get samples from source buffer */ + ret = phase_vocoder_source_s32(cd, source, source_frames); + if (ret) + return ret; + + /* Do STFT, processing and inverse STFT */ + ret = stft_do_fft_ifft(mod); + if (ret) + return ret; + } + + /* Get samples from source buffer */ + if (cd->state.first_output_ifft_done) + ret = phase_vocoder_sink_s32(cd, sink, sink_frames); + else + ret = phase_vocoder_output_zeros_s32(cd, sink, sink_frames); + + return ret; +} +#endif /* CONFIG_FORMAT_S32LE */ + +#if CONFIG_FORMAT_S24LE +static int phase_vocoder_s24(const struct processing_module *mod, struct sof_source *source, + struct sof_sink *sink, uint32_t source_frames, uint32_t sink_frames) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + int ret; + + if (phase_vocoder_check_fft_run_need(cd)) { + /* Get samples from source buffer */ + ret = phase_vocoder_source_s24(cd, source, source_frames); + if (ret) + return ret; + + /* Do STFT, processing and inverse STFT */ + ret = stft_do_fft_ifft(mod); + if (ret) + return ret; + } + + /* Get samples from source buffer */ + if (cd->state.first_output_ifft_done) + ret = phase_vocoder_sink_s24(cd, sink, sink_frames); + else + /* Use the s32 function */ + ret = phase_vocoder_output_zeros_s32(cd, sink, sink_frames); + + return ret; +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S16LE +static int phase_vocoder_output_zeros_s16(struct phase_vocoder_comp_data *cd, struct sof_sink *sink, + int frames) +{ + int16_t *y, *y_start, *y_end; + int samples = frames * cd->stream_channels; + size_t bytes = samples * sizeof(int16_t); + int samples_without_wrap; + int y_size; + int ret; + + /* Get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s16(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + y_end = y_start + y_size; + while (samples) { + /* Find out samples to process before first wrap or end of data. */ + samples_without_wrap = y_end - y; + samples_without_wrap = MIN(samples_without_wrap, samples); + memset(y, 0, samples_without_wrap * sizeof(int16_t)); + y += samples_without_wrap; + + /* Check for wrap */ + if (y >= y_end) + y -= y_size; + + /* Update processed samples count for next loop iteration. */ + samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + sink_commit_buffer(sink, bytes); + return 0; +} + +static int phase_vocoder_s16(const struct processing_module *mod, struct sof_source *source, + struct sof_sink *sink, uint32_t source_frames, uint32_t sink_frames) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + int ret; + + if (phase_vocoder_check_fft_run_need(cd)) { + /* Get samples from source buffer */ + ret = phase_vocoder_source_s16(cd, source, source_frames); + if (ret) + return ret; + + /* Do STFT, processing and inverse STFT */ + ret = stft_do_fft_ifft(mod); + if (ret) + return ret; + } + + /* Get samples from source buffer */ + if (cd->state.first_output_ifft_done) + ret = phase_vocoder_sink_s16(cd, sink, sink_frames); + else + ret = phase_vocoder_output_zeros_s16(cd, sink, sink_frames); + + return ret; +} +#endif /* CONFIG_FORMAT_S16LE */ + +/* This struct array defines the used processing functions for + * the PCM formats. + */ +const struct phase_vocoder_proc_fnmap phase_vocoder_functions[] = { +#if CONFIG_FORMAT_S16LE + {SOF_IPC_FRAME_S16_LE, phase_vocoder_s16}, +#endif +#if CONFIG_FORMAT_S24LE + {SOF_IPC_FRAME_S24_4LE, phase_vocoder_s24}, +#endif +#if CONFIG_FORMAT_S32LE + {SOF_IPC_FRAME_S32_LE, phase_vocoder_s32}, +#endif +}; + +/** + * phase_vocoder_find_proc_func() - Find suitable processing function. + * @src_fmt: Enum value for PCM format. + * + * This function finds the suitable processing function to use for + * the used PCM format. If not found, return NULL. + * + * Return: Pointer to processing function for the requested PCM format. + */ +phase_vocoder_func phase_vocoder_find_proc_func(enum sof_ipc_frame src_fmt) +{ + int i; + + /* Find suitable processing function from map */ + for (i = 0; i < ARRAY_SIZE(phase_vocoder_functions); i++) + if (src_fmt == phase_vocoder_functions[i].frame_fmt) + return phase_vocoder_functions[i].phase_vocoder_function; + + return NULL; +} diff --git a/src/audio/phase_vocoder/phase_vocoder_setup.c b/src/audio/phase_vocoder/phase_vocoder_setup.c new file mode 100644 index 000000000000..c6baf5b8908a --- /dev/null +++ b/src/audio/phase_vocoder/phase_vocoder_setup.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include "phase_vocoder.h" + +#include +#include +#include + +#define STFT_MAX_ALLOC_SIZE 65536 + +LOG_MODULE_REGISTER(phase_vocoder_setup, CONFIG_SOF_LOG_LEVEL); + +static void phase_vocoder_init_buffer(struct phase_vocoder_buffer *buf, int32_t *base, int size) +{ + buf->addr = base; + buf->end_addr = base + size; + buf->r_ptr = base; + buf->w_ptr = base; + buf->s_free = size; + buf->s_avail = 0; + buf->s_length = size; +} + +static int phase_vocoder_get_window(struct phase_vocoder_state *state, + enum sof_phase_vocoder_fft_window_type name) +{ + struct phase_vocoder_fft *fft = &state->fft; + + switch (name) { + case STFT_RECTANGULAR_WINDOW: + win_rectangular_32b(state->window, fft->fft_size); + return 0; + case STFT_BLACKMAN_WINDOW: + win_blackman_32b(state->window, fft->fft_size, WIN_BLACKMAN_A0_Q31); + return 0; + case STFT_HAMMING_WINDOW: + win_hamming_32b(state->window, fft->fft_size); + return 0; + case STFT_HANN_WINDOW: + win_hann_32b(state->window, fft->fft_size); + return 0; + + default: + return -EINVAL; + } +} + +int phase_vocoder_setup(struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + struct sof_phase_vocoder_config *config = cd->config; + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_fft *fft = &state->fft; + struct phase_vocoder_polar *polar = &state->polar; + size_t sample_buffers_size; + size_t ibuf_size; + size_t obuf_size; + size_t prev_size; + int32_t *addr; + int channels; + int ret; + int i; + + comp_dbg(dev, "phase_vocoder_setup()"); + + /* Check size */ + if (config->size != sizeof(struct sof_phase_vocoder_config)) { + comp_err(dev, "Illegal configuration size %d.", config->size); + return -EINVAL; + } + + if (config->sample_frequency != cd->sample_rate) + comp_warn(dev, "Config sample_frequency does not match stream"); + + if (config->mono) { + cd->process_channels = 1; + if (cd->stream_channels > 1) + cd->mono_mix_coef = (int32_t)((1LL << 31) / cd->stream_channels); + else + cd->mono_mix_coef = INT32_MAX; /* Q1.31 1.0 */ + + comp_info(dev, "mono_mix_coef %d", cd->mono_mix_coef); + } else { + cd->process_channels = cd->stream_channels; + cd->mono_mix_coef = 0; + } + + fft->fft_size = config->frame_length; + fft->fft_hop_size = config->frame_shift; + fft->half_fft_size = (fft->fft_size >> 1) + 1; + + /* FFT size needs to be a multiple of 4 for Xtensa HiFi SIMD, + * and FFT hop size needs to be a multiple of 2. Check also + * for otherwise sane values. + */ + if (fft->fft_size <= 0 || fft->fft_hop_size <= 0 || + fft->fft_hop_size > fft->fft_size || + (fft->fft_size & 3) || (fft->fft_hop_size & 1)) { + comp_err(dev, "FFT size %d or hop size %d are invalid.", + fft->fft_size, fft->fft_hop_size); + return -EINVAL; + } + + comp_info(dev, "fft_size = %d, fft_hop_size = %d, window = %d", fft->fft_size, + fft->fft_hop_size, config->window); + + /* Calculated parameters */ + prev_size = fft->fft_size - fft->fft_hop_size; + ibuf_size = fft->fft_hop_size + cd->max_input_frames; + obuf_size = fft->fft_size + fft->fft_hop_size; + state->prev_data_size = prev_size; + + /* Allocate buffer for input samples, overlap buffer, window */ + channels = cd->process_channels; + sample_buffers_size = + sizeof(int32_t) * (channels * (ibuf_size + obuf_size + prev_size) + fft->fft_size); + + if (sample_buffers_size > STFT_MAX_ALLOC_SIZE) { + comp_err(dev, "Illegal allocation size"); + return -EINVAL; + } + + addr = mod_balloc(mod, sample_buffers_size); + if (!addr) { + comp_err(dev, "Failed buffer allocate"); + ret = -ENOMEM; + goto exit; + } + + memset(addr, 0, sample_buffers_size); + state->buffers = addr; + for (i = 0; i < channels; i++) { + phase_vocoder_init_buffer(&state->ibuf[i], addr, ibuf_size); + addr += ibuf_size; + phase_vocoder_init_buffer(&state->obuf[i], addr, obuf_size); + addr += obuf_size; + state->prev_data[i] = addr; + addr += prev_size; + } + state->window = addr; + + /* Allocate buffers for FFT input and output data */ + fft->fft_buffer_size = fft->fft_size * sizeof(struct icomplex32); + fft->fft_buf = mod_balloc(mod, fft->fft_buffer_size); + if (!fft->fft_buf) { + comp_err(dev, "Failed FFT buffer allocate"); + ret = -ENOMEM; + goto cleanup; + } + + fft->fft_out = mod_balloc(mod, fft->fft_buffer_size); + if (!fft->fft_out) { + comp_err(dev, "Failed FFT output allocate"); + ret = -ENOMEM; + goto cleanup; + } + + /* Setup FFT */ + fft->fft_plan = mod_fft_plan_new(mod, fft->fft_buf, fft->fft_out, fft->fft_size, 32); + if (!fft->fft_plan) { + comp_err(dev, "Failed FFT init"); + ret = -EINVAL; + goto cleanup; + } + + fft->ifft_plan = mod_fft_plan_new(mod, fft->fft_out, fft->fft_buf, fft->fft_size, 32); + if (!fft->ifft_plan) { + comp_err(dev, "Failed IFFT init"); + ret = -EINVAL; + goto cleanup; + } + + /* Setup window */ + ret = phase_vocoder_get_window(state, config->window); + if (ret < 0) { + comp_err(dev, "Failed Window function"); + goto cleanup; + } + + /* Need to compensate the window function gain */ + state->gain_comp = config->window_gain_comp; + + /* Allocate buffers for polar format data for magnitude and phase interpolation */ + state->phase_vocoder_polar_bytes = + channels * fft->half_fft_size * (2 * sizeof(struct ipolar32) + 3 * sizeof(int32_t)); + comp_info(dev, "polar buffers size %zu", state->phase_vocoder_polar_bytes); + addr = mod_balloc(mod, state->phase_vocoder_polar_bytes); + if (!addr) { + comp_err(dev, "Failed polar data buffer allocate"); + ret = -ENOMEM; + goto cleanup; + } + + memset(addr, 0, state->phase_vocoder_polar_bytes); + polar->polar_buffer = addr; + for (i = 0; i < channels; i++) { + polar->polar[i] = (struct ipolar32 *)addr; + addr = (int32_t *)((struct ipolar32 *)addr + fft->half_fft_size); + } + + for (i = 0; i < channels; i++) { + polar->polar_prev[i] = (struct ipolar32 *)addr; + addr = (int32_t *)((struct ipolar32 *)addr + fft->half_fft_size); + } + + for (i = 0; i < channels; i++) { + polar->angle_delta[i] = addr; + addr += fft->half_fft_size; + } + + for (i = 0; i < channels; i++) { + polar->angle_delta_prev[i] = addr; + addr += fft->half_fft_size; + } + + for (i = 0; i < channels; i++) { + polar->output_phase[i] = addr; + addr += fft->half_fft_size; + } + + /* Use FFT buffer as scratch */ + polar->polar_tmp = (struct ipolar32 *)fft->fft_out; + comp_dbg(dev, "phase_vocoder_setup(), done"); + return 0; + +cleanup: + phase_vocoder_free_buffers(mod); +exit: + return ret; +} + +void phase_vocoder_free_buffers(struct processing_module *mod) +{ + struct phase_vocoder_comp_data *cd = module_get_private_data(mod); + struct phase_vocoder_state *state = &cd->state; + struct phase_vocoder_fft *fft = &state->fft; + struct phase_vocoder_polar *polar = &state->polar; + + /* All free helpers tolerate NULL; clear pointers so a subsequent reset + * does not double-free if setup() failed mid-way. + */ + mod_fft_plan_free(mod, fft->ifft_plan); + fft->ifft_plan = NULL; + mod_fft_plan_free(mod, fft->fft_plan); + fft->fft_plan = NULL; + mod_free(mod, fft->fft_buf); + fft->fft_buf = NULL; + mod_free(mod, fft->fft_out); + fft->fft_out = NULL; + mod_free(mod, state->buffers); + state->buffers = NULL; + mod_free(mod, polar->polar_buffer); + polar->polar_buffer = NULL; +} diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index b08ef224ae1e..db8a69e63646 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -966,6 +966,7 @@ void sys_comp_module_mixout_interface_init(void); void sys_comp_module_multiband_drc_interface_init(void); void sys_comp_module_mux_interface_init(void); void sys_comp_module_nxp_eap_interface_init(void); +void sys_comp_module_phase_vocoder_interface_init(void); void sys_comp_module_rtnr_interface_init(void); void sys_comp_module_selector_interface_init(void); void sys_comp_module_sound_dose_interface_init(void); diff --git a/tools/rimage/config/lnl.toml.h b/tools/rimage/config/lnl.toml.h index 7a574d4ae906..faefbb8acadb 100644 --- a/tools/rimage/config/lnl.toml.h +++ b/tools/rimage/config/lnl.toml.h @@ -162,5 +162,9 @@ #include