Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 22 additions & 28 deletions zephyr/lib/fast-get.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,40 +277,34 @@ void fast_put(struct k_heap *heap, const void *sram_ptr)
LOG_ERR("Put called to unknown address %p", sram_ptr);
goto out;
}

entry->refcount--;

if (!entry->refcount) {
#if CONFIG_USERSPACE
/* For large buffers, we need to:
* 1. Free the heap buffer FIRST (while partition still grants us access)
* 2. Then remove the partition (to prevent partition leaks)
* This order is critical - we must free while we still have access.
*/
if (entry->size > FAST_GET_MAX_COPY_SIZE) {
struct k_mem_partition part;
struct k_mem_domain *domain = entry->thread->mem_domain_info.mem_domain;
void *addr = entry->sram_ptr;

sof_heap_free(heap, addr);

part.start = (uintptr_t)addr;
part.size = ALIGN_UP(entry->size, CONFIG_MM_DRV_PAGE_SIZE);
part.attr = K_MEM_PARTITION_P_RO_U_RO | XTENSA_MMU_CACHED_WB;

int err = k_mem_domain_remove_partition(domain, &part);

if (err < 0)
LOG_WRN("partition removal failed err=%d", err);
LOG_DBG("freeing buffer %p", entry->sram_ptr);
sof_heap_free(heap, entry->sram_ptr);
}

} else
#endif /* CONFIG_USERSPACE */
{
/* Small buffers have no partitions, just free */
sof_heap_free(heap, entry->sram_ptr);
}
#if CONFIG_USERSPACE
if (entry->size > FAST_GET_MAX_COPY_SIZE) {
struct k_mem_partition part = {
.start = (uintptr_t)entry->sram_ptr,
.size = ALIGN_UP(entry->size, CONFIG_MM_DRV_PAGE_SIZE),
.attr = K_MEM_PARTITION_P_RO_U_RO | XTENSA_MMU_CACHED_WB,
};
struct k_mem_domain *domain = k_current_get()->mem_domain_info.mem_domain;

LOG_DBG("removing partition %p size %#zx from thread %p",
(void *)part.start, part.size, k_current_get());
int err = k_mem_domain_remove_partition(domain, &part);

Comment on lines +294 to +300
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

k_mem_domain_remove_partition() operates on a memory domain, which can be shared by multiple threads. Removing the partition from k_current_get()->mem_domain_info.mem_domain can therefore revoke access for other threads in the same domain that still hold references (entry->refcount > 0). To avoid use-after-revoke faults, this code should track granted partitions per-domain (and only remove when the last user in that domain releases), or otherwise enforce/document that each fast_get() user has a unique mem_domain.

Copilot uses AI. Check for mistakes.
if (err)
LOG_WRN("partition removal failed: %d", err);
}
Comment on lines +288 to +303
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

Partition removal is now executed on every fast_put() for large buffers, even when entry->refcount remains > 0. This breaks fast_get()/fast_put() reference counting: a thread can call fast_get() multiple times and still expect access after only some puts, but the first put would revoke access by removing the partition while the buffer is still in use. Partition removal needs to be tied to the last reference for the relevant owner (e.g., per-thread/per-domain refcount), not performed unconditionally on each put.

Copilot uses AI. Check for mistakes.
Comment on lines +288 to +303
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

This change targets CONFIG_USERSPACE behavior (partition add/remove), but existing fast_get/fast_put unit tests don’t appear to exercise userspace + large-buffer paths (e.g., multi-thread, partition add/remove, and the intended leak prevention). Consider adding a Zephyr ztest that enables CONFIG_USERSPACE and validates that: (1) multiple gets/puts from the same thread keep access until the final put, and (2) multiple threads (potentially in different domains) don’t leak partitions after puts.

Copilot uses AI. Check for mistakes.
#endif

if (!entry->refcount)
memset(entry, 0, sizeof(*entry));
}
out:
LOG_DBG("put %p, DRAM %p size %u refcnt %u", sram_ptr, entry ? entry->dram_ptr : 0,
entry ? entry->size : 0, entry ? entry->refcount : 0);
Expand Down
Loading