vpage / vregion: a minimal version with a test#10636
vpage / vregion: a minimal version with a test#10636lyakh wants to merge 3 commits intothesofproject:mainfrom
Conversation
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> Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@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) 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> Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
Add a boot-time test for basic vregion functionality. Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
There was a problem hiding this comment.
Pull request overview
Introduces a minimal “virtual pages” allocator and “virtual regions” (partitioned into interim + lifetime allocators) for SOF on Zephyr/ACE, along with a basic boot test and the required Kconfig/build/board configuration plumbing.
Changes:
- Add
vpagecontiguous virtual-page allocator andvregionallocator built on top of it. - Wire new sources and configs into Zephyr build + Kconfig (including new virtual region attribute).
- Add a Ztest-based boot test for
vregion, and bump ACE board virtual region count.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
zephyr/test/vregion.c |
Adds a basic vregion allocator test under boot tests. |
zephyr/test/CMakeLists.txt |
Builds the new test when CONFIG_SOF_VREGIONS is enabled. |
zephyr/lib/vregion.c |
Implements vregion (interim k_heap + lifetime linear allocator). |
zephyr/lib/vpages.c |
Implements vpage allocator and registers its init via SYS_INIT. |
zephyr/lib/alloc.c |
Adjusts virtual heap bundle sizing/comments to match new region sizing. |
zephyr/include/sof/lib/vregion.h |
Public API for vregion. |
zephyr/include/sof/lib/vpages.h |
Public API for vpage. |
zephyr/include/sof/lib/regions_mm.h |
Adds VIRTUAL_REGION_VPAGES_ATTR. |
zephyr/Kconfig |
Adds SOF_VREGIONS, SOF_VPAGE_MAX_ALLOCS, SOF_ZEPHYR_VIRTUAL_PAGE_REGION_SIZE, and updates heap region sizing defaults/wording. |
zephyr/CMakeLists.txt |
Builds vpages.c/vregion.c when CONFIG_SOF_VREGIONS is enabled. |
app/boards/intel_adsp_ace40_nvls.conf |
Increases virtual region count to 3 for VPAGES region. |
app/boards/intel_adsp_ace40_nvl.conf |
Increases virtual region count to 3 for VPAGES region. |
app/boards/intel_adsp_ace30_wcl.conf |
Increases virtual region count to 3 for VPAGES region. |
app/boards/intel_adsp_ace30_ptl.conf |
Increases virtual region count to 3 for VPAGES region. |
app/boards/intel_adsp_ace20_lnl.conf |
Increases virtual region count to 3 for VPAGES region. |
app/boards/intel_adsp_ace15_mtpm.conf |
Increases virtual region count to 3 for VPAGES region. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /** | ||
| * @brief Initialize virtual page allocator | ||
| * | ||
| * Initializes a virtual page allocator that manages a virtual memory region | ||
| * using a page table and block structures. | ||
| * | ||
| * @retval 0 if successful. | ||
| * @retval -ENOMEM on creation failure. | ||
| */ | ||
| int vpage_init(void); | ||
|
|
There was a problem hiding this comment.
vpages.h declares int vpage_init(void);, but zephyr/lib/vpages.c only defines init_vpages() as a SYS_INIT hook and does not provide a vpage_init() symbol. This makes the public header/API inconsistent and will cause link failures for any caller. Either add an exported vpage_init() wrapper or remove the declaration if initialization is exclusively via SYS_INIT.
| /** | |
| * @brief Initialize virtual page allocator | |
| * | |
| * Initializes a virtual page allocator that manages a virtual memory region | |
| * using a page table and block structures. | |
| * | |
| * @retval 0 if successful. | |
| * @retval -ENOMEM on creation failure. | |
| */ | |
| int vpage_init(void); | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| /** | |
| * @brief Allocate virtual pages | |
| * Allocates a specified number of contiguous virtual memory pages by mapping | |
| * physical pages. | |
| * | |
| * @param[in] pages Number of 4kB pages to allocate. | |
| * | |
| * @return Pointer to the allocated virtual memory region, or NULL on failure. | |
| */ | |
| void *vpage_alloc(unsigned int pages); | |
| /** | |
| * @brief Free virtual pages | |
| * Frees previously allocated virtual memory pages and unmaps them. | |
| * | |
| * @param[in] ptr Pointer to the memory pages to free. | |
| */ | |
| void vpage_free(void *ptr); |
| if (!lifetime_size || !interim_size) { | ||
| LOG_ERR("error: invalid vregion lifetime size %d or interim size %d", | ||
| lifetime_size, interim_size); | ||
| return NULL; |
There was a problem hiding this comment.
vregion_create() logs lifetime_size/interim_size with %d, but these are size_t. This is a format mismatch that can cause build failures with -Wformat and incorrect output. Please use %zu (and similarly update other LOG_* size/used logs in this file).
| LOG_INF("new at base %p size %#x pages %d struct embedded at %p", | ||
| (void *)vr->base, total_size, pages, (void *)vr); | ||
| LOG_DBG(" interim size %#x at %p", interim_size, (void *)vr->interim.heap.heap.init_mem); | ||
| LOG_DBG(" lifetime size %#x at %p", lifetime_size, (void *)vr->lifetime.base); |
There was a problem hiding this comment.
The LOG_INF/LOG_DBG calls here print total_size/interim_size/lifetime_size using %#x, but these are size_t. On 64-bit builds this can truncate and can fail with -Wformat. Prefer %zu or %#zx for sizes.
| if (!alignment) | ||
| alignment = 4; /* default align 4 bytes */ | ||
|
|
||
| switch (type) { | ||
| case VREGION_MEM_TYPE_INTERIM: | ||
| return interim_alloc(&vr->interim, size, alignment); | ||
| case VREGION_MEM_TYPE_LIFETIME: | ||
| return lifetime_alloc(&vr->lifetime, size, alignment); |
There was a problem hiding this comment.
vregion_alloc_align() accepts any non-zero alignment and passes it to k_heap_aligned_alloc() / uses it in ALIGN_UP(). Both typically require a power-of-two alignment; non-power-of-two values can assert or behave incorrectly. Consider validating alignment (power-of-two and at least sizeof(void *) if required) and returning NULL with an error log on invalid input.
| LOG_DBG("destroy %p size %#x pages %d", (void *)vr->base, vr->size, vr->pages); | ||
| LOG_DBG(" lifetime used %d free count %d", vr->lifetime.used, vr->lifetime.free_count); |
There was a problem hiding this comment.
vregion_destroy() logs vr->size and vr->lifetime.used with %#x/%d, but these are size_t. Please update these format strings to the correct size_t specifiers to avoid -Wformat issues.
| /* now find the virtual region in all memory regions */ | ||
| virtual_memory_regions = sys_mm_drv_query_memory_regions(); | ||
| SYS_MM_DRV_MEMORY_REGION_FOREACH(virtual_memory_regions, region) { | ||
| if (region->attr == VIRTUAL_REGION_VPAGES_ATTR) { | ||
| vpage_ctx.virtual_region = region; | ||
| break; | ||
| } | ||
| } | ||
| sys_mm_drv_query_memory_regions_free(virtual_memory_regions); | ||
|
|
||
| /* check for a valid region */ | ||
| if (!vpage_ctx.virtual_region) { | ||
| LOG_ERR("error: no valid virtual region found"); | ||
| k_panic(); | ||
| } | ||
|
|
||
| block_count = region->size / CONFIG_MM_DRV_PAGE_SIZE; | ||
| if (block_count == 0) { | ||
| LOG_ERR("error: virtual region too small %d", region->size); | ||
| k_panic(); |
There was a problem hiding this comment.
init_vpages() stores vpage_ctx.virtual_region = region from sys_mm_drv_query_memory_regions(), then calls sys_mm_drv_query_memory_regions_free(). If the returned list owns the region storage (as the _free API suggests), vpage_ctx.virtual_region and the subsequent region->size access become dangling/use-after-free. Consider either keeping virtual_memory_regions for the allocator lifetime, or copying the found sys_mm_drv_region into persistent storage and using vpage_ctx.virtual_region (not the iterator variable) afterwards.
| /* find the allocation element */ | ||
| for (int i = 0; i < VPAGE_MAX_ALLOCS; i++) { | ||
| if (vpage_ctx.velems[i].pages > 0 && vpage_ctx.velems[i].vpage == alloc_idx) { | ||
| pages = vpage_ctx.velems[i].pages; | ||
|
|
||
| LOG_DBG("found allocation element %d pages %u vpage %u for ptr %p", | ||
| i, vpage_ctx.velems[i].pages, | ||
| vpage_ctx.velems[i].vpage, ptr); | ||
|
|
||
| /* clear the element */ | ||
| vpage_ctx.velems[i].pages = 0; | ||
| vpage_ctx.velems[i].vpage = 0; | ||
| vpage_ctx.num_elems_in_use--; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| /* check we found allocation element */ | ||
| if (pages == 0) { | ||
| LOG_ERR("error: invalid page pointer %p not found", ptr); | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| /* unmap the pages from virtual region */ | ||
| ret = sys_mm_drv_unmap_region((void *)ptr, pages * CONFIG_MM_DRV_PAGE_SIZE); | ||
| if (ret < 0) { | ||
| LOG_ERR("error: failed to unmap virtual region %p pages %u, error %d", | ||
| ptr, pages, ret); | ||
| return ret; | ||
| } |
There was a problem hiding this comment.
vpages_free_and_unmap() clears the allocation tracking element (and decrements num_elems_in_use) before unmapping/freeing. If sys_mm_drv_unmap_region() or sys_mem_blocks_free_contiguous() fails, the allocator will have lost the bookkeeping for a still-allocated range, making the allocation unfreeable and corrupting accounting. Update bookkeeping only after the unmap+free operations succeed (or restore it on failure).
| if (ret < 0) | ||
| LOG_ERR("vpage_alloc failed %d for %d pages, total %d free %d", | ||
| ret, pages, vpage_ctx.total_pages, vpage_ctx.free_pages); | ||
| else | ||
| LOG_INF("vpage_alloc ptr %p pages %u free %u/%u", ptr, pages, vpage_ctx.free_pages, | ||
| vpage_ctx.total_pages); |
There was a problem hiding this comment.
Several LOG_* calls here use %d for unsigned values (e.g. pages, total_pages/free_pages). This can trigger -Wformat warnings (often treated as errors) and prints incorrectly for large values. Please use the correct unsigned specifiers like %u.
| block_count = region->size / CONFIG_MM_DRV_PAGE_SIZE; | ||
| if (block_count == 0) { | ||
| LOG_ERR("error: virtual region too small %d", region->size); | ||
| k_panic(); |
There was a problem hiding this comment.
init_vpages() logs and checks region->size using %d, but size is typically size_t. This is a format mismatch and can break builds with -Wformat. Use %zu/%#zx as appropriate and prefer vpage_ctx.virtual_region->size once the region is selected.
| if (ret < 0) { | ||
| LOG_ERR("error: failed to map virtual region %p to physical region %p, error %d", | ||
| vaddr, vpage_ctx.virtual_region->addr, ret); | ||
| sys_mem_blocks_free(&vpage_ctx.vpage_blocks, pages, &vaddr); |
There was a problem hiding this comment.
In vpages_alloc_and_map(), the error path after sys_mm_drv_map_region_safe() uses sys_mem_blocks_free(&vpage_ctx.vpage_blocks, pages, &vaddr). Since the allocation was done with sys_mem_blocks_alloc_contiguous(), this should be freed with the matching contiguous free API; otherwise the virtual blocks may remain allocated and leak.
| sys_mem_blocks_free(&vpage_ctx.vpage_blocks, pages, &vaddr); | |
| sys_mem_blocks_free_contiguous(&vpage_ctx.vpage_blocks, pages, &vaddr); |
This is a slightly simplified version of #10281 with an added test