Skip to content

Commit 86587ca

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 196dd3b commit 86587ca

3 files changed

Lines changed: 218 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
@@ -314,4 +314,30 @@ void ipc_complete_cmd(struct ipc *ipc);
314314
/* GDB stub: should enter GDB after completing the IPC processing */
315315
extern bool ipc_enter_gdb;
316316

317+
#ifdef CONFIG_SOF_USERSPACE_LL
318+
/**
319+
* @brief Forward an IPC command to the user-space thread and wait for it.
320+
*
321+
* Protocol-agnostic: only the two raw message words are handed across the
322+
* kernel/user boundary. The active IPC major's ipc_user_thread_dispatch()
323+
* interprets them in the user thread.
324+
*
325+
* @param primary Primary message word
326+
* @param extension Extension message word
327+
* @return Result code from user thread processing
328+
*/
329+
int ipc_user_forward_cmd(uint32_t primary, uint32_t extension);
330+
331+
/**
332+
* @brief Protocol-specific dispatch of a forwarded IPC command.
333+
*
334+
* Runs in the user-space IPC thread. A __weak default returns -ENOSYS;
335+
* the active IPC major (e.g. IPC4) provides the real implementation.
336+
*
337+
* @param ipc_user User IPC context holding the forwarded message words
338+
* @return Result code to report back to the host
339+
*/
340+
int ipc_user_thread_dispatch(struct ipc_user *ipc_user);
341+
#endif
342+
317343
#endif /* __SOF_DRIVERS_IPC_H__ */

src/ipc/ipc-common.c

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,193 @@ void ipc_schedule_process(struct ipc *ipc)
309309
#endif
310310
}
311311

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

316500
__cold int ipc_init(struct sof *sof)
317501
{

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)