From 3dda59aa10c8e5a458bfa6bfc1d29f17ebb3c611 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 3 Jun 2026 17:56:45 +0300 Subject: [PATCH 01/20] 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. Signed-off-by: Jyri Sarha --- src/include/sof/lib/vregion.h | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) 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 4f71ecd61a7d7b95cf46669b8d9f24d53c913f91 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 1 Jun 2026 23:19:28 +0300 Subject: [PATCH 02/20] 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 502103693689..862189d3626d 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 e21e498f0471..cdd9b9f4064e 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -150,4 +150,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 ab3367939d037dd7c30c5f5295b927e5dc8d0acb Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 3 Jun 2026 18:57:12 +0300 Subject: [PATCH 03/20] 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 c00d82ce567edb48bd3bb4071a05e9e537781055 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 3 Jun 2026 13:33:15 +0300 Subject: [PATCH 04/20] 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 61ef7a4ec6b1d4c620980ded55292eb9705e1570 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 29 Apr 2026 17:23:27 +0300 Subject: [PATCH 05/20] debug_stream: make ds_send_text_record() a syscall Refactor ds_vamsg() to separate the text formatting from the debug stream record sending. The new ds_send_text_record() takes a pre-formatted text buffer and length, and is declared as a Zephyr syscall when CONFIG_USERSPACE is enabled. ds_vamsg() now formats the message into a local buffer and calls ds_send_text_record(), which can be invoked from both user and supervisor contexts. Add the z_vrfy wrapper with K_SYSCALL_MEMORY_READ validation and register the syscall header in CMakeLists.txt. Define DS_TEXT_MSG_MAX_LEN (128) in the shared user header to keep the buffer size consistent between user and kernel sides. Signed-off-by: Jyri Sarha --- .../debug_stream/debug_stream_text_msg.c | 43 ++++++++++++++++--- src/include/user/debug_stream_text_msg.h | 12 ++++++ zephyr/CMakeLists.txt | 2 + 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/debug/debug_stream/debug_stream_text_msg.c b/src/debug/debug_stream/debug_stream_text_msg.c index 97db0fd29330..33769b6aa3ef 100644 --- a/src/debug/debug_stream/debug_stream_text_msg.c +++ b/src/debug/debug_stream/debug_stream_text_msg.c @@ -13,21 +13,28 @@ #include +#ifdef CONFIG_USERSPACE +#include +#endif + LOG_MODULE_REGISTER(debug_stream_text_msg); -void ds_vamsg(const char *format, va_list args) +#ifdef CONFIG_USERSPACE +void z_impl_ds_send_text_record(const char *text, size_t len) +#else +void ds_send_text_record(const char *text, size_t len) +#endif { struct { struct debug_stream_text_msg msg; - char text[128]; + char text[DS_TEXT_MSG_MAX_LEN]; } __packed buf = { 0 }; - ssize_t len; - - len = vsnprintf(buf.text, sizeof(buf.text), format, args); - if (len < 0) + if (!text || len == 0) return; + len = MIN(len, sizeof(buf.text)); + memcpy(buf.text, text, len); buf.msg.hdr.id = DEBUG_STREAM_RECORD_ID_TEXT_MSG; buf.msg.hdr.size_words = SOF_DIV_ROUND_UP(sizeof(buf.msg) + len, @@ -35,6 +42,30 @@ void ds_vamsg(const char *format, va_list args) debug_stream_slot_send_record(&buf.msg.hdr); } +#ifdef CONFIG_USERSPACE +static inline void z_vrfy_ds_send_text_record(const char *text, size_t len) +{ + len = MIN(len, DS_TEXT_MSG_MAX_LEN); + K_OOPS(K_SYSCALL_MEMORY_READ(text, len)); + z_impl_ds_send_text_record(text, len); +} +#include +#endif + +void ds_vamsg(const char *format, va_list args) +{ + char text[DS_TEXT_MSG_MAX_LEN]; + ssize_t len; + + len = vsnprintf(text, sizeof(text), format, args); + + if (len < 0) + return; + len = MIN(len, sizeof(text)); + + ds_send_text_record(text, len); +} + void ds_msg(const char *format, ...) { va_list args; diff --git a/src/include/user/debug_stream_text_msg.h b/src/include/user/debug_stream_text_msg.h index debfaad7042e..70983fed6de6 100644 --- a/src/include/user/debug_stream_text_msg.h +++ b/src/include/user/debug_stream_text_msg.h @@ -24,4 +24,16 @@ struct debug_stream_text_msg { void ds_msg(const char *format, ...); void ds_vamsg(const char *format, va_list ap); +#define DS_TEXT_MSG_MAX_LEN 128 + +#if defined(__ZEPHYR__) && defined(CONFIG_USERSPACE) +__syscall void ds_send_text_record(const char *text, size_t len); +#else +void ds_send_text_record(const char *text, size_t len); +#endif + +#if defined(__ZEPHYR__) && defined(CONFIG_USERSPACE) +#include +#endif + #endif /* __SOC_DEBUG_STREAM_TEXT_MSG_H__ */ diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 16575d5e4eec..320b73b4ae0d 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -621,6 +621,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(${SOF_SRC_PATH}/include/user/debug_stream_text_msg.h) + zephyr_library_link_libraries(SOF) target_link_libraries(SOF INTERFACE zephyr_interface) From 688390024f2bdeed641544a600e6310032241f4a Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 28 May 2026 17:59:52 +0300 Subject: [PATCH 06/20] 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 33769b6aa3ef..650f61a90fc8 100644 --- a/src/debug/debug_stream/debug_stream_text_msg.c +++ b/src/debug/debug_stream/debug_stream_text_msg.c @@ -74,6 +74,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 1acc1d98a356dd096477c37cf22a65246a810942 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 5 Mar 2026 21:49:22 +0200 Subject: [PATCH 07/20] module-adapter: Move pipeline_comp_dp_task_init() after mod struct inits Move pipeline_comp_dp_task_init() call after module private data initializations so that the struct module_config is available already at pipeline_comp_dp_task_init() init time. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 01126446860b..c356dd93764c 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -245,6 +245,17 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, struct comp_dev *dev = mod->dev; + dst = &mod->priv.cfg; + /* + * NOTE: dst->ext_data points to stack variable and contains + * pointers to IPC payload mailbox, so its only valid in + * functions that called from this function. This why + * the pointer is set NULL before this function exits. + */ +#if CONFIG_IPC_MAJOR_4 + dst->ext_data = &ext_data; +#endif + #if CONFIG_ZEPHYR_DP_SCHEDULER /* create a task for DP processing */ if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP) { @@ -257,16 +268,6 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, } #endif /* CONFIG_ZEPHYR_DP_SCHEDULER */ - dst = &mod->priv.cfg; - /* - * NOTE: dst->ext_data points to stack variable and contains - * pointers to IPC payload mailbox, so its only valid in - * functions that called from this function. This why - * the pointer is set NULL before this function exits. - */ -#if CONFIG_IPC_MAJOR_4 - dst->ext_data = &ext_data; -#endif ret = module_adapter_init_data(dev, dst, config, &spec); if (ret) { comp_err(dev, "%d: module init data failed", From b3ea8e3c4a911f8348b7b5cd4b8fe06f527e89ba Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 5 Mar 2026 19:53:46 +0200 Subject: [PATCH 08/20] module-adapter: Size DP heap from IPC ext init data Use IPC module init extended data (the dp_data) to determine DP module heap size when available. Add Kconfig option SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE (default 20480) as fallback when extended init data is not present or does not provide heap sizes. Sanity-check the requested sizes (reject values above 64 MB) and log the allocated heap size. Also pass ext_init through module_adapter_mem_alloc() to module_adapter_dp_heap_new() and fix a minor comment typo. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 51 ++++++++++++++++------- zephyr/Kconfig | 9 ++++ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index c356dd93764c..d78b8157e07e 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -59,23 +59,37 @@ struct comp_dev *module_adapter_new(const struct comp_driver *drv, #endif static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config *config, + const struct module_ext_init_data *ext_init, size_t *heap_size) { /* src-lite with 8 channels has been seen allocating 14k in one go */ - /* FIXME: the size will be derived from configuration */ - const size_t buf_size = 28 * 1024; + size_t buf_size = CONFIG_SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE; + size_t lifetime_size = 4096; - /* - * 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); +#if CONFIG_IPC_MAJOR_4 + if (config->ipc_extended_init && ext_init && ext_init->dp_data && + ext_init->dp_data->heap_bytes > 0) { + if (ext_init->dp_data->heap_bytes > 64*1024*1024) { + LOG_ERR("Bad heap size %u bytes for %#x", + ext_init->dp_data->heap_bytes, config->id); + return NULL; + } + + buf_size = ext_init->dp_data->heap_bytes; + + LOG_INF("%zu byte heap size requested in IPC for %#x", buf_size, config->id); + } +#endif + + *heap_size = buf_size; + + return vregion_create(lifetime_size, buf_size - lifetime_size); } -static struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, - const struct comp_ipc_config *config) +static +struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const struct module_ext_init_data *ext_init) { struct k_heap *mod_heap; struct vregion *mod_vreg; @@ -94,7 +108,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && IS_ENABLED(CONFIG_USERSPACE) && !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) { - mod_vreg = module_adapter_dp_heap_new(config, &heap_size); + mod_vreg = module_adapter_dp_heap_new(config, ext_init, &heap_size); if (!mod_vreg) { comp_cl_err(drv, "Failed to allocate DP module heap / vregion"); return NULL; @@ -231,8 +245,14 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, return NULL; } #endif + const struct module_ext_init_data *ext_init = +#if CONFIG_IPC_MAJOR_4 + &ext_data; +#else + NULL; +#endif - struct processing_module *mod = module_adapter_mem_alloc(drv, config); + struct processing_module *mod = module_adapter_mem_alloc(drv, config, ext_init); if (!mod) return NULL; @@ -249,8 +269,9 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, /* * NOTE: dst->ext_data points to stack variable and contains * pointers to IPC payload mailbox, so its only valid in - * functions that called from this function. This why - * the pointer is set NULL before this function exits. + * functions that are called from this function. This is + * why the pointer is set to NULL before this function + * exits. */ #if CONFIG_IPC_MAJOR_4 dst->ext_data = &ext_data; diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 998abae8a969..647a478af561 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -61,6 +61,15 @@ config SOF_ZEPHYR_HEAP_SIZE NOTE: Keep in mind that the heap size should not be greater than the physical memory size of the system defined in DT (and this includes baseFW text/data). +config SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE + int "Default heap size for DP userspace threads" + default 20480 + help + Defines the default heap size for userspace DP processing + threads. The value can be overridden with IPC module init + ext_init module payload. The default is derived from what is + required for SRC module to produce all supported conversions. + config SOF_USERSPACE_USE_SHARED_HEAP bool "Use shared heap for SOF userspace modules" depends on USERSPACE From f627dfaacb216a7798c61ed991e65e44b00da7ee Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 5 Mar 2026 21:35:41 +0200 Subject: [PATCH 09/20] pipeline: Use dp_data stack size for DP task Use IPC module init extended data (dp_data stack_bytes) to set the DP processing thread stack size when available. Fall back to TASK_DP_STACK_SIZE when ext init data is not present or does not provide a stack size. Add Kconfig option ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE (default 2048) to enforce a minimum stack size regardless of what the IPC payload requests. Signed-off-by: Jyri Sarha --- src/audio/pipeline/pipeline-schedule.c | 12 +++++++++++- zephyr/Kconfig | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index 45fd1eed639c..5ad8a5074f0e 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -388,6 +388,7 @@ int pipeline_comp_dp_task_init(struct comp_dev *comp) { /* DP tasks are guaranteed to have a module_adapter */ struct processing_module *mod = comp_mod(comp); + size_t stack_size = TASK_DP_STACK_SIZE; struct task_ops ops = { .run = dp_task_run, .get_deadline = NULL, @@ -403,8 +404,17 @@ int pipeline_comp_dp_task_init(struct comp_dev *comp) unsigned int flags = IS_ENABLED(CONFIG_USERSPACE) ? K_USER : 0; #endif + if (mod->priv.cfg.ext_data && mod->priv.cfg.ext_data->dp_data && + mod->priv.cfg.ext_data->dp_data->stack_bytes > 0) { + stack_size = MAX(mod->priv.cfg.ext_data->dp_data->stack_bytes, + CONFIG_ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE); + comp_info(comp, "stack size set to %zu, %zu requested, min allowed %zu", + stack_size, mod->priv.cfg.ext_data->dp_data->stack_bytes, + CONFIG_ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE); + } + return scheduler_dp_task_init(&comp->task, SOF_UUID(dp_task_uuid), &ops, mod, - comp->ipc_config.core, TASK_DP_STACK_SIZE, flags); + comp->ipc_config.core, stack_size, flags); } #endif /* CONFIG_ZEPHYR_DP_SCHEDULER */ diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 647a478af561..edab71a1a502 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -222,6 +222,15 @@ config ZEPHYR_DP_SCHEDULER DP modules can be located in dieffrent cores than LL pipeline modules, may have different tick (i.e. 300ms for speech reccognition, etc.) +config ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE + int "Minimum stack size for DP processing thread" + default 512 + help + Defines the minimum stack size allowed for DP processing + threads despite what is requested in the module init IPC + ext_init payload. If the stack size requested in the IPC is + smaller than this, then the value defined here takes over. + config CROSS_CORE_STREAM bool "Enable cross-core connected pipelines" default y if IPC_MAJOR_4 From 4eff999c87aa66a4a12257b6990cdd4beb0cb62b Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 8 May 2026 00:05:29 +0300 Subject: [PATCH 10/20] 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..1dbc2448e74f 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 comp id %#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 76c65e63aa64c14050cdea637df71b6e708883b7 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 11 May 2026 22:17:53 +0300 Subject: [PATCH 11/20] audio: src: move filter and delay line allocation to init phase Refactor SRC and SRC-lite so that filter stage setup and delay line allocation happen during the module init callback instead of prepare. This ensures the bulk of SRC memory allocation occurs while the vregion allocator is still in its lifetime phase, before the interim heap is created. The allocations then persist across prepare/reset cycles without needing to be re-allocated each time. A new setup_stages() callback is added to struct comp_data, set by each variant (src.c, src_lite.c) to point at its own coefficient tables. The common src_allocate_delay_lines() is factored out of the old prepare path into src_common.c. For IPC4, src_init_stages() calls setup_stages() and src_allocate_delay_lines() at init time. The prepare path (src_prepare_do) only validates rates and sets downstream params. For IPC3, src_init_stages() is a no-op and src_prepare_do() retains the original behavior of doing full setup at prepare time, since IPC3 cannot be tested at this time. Signed-off-by: Jyri Sarha --- src/audio/src/src.c | 52 ++++++++++++++++--------- src/audio/src/src_common.c | 80 ++++++++++++++++++++++++++++++++++++++ src/audio/src/src_common.h | 5 +++ src/audio/src/src_ipc3.c | 24 ++++++++++++ src/audio/src/src_ipc4.c | 64 ++++++++++++++++++++++++++++++ src/audio/src/src_lite.c | 53 +++++++++++++++---------- 6 files changed, 240 insertions(+), 38 deletions(-) diff --git a/src/audio/src/src.c b/src/audio/src/src.c index 9870ad911ca8..b75ff5a4cce7 100644 --- a/src/audio/src/src.c +++ b/src/audio/src/src.c @@ -34,19 +34,16 @@ LOG_MODULE_DECLARE(src, CONFIG_SOF_LOG_LEVEL); -static int src_prepare(struct processing_module *mod, - struct sof_source **sources, int num_of_sources, - struct sof_sink **sinks, int num_of_sinks) +/* Set rate table pointers, compute rate indices, and copy filter stages. + * Must be in src.c because src_table1/2, src_in_fs, etc. come from + * the coefficient headers included by this file. + */ +static int src_setup_stages(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); struct src_param *a = &cd->param; int ret; - comp_info(mod->dev, "entry"); - - if (num_of_sources != 1 || num_of_sinks != 1) - return -EINVAL; - a->in_fs = src_in_fs; a->out_fs = src_out_fs; a->num_in_fs = NUM_IN_FS; @@ -54,27 +51,46 @@ static int src_prepare(struct processing_module *mod, a->max_fir_delay_size_xnch = (PLATFORM_MAX_CHANNELS * MAX_FIR_DELAY_SIZE); a->max_out_delay_size_xnch = (PLATFORM_MAX_CHANNELS * MAX_OUT_DELAY_SIZE); - src_get_source_sink_params(mod->dev, sources[0], sinks[0]); - ret = src_param_set(mod->dev, cd); if (ret < 0) return ret; - ret = src_allocate_copy_stages(mod, a, - src_table1[a->idx_out][a->idx_in], - src_table2[a->idx_out][a->idx_in]); - if (ret < 0) - return ret; + return src_allocate_copy_stages(mod, a, + src_table1[a->idx_out][a->idx_in], + src_table2[a->idx_out][a->idx_in]); +} - ret = src_params_general(mod, sources[0], sinks[0]); +static int src_do_init(struct processing_module *mod) +{ + struct comp_data *cd; + int ret; + + ret = src_init(mod); if (ret < 0) return ret; - return src_prepare_general(mod, sources[0], sinks[0]); + cd = module_get_private_data(mod); + cd->setup_stages = src_setup_stages; + + return src_init_stages(mod); +} + +static int src_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) +{ + comp_info(mod->dev, "entry"); + + if (num_of_sources != 1 || num_of_sinks != 1) + return -EINVAL; + + src_get_source_sink_params(mod->dev, sources[0], sinks[0]); + + return src_prepare_do(mod, sources[0], sinks[0]); } static const struct module_interface src_interface = { - .init = src_init, + .init = src_do_init, .prepare = src_prepare, .process = src_process, .is_ready_to_process = src_is_ready_to_process, diff --git a/src/audio/src/src_common.c b/src/audio/src/src_common.c index 3a9c7dac280f..e4ccf76ae3ba 100644 --- a/src/audio/src/src_common.c +++ b/src/audio/src/src_common.c @@ -574,6 +574,86 @@ int src_params_general(struct processing_module *mod, return 0; } +/* Allocate delay lines and initialize the polyphase SRC filter. + * Assumes that cd->param rate table pointers (in_fs, out_fs, etc.) + * and stage pointers (stage1, stage2) are already set up via + * cd->setup_stages(). + */ +int src_allocate_delay_lines(struct processing_module *mod) +{ + struct comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + size_t delay_lines_size; + int32_t *buffer_start; + int n; + int ret; + + /* For LL modules dev->period is already set from the pipeline. + * Compute dev->frames so buffer sizing works. + */ + if (!dev->frames) + component_set_nearest_period_frames(dev, cd->sink_rate); + + if (!cd->sink_rate) { + comp_err(dev, "zero sink rate"); + return -EINVAL; + } + + cd->source_frames = dev->frames * cd->source_rate / cd->sink_rate; + cd->sink_frames = dev->frames; + + /* Allocate needed memory for delay lines */ + ret = src_buffer_lengths(dev, cd, cd->channels_count); + if (ret < 0) { + comp_err(dev, "src_buffer_lengths() failed"); + return ret; + } + + delay_lines_size = ALIGN_UP(sizeof(int32_t) * cd->param.total, 8); + if (delay_lines_size == 0) { + comp_err(dev, "delay_lines_size = 0"); + return -EINVAL; + } + + mod_free(mod, cd->delay_lines); + + cd->delay_lines = mod_alloc(mod, delay_lines_size); + if (!cd->delay_lines) { + comp_err(dev, "failed to alloc cd->delay_lines, delay_lines_size = %zu", + delay_lines_size); + return -ENOMEM; + } + + memset(cd->delay_lines, 0, delay_lines_size); + buffer_start = cd->delay_lines + ALIGN_UP(cd->param.sbuf_length, 2); + + /* Initialize SRC for actual sample rate */ + n = src_polyphase_init(&cd->src, &cd->param, buffer_start); + + /* Reset stage buffer */ + cd->sbuf_r_ptr = cd->delay_lines; + cd->sbuf_w_ptr = cd->delay_lines; + cd->sbuf_avail = 0; + + switch (n) { + case 0: + cd->src_func = src_copy_sxx; + break; + case 1: + cd->src_func = src_1s; + break; + case 2: + cd->src_func = src_2s; + break; + default: + comp_info(dev, "missing coefficients for requested rates combination"); + cd->src_func = src_fallback; + return -EINVAL; + } + + return 0; +} + int src_param_set(struct comp_dev *dev, struct comp_data *cd) { struct src_param *a = &cd->param; diff --git a/src/audio/src/src_common.h b/src/audio/src/src_common.h index 98f29a263131..126b112737d3 100644 --- a/src/audio/src/src_common.h +++ b/src/audio/src/src_common.h @@ -167,6 +167,7 @@ struct comp_data { int (*src_func)(struct comp_data *cd, struct sof_source *source, struct sof_sink *sink); void (*polyphase_func)(struct src_stage_prm *s); + int (*setup_stages)(struct processing_module *mod); }; #if CONFIG_IPC_MAJOR_4 @@ -218,6 +219,7 @@ static inline int src_fallback(struct comp_data *cd, int src_allocate_copy_stages(struct processing_module *mod, struct src_param *prm, const struct src_stage *stage_src1, const struct src_stage *stage_src2); +int src_allocate_delay_lines(struct processing_module *mod); int src_rate_check(const void *spec); int src_set_params(struct processing_module *mod, struct sof_sink *sink); @@ -227,6 +229,9 @@ int src_prepare_general(struct processing_module *mod, struct sof_source *source, struct sof_sink *sink); int src_init(struct processing_module *mod); +int src_init_stages(struct processing_module *mod); +int src_prepare_do(struct processing_module *mod, + struct sof_source *source, struct sof_sink *sink); int src_copy_sxx(struct comp_data *cd, struct sof_source *source, struct sof_sink *sink); diff --git a/src/audio/src/src_ipc3.c b/src/audio/src/src_ipc3.c index 541efcdb24be..636c50bfdcea 100644 --- a/src/audio/src/src_ipc3.c +++ b/src/audio/src/src_ipc3.c @@ -195,3 +195,27 @@ int src_init(struct processing_module *mod) return 0; } +/* IPC3: No filter allocation at init, change ipc3 behavior as little as possible */ +int src_init_stages(struct processing_module *mod) +{ + return 0; +} + +/* IPC3: Full filter setup at prepare time */ +int src_prepare_do(struct processing_module *mod, + struct sof_source *source, struct sof_sink *sink) +{ + struct comp_data *cd = module_get_private_data(mod); + int ret; + + ret = cd->setup_stages(mod); + if (ret < 0) + return ret; + + ret = src_params_general(mod, source, sink); + if (ret < 0) + return ret; + + return src_prepare_general(mod, source, sink); +} + diff --git a/src/audio/src/src_ipc4.c b/src/audio/src/src_ipc4.c index 91f286347a5f..6e6f3c51b7be 100644 --- a/src/audio/src/src_ipc4.c +++ b/src/audio/src/src_ipc4.c @@ -246,3 +246,67 @@ __cold int src_init(struct processing_module *mod) return 0; } +/* Called after src_init() and setup_stages callback is set. + * Allocate filter stages and delay lines at init time. + */ +int src_init_stages(struct processing_module *mod) +{ + struct comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + int ret; + + ret = cd->setup_stages(mod); + if (ret < 0) + return ret; + + /* For DP modules, dev->period is not yet set at init time (it's + * computed in src_set_params at prepare). Derive it here from the + * IPC config's output buffer size so that delay line allocation + * uses correct buffer sizes. + */ + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP && !dev->frames) { + uint32_t frame_bytes = cd->channels_count * cd->sample_container_bytes; + + if (frame_bytes && cd->sink_rate) { + dev->period = 1000000ULL * + (cd->ipc_config.base.obs / frame_bytes) / + cd->sink_rate; + dev->period /= LL_TIMER_PERIOD_US; + dev->period *= LL_TIMER_PERIOD_US; + component_set_nearest_period_frames(dev, cd->sink_rate); + } + } + + return src_allocate_delay_lines(mod); +} + +/* At prepare time just verify rates and set downstream params */ +int src_prepare_do(struct processing_module *mod, + struct sof_source *source, struct sof_sink *sink) +{ + struct comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + int ret; + + if (cd->source_rate != cd->ipc_config.base.audio_fmt.sampling_frequency || + cd->sink_rate != cd->ipc_config.sink_rate) { + comp_err(mod->dev, "rate mismatch: source %u/%u sink %u/%u", + cd->source_rate, + cd->ipc_config.base.audio_fmt.sampling_frequency, + cd->sink_rate, cd->ipc_config.sink_rate); + return -EINVAL; + } + + ret = src_set_params(mod, sink); + if (ret < 0) { + comp_err(mod->dev, "set params failed."); + return ret; + } + + /* Update frame counts with final dev->frames from src_set_params */ + cd->source_frames = dev->frames * cd->source_rate / cd->sink_rate; + cd->sink_frames = dev->frames; + + return src_prepare_general(mod, source, sink); +} + diff --git a/src/audio/src/src_lite.c b/src/audio/src/src_lite.c index 9d5593ff34ca..89d205fc7b85 100644 --- a/src/audio/src/src_lite.c +++ b/src/audio/src/src_lite.c @@ -14,24 +14,16 @@ LOG_MODULE_REGISTER(src_lite, CONFIG_SOF_LOG_LEVEL); -/* - * This function is 100% identical to src_prepare(), but it's - * assigning different coefficient arrays because it's including - * different headers. +/* Set rate table pointers, compute rate indices, and copy filter stages. + * Must be in src_lite.c because src_table1/2, src_in_fs, etc. come from + * the coefficient headers included by this file. */ -static int src_lite_prepare(struct processing_module *mod, - struct sof_source **sources, int num_of_sources, - struct sof_sink **sinks, int num_of_sinks) +static int src_lite_setup_stages(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); struct src_param *a = &cd->param; int ret; - comp_info(mod->dev, "entry"); - - if (num_of_sources != 1 || num_of_sinks != 1) - return -EINVAL; - a->in_fs = src_in_fs; a->out_fs = src_out_fs; a->num_in_fs = NUM_IN_FS; @@ -43,21 +35,42 @@ static int src_lite_prepare(struct processing_module *mod, if (ret < 0) return ret; - ret = src_allocate_copy_stages(mod, a, - src_table1[a->idx_out][a->idx_in], - src_table2[a->idx_out][a->idx_in]); - if (ret < 0) - return ret; + return src_allocate_copy_stages(mod, a, + src_table1[a->idx_out][a->idx_in], + src_table2[a->idx_out][a->idx_in]); +} + +static int src_lite_do_init(struct processing_module *mod) +{ + struct comp_data *cd; + int ret; - ret = src_params_general(mod, sources[0], sinks[0]); + ret = src_init(mod); if (ret < 0) return ret; - return src_prepare_general(mod, sources[0], sinks[0]); + cd = module_get_private_data(mod); + cd->setup_stages = src_lite_setup_stages; + + return src_init_stages(mod); +} + +static int src_lite_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) +{ + comp_info(mod->dev, "entry"); + + if (num_of_sources != 1 || num_of_sinks != 1) + return -EINVAL; + + src_get_source_sink_params(mod->dev, sources[0], sinks[0]); + + return src_prepare_do(mod, sources[0], sinks[0]); } const struct module_interface src_lite_interface = { - .init = src_init, + .init = src_lite_do_init, .prepare = src_lite_prepare, .process = src_process, .is_ready_to_process = src_is_ready_to_process, From c420b504284bdc38f295efbc886f73a2d2ad4875 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 12 May 2026 19:31:41 +0300 Subject: [PATCH 12/20] 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 struct is placed at the base, followed by lifetime allocations growing upward. The interim k_heap is created lazily on the first interim allocation from 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 lifetime allocation requests are redirected to the interim heap with a warning. The vregion now tracks an internal allocation mode (lifetime or interim). All allocations start in lifetime mode. The vregion_set_interim() function switches to interim mode; module_prepare() calls it to transition. The enum vregion_mem_type parameter has been removed from the vregion_alloc*() API since the mode is now internal state. 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 - lifetime_alloc(): Falls back to interim with warning if called after interim is initialized - interim_alloc(): Triggers interim_heap_init() on first call - vregion_free(): Guards interim pointer range check with interim_initialized flag - module_prepare(): Calls vregion_set_interim() to switch mode Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module/generic.c | 4 + src/audio/module_adapter/module_adapter.c | 9 +- src/audio/smart_amp/llext/CMakeLists.txt | 26 ++++ src/audio/smart_amp/llext/llext.toml.h | 6 + src/audio/smart_amp/llext/smart_amp.toml | 21 +++ src/include/sof/lib/vregion.h | 50 +++---- src/lib/objpool.c | 4 +- zephyr/include/rtos/alloc.h | 5 +- zephyr/lib/fast-get.c | 2 +- zephyr/lib/vregion.c | 153 +++++++++++++++------- zephyr/test/vregion.c | 38 ++++-- 11 files changed, 223 insertions(+), 95 deletions(-) create mode 100644 src/audio/smart_amp/llext/CMakeLists.txt create mode 100644 src/audio/smart_amp/llext/llext.toml.h create mode 100644 src/audio/smart_amp/llext/smart_amp.toml diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index b62ae38f415f..a1f31e7f42b4 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -502,6 +502,10 @@ int module_prepare(struct processing_module *mod, comp_dbg(dev, "entry"); + /* Switch vregion to interim allocation mode for prepare and beyond */ + if (md->resources.alloc && md->resources.alloc->vreg) + vregion_set_interim(md->resources.alloc->vreg); + #if CONFIG_IPC_MAJOR_3 if (mod->priv.state == MODULE_IDLE) return 0; diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index d78b8157e07e..84dcbaabfcea 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -64,7 +64,6 @@ static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config * { /* src-lite with 8 channels has been seen allocating 14k in one go */ size_t buf_size = CONFIG_SOF_USERSPACE_DP_DEFAULT_HEAP_SIZE; - size_t lifetime_size = 4096; #if CONFIG_IPC_MAJOR_4 if (config->ipc_extended_init && ext_init && ext_init->dp_data && @@ -83,7 +82,7 @@ static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config * *heap_size = buf_size; - return vregion_create(lifetime_size, buf_size - lifetime_size); + return vregion_create(buf_size); } static @@ -123,9 +122,9 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv 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"); @@ -150,7 +149,7 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv * 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/smart_amp/llext/CMakeLists.txt b/src/audio/smart_amp/llext/CMakeLists.txt new file mode 100644 index 000000000000..4ffd86248902 --- /dev/null +++ b/src/audio/smart_amp/llext/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (c) 2024 Intel Corporation. +# SPDX-License-Identifier: Apache-2.0 + +if(CONFIG_MAXIM_DSM) + if(NOT CONFIG_MAXIM_DSM_STUB) + sof_llext_build("smart_amp" + SOURCES ../smart_amp.c + ../smart_amp_generic.c + ../smart_amp_maxim_dsm.c + ${CMAKE_CURRENT_LIST_DIR}/lib/release/dsm_lib/libdsm.a + ) + else() + sof_llext_build("smart_amp" + SOURCES ../smart_amp.c + ../smart_amp_generic.c + ../smart_amp_maxim_dsm.c + ../maxim_dsm_stub.c + ) + endif() +else() + sof_llext_build("smart_amp" + SOURCES ../smart_amp.c + ../smart_amp_generic.c + ../smart_amp_passthru.c + ) +endif() diff --git a/src/audio/smart_amp/llext/llext.toml.h b/src/audio/smart_amp/llext/llext.toml.h new file mode 100644 index 000000000000..b7a3322dc906 --- /dev/null +++ b/src/audio/smart_amp/llext/llext.toml.h @@ -0,0 +1,6 @@ +#include +#define LOAD_TYPE "2" +#include "../igo_nr.toml" + +[module] +count = __COUNTER__ diff --git a/src/audio/smart_amp/llext/smart_amp.toml b/src/audio/smart_amp/llext/smart_amp.toml new file mode 100644 index 000000000000..f75cdc22f952 --- /dev/null +++ b/src/audio/smart_amp/llext/smart_amp.toml @@ -0,0 +1,21 @@ +#ifndef LOAD_TYPE +#define LOAD_TYPE "0" +#endif + + REM # smart amp module config + [[module.entry]] + name = "IGO_NR" + uuid = UUIDREG_STR_SMART_AMP + 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/include/sof/lib/vregion.h b/src/include/sof/lib/vregion.h index d64249ed64c8..a9ea64c0d5d6 100644 --- a/src/include/sof/lib/vregion.h +++ b/src/include/sof/lib/vregion.h @@ -31,14 +31,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 +76,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 +136,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 +149,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 cdd9b9f4064e..03a6bb5ce410 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -177,10 +177,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..66ca649c7cf6 100644 --- a/zephyr/lib/vregion.c +++ b/zephyr/lib/vregion.c @@ -87,8 +87,12 @@ 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 */ + bool interim_initialized; /* true after k_heap_init for interim */ /* lifetime heap */ struct vlinear_heap lifetime; /* lifetime linear 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. 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,21 +145,18 @@ 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 right after the vregion struct */ + 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); + /* interim heap is not initialized yet - will be created lazily */ + vr->interim_initialized = false; + + /* start in lifetime allocation mode */ + vr->type = VREGION_MEM_TYPE_LIFETIME; k_mutex_init(&vr->lock); /* The creator is the first user */ @@ -163,8 +165,6 @@ struct vregion *vregion_create(size_t lifetime_size, size_t interim_size) /* log the new vregion */ LOG_INF("new at base %p size %#zx pages %u struct embedded 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,19 +212,69 @@ struct vregion *vregion_put(struct vregion *vr) return NULL; } +void vregion_set_interim(struct vregion *vr) +{ + if (!vr) + return; + + k_mutex_lock(&vr->lock, K_FOREVER); + + if (vr->type == VREGION_MEM_TYPE_INTERIM) + LOG_WRN("vregion %p already in interim mode", (void *)vr); + else + vr->type = VREGION_MEM_TYPE_INTERIM; + + k_mutex_unlock(&vr->lock); +} +EXPORT_SYMBOL(vregion_set_interim); + +/** + * @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; + size_t interim_size; + + /* interim heap starts right after current lifetime pointer, page-aligned */ + interim_base = UINT_TO_POINTER(ALIGN_UP(POINTER_TO_UINT(vr->lifetime.ptr), + CONFIG_MM_DRV_PAGE_SIZE)); + interim_size = (vr->base + vr->size) - interim_base; + + 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->interim_initialized = true; +} + /** * @brief Allocate memory with alignment from the virtual region dynamic heap. * + * @param[in] vr Pointer to the virtual region instance. * @param[in] heap Pointer to the 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 *interim_alloc(struct interim_heap *heap, +static void *interim_alloc(struct vregion *vr, struct interim_heap *heap, size_t size, size_t align) { void *ptr; + if (!vr->interim_initialized) + interim_heap_init(vr); + ptr = k_heap_aligned_alloc(&heap->heap, align, size, K_NO_WAIT); if (!ptr) LOG_WRN("interim alloc failed for %d bytes align %d", @@ -247,19 +297,29 @@ 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] vr Pointer to the virtual region instance. * @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, +static void *lifetime_alloc(struct vregion *vr, struct vlinear_heap *heap, size_t size, size_t align) { void *ptr; uint8_t *aligned_ptr; size_t heap_obj_size; + /* If interim heap already exists, lifetime partition is sealed */ + if (vr->interim_initialized) { + LOG_WRN("lifetime alloc after interim created, using interim for %zu bytes", size); + return interim_alloc(vr, &vr->interim, size, align); + } + /* align heap pointer to alignment requested */ aligned_ptr = UINT_TO_POINTER(ALIGN_UP(POINTER_TO_UINT(heap->ptr), align)); @@ -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->interim_initialized && + 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,15 +413,15 @@ 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); + p = interim_alloc(vr, &vr->interim, size, alignment); break; case VREGION_MEM_TYPE_LIFETIME: - p = lifetime_alloc(&vr->lifetime, size, alignment); + p = lifetime_alloc(vr, &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..036ac954ee3d 100644 --- a/zephyr/test/vregion.c +++ b/zephyr/test/vregion.c @@ -17,8 +17,12 @@ 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); + /* + * 3 pages of memsize + struct overhead → 4 pages total. + * Two 6000-byte lifetime allocs consume ~3 pages, leaving + * ~1 page for the interim heap. + */ + struct vregion *vreg = vregion_create(3 * CONFIG_MM_DRV_PAGE_SIZE); zassert_not_null(vreg); @@ -27,40 +31,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, 2000, 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 +78,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, 2000); zassert_not_null(ptr); } @@ -83,10 +93,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 1252ff98af19437875ef7b09f11d46aa2ef81500 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 2 Jun 2026 21:00:06 +0300 Subject: [PATCH 13/20] lib: vregion: fix interim heap init alignment and small size assert Reduce interim heap base alignment from CONFIG_MM_DRV_PAGE_SIZE to CONFIG_DCACHE_LINE_SIZE. Page-size alignment wastes too much space and can leave insufficient room for the interim heap. Add a guard to skip k_heap_init() when the remaining interim size is too small (< 1024 bytes), which would otherwise trigger an assert failure in sys_heap_init(). Mark the vregion as VREGION_MEM_TYPE_INVALID in that case. Signed-off-by: Jyri Sarha --- src/include/sof/lib/vregion.h | 1 + zephyr/lib/vregion.c | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/include/sof/lib/vregion.h b/src/include/sof/lib/vregion.h index a9ea64c0d5d6..3fcdcb32b1bc 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 initializatoin failed */ }; #if CONFIG_SOF_VREGIONS diff --git a/zephyr/lib/vregion.c b/zephyr/lib/vregion.c index 66ca649c7cf6..187d1ee89360 100644 --- a/zephyr/lib/vregion.c +++ b/zephyr/lib/vregion.c @@ -243,7 +243,7 @@ static void interim_heap_init(struct vregion *vr) /* interim heap starts right after current lifetime pointer, page-aligned */ interim_base = UINT_TO_POINTER(ALIGN_UP(POINTER_TO_UINT(vr->lifetime.ptr), - CONFIG_MM_DRV_PAGE_SIZE)); + CONFIG_DCACHE_LINE_SIZE)); interim_size = (vr->base + vr->size) - interim_base; LOG_INF("creating interim heap: lifetime used %zu, interim available %zu at %p", @@ -254,6 +254,17 @@ static void interim_heap_init(struct vregion *vr) vr->interim.heap.heap.init_mem = interim_base; vr->interim.heap.heap.init_bytes = interim_size; + /* + * 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 ehough. + */ + if (interim_size < 1024) { + LOG_WRN("Too little memory, %u bytes, left for interim heap", interim_size); + vr->type = VREGION_MEM_TYPE_INVALID; + return; + } + k_heap_init(&vr->interim.heap, interim_base, interim_size); vr->interim_initialized = true; } From 1c2831dc5e03d61c22857d9c6ef969e6c6402e7c Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 26 May 2026 22:35:24 +0300 Subject: [PATCH 14/20] vregion: call vregion_set_interim() from pipeline_comp_complete() Move the vregion_set_interim() call from module_prepare() to pipeline_comp_complete(). For DP modules vregion_set_interim() cannot be called from userspace, so call it directly from the pipeline completion path which runs in kernel context. Add a null check for the alloc object before dereferencing it. --- src/audio/module_adapter/module/generic.c | 4 ---- src/audio/pipeline/pipeline-graph.c | 10 +++++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index a1f31e7f42b4..b62ae38f415f 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -502,10 +502,6 @@ int module_prepare(struct processing_module *mod, comp_dbg(dev, "entry"); - /* Switch vregion to interim allocation mode for prepare and beyond */ - if (md->resources.alloc && md->resources.alloc->vreg) - vregion_set_interim(md->resources.alloc->vreg); - #if CONFIG_IPC_MAJOR_3 if (mod->priv.state == MODULE_IDLE) return 0; diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 47d5d0127fd0..9b426c880c43 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 @@ -287,8 +289,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->priv.resources.alloc) + vregion_set_interim(mod->priv.resources.alloc->vreg); + } current->priority = ppl_data->p->priority; From d650f079542624a6836e2ccfbcfc2b5ce61fe5bb Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 26 May 2026 23:49:02 +0300 Subject: [PATCH 15/20] module_adapter: fix memory API debug check for DP modules Add dp_thread field to module_resources so that the memory API debug check accepts allocations from both the IPC thread (rsrc_mngr) and the DP thread. Update MEM_API_CHECK_THREAD to check both thread IDs. Also change mod_alloc_ctx allocation from rmalloc to rzalloc to zero-initialize the structure. The code assumes the dp_thread member is NULL in case of a non-DP module. Both threads using the memory tracking features is not a problem since the memory operations are synchronized with semaphores and events so that both threads are never doing those operations at the same time. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module/generic.c | 5 +++-- src/audio/module_adapter/module_adapter.c | 2 +- src/include/sof/audio/module_adapter/module/generic.h | 1 + src/schedule/zephyr_dp_schedule_application.c | 4 ++++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index b62ae38f415f..73b043f95740 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -28,8 +28,9 @@ /* 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()); \ + k_tid_t tid = k_current_get(); \ + if ((res)->rsrc_mngr != tid && (res)->dp_thread != tid) \ + LOG_WRN("cur %p != mngr %p or dp %p", tid, (res)->rsrc_mngr, (res)->dp_thread); \ } while (0) #else #define MEM_API_CHECK_THREAD(res) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 84dcbaabfcea..3fb273a51c84 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -131,7 +131,7 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv goto emod; } - struct mod_alloc_ctx *alloc = rmalloc(flags, sizeof(*alloc)); + struct mod_alloc_ctx *alloc = rzalloc(flags, sizeof(*alloc)); if (!alloc) goto ealloc; diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 94827d86cd9f..de22610b6251 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -135,6 +135,7 @@ struct module_resources { struct mod_alloc_ctx *alloc; #if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) k_tid_t rsrc_mngr; + k_tid_t dp_thread; #endif }; diff --git a/src/schedule/zephyr_dp_schedule_application.c b/src/schedule/zephyr_dp_schedule_application.c index 1dbc2448e74f..9a1359070104 100644 --- a/src/schedule/zephyr_dp_schedule_application.c +++ b/src/schedule/zephyr_dp_schedule_application.c @@ -504,6 +504,10 @@ int scheduler_dp_task_init(struct task **task, const struct sof_uuid_entry *uid, 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); +#if CONFIG_MODULE_MEMORY_API_DEBUG + /* For a DP module the reource manager can also be the DP thread */ + mod->priv.resources.dp_thread = pdata->thread_id; +#endif unsigned int pidx; size_t size; From 8443be4ac1de9dba1a58d11e640b28d87fbc454b Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 27 May 2026 01:53:46 +0300 Subject: [PATCH 16/20] pipeline: allocate shared vregion for LL modules Create a per-pipeline vregion in pipeline_new() when the IPC4 pipeline extension payload specifies lifetime or interim heap sizes. The vregion size is the sum of both. Store the vregion pointer in struct pipeline. In module_adapter_new_ext() resolve the pipeline pointer before module_adapter_mem_alloc() so the pipeline's vregion can be passed down. LL modules on a pipeline with a vregion use it as their allocation backend via vregion_get(), instead of the driver's default heap. DP modules continue to create their own per-module vregion. Call vregion_set_interim() for the pipeline vregion in pipeline_complete() to switch the allocator to interim mode after all lifetime allocations are done. Release the pipeline vregion with vregion_put() in ipc_pipeline_free() before calling pipeline_free(). Warn if the refcount does not reach zero, indicating a module still holds a reference. Also fix a pre-existing leak in module_adapter_mem_free(): always free the per-module mod_alloc_ctx for LL modules regardless of the vregion refcount, since alloc is allocated from the system heap, not from the vregion. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 36 ++++++++++++++++++----- src/audio/pipeline/pipeline-graph.c | 14 +++++++++ src/include/sof/audio/pipeline.h | 2 ++ src/ipc/ipc4/helper.c | 5 ++++ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 3fb273a51c84..d3c18e7ba612 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -88,7 +88,8 @@ static struct vregion *module_adapter_dp_heap_new(const struct comp_ipc_config * static struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, const struct comp_ipc_config *config, - const struct module_ext_init_data *ext_init) + const struct module_ext_init_data *ext_init, + struct vregion *ppl_vreg) { struct k_heap *mod_heap; struct vregion *mod_vreg; @@ -113,6 +114,10 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv return NULL; } mod_heap = NULL; + } else if (ppl_vreg && config->proc_domain == COMP_PROCESSING_DOMAIN_LL) { + mod_vreg = vregion_get(ppl_vreg); + mod_heap = NULL; + heap_size = 0; } else { mod_heap = drv->user_heap; heap_size = 0; @@ -193,10 +198,12 @@ static void module_adapter_mem_free(struct processing_module *mod) #endif if (alloc->vreg) { struct vregion *mod_vreg = alloc->vreg; + uint32_t proc_domain = mod->dev->ipc_config.proc_domain; vregion_free(mod_vreg, mod->dev); vregion_free(mod_vreg, mod); - if (!vregion_put(mod_vreg)) + /* DP module alloc is freed later, but for LL modules we free it here */ + if (!vregion_put(mod_vreg) || proc_domain == COMP_PROCESSING_DOMAIN_LL) rfree(alloc); } else { sof_heap_free(mod_heap, mod->dev); @@ -251,7 +258,25 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, NULL; #endif - struct processing_module *mod = module_adapter_mem_alloc(drv, config, ext_init); +#if CONFIG_IPC_MAJOR_4 + struct ipc_comp_dev *ipc_pipe; + struct ipc *ipc = ipc_get(); + struct vregion *ppl_vreg = NULL; + + /* resolve the pipeline pointer early to pass its vregion to mem_alloc */ + ipc_pipe = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_PIPELINE, config->pipeline_id, + IPC_COMP_IGNORE_REMOTE); + if (ipc_pipe && ipc_pipe->pipeline) + ppl_vreg = ipc_pipe->pipeline->vreg; +#endif + + struct processing_module *mod = module_adapter_mem_alloc(drv, config, ext_init, +#if CONFIG_IPC_MAJOR_4 + ppl_vreg +#else + NULL +#endif + ); if (!mod) return NULL; @@ -310,12 +335,7 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, goto err; #if CONFIG_IPC_MAJOR_4 - struct ipc_comp_dev *ipc_pipe; - struct ipc *ipc = ipc_get(); - /* set the pipeline pointer if ipc_pipe is valid */ - ipc_pipe = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_PIPELINE, config->pipeline_id, - IPC_COMP_IGNORE_REMOTE); if (ipc_pipe) { dev->pipeline = ipc_pipe->pipeline; diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 9b426c880c43..4e1029c31f7a 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -135,6 +136,15 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ /* init pipeline */ p->heap = heap; + + /* Create vregion for pipeline and its modules if size info is available */ + if (pparams && pparams->mem_data && pparams->mem_data->heap_bytes) { + alloc->vreg = vregion_create(pparams->mem_data->heap_bytes); + if (!alloc->vreg) + pipe_cl_err("Failed to create pipeline vregion of %zu bytes, using heap", + pparams->mem_data->heap_bytes); + } + p->comp_id = comp_id; p->priority = priority; p->pipeline_id = pipeline_id; @@ -337,6 +347,10 @@ int pipeline_complete(struct pipeline *p, struct comp_dev *source, p->source_comp = source; p->sink_comp = sink; + + if (p->vreg) + vregion_set_interim(p->vreg); + p->status = COMP_STATE_READY; /* show heap status */ diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index 913a569c208c..e09920baf774 100644 --- a/src/include/sof/audio/pipeline.h +++ b/src/include/sof/audio/pipeline.h @@ -26,6 +26,7 @@ struct comp_dev; struct ipc; struct ipc_msg; struct k_heap; +struct vregion; /* * Pipeline status to stop execution of current path, but to keep the @@ -54,6 +55,7 @@ struct k_heap; */ struct pipeline { struct k_heap *heap; /**< heap used for allocating this pipeline */ + struct vregion *vreg; /**< shared vregion for pipeline modules */ uint32_t comp_id; /**< component id for pipeline */ uint32_t pipeline_id; /**< pipeline id */ uint32_t sched_id; /**< Scheduling component id */ diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 88b181450521..a81c6c36237e 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -513,6 +513,11 @@ __cold int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) return ret; } + if (ipc_pipe->pipeline->vreg) { + if (vregion_put(ipc_pipe->pipeline->vreg)) + tr_warn(&ipc_tr, "pipeline vregion still in use"); + } + /* free buffer, delete all tasks and remove from list */ ret = pipeline_free(ipc_pipe->pipeline); if (ret < 0) { From a2d5d59be7c8ff3ed280eae0899abdd47bcda404 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 28 May 2026 22:03:00 +0300 Subject: [PATCH 17/20] pipeline: add alloc context object to pipeline Replace the bare vregion pointer in struct pipeline with a mod_alloc_ctx object that bundles both the optional vregion and the heap pointer. The alloc context is always created in pipeline_new() and freed symmetrically in pipeline_free(), removing the separate cleanup that was in ipc_pipeline_free(). The pipeline object itself is now allocated through sof_ctx_zalloc() so it resides in the vregion when one is available, falling back to the default heap otherwise. LL modules share the pipeline's alloc context instead of only sharing the raw vregion pointer. A use_ppl_alloc flag gates the sharing to LL modules only, so DP modules continue to create their own vregion and alloc context as before. module_adapter_mem_free() detects whether a module's alloc belongs to its pipeline and either just releases the vregion reference (ppl_alloc case) or tears down the module's own alloc. Signed-off-by: Jyri Sarha --- src/audio/module_adapter/module_adapter.c | 84 ++++++++++++++--------- src/audio/pipeline/pipeline-graph.c | 41 +++++++---- src/include/sof/audio/pipeline.h | 4 +- src/ipc/ipc4/helper.c | 5 -- 4 files changed, 83 insertions(+), 51 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index d3c18e7ba612..27cce0b66259 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -89,7 +89,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv, const struct comp_ipc_config *config, const struct module_ext_init_data *ext_init, - struct vregion *ppl_vreg) + struct mod_alloc_ctx *ppl_alloc) { struct k_heap *mod_heap; struct vregion *mod_vreg; @@ -105,6 +105,8 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv uint32_t flags = config->proc_domain == COMP_PROCESSING_DOMAIN_DP ? SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT : SOF_MEM_FLAG_USER; size_t heap_size; + bool use_ppl_alloc = ppl_alloc && + config->proc_domain == COMP_PROCESSING_DOMAIN_LL; if (config->proc_domain == COMP_PROCESSING_DOMAIN_DP && IS_ENABLED(CONFIG_USERSPACE) && !IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) { @@ -114,9 +116,9 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv return NULL; } mod_heap = NULL; - } else if (ppl_vreg && config->proc_domain == COMP_PROCESSING_DOMAIN_LL) { - mod_vreg = vregion_get(ppl_vreg); - mod_heap = NULL; + } else if (use_ppl_alloc) { + mod_vreg = ppl_alloc->vreg ? vregion_get(ppl_alloc->vreg) : NULL; + mod_heap = ppl_alloc->heap; heap_size = 0; } else { mod_heap = drv->user_heap; @@ -124,26 +126,37 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv mod_vreg = NULL; } - if (!mod_vreg) + if (use_ppl_alloc) { + /* LL modules use the pipeline's alloc context */ + mod = sof_ctx_alloc(ppl_alloc, flags, sizeof(*mod), 0); + } else if (!mod_vreg) { mod = sof_heap_alloc(mod_heap, flags, sizeof(*mod), 0); - else if (flags & SOF_MEM_FLAG_COHERENT) + } else if (flags & SOF_MEM_FLAG_COHERENT) { mod = vregion_alloc_coherent(mod_vreg, sizeof(*mod)); - else + } else { mod = vregion_alloc(mod_vreg, sizeof(*mod)); + } if (!mod) { comp_cl_err(drv, "failed to allocate memory for module"); goto emod; } - struct mod_alloc_ctx *alloc = rzalloc(flags, sizeof(*alloc)); + struct mod_alloc_ctx *alloc; + + if (use_ppl_alloc) { + /* LL modules share the pipeline's alloc context */ + alloc = ppl_alloc; + } else { + alloc = rzalloc(flags, sizeof(*alloc)); + if (!alloc) + goto ealloc; - if (!alloc) - goto ealloc; + alloc->heap = mod_heap; + alloc->vreg = mod_vreg; + } memset(mod, 0, sizeof(*mod)); - alloc->heap = mod_heap; - alloc->vreg = mod_vreg; mod->priv.resources.alloc = alloc; mod_resource_init(mod); @@ -153,7 +166,9 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv * then it can be cached. Effectively it can be only cached in * single-core configurations. */ - if (mod_vreg) + if (use_ppl_alloc) + dev = sof_ctx_alloc(ppl_alloc, SOF_MEM_FLAG_COHERENT, sizeof(*dev), 0); + else if (mod_vreg) dev = vregion_alloc_coherent(mod_vreg, sizeof(*dev)); else dev = sof_heap_alloc(mod_heap, SOF_MEM_FLAG_COHERENT, sizeof(*dev), 0); @@ -172,14 +187,20 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv return mod; edev: - rfree(alloc); + if (!use_ppl_alloc) + rfree(alloc); ealloc: - if (mod_vreg) + if (use_ppl_alloc) + sof_ctx_free(ppl_alloc, mod); + else if (mod_vreg) vregion_free(mod_vreg, mod); else sof_heap_free(mod_heap, mod); emod: - vregion_put(mod_vreg); + if (use_ppl_alloc) + vregion_put(ppl_alloc->vreg); + else + vregion_put(mod_vreg); return NULL; } @@ -187,27 +208,26 @@ struct processing_module *module_adapter_mem_alloc(const struct comp_driver *drv static void module_adapter_mem_free(struct processing_module *mod) { struct mod_alloc_ctx *alloc = mod->priv.resources.alloc; - struct k_heap *mod_heap = alloc->heap; + bool ppl_alloc = mod->dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL && + mod->dev->pipeline && mod->dev->pipeline->alloc == alloc; /* * In principle it shouldn't even be needed to free individual objects * on the module heap since we're freeing the heap itself too */ #if CONFIG_IPC_MAJOR_4 - sof_heap_free(mod_heap, mod->priv.cfg.input_pins); + sof_heap_free(alloc->heap, mod->priv.cfg.input_pins); #endif - if (alloc->vreg) { - struct vregion *mod_vreg = alloc->vreg; - uint32_t proc_domain = mod->dev->ipc_config.proc_domain; - - vregion_free(mod_vreg, mod->dev); - vregion_free(mod_vreg, mod); - /* DP module alloc is freed later, but for LL modules we free it here */ - if (!vregion_put(mod_vreg) || proc_domain == COMP_PROCESSING_DOMAIN_LL) + sof_ctx_free(alloc, mod->dev); + sof_ctx_free(alloc, mod); + + if (ppl_alloc) { + /* alloc belongs to pipeline, just release vregion reference */ + vregion_put(alloc->vreg); + } else if (alloc->vreg) { + if (!vregion_put(alloc->vreg)) rfree(alloc); } else { - sof_heap_free(mod_heap, mod->dev); - sof_heap_free(mod_heap, mod); rfree(alloc); } } @@ -261,18 +281,18 @@ struct comp_dev *module_adapter_new_ext(const struct comp_driver *drv, #if CONFIG_IPC_MAJOR_4 struct ipc_comp_dev *ipc_pipe; struct ipc *ipc = ipc_get(); - struct vregion *ppl_vreg = NULL; + struct mod_alloc_ctx *ppl_alloc = NULL; - /* resolve the pipeline pointer early to pass its vregion to mem_alloc */ + /* resolve the pipeline pointer early to pass its alloc to mem_alloc */ ipc_pipe = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_PIPELINE, config->pipeline_id, IPC_COMP_IGNORE_REMOTE); if (ipc_pipe && ipc_pipe->pipeline) - ppl_vreg = ipc_pipe->pipeline->vreg; + ppl_alloc = ipc_pipe->pipeline->alloc; #endif struct processing_module *mod = module_adapter_mem_alloc(drv, config, ext_init, #if CONFIG_IPC_MAJOR_4 - ppl_vreg + ppl_alloc #else NULL #endif diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 4e1029c31f7a..06884dd01967 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -115,6 +115,7 @@ void pipeline_posn_init(struct sof *sof) struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_t priority, uint32_t comp_id, struct create_pipeline_params *pparams) { + struct mod_alloc_ctx *alloc; struct sof_ipc_stream_posn posn; struct pipeline *p; int ret; @@ -125,17 +126,13 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ /* show heap status */ heap_trace_all(0); - /* allocate new pipeline */ - p = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*p), 0); - if (!p) { - pipe_cl_err("Out of Memory"); + alloc = rzalloc(SOF_MEM_FLAG_USER, sizeof(*alloc)); + if (!alloc) { + pipe_cl_err("Failed to allocate pipeline alloc context"); return NULL; } - memset(p, 0, sizeof(*p)); - - /* init pipeline */ - p->heap = heap; + alloc->heap = heap; /* Create vregion for pipeline and its modules if size info is available */ if (pparams && pparams->mem_data && pparams->mem_data->heap_bytes) { @@ -145,6 +142,16 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ pparams->mem_data->heap_bytes); } + p = sof_ctx_zalloc(alloc, SOF_MEM_FLAG_USER, sizeof(*p), 0); + if (!p) { + pipe_cl_err("Out of Memory"); + goto free_alloc; + } + + /* init pipeline */ + p->heap = heap; + p->alloc = alloc; + p->comp_id = comp_id; p->priority = priority; p->pipeline_id = pipeline_id; @@ -177,7 +184,10 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ return p; free: - sof_heap_free(heap, p); + sof_ctx_free(alloc, p); +free_alloc: + vregion_put(alloc->vreg); + rfree(alloc); return NULL; } @@ -250,6 +260,8 @@ void pipeline_disconnect(struct comp_dev *comp, struct comp_buffer *buffer, int /* pipelines must be inactive */ int pipeline_free(struct pipeline *p) { + struct mod_alloc_ctx *alloc = p->alloc; + pipe_dbg(p, "entry"); /* @@ -270,7 +282,12 @@ int pipeline_free(struct pipeline *p) pipeline_posn_offset_put(p->posn_offset); /* now free the pipeline */ - sof_heap_free(p->heap, p); + sof_ctx_free(alloc, p); + + /* free alloc context and vregion */ + if (vregion_put(alloc->vreg)) + pipe_cl_warn("pipeline vregion still in use"); + rfree(alloc); /* show heap status */ heap_trace_all(0); @@ -348,8 +365,8 @@ int pipeline_complete(struct pipeline *p, struct comp_dev *source, p->source_comp = source; p->sink_comp = sink; - if (p->vreg) - vregion_set_interim(p->vreg); + if (p->alloc && p->alloc->vreg) + vregion_set_interim(p->alloc->vreg); p->status = COMP_STATE_READY; diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index e09920baf774..3bd9d039a00d 100644 --- a/src/include/sof/audio/pipeline.h +++ b/src/include/sof/audio/pipeline.h @@ -26,7 +26,7 @@ struct comp_dev; struct ipc; struct ipc_msg; struct k_heap; -struct vregion; +struct mod_alloc_ctx; /* * Pipeline status to stop execution of current path, but to keep the @@ -55,7 +55,7 @@ struct vregion; */ struct pipeline { struct k_heap *heap; /**< heap used for allocating this pipeline */ - struct vregion *vreg; /**< shared vregion for pipeline modules */ + struct mod_alloc_ctx *alloc; /**< shared alloc context for pipeline modules */ uint32_t comp_id; /**< component id for pipeline */ uint32_t pipeline_id; /**< pipeline id */ uint32_t sched_id; /**< Scheduling component id */ diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index a81c6c36237e..88b181450521 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -513,11 +513,6 @@ __cold int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) return ret; } - if (ipc_pipe->pipeline->vreg) { - if (vregion_put(ipc_pipe->pipeline->vreg)) - tr_warn(&ipc_tr, "pipeline vregion still in use"); - } - /* free buffer, delete all tasks and remove from list */ ret = pipeline_free(ipc_pipe->pipeline); if (ret < 0) { From fe7fbe1d8ab24f6f6b6e83f8cb1a2dd0f1073332 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 8 May 2026 00:07:29 +0300 Subject: [PATCH 18/20] 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 eefe685b19d3445481cc6b301570a805f7104caf Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 8 May 2026 00:15:42 +0300 Subject: [PATCH 19/20] 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. For now the biggest memory consumers, the SRAM copy of the filter coefficients, and the filter memory, are not allocated in init phase, but in prepare. That is why the interim requirement is so high and lifetime requirement so low. This can be changed but it needs changes in SRC processing module code. Signed-off-by: Jyri Sarha --- tools/topology/topology2/cavs-nocodec.conf | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/topology/topology2/cavs-nocodec.conf b/tools/topology/topology2/cavs-nocodec.conf index fea906f98741..320a8e8ff02c 100644 --- a/tools/topology/topology2/cavs-nocodec.conf +++ b/tools/topology/topology2/cavs-nocodec.conf @@ -698,9 +698,11 @@ 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 + interim_heap_bytes_requirement "$[(24 * 1024)]" + lifetime_heap_bytes_requirement 4096 + shared_bytes_requirement 0 } } } @@ -1382,9 +1384,11 @@ 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 + interim_heap_bytes_requirement "$[(24 * 1024)]" + lifetime_heap_bytes_requirement 4096 + shared_bytes_requirement 0 } } From b59e0c92d0c6b66665bc4789257413c7ab13c021 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 2 Jun 2026 23:28:42 +0300 Subject: [PATCH 20/20] 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 | 6 ++--- .../include/components/widget-common.conf | 27 +++---------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/tools/topology/topology2/include/common/tokens.conf b/tools/topology/topology2/include/common/tokens.conf index 48b498280130..987dd86c5392 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" { @@ -68,7 +66,7 @@ Object.Base.VendorToken { } "7" { - name "asrc" + name "asrc" rate_out 321 operation_mode 323 } 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