Skip to content

[DRAFT] userspace LL/audio test PR#10558

Draft
kv2019i wants to merge 80 commits intothesofproject:mainfrom
kv2019i:feat/userspace-ll-wip
Draft

[DRAFT] userspace LL/audio test PR#10558
kv2019i wants to merge 80 commits intothesofproject:mainfrom
kv2019i:feat/userspace-ll-wip

Conversation

@kv2019i
Copy link
Copy Markdown
Collaborator

@kv2019i kv2019i commented Feb 19, 2026

SOF has recently gained ability to run DP (=preemptable audio tasks) in Zephyr user-space.

This PR is an early stage pull-request for changes to extend this capability to all of the audio pipeline code, and specifically the LL (low-latency) tasks.

This early stage as the design is not set in stone and the PR uses a number of short cuts in order to move (and tests) incrementally larger sets of audio functionality.

@kv2019i
Copy link
Copy Markdown
Collaborator Author

kv2019i commented Feb 19, 2026

Running the pipeline_two_components_user test added in this PR, on a Intel PTL, looks like this:

START - pipeline_two_components_user
[    0.000163] <inf> sof_boot_test: pipeline_two_components: copier module_id = 3
[    0.000163] <inf> sof_boot_test: pipeline_two_components: running test with user memory domain
[    0.000163] <inf> pipe: pipeline_new: pipeline new pipe_id 2 priority 0
[    0.000163] <dbg> ipc: ipc4_get_drv: found type 22, uuid 0xa00bc350U
[    0.000163] <dbg> module_adapter: module_adapter_new_ext: start
[    0.000163] <dbg> module_adapter: module_adapter_mem_alloc: using ll user heap for module
[    0.000165] <dbg> module_adapter: module_init: comp:2 0x3 entry
[    0.000165] <dbg> dma: z_impl_sof_dma_get: dma_get(), dma-probe id = 0
[    0.000165] <inf> dma: z_impl_sof_dma_get: dma_get() ID 0 sref = 1 busy channels 0
[    0.000165] <dbg> module_adapter: module_init: comp:2 0x3 done
[    0.000165] <dbg> module_adapter: module_adapter_new_ext: comp:2 0x3 done
[    0.000165] <dbg> ipc: ipc4_add_comp_dev: add comp 0x3
[    0.000165] <dbg> ll_schedule: zephyr_domain_thread_tid: entry
[    0.000165] <dbg> component: comp_grant_access_to_thread: grant access to mutex 0x400bf798 for thread 0x400bf6c0
[    0.000165] <dbg> component: comp_grant_access_to_thread: grant access to mutex 0x400bf798 for thread 0x401894e0
[    0.000165] <inf> sof_boot_test: pipeline_two_components: host copier created, comp_id = 0x3
[    0.000165] <dbg> ipc: ipc4_get_drv: found type 22, uuid 0xa00bc350U
[    0.000165] <dbg> module_adapter: module_adapter_new_ext: start
[    0.000165] <dbg> module_adapter: module_adapter_mem_alloc: using ll user heap for module
[    0.000165] <dbg> module_adapter: module_init: comp:2 0x10003 entry
[    0.000165] <inf> copier: copier_dai_init: comp:2 0x10003 dd 0x4019d280 initialized, index -1
[    0.000165] <dbg> dma: z_impl_sof_dma_get: dma_get(), dma-probe id = 0
[    0.000165] <inf> dma: z_impl_sof_dma_get: dma_get() ID 0 sref = 1 busy channels 0
[    0.000165] <inf> ipc: dai_config: comp:2 0x10003 dai type = 3 index = 0 dd 0x4019d280
[    0.000165] <dbg> ipc: dai_config: comp:2 0x10003 Alloc dai_spec_config 0xa019d480 size size 84
[    0.000165] <dbg> module_adapter: module_init: comp:2 0x10003 done
[    0.000165] <dbg> module_adapter: module_adapter_new_ext: comp:2 0x10003 done
[    0.000165] <dbg> ipc: ipc4_add_comp_dev: add comp 0x10003
[    0.000165] <dbg> ll_schedule: zephyr_domain_thread_tid: entry
[    0.000165] <dbg> component: comp_grant_access_to_thread: grant access to mutex 0x400bf7e0 for thread 0x400bf6c0
[    0.000165] <dbg> component: comp_grant_access_to_thread: grant access to mutex 0x400bf7e0 for thread 0x401894e0
[    0.000165] <inf> sof_boot_test: pipeline_two_components: DAI copier created, comp_id = 0x10003
[    0.000166] <dbg> buffer: buffer_alloc: buffer_alloc()
[    0.000166] <dbg> buffer: buffer_alloc_struct: buffer_alloc_struct()
[    0.000166] <dbg> userspace_helper: user_grant_dai_access_all: Granted DAI access to thread 0x401894e0 for 45 devices
[    0.000166] <dbg> userspace_helper: user_grant_dma_access_all: Granted DMA device access: dma@72c00 to thread 0x401894e0
[    0.000166] <dbg> userspace_helper: user_grant_dma_access_all: Granted DMA device access: dma@72800 to thread 0x401894e0
[    0.000166] <dbg> userspace_helper: user_grant_dma_access_all: Granted DMA device access: dma@79800 to thread 0x401894e0
[    0.000166] <dbg> userspace_helper: user_grant_dma_access_all: Granted DMA device access: dma@79400 to thread 0x401894e0
[    0.000166] <inf> sof_boot_test: pipeline_two_components: user thread started, waiting for completion
[    0.000170] <dbg> pipe: pipeline_reset: pipe:2 0x3 pipe reset
[    0.000170] <dbg> pipe: pipeline_comp_reset: pipe:2 0x3 current->comp.id = 0x3, dir = 0
[    0.000170] <dbg> module_adapter: module_adapter_reset: comp:2 0x3 resetting
[    0.000170] <dbg> copier: copier_reset: comp:2 0x3 entry
[    0.000170] <dbg> buffer: comp_buffer_free: buf:0 0x0 buffer_free()
[    0.000170] <dbg> module_adapter: module_adapter_reset: comp:2 0x3 done
[    0.000170] <dbg> pipe: pipeline_comp_reset: pipe:2 0x3 current->comp.id = 0x10003, dir = 0
[    0.000170] <dbg> module_adapter: module_adapter_reset: comp:2 0x10003 resetting
[    0.000170] <dbg> copier: copier_reset: comp:2 0x10003 entry
[    0.000170] <dbg> buffer: comp_buffer_free: buf:0 0x0 buffer_free()
[    0.000170] <dbg> module_adapter: module_adapter_reset: comp:2 0x10003 done
[    0.000170] <dbg> pipe: pipeline_disconnect: comp:2 0x3 disconnect buffer 0 as sink
[    0.000170] <dbg> pipe: pipeline_disconnect: comp:2 0x10003 disconnect buffer 0 as source
[    0.000170] <dbg> buffer: comp_buffer_free: buf:0 0x0 buffer_free()
[    0.000170] <dbg> buffer: comp_buffer_free: buf:0 0x0 buffer_free()
[    0.000170] <dbg> module_adapter: module_adapter_reset: comp:2 0x10003 done
[    0.000170] <dbg> pipe: pipeline_disconnect: comp:2 0x3 disconnect buffer 0 as sink
[    0.000170] <dbg> pipe: pipeline_disconnect: comp:2 0x10003 disconnect buffer 0 as source
[    0.000170] <dbg> buffer: comp_buffer_free: buf:0 0x0 buffer_free()
[    0.000170] <dbg> module_adapter: module_adapter_free: comp:2 0x3 start
[    0.000170] <inf> dma: z_impl_sof_dma_put: dma_put(), dma = 0x400c8488, sref = 0
[    0.000170] <dbg> module_adapter: module_adapter_free: comp:2 0x10003 start
[    0.000170] <inf> dma: z_impl_sof_dma_put: dma_put(), dma = 0x400c8518, sref = 0
[    0.000170] <dbg> pipe: pipeline_free: pipe:2 0x3 entry
[    0.000170] <inf> sof_boot_test: pipeline_two_components: two component pipeline test complete
 PASS - pipeline_two_components_user in 0.023 seconds

@kv2019i kv2019i force-pushed the feat/userspace-ll-wip branch from 9cdb1be to 37b0412 Compare March 9, 2026 17:07
@kv2019i
Copy link
Copy Markdown
Collaborator Author

kv2019i commented Mar 9, 2026

V2 snapshot pushed:

  • pipeline_two_components() tests now passes on Intel PTL hardware. a pipeline with two components is created, resources set up, pipeline is run with LL scheduler and then torn down freeing up all resources
  • introduces change to schedule.h interface to support LL scheduler
  • multiple limitations still in code (marked with WIP/HACK prefixes): limited to single core, some impact to non-user builds

@kv2019i kv2019i force-pushed the feat/userspace-ll-wip branch from 37b0412 to 7317b18 Compare March 17, 2026 18:29
@kv2019i
Copy link
Copy Markdown
Collaborator Author

kv2019i commented Mar 17, 2026

V3 snapshot pushed:

  • added skeleton implementation for user-space IPC handling (only one IPC message type supported for now)
  • added a test case (ipc4_create_pipeline_check) to test the above

@kv2019i
Copy link
Copy Markdown
Collaborator Author

kv2019i commented Mar 17, 2026

Test output for the new test added in V3:

[    0.000063] <inf> ipc: ipc_cmd: rx   : 0x110a1800|0x0
[    0.000063] <dbg> ipc: ipc_user_forward_cmd: IPC: forward cmd 110a1800
[    0.000063] <dbg> ipc: ipc_user_thread_fn: IPC user wake, mask 1
[    0.000063] <dbg> ipc: ipc_pipeline_new: ipc: pipeline id = 10
[    0.000063] <inf> ipc: ipc4_create_pipeline: pipe_desc a00f9f74, instance 10
[    0.000063] <inf> pipe: pipeline_new: pipeline new pipe_id 10 priority 3
[    0.000063] <wrn> pipe: pipeline_new: pipeline trace settings cannot be copied
[    0.000063] <wrn> pipe: pipeline_new: pipeline position reporting not available
[    0.000063] <inf> ipc: ipc4_create_pipeline: pipeline_new() -> 0xa01ba580
[    0.000063] <inf> ipc: ipc4_create_pipeline: success
[    0.000063] <err> ipc_service: ipc_service_release_rx_buffer: Endpoint not registered

[    0.000063] <wrn> ipc: ipc_platform_complete_cmd: ipc_service_release_rx_buffer() failed: -2
[    0.000063] <dbg> ipc: ipc_cmd: tx-reply     : 0x31000000|0x0
[    0.000063] <wrn> ipc: ipc_msg_send: Skipping IPC worker schedule. TODO to fix

[    0.000063] <inf> sof_boot_test: ipc4_create_pipeline_check: ipc_cmd() returned for pipeline id=10
[    0.000063] <inf> sof_boot_test: ipc4_create_pipeline_check: pipeline verified in IPC comp list
[    0.000063] <dbg> pipe: pipeline_free: pipe:10 0x0 entry
[    0.000063] <inf> sof_boot_test: ipc4_create_pipeline_check: ipc4 create pipeline test complete
[    0.000063] <inf> main: sof_app_main: SOF on intel_adsp
[    0.000063] <err> ipc_service: ipc_service_get_tx_buffer_size: Endpoint not registered

[    0.000063] <err> ipc_service: ipc_service_send: Endpoint not registered

[    0.000063] <err> main: sof_app_main: SOF initialization failed
[    0.000063] <inf> main: sof_app_main: SOF initialized
===================================================================
PROJECT EXECUTION SUCCESSFUL

/* works? yes */
//return 0;

printk("ipc %p\n", ipc);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Oops, these shouldn't be here. :)


/* create the pipeline */
pipe = pipeline_new(NULL, pipe_desc->primary.r.instance_id,
pipe = pipeline_new(heap, pipe_desc->primary.r.instance_id,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@lyakh @jsarha @lgirdwood This is where you'd plug in the vregions stuff to pass a separate heap to each pipe (based on topology description of its needs). In this series, as a placeholder, I use the zephyr_ll_user_heap() instead.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

right, maybe put a comment there for now to make re-discovery easier

Copy link
Copy Markdown
Collaborator

@lyakh lyakh left a comment

Choose a reason for hiding this comment

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

last reviewed commit so far "schedule: zephyr_ll: implement thread_init/free domain ops"

#ifdef CONFIG_SOF_USERSPACE_LL
void comp_grant_access_to_thread(const struct comp_dev *dev, struct k_thread *th)
{
assert(dev->list_mutex);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

description a bit confusing - this is only granting access to a mutex. Also list_mutex is only added to comp_dev in the next commit.

}

stream_addr = rballoc_align(flags, size, align);
stream_addr = sof_heap_alloc(heap, flags, size, align);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the commit, that is mentioned in the commit message, only moved buffer context objects to particular heaps. This commit moves actual data buffers to them too, which is different and (arguably) more risky

#define HDA_DMA_BUFFER_PERIOD_COUNT 4

SHARED_DATA struct sof_dma dma[] = {
APP_TASK_DATA SHARED_DATA struct sof_dma dma[] = {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

do I understand correctly, that this kind of userspace access makes that data writable to userspace?

comp_err(dev, "requested channel %d is busy", hda_chan);
return -ENODEV;
}
hd->chan = &hd->dma->chan[channel];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is this also not needed for the legacy mode? Also below

uint64_t next_sync;
uint64_t period_in_cycles;
#endif
struct k_heap *heap;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

wasn't this already referenced in the previous commit?


k_spinlock_init(&dd->dai->lock);
#ifdef CONFIG_SOF_USERSPACE_LL
dd->dai->lock = k_object_alloc(K_OBJ_MUTEX);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

check for NULL? Possibly in other locations too

k_mutex_lock(dai->lock, K_FOREVER);
props = dai_get_properties(dai->dev, direction, stream_id);
hs_id = props->dma_hs_id;
ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm guessing this is made a syscall in one of the commits

mod_heap = &mod_heap_user->heap;
} else {
#ifdef CONFIG_SOF_USERSPACE_LL
mod_heap = zephyr_ll_user_heap();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

looks good, but this else is entered under multiple conditions, might need to double-check

.schedule_task_before = zephyr_ll_task_schedule_before,
.schedule_task_after = zephyr_ll_task_schedule_after,
.schedule_task_free = zephyr_ll_task_free,
.schedule_task_free = zephyr_ll_task_sched_free,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

let's "spend" 3 more characters and make it ..._schedule_free()

return -ENOMEM;
}
tr_err(&ll_tr, "Failed to allocate thread object for core %d", core);
dt->handler = NULL;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

duplicate of line 318

{
const struct sof_man_fw_desc *desc = basefw_vendor_get_manifest();
const struct sof_man_module *mod;
uint32_t i;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

int / unsigned int

uint32_t i;

if (!desc)
return -1;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

-E...

return (int)i;
}

return -1;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ditto

union ipc4_connector_node_id node_id;
uint32_t dma_buffer_size;
uint32_t config_length;
} __packed __aligned(4);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

does __aligned actually make sense in a type definition?

pipe_msg.extension.dat = ipc_user->ipc_msg_ext;

/* Execute pipeline creation in user context */
ipc_user->result = ipc_pipeline_new(ipc_user->ipc, (ipc_pipe_new *)&pipe_msg);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

that's brave! ;-) I'd put a huge "TODO" here to make sure not to ship this by chance :-)


/* create the pipeline */
pipe = pipeline_new(NULL, pipe_desc->primary.r.instance_id,
pipe = pipeline_new(heap, pipe_desc->primary.r.instance_id,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

right, maybe put a comment there for now to make re-discovery easier

kv2019i added 20 commits March 27, 2026 12:43
Set CONFIG_LOG_PROCESS_TRIGGER_THRESHOLD=1 as the default behaviour.

Given most actions in the DSP FW happen every LL (msec) tick, having
defer logic of upto 100msec in the logging subsystem is not really
helpful. Logging thread should be preempted by higher priority threads
in any case.

This setting will cause the logger thread to be woken up (with a
semaphore) every time a log entry is printed and no timer is used at
all. The actual logging will not happen until logger thread gets to run,
but the wakeup request is immediate.

This setting works better with logging backends that have a limited
internal buffer (like mtrace and Zephyr winconsole) that are commonly
used with SOF targets.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Build fails with CONFIG_XRUN_NOTIFICATIONS_ENABLE=n due
to invalid access to "md->eos_delay_configured". Fix the build
error by proper ifdefs.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Keep the per-thread stack and cpu LOG_DBG print and add a separate
LOG_INF that fires only for the Zephyr idle thread. The info-level
print reports per-core CPU utilization calculated as the inverse of
the idle thread load percentage.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Do not send IPC in case SOF is built with CONFIG_SOF_BOOT_TEST_STANDALONE
and IPC subsystem is not fully initialized.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
If CONFIG_SOF_BOOT_TEST_STANDALONE is set, ipc_init() is terminated
early. This ensures SOF will not start to generate or respond
to IPC messages that could potentially interfere with standalone
test cases (some of which send and receive IPCs).

The current implementation leaves the component list uninitialized
and this can cause trouble to standalone tests that want to utilzie
common IPC code to build messages.

Fix this problem by executing more of ipc_init() also in the standalone
mode.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add a method to add access to a component object to a particular
thread. This is required as component object state includes kernel
objects and to use these from user-space, access needs to be granted.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
In userspace LL builds, use a mutex to protect component
connections. This code shoudl work for kernel builds, but at least
now add new code under a ifdef to avoid the cost of additional
mutex object.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add sof_sys_user_heap_get() to get heap object to use for SOF audio
application heap allocations. When SOF is built with
CONFIG_SOF_USERSPACE_LL, this will return a heap that can be
used for user-space code.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add APP_SYSUSER_BSS() and APP_SYSUSER_DATA() macros to put global
objects to a memory partition that is available to the user context
used to run main SOF application. If SOF is run in kernel space,
these are no-ops, but if CONFIG_SOF_USERSPACE_LL is set, a dedicated
memory partition is used.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add a built option to make sof_heap_allo/free available as
system calls to user-space.

Add a test case for the functions that runs in a user-space thread.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Separate the state for LL scheduler memory into kernel and user
accessible resources. The pointer to the LL heap must be accessible
from user-space, so that user space can allocate memory and pass
the heap pointer as argument.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Turn the pdata->sem into a dynamic object in userspace LL builds.
Keep statically allocated semaphore for kernel LL builds.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Move the user-accessible heap pointer to common partition defined
in userspace_helper.h.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add a heap parameter to DMA scatter-gather allocation interface. This
makes it possible to control how allocations are done for the DMA
buffers.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Continue the work in commit 9567234 ("buffer: allocate on specific
heap") and add ability to specify the heap to all buffer interface
functions.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
kv2019i added 27 commits March 27, 2026 15:32
When LL scheduler is run in user-space, use a different Zephyr
thread name.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
The COHERENT_CHECK_NONSHARED_CORES debug macros call cpu_get_id()
which invokes arch_proc_id() - a privileged hardware register read
that faults in user-space context. Disable the entire debug block at
compile time when CONFIG_SOF_USERSPACE_LL is enabled. This also fixes
the same latent issue in CORE_CHECK_STRUCT and CORE_CHECK_STRUCT_INIT.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Annotate the static comp_driver_list with APP_SYSUSER_BSS so that it
is placed in the sysuser memory partition. This makes the component
driver registry accessible from user-space threads when
CONFIG_SOF_USERSPACE_LL is enabled.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Replace all calls to rzalloc() and rballoc() with calls to sof_heap_alloc()
with heap set based on how SOF is built. If audio pipelines are run
fully in user-space, the sof_sys_user_heap_get() should be used.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
In CONFIG_SOF_USERSPACE_LL builds, irq_local_disable() is not
available. Replace the no-op irq guard with the LL scheduler's own
k_mutex to prevent the scheduler from processing tasks while pipeline
state is being updated. The k_mutex is re-entrant so schedule_task()
calls inside the critical section are safe.

Add zephyr_ll_lock_sched() and zephyr_ll_unlock_sched() helpers that
acquire and release the scheduler mutex, and call them from
pipeline_schedule_triggered() under CONFIG_SOF_USERSPACE_LL.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
The mod_heap_user (dp_heap_user) reference count management in
comp_buffer_free() does not belong there - a buffer's free method
should not be responsible for freeing a module's private heap.
Additionally, in CONFIG_SOF_USERSPACE_LL builds, comp_buffer_free()
may run in user-space context where rfree() of kernel-allocated
memory is not ok.

- Add dp_heap_put() helper to dp_schedule.h for reference-counted
  DP heap release
- Move buffer-side client_count decrements to IPC layer
  (ipc_comp_disconnect, ipc_pipeline_module_free, ipc_comp_connect
  error paths)
- Move raw data buffer client_count management to
  module_adapter_free/prepare with DP domain guard
- Fix module_adapter_mem_free() where CONFIG_SOF_USERSPACE_LL
  incorrectly reassigned mod_heap for DP modules
- Fix client_count increment guard in ipc_comp_connect() to check
  dp pointer rather than dp_heap

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Start a separate user thread to handle application IPC messages
when SOF is built with CONFIG_SOF_USERSPACE_LL.

This is a in-progress implementation that only handles one
IPC message type and is thus not a full implementation. This does
allow to proceed to test IPC user thread creation and the basic
mechanism to handle messages.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
The ll_block()/ll_unblock() macros use irq_local_disable() and
irq_local_enable() for the same-core binding path, or when
CONFIG_CROSS_CORE_STREAM is not enabled. These are not available
in user-space.

Use zephyr_ll_lock_sched()/zephyr_ll_unlock_sched() when building
with CONFIG_SOF_USERSPACE_LL to acquire the LL scheduler's own
k_mutex instead.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Use sof_heap_alloc() and the new sof_sys_user_heap_get() to route
the allocs to correct heap based on SOF build options. If the audio
pipeline code is run completely in user-space, a compatible heap
should be used.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Make ipc_msg_reply() a system call, so it can be called from
audio thread even when audio thread is running in a user-space
thread.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Use sof_dma_get_status() call to allow the audio pipeline to
be run in user-space.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Replace rfree() calls with sof_heap_free() using the system user heap
for IPC component and pipeline objects. In ipc4_create_pipeline(),
replace the conditional zephyr_ll_user_heap() with the generic
sof_sys_user_heap_get() to unify the allocation path across
configurations.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add a new test to userspace_ll set that takes a step towards
running full audio pipelines in user-space. The test creates a pipeline
with two components (IPC4 host and DAI copiers), does pipeline
prepare, one copy cycle in prepared state and tears down the pipeline.

One user-space thread is created to manage the pipelines. This would
be equivalent to user IPC handler thread. Another user-space thread
is created for the LL scheduler.

This test has some limitation, but is a useful test point to
test resource allocations in pipeline, component and module adapter
code. The code adds a reference test case where the same flow is fully
run in kernel space.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add a test that sends SOF_IPC4_GLB_CREATE_PIPELINE message
via ipc_cmd() interface. This test can be used to test SOF when built
with CONFIG_SOF_USERSPACE_LL. Handling of audio pipeline IPC
messages (like CREATE_PIPELINE) will be routed to a user-space
IPC thread.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Disable the code to set up pipeline position registers
using the sof_get() singleton instance. This design is not
feasible when pipelnes are running in user-space and an alternative
implementation is needed.

This is not required in initial LL user testing as position
reporting via pipeline register is not used in all configurations.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
This is a set of temporary changes to audio code to remove calls
to privileged interfaces that are not mandatory to run simple
audio tests.

These need proper solutions to be able to run all use-cases in user
LL version.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
@kv2019i kv2019i force-pushed the feat/userspace-ll-wip branch from 7317b18 to c029d49 Compare March 27, 2026 13:37
@kv2019i
Copy link
Copy Markdown
Collaborator Author

kv2019i commented Mar 27, 2026

V4 snapshot pushed:

  • this is a significant milestone as this version allows to set up a full pipeline in user-space and this can be tested at aplay level (tested on Intel PTL with playback PCM0 of upstream sof-ptl-nocodec.tplg topology)
  • no changes needed to Zephyr or topology
  • limitations: multiple WIP changes that break other functionality and e.g. IPC implementation is very much in progress

@kv2019i
Copy link
Copy Markdown
Collaborator Author

kv2019i commented Mar 27, 2026

Example output with V4 patchset:

--cut--
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: sofnocodec [sof-nocodec], device 0: Port0 (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: sofnocodec [sof-nocodec], device 1: Port1 (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: sofnocodec [sof-nocodec], device 2: Port2 (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: sofnocodec [sof-nocodec], device 31: Deepbuffer Port0 (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
$ aplay -Dplughw:0,0 -d120 -fdat -traw /dev/zero && echo success
Playing raw data '/dev/zero' : Signed 16 bit Little Endian, Rate 48000 Hz, Stereo
success
$
--cut--

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.

2 participants