@@ -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
312495static void ipc_user_init (void )
313496{
314497}
498+ #endif /* CONFIG_SOF_USERSPACE_LL */
315499
316500__cold int ipc_init (struct sof * sof )
317501{
0 commit comments