@@ -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
313494static 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{
0 commit comments