Skip to content

Conversation

@lgirdwood
Copy link
Member

@lgirdwood lgirdwood commented Oct 5, 2025

WIP: To be split into smaller patches.

  1. Add a simple contiguous virtual page allocator.

  2. Add a virtual region per pipeline/DP module.

  3. Integrate into mod_alloc() and mod_free().

  4. TODO: Make sure all pipeline resources exist within same virtual
    region.

@lgirdwood
Copy link
Member Author

@jsarha fyi - this will be needed by your updates.

return;

/* simple free, just increment free count, this is for tuning only */
p->batch_free_count++;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be decremented during allocator's alloc() call?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, intention here is we should be able to see if this is > 0 then there is room for heap optimization.


/* map the virtual blocks in virtual region to free physical blocks */
ret = sys_mm_drv_map_region_safe(page_context.virtual_region, vaddr,
0, pages * CONFIG_MM_DRV_PAGE_SIZE, SYS_MM_MEM_PERM_RW);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is allocating physical pages from anywhere in L2 memory?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Free physical pages from L2 SRAM

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Free physical pages from L2 SRAM

@lgirdwood right, but shouldn't this be allocating from a pre-allocated set of pages for this "heap" or am I misunderstanding the concept?

/* allocate memory */
ptr = p->batch_ptr;
p->batch_ptr += size;
p->batch_used += size;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are "batch" and "scratch" allocator names commonly used? I found a couple of references to scratch allocators, e.g. https://stackoverflow.com/questions/74956010/what-are-the-differences-between-block-stack-and-scratch-allocators#75044083 but there it's defined as "once allocated, never freed," similar to this your batch allocator?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and I've also found scratch memory as "temporary", but lets rename based on use. i.e. static and dynamic

Copy link
Contributor

@jsarha jsarha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be pretty straight forward to integrate this with my IPC code and the latest version of mod_alloc() PR. Could not find anything wrong in the page allocator code either.

switch (container->type) {
case MOD_RES_HEAP:
#if CONFIG_SOF_PACOVR
/* do we need to use the scratch heap or the batch heap? */
Copy link
Contributor

@jsarha jsarha Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think this would work. Its not guaranteed that memory allocated in initialization phase is freed in also in initialization phase. For the moment there are modules freeing memory at initialized phase, that was allocated during init pahse.

It should not be too hard to write a single pacovr_free() that knows from the pointer's memory range if it was allocated from scratch or batch (=> dynamic or static) heap.

@lgirdwood lgirdwood changed the title wip: alloc: add scratch & batch heaps on top of virtual pages wip: alloc: add static linear & dynamic zephyr heaps on top of virtual pages Oct 8, 2025
@lgirdwood
Copy link
Member Author

@jsarha updated, can you try this with your topology patch for pipeline memory. Thanks !

@lgirdwood lgirdwood marked this pull request as ready for review October 8, 2025 12:07
Copilot AI review requested due to automatic review settings October 8, 2025 12:07
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new PACOVR (Pre Allocated COntiguous Virtual memory Region) memory allocator system for pipeline resource management. The system provides virtual page allocation with both dynamic heap and static linear allocators built on top.

  • Adds virtual page allocator for contiguous memory regions
  • Implements PACOVR allocator with dynamic heap and static allocation capabilities
  • Integrates PACOVR into module allocation functions and pipeline lifecycle

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
zephyr/lib/vpages.c Virtual page allocator implementation with mapping/unmapping functionality
zephyr/lib/pacovr.c PACOVR allocator with dynamic heap and static linear allocation
zephyr/include/sof/lib/vpage.h API header for virtual page allocator
zephyr/include/sof/lib/pacovr.h API header for PACOVR allocator
zephyr/Kconfig Configuration options for PACOVR and virtual page elements
zephyr/CMakeLists.txt Build system integration for new virtual memory files
src/include/sof/audio/pipeline.h Pipeline structure extension to include PACOVR instance
src/audio/pipeline/pipeline-graph.c Pipeline creation/destruction integration with PACOVR
src/audio/module_adapter/module/generic.c Module allocation functions updated to use PACOVR
scripts/xtensa-build-zephyr.py Additional symlink creation for firmware deployment

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

void vpage_free(void *ptr)
{
k_mutex_lock(&page_context.lock, K_FOREVER);
vpages_free_and_unmap((uintptr_t *)ptr);
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'ret' is not in scope here. The return value from vpages_free_and_unmap() should be captured and checked.

Suggested change
vpages_free_and_unmap((uintptr_t *)ptr);
int ret = vpages_free_and_unmap((uintptr_t *)ptr);

Copilot uses AI. Check for mistakes.
* @brief Free virtual pages
* Frees previously allocated virtual memory pages and unmaps them.
*
* @param ptr
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation for the ptr parameter in the function comment block.

Suggested change
* @param ptr
* @param ptr Pointer to the start of the virtual memory pages to be freed.
* Must be page-aligned and previously allocated by the virtual page allocator.

Copilot uses AI. Check for mistakes.
Comment on lines 23 to 24
* @param[in] batch_size Size of the PACOVR batch region.
* @param[in] scratch_size Size of the scratch heap.
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter names in the documentation (batch_size, scratch_size) don't match the actual function parameter names (static_size, dynamic_size) in the implementation.

Copilot uses AI. Check for mistakes.
size_t total_size;

if (!static_size || !dynamic_size) {
LOG_ERR("error: invalid pacovr static size %d or dynamic size %d",
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using %d format specifier for size_t variables. Should use %zu for size_t to avoid potential format string issues.

Suggested change
LOG_ERR("error: invalid pacovr static size %d or dynamic size %d",
LOG_ERR("error: invalid pacovr static size %zu or dynamic size %zu",

Copilot uses AI. Check for mistakes.

/* check we have enough static space left */
if (p->static_used + size > p->static_size) {
LOG_ERR("error: pacovr static alloc failed for %d bytes, only %d bytes free",
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using %d format specifier for size_t variables. Should use %zu for size_t to avoid potential format string issues.

Suggested change
LOG_ERR("error: pacovr static alloc failed for %d bytes, only %d bytes free",
LOG_ERR("error: pacovr static alloc failed for %zu bytes, only %zu bytes free",

Copilot uses AI. Check for mistakes.
*
* @return Pointer to the allocated virtual memory region, or NULL on failure.
*/
void *vpage_alloc(uint32_t pages);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsigned int?

* @param ptr Pointer to store the address of allocated pages.
* @retval 0 if successful.
*/
static int vpages_alloc_and_map(uint32_t pages, void **ptr)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsigned int

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function returns an error code as a negative value.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function returns an error code as a negative value.

I meant for pages

err, pages, page_context.total_pages, page_context.free_pages);
}
LOG_INF("vpage_alloc ptr %p pages %d free %d/%d", ptr, pages, page_context.free_pages,
page_context.total_pages);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else for this?


/* allocate memory for bitmap bundles */
bundles = rzalloc(SOF_MEM_FLAG_KERNEL | SOF_MEM_FLAG_COHERENT,
bitmap_num_bundles * sizeof(uint32_t));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we know the size from line 277, so bundles could be static?

@lgirdwood lgirdwood changed the title wip: alloc: add static linear & dynamic zephyr heaps on top of virtual pages wip: alloc: virtual regions and vpage allocator. Oct 24, 2025
Copy link
Collaborator

@softwarecki softwarecki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, here’s my round of nitpicking:

  1. Looks like the ghost of memory zone is back, now wearing a fancy new name: region type. Quick question - will cacheability secretly depend on this type? Or is that just a plot twist waiting to happen?
  2. Consider moving page mapping logic into vregion. For lifetime allocators, mapping pages progressively as needed could help reduce memory usage.
  3. What is the purpose of the text region type? Whether vregion it's for a pipeline or a module, module executable code can be shared across multiple pipelines, so this seems wrong place.
  4. Should memory management code be integrated into Zephyr? My understanding was that this was the intended direction for VMH.
  5. Function naming feels inconsistent: *_vpages vs vregion_*. Worth aligning these.

I submitted a change request because this design looks like it's meant to replace the virtual heap, but it's missing a critical feature for deep buffering: predefined buffer sizes. The buffer size provided via ipc is only a hint. The firmware tries to allocate the largest possible buffer, and if memory is tight, it falls back to a smaller one. If the requested size exceeds available memory, allocation should still create the largest feasible buffer. Without predefined buffers it will lead to saturate memory and block further allocations. With predefined buffer sizes, we can pick the largest available option while leaving others for future allocations.

* @param ptr Pointer to store the address of allocated pages.
* @retval 0 if successful.
*/
static int vpages_alloc_and_map(uint32_t pages, void **ptr)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function returns an error code as a negative value.

/* store the size and virtual page number in first free alloc element,
* we have already checked for a free element before the mapping.
*/
for (int i = 0; i < VPAGE_MAX_ALLOCS; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use page_context.num_elems as array index.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed the variable name, as this the number of elems in use rather than total number of elems to search.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rather use lists to avoid scanning the array each time. Can be a follow-up optimisation

* Allocates virtual memory pages from the virtual page allocator.
*
* @param pages Number of 4kB pages to allocate.
* @retval ptr to allocated pages if successful.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are not the ptr param.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

int ret;

/* check for valid ptr which must be page aligned */
if (!ptr || ((uintptr_t)ptr % CONFIG_MM_DRV_PAGE_SIZE) != 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IS_ALIGNED(ptr, CONFIG_MM_DRV_PAGE_SIZE)

  • optional || !page_context.num_elems.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

LOG_DBG("found allocation element %d pages %d vpage %d for ptr %p",
i, page_context.velems[i].pages,
page_context.velems[i].vpage, ptr);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To eliminate the loop during page allocation, I suggest replace the removed element with the last item in the array. When allocating, the new element is always added at the end of the array.

page_context.num_elems--;
page_context.velems[i] = page_context.velems[page_context.num_elems];
page_context.velems[page_context.num_elems].pages = 0;
page_context.velems[page_context.num_elems].vpage = 0;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment above about renaming the variable name to make meaning clearer..

/* calculate new heap object size for object and alignments */
heap_obj_size = aligned_ptr - heap->ptr + size;

/* check we have enough shared static space left */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shared?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

* @param[in] vr Pointer to the virtual region instance.
* @param[in] ptr Pointer to the memory to free.
*/
void lifetime_free(struct vregion *vr, struct vlinear_heap *heap, void *ptr)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing static? lifetime_alloc is static.
Unused vr param.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

return interim_alloc(vr, &vr->interim_shared, size, alignment);
case VREGION_MEM_TYPE_LIFETIME_SHARED:
return lifetime_alloc(vr, &vr->lifetime_shared, size,
alignment < CONFIG_DCACHE_LINE_SIZE ? CONFIG_DCACHE_LINE_SIZE : alignment,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this logic to the lifetime_alloc function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This avoids the need to pass a shared flag today.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAX(alignment, CONFIG_DCACHE_LINE_SIZE)

}

/* allocate module in correct vregion*/
//TODO: add coherent flag for cross core DP modules
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding the ZERO flag as well to replace rzalloc and avoid introducing additional memset calls.

return;
}

LOG_ERR("error: vregion free invalid pointer %p", ptr);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert / panic?

@lgirdwood
Copy link
Member Author

Alright, here’s my round of nitpicking:

  1. Looks like the ghost of memory zone is back, now wearing a fancy new name: region type. Quick question - will cacheability secretly depend on this type? Or is that just a plot twist waiting to happen?

No ghosts here, the old zone was tied to very limited memory types and capabilities from ancient HW e.g. to make sure we didn't squander things like DMAable memory etc.

A region is partitioned into different areas tied to usage in our pipeline lifecycle. There is not type like DMA, LP, HP RAM etc its all just a virtual page. A region is partitioned this way as its easier to then assign attributes for sharing to different cores/domains and to also limit execution (all where permitted by HW).

  1. Consider moving page mapping logic into vregion. For lifetime allocators, mapping pages progressively as needed could help reduce memory usage.

What would moving the remapping logic gain us ? Keeping the page allocator in a separate file/API keeps things simpler and exposes a page allocator API that can be used as needed independently of regions.

Agree, growing the lifetime allocators would save some pages if the runtime size < topology size, but this would lead to fragmentation. i.e. we have to track several virtual page (start, size) per pipeline. This could be something that can come later if we want to add more tracking, but keeping it simple is key at first.

  1. What is the purpose of the text region type? Whether vregion it's for a pipeline or a module, module executable code can be shared across multiple pipelines, so this seems wrong place.

So its an optional usage partition with main use being TEXT for 3P DP modules. This keeps domain tracking easier and these pages will get RO EXEC permission for that domain.

  1. Should memory management code be integrated into Zephyr? My understanding was that this was the intended direction for VMH.

Not atm. We need to get this working for audio as first step. If the vpages part is useful and generic, yes then it can go to Zephyr. The vregion part is probably too specific for our use case.

  1. Function naming feels inconsistent: *_vpages vs vregion_*. Worth aligning these.

These are 2 separate APIs hence the difference in naming.

I submitted a change request because this design looks like it's meant to replace the virtual heap, but it's missing a critical feature for deep buffering: predefined buffer sizes. The buffer size provided via ipc is only a hint. The firmware tries to allocate the largest possible buffer, and if memory is tight, it falls back to a smaller one. If the requested size exceeds available memory, allocation should still create the largest feasible buffer. Without predefined buffers it will lead to saturate memory and block further allocations. With predefined buffer sizes, we can pick the largest available option while leaving others for future allocations.

This is factored in to the vregion size allocation for KPB and Deepbuffer like use cases today. i.e. SW has to include in the total memory budget. Its not perfect but I dont want to change too much here until pipeline 2.0 is completed (and at that point we can factor in the pipeline 2.0 changes around the buffers).

Currently the symlink for sof-ipc4/platform/sof-basefw.ri is not
created. Fix this so that deployable builds can be copied directly
to target from staging directory.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Use a relative directory to script dir for default workspace
if workspace is not set.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Determine workspace if environment variable not defined.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add a simple virtual page allocator that uses the Zephyr memory mapping
infrastructure to allocate pages from a virtual region and map them
to physical pages.

Due to simplicity, the number of allocations is limited to
CONFIG_SOF_VPAGE_MAX_ALLOCS

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add support for per pipeline and per module virtual memory regions.

The intention is to provide a single virtual memory region per pipeline or
per DP module that can simplify module/pipeline memory management.

The region takes advantage of the way pipeline and modules are constructed,
destroyed and used during their lifetimes.

1) memory tracking - 1 pointer/size per pipeline or DP module.
2) memory read/write/execute permissions
3) memory sharing across cores and domains.
4) cache utilization.

Modules and pipelines will allocate from their region only and this
will be abstracted via the allocation APIs.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Split into a common and heap derived allocator files. This is in
preparation for memory regions.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Allocate a vregion for pipeline module allocations.

TODO: get the sizes from SW.
TODO: abstract usage.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Make DP modules use their own vregion for allocations.

TODO: abstract usage.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
In preparation for pipeline specific memory regions make sure we get
the pipeline prior to module memory allocation and pass teh pipeline
to the module allocation API.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Convert rmalloc and rballoc APIs to mod_alloc APIs.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Convert rmalloc and rballoc APIs to mod_alloc APIs.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Convert rmalloc and rballoc APIs to mod_alloc APIs.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Use mod_alloc() API internally within fast-get() and fast-put() so that
resources will be tracked at the module level.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add a module memory abstraction for virtual region usage.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Fix valgrind memory leak warnings by freeing resources.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add support for cmocka usage of mod_alloc() APIs directly and indirectly
via fast_get() APIs. This change means adding module API support into the
fast-get-test since the module will track the resource usage.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Kernel prints on IPC timeout, useful if oops is not generated.
Developer aid.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
ipc4_audio_format_to_stream_params() will clear params and hence
clear the previously set buffer size. Fix.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
No need to keep reallocating ipc4 stream params as IPC4 usage has a
constant size with 0 extended data. Only allocate once.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants