Skip to content

Commit 72287ac

Browse files
committed
ipc: add generic user-space IPC handling thread
Add a dedicated user-space thread for handling IPC commands that operate on audio pipelines when CONFIG_SOF_USERSPACE_LL is enabled. This provides only the protocol-agnostic infrastructure: - ipc_user_init() creates the K_USER thread, allocates its k_sem/k_event handshake objects, sets up the memory domain and access grants, and spins up the LL scheduler context. - ipc_user_forward_cmd() forwards the two raw IPC message words from the kernel IPC handler to the user thread via k_event signaling, sets IPC_TASK_IN_THREAD so the host is not signaled until the user thread completes, and collects the result through a k_sem handshake. - ipc_user_thread_fn() runs the wait loop and delegates the actual command interpretation to ipc_user_thread_dispatch(), a __weak hook overridden by the active IPC major. No IPC-major-specific knowledge lives here; the IPC4 dispatch is added in a following commit. The generic infra can be extended to cover other IPC protocol variants. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
1 parent 63e118f commit 72287ac

3 files changed

Lines changed: 216 additions & 0 deletions

File tree

src/include/sof/ipc/common.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,4 +346,30 @@ void ipc_complete_cmd(struct ipc *ipc);
346346
/* GDB stub: should enter GDB after completing the IPC processing */
347347
extern bool ipc_enter_gdb;
348348

349+
#ifdef CONFIG_SOF_USERSPACE_LL
350+
/**
351+
* @brief Forward an IPC command to the user-space thread and wait for it.
352+
*
353+
* Protocol-agnostic: only the two raw message words are handed across the
354+
* kernel/user boundary. The active IPC major's ipc_user_thread_dispatch()
355+
* interprets them in the user thread.
356+
*
357+
* @param primary Primary message word
358+
* @param extension Extension message word
359+
* @return Result code from user thread processing
360+
*/
361+
int ipc_user_forward_cmd(uint32_t primary, uint32_t extension);
362+
363+
/**
364+
* @brief Protocol-specific dispatch of a forwarded IPC command.
365+
*
366+
* Runs in the user-space IPC thread. A __weak default returns -ENOSYS;
367+
* the active IPC major (e.g. IPC4) provides the real implementation.
368+
*
369+
* @param ipc_user User IPC context holding the forwarded message words
370+
* @return Result code to report back to the host
371+
*/
372+
int ipc_user_thread_dispatch(struct ipc_user *ipc_user);
373+
#endif
374+
349375
#endif /* __SOF_DRIVERS_IPC_H__ */

src/ipc/ipc-common.c

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,192 @@ void ipc_schedule_process(struct ipc *ipc)
310310
#endif
311311
}
312312

313+
#ifdef CONFIG_SOF_USERSPACE_LL
314+
/* Generic user-space IPC handling thread infrastructure. Protocol-specific
315+
* command interpretation lives in ipc_user_thread_dispatch(), implemented by
316+
* the active IPC major (see src/ipc/ipc4/).
317+
*/
318+
319+
#define IPC_USER_EVENT_CMD BIT(0)
320+
#define IPC_USER_EVENT_STOP BIT(1)
321+
322+
static struct k_thread ipc_user_thread;
323+
static K_THREAD_STACK_DEFINE(ipc_user_stack, CONFIG_SOF_IPC_USER_THREAD_STACK_SIZE);
324+
325+
/**
326+
* @brief Forward an IPC command to the user-space thread.
327+
*
328+
* Called from kernel context (IPC EDF task) to forward the message words
329+
* to the user-space thread for processing. Sets IPC_TASK_IN_THREAD in
330+
* task_mask so the host is not signaled until the user thread completes.
331+
* Blocks until the user thread finishes processing and returns the result.
332+
*
333+
* @param primary Primary message word
334+
* @param extension Extension message word
335+
* @return Result from user thread processing
336+
*/
337+
int ipc_user_forward_cmd(uint32_t primary, uint32_t extension)
338+
{
339+
struct ipc *ipc = ipc_get();
340+
struct ipc_user *pdata = ipc->ipc_user_pdata;
341+
k_spinlock_key_t key;
342+
int ret;
343+
344+
LOG_DBG("IPC: forward cmd %08x", primary);
345+
346+
/* Copy message words — original buffer may be reused */
347+
pdata->ipc_msg_pri = primary;
348+
pdata->ipc_msg_ext = extension;
349+
pdata->ipc = ipc;
350+
351+
/* Prevent host completion until user thread finishes */
352+
key = k_spin_lock(&ipc->lock);
353+
ipc->task_mask |= IPC_TASK_IN_THREAD;
354+
k_spin_unlock(&ipc->lock, key);
355+
356+
/* Wake the user thread */
357+
k_event_set(pdata->event, IPC_USER_EVENT_CMD);
358+
359+
/* Wait for user thread to complete */
360+
ret = k_sem_take(pdata->sem, K_MSEC(10));
361+
if (ret) {
362+
LOG_ERR("IPC user: sem error %d\n", ret);
363+
return ret;
364+
}
365+
366+
/* Clear the task mask bit and check for completion */
367+
key = k_spin_lock(&ipc->lock);
368+
ipc->task_mask &= ~IPC_TASK_IN_THREAD;
369+
ipc_complete_cmd(ipc);
370+
k_spin_unlock(&ipc->lock, key);
371+
372+
return pdata->result;
373+
}
374+
375+
/**
376+
* @brief Protocol-specific dispatch of a forwarded IPC command.
377+
*
378+
* Weak default; the active IPC major overrides this. Runs in the user
379+
* thread context with the forwarded message words in @p ipc_user.
380+
*/
381+
__weak int ipc_user_thread_dispatch(struct ipc_user *ipc_user)
382+
{
383+
ARG_UNUSED(ipc_user);
384+
return -ENOSYS;
385+
}
386+
387+
/**
388+
* User-space IPC thread entry point. p1 points to the ipc_user context
389+
* shared with the kernel launcher.
390+
*/
391+
static void ipc_user_thread_fn(void *p1, void *p2, void *p3)
392+
{
393+
struct ipc_user *ipc_user = p1;
394+
395+
ARG_UNUSED(p2);
396+
ARG_UNUSED(p3);
397+
398+
__ASSERT(k_is_user_context(), "expected user context");
399+
400+
/* Signal startup complete — unblocks init waiting on semaphore */
401+
k_sem_give(ipc_user->sem);
402+
LOG_INF("IPC user-space thread started");
403+
404+
for (;;) {
405+
uint32_t mask = k_event_wait_safe(ipc_user->event,
406+
IPC_USER_EVENT_CMD | IPC_USER_EVENT_STOP,
407+
false, K_MSEC(5000));
408+
409+
LOG_DBG("IPC user wake, mask %u", mask);
410+
411+
if (mask & IPC_USER_EVENT_CMD) {
412+
ipc_user->result = ipc_user_thread_dispatch(ipc_user);
413+
414+
/* Signal completion — kernel side will finish IPC */
415+
k_sem_give(ipc_user->sem);
416+
}
417+
418+
if (mask & IPC_USER_EVENT_STOP)
419+
break;
420+
}
421+
}
422+
423+
__cold int ipc_user_init(void)
424+
{
425+
struct ipc *ipc = ipc_get();
426+
struct ipc_user *ipc_user = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER,
427+
sizeof(*ipc_user), 0);
428+
int ret;
429+
430+
if (!ipc_user) {
431+
LOG_ERR("user IPC pdata alloc failed");
432+
return -ENOMEM;
433+
}
434+
435+
ipc_user->sem = k_object_alloc(K_OBJ_SEM);
436+
if (!ipc_user->sem) {
437+
LOG_ERR("user IPC sem alloc failed");
438+
k_panic();
439+
}
440+
441+
ret = k_mem_domain_add_partition(zephyr_ll_mem_domain(), &ipc_context_part);
442+
if (ret < 0)
443+
LOG_WRN("ipc context partition add failed: %d", ret);
444+
445+
k_sem_init(ipc_user->sem, 0, 1);
446+
447+
/* Allocate kernel objects for the user-space thread */
448+
ipc_user->event = k_object_alloc(K_OBJ_EVENT);
449+
if (!ipc_user->event) {
450+
LOG_ERR("user IPC event alloc failed");
451+
k_panic();
452+
}
453+
k_event_init(ipc_user->event);
454+
455+
k_thread_create(&ipc_user_thread, ipc_user_stack,
456+
CONFIG_SOF_IPC_USER_THREAD_STACK_SIZE,
457+
ipc_user_thread_fn, ipc_user, NULL, NULL,
458+
-1, K_USER, K_FOREVER);
459+
460+
ipc_user->thread = &ipc_user_thread;
461+
k_thread_access_grant(&ipc_user_thread, ipc_user->sem, ipc_user->event);
462+
user_grant_dai_access_all(&ipc_user_thread);
463+
user_grant_dma_access_all(&ipc_user_thread);
464+
user_access_to_mailbox(zephyr_ll_mem_domain(), &ipc_user_thread);
465+
user_ll_grant_access(&ipc_user_thread, PLATFORM_PRIMARY_CORE_ID);
466+
k_mem_domain_add_thread(zephyr_ll_mem_domain(), &ipc_user_thread);
467+
468+
k_thread_cpu_pin(&ipc_user_thread, PLATFORM_PRIMARY_CORE_ID);
469+
k_thread_name_set(&ipc_user_thread, "ipc_user");
470+
471+
/* Store references in ipc struct so kernel handler can forward commands */
472+
ipc->ipc_user_pdata = ipc_user;
473+
474+
k_thread_start(&ipc_user_thread);
475+
476+
struct task *task = zephyr_ll_task_alloc();
477+
478+
schedule_task_init_ll(task, SOF_UUID(ipc_uuid), SOF_SCHEDULE_LL_TIMER,
479+
0, NULL, NULL, cpu_get_id(), 0);
480+
ipc_user->audio_thread = scheduler_init_context(task);
481+
482+
/* Grant ipc_user thread permission on the audio thread object.
483+
* Needed so user-space dai_common_new() can call
484+
* k_thread_access_grant(audio_thread, dai_mutex) from user context.
485+
*/
486+
k_thread_access_grant(&ipc_user_thread, ipc_user->audio_thread);
487+
488+
/* Wait for user thread startup — consumes the initial k_sem_give from thread */
489+
k_sem_take(ipc->ipc_user_pdata->sem, K_FOREVER);
490+
491+
return 0;
492+
}
493+
#else
313494
static int ipc_user_init(void)
314495
{
315496
return 0;
316497
}
498+
#endif /* CONFIG_SOF_USERSPACE_LL */
317499

318500
__cold int ipc_init(struct sof *sof)
319501
{

zephyr/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,11 @@ config STACK_SIZE_IPC_TX
327327
default 2048
328328
help
329329
IPC sender work-queue thread stack size. Keep a power of 2.
330+
331+
config SOF_IPC_USER_THREAD_STACK_SIZE
332+
int "IPC user thread stack size"
333+
default 8192
334+
depends on SOF_USERSPACE_LL
335+
help
336+
Stack size for the userspace IPC thread.
337+
Keep a power of 2.

0 commit comments

Comments
 (0)