diff --git a/app/overlays/ptl/ll_userspace_overlay.conf b/app/overlays/ptl/ll_userspace_overlay.conf new file mode 100644 index 000000000000..176459e581e0 --- /dev/null +++ b/app/overlays/ptl/ll_userspace_overlay.conf @@ -0,0 +1,26 @@ +CONFIG_SOF_USERSPACE_LL=y + +# Problem with DSP panics due to illegal instruction hit in user-space if cold +# store execution is enabled. Disable it for now until rootcause is found. +CONFIG_COLD_STORE_EXECUTE_DRAM=n + +# temporary (but for now mandatory) settings +CONFIG_SOF_ZEPHYR_USERSPACE_MODULE_HEAP_SIZE=16384 + +# make the drivers work in user-space +CONFIG_SOF_USERSPACE_INTERFACE_DMA=y +CONFIG_DAI_USERSPACE=y + +# disable features that don't work in user-space (at least yet) +CONFIG_COLD_STORE_EXECUTE_DEBUG=n +CONFIG_CROSS_CORE_STREAM=n +CONFIG_INTEL_ADSP_MIC_PRIVACY=n +CONFIG_XRUN_NOTIFICATIONS_ENABLE=n +CONFIG_SOF_BOOT_TEST_ALLOWED=n +CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS=n +CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS=n + +# disable llext (hits privilege issues in user-space now) +CONFIG_LLEXT_STORAGE_WRITABLE=n +CONFIG_LLEXT_EXPERIMENTAL=n +CONFIG_MODULES=n diff --git a/posix/include/rtos/mutex.h b/posix/include/rtos/mutex.h index 19b360bdaea5..657f9131d1d7 100644 --- a/posix/include/rtos/mutex.h +++ b/posix/include/rtos/mutex.h @@ -62,4 +62,52 @@ static inline int sys_mutex_unlock(struct sys_mutex *mutex) return 0; } +/* provide a no-op implementation for zephyr/sys/sem.h */ + +struct sys_sem { +}; + +static inline int sys_sem_init(struct sys_sem *sem, unsigned int initial_count, + unsigned int limit) +{ + return 0; +} + +static inline int sys_sem_give(struct sys_sem *sem) +{ + return 0; +} + +static inline int sys_sem_take(struct sys_sem *sem, k_timeout_t timeout) +{ + return 0; +} + +/** + * @brief User-space accessible mutex stub for host/testbench builds. + */ +struct sof_umutex { + struct k_mutex mutex; /**< Inline k_mutex for POSIX (no dynamic alloc needed) */ +}; + +static inline int sof_umutex_init(struct sof_umutex *umutex) +{ + return k_mutex_init(&umutex->mutex); +} + +static inline int sof_umutex_lock(struct sof_umutex *umutex, k_timeout_t timeout) +{ + return k_mutex_lock(&umutex->mutex, timeout); +} + +static inline int sof_umutex_unlock(struct sof_umutex *umutex) +{ + return k_mutex_unlock(&umutex->mutex); +} + +static inline void sof_umutex_free(struct sof_umutex *umutex) +{ + /* No-op on POSIX — no kernel objects to free */ +} + #endif diff --git a/posix/include/rtos/umutex.h b/posix/include/rtos/umutex.h new file mode 100644 index 000000000000..1b2007cf059a --- /dev/null +++ b/posix/include/rtos/umutex.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. +// + +#ifndef __POSIX_RTOS_UMUTEX_H__ +#define __POSIX_RTOS_UMUTEX_H__ + +#include + +/* sof_umutex type and operations are defined in rtos/mutex.h for POSIX */ + +#endif /* __POSIX_RTOS_UMUTEX_H__ */ diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 1f7d362ffdc2..3bb4ec58f6bb 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -42,6 +42,14 @@ config HOST_DMA_STREAM_SYNCHRONIZATION for each group, different than the default one determined by the system tick frequency. This feature will allow host lower power consumption in scenarios with deep buffering. +config HOST_DMA_IPC_POSITION_UPDATES + bool "Support for stream position updates via IPC messages" + default y if IPC_MAJOR_3 + help + Support firmware functionality to report stream position updates + by sending a IPC message whenever one period of audio is transfferred. + Most platforms provide more efficient ways to query the DMA status. + config COMP_CHAIN_DMA bool "Chain DMA component" depends on IPC_MAJOR_4 diff --git a/src/audio/chain_dma.c b/src/audio/chain_dma.c index 15929dd6941c..64afd7fef1bf 100644 --- a/src/audio/chain_dma.c +++ b/src/audio/chain_dma.c @@ -15,7 +15,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -59,6 +61,15 @@ struct chain_dma_data { bool xrun_notification_sent; #endif +#ifdef CONFIG_SOF_USERSPACE_LL + /** Kernel workqueue scheduling for chain DMA in userspace builds. + * Chain DMA needs kernel context for DMA operations, so it cannot + * run on the user-space LL timer thread. + */ + struct k_work_delayable dma_work; + bool stopped; +#endif + /* local host DMA config */ struct sof_dma *dma_host; struct dma_chan_data *chan_host; @@ -269,6 +280,25 @@ static enum task_state chain_task_run(void *data) return SOF_TASK_STATE_RESCHEDULE; } +#ifdef CONFIG_SOF_USERSPACE_LL +/** Kernel workqueue handler for chain DMA periodic task. + * Runs chain_task_run() in kernel context and reschedules if needed. + */ +static void chain_dma_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct chain_dma_data *cd = CONTAINER_OF(dwork, struct chain_dma_data, dma_work); + enum task_state state; + + if (cd->stopped) + return; + + state = chain_task_run(cd); + if (state == SOF_TASK_STATE_RESCHEDULE && !cd->stopped) + k_work_reschedule(dwork, K_USEC(LL_TIMER_PERIOD_US)); +} +#endif + static int chain_task_start(struct comp_dev *dev) { struct chain_dma_data *cd = comp_get_drvdata(dev); @@ -310,6 +340,12 @@ static int chain_task_start(struct comp_dev *dev) } } +#ifdef CONFIG_SOF_USERSPACE_LL + cd->stopped = false; + k_work_init_delayable(&cd->dma_work, chain_dma_work_handler); + k_work_reschedule(&cd->dma_work, K_NO_WAIT); + cd->chain_task.state = SOF_TASK_STATE_QUEUED; +#else ret = schedule_task_init_ll(&cd->chain_task, SOF_UUID(chain_dma_uuid), SOF_SCHEDULE_LL_TIMER, SOF_TASK_PRI_HIGH, chain_task_run, cd, 0, 0); @@ -324,16 +360,19 @@ static int chain_task_start(struct comp_dev *dev) schedule_task_free(&cd->chain_task); goto error_task; } +#endif pm_policy_state_lock_get(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES); return 0; +#ifndef CONFIG_SOF_USERSPACE_LL error_task: chain_host_stop(dev); chain_link_stop(dev); return ret; +#endif } static int chain_task_pause(struct comp_dev *dev) @@ -341,10 +380,18 @@ static int chain_task_pause(struct comp_dev *dev) struct chain_dma_data *cd = comp_get_drvdata(dev); int ret, ret2; +#ifdef CONFIG_SOF_USERSPACE_LL + if (cd->chain_task.state == SOF_TASK_STATE_FREE) + return 0; + + cd->stopped = true; + cd->first_data_received = false; +#else if (cd->chain_task.state == SOF_TASK_STATE_FREE) return 0; cd->first_data_received = false; +#endif if (cd->stream_direction == SOF_IPC_STREAM_PLAYBACK) { ret = chain_host_stop(dev); ret2 = chain_link_stop(dev); @@ -355,7 +402,12 @@ static int chain_task_pause(struct comp_dev *dev) if (!ret) ret = ret2; +#ifdef CONFIG_SOF_USERSPACE_LL + k_work_cancel_delayable_sync(&cd->dma_work, &(struct k_work_sync){}); + cd->chain_task.state = SOF_TASK_STATE_FREE; +#else schedule_task_free(&cd->chain_task); +#endif pm_policy_state_lock_put(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES); return ret; @@ -504,6 +556,7 @@ __cold static int chain_task_init(struct comp_dev *dev, uint8_t host_dma_id, uin uint32_t fifo_size) { struct chain_dma_data *cd = comp_get_drvdata(dev); + struct mod_alloc_ctx *alloc_ctx = NULL; uint32_t addr_align; size_t buff_size; void *buff_addr; @@ -582,8 +635,14 @@ __cold static int chain_task_init(struct comp_dev *dev, uint8_t host_dma_id, uin } fifo_size = ALIGN_UP_INTERNAL(fifo_size, addr_align); + +#ifdef CONFIG_SOF_USERSPACE_LL + alloc_ctx = ipc_get()->ll_alloc; +#endif + /* allocate not shared buffer */ - cd->dma_buffer = buffer_alloc(NULL, fifo_size, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_DMA, + cd->dma_buffer = buffer_alloc(alloc_ctx, fifo_size, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_DMA, addr_align, BUFFER_USAGE_NOT_SHARED); if (!cd->dma_buffer) { @@ -643,14 +702,31 @@ __cold static struct comp_dev *chain_task_create(const struct comp_driver *drv, if (host_dma_id >= max_chain_number) return NULL; +#ifdef CONFIG_SOF_USERSPACE_LL + dev = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(*dev), 0); + if (!dev) + return NULL; + + memset(dev, 0, sizeof(*dev)); + comp_init(drv, dev, sizeof(*dev)); +#else dev = comp_alloc(drv, sizeof(*dev)); if (!dev) return NULL; +#endif +#ifdef CONFIG_SOF_USERSPACE_LL + cd = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER, sizeof(*cd), 0); +#else cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd)); +#endif if (!cd) goto error; + memset(cd, 0, sizeof(*cd)); + cd->first_data_received = false; cd->cs = scs ? 2 : 4; cd->chain_task.state = SOF_TASK_STATE_INIT; @@ -661,9 +737,17 @@ __cold static struct comp_dev *chain_task_create(const struct comp_driver *drv, if (!ret) return dev; +#ifdef CONFIG_SOF_USERSPACE_LL + sof_heap_free(sof_sys_user_heap_get(), cd); +#else rfree(cd); +#endif error: +#ifdef CONFIG_SOF_USERSPACE_LL + sof_heap_free(sof_sys_user_heap_get(), dev); +#else comp_free_device(dev); +#endif return NULL; } @@ -674,8 +758,16 @@ __cold static void chain_task_free(struct comp_dev *dev) assert_can_be_cold(); chain_release(dev); +#ifdef CONFIG_SOF_USERSPACE_LL + sof_heap_free(sof_sys_user_heap_get(), cd); +#else rfree(cd); +#endif +#ifdef CONFIG_SOF_USERSPACE_LL + sof_heap_free(sof_sys_user_heap_get(), dev); +#else comp_free_device(dev); +#endif } static const struct comp_driver comp_chain_dma = { diff --git a/src/audio/component.c b/src/audio/component.c index da597023cf73..a895dc642523 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -38,6 +38,14 @@ LOG_MODULE_REGISTER(component, CONFIG_SOF_LOG_LEVEL); static APP_SYSUSER_BSS SHARED_DATA struct comp_driver_list cd; +#ifdef CONFIG_SOF_USERSPACE_LL +struct comp_driver_list *comp_drivers_get(void) +{ + return platform_shared_get(&cd, sizeof(cd)); +} +EXPORT_SYMBOL(comp_drivers_get); +#endif + SOF_DEFINE_REG_UUID(component); DECLARE_TR_CTX(comp_tr, SOF_UUID(component_uuid), LOG_LEVEL_INFO); diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index 93e018408bf3..5268f810a525 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -824,7 +825,9 @@ __cold static int set_chmap(struct comp_dev *dev, const void *data, size_t data_ pcm_converter_func process; pcm_converter_func converters[IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT]; int i; +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t irq_flags; +#endif assert_can_be_cold(); @@ -878,15 +881,26 @@ __cold static int set_chmap(struct comp_dev *dev, const void *data, size_t data_ } } - /* Atomically update chmap, process and converters */ + /* Atomically update chmap, process and converters. + * In user-space builds irq_local_disable() is privileged, + * use the LL scheduler lock instead. + */ +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_sched(cpu_get_id()); +#else irq_local_disable(irq_flags); +#endif cd->dd[0]->chmap = chmap_cfg->channel_map; cd->dd[0]->process = process; for (i = 0; i < IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT; i++) cd->converter[i] = converters[i]; +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(cpu_get_id()); +#else irq_local_enable(irq_flags); +#endif return 0; } diff --git a/src/audio/copier/host_copier.h b/src/audio/copier/host_copier.h index 71ce89cf315b..9e59e4fe07f3 100644 --- a/src/audio/copier/host_copier.h +++ b/src/audio/copier/host_copier.h @@ -106,7 +106,9 @@ struct host_data { /* stream info */ struct sof_ipc_stream_posn posn; /* TODO: update this */ +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES struct ipc_msg *msg; /**< host notification */ +#endif #if CONFIG_XRUN_NOTIFICATIONS_ENABLE bool xrun_notification_sent; #endif diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index e49d3e0f0122..6150c862a543 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -229,55 +229,62 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, /* called from ipc/ipc3/dai.c */ int dai_get_handshake(struct dai *dai, int direction, int stream_id) { - k_spinlock_key_t key = k_spin_lock(&dai->lock); - const struct dai_properties *props = dai_get_properties(dai->dev, direction, - stream_id); - int hs_id = props->dma_hs_id; + struct dai_properties props; + int ret; - k_spin_unlock(&dai->lock, key); + sof_umutex_lock(&dai->lock, K_FOREVER); + ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); + sof_umutex_unlock(&dai->lock); + if (ret < 0) + return ret; - return hs_id; + return props.dma_hs_id; } /* called from ipc/ipc3/dai.c and ipc/ipc4/dai.c */ int dai_get_fifo_depth(struct dai *dai, int direction) { - const struct dai_properties *props; - k_spinlock_key_t key; - int fifo_depth; + struct dai_properties props; + int ret; if (!dai) return 0; - key = k_spin_lock(&dai->lock); - props = dai_get_properties(dai->dev, direction, 0); - fifo_depth = props->fifo_depth; - k_spin_unlock(&dai->lock, key); + sof_umutex_lock(&dai->lock, K_FOREVER); + ret = dai_get_properties_copy(dai->dev, direction, 0, &props); + sof_umutex_unlock(&dai->lock); + if (ret < 0) + return 0; - return fifo_depth; + return props.fifo_depth; } int dai_get_stream_id(struct dai *dai, int direction) { - k_spinlock_key_t key = k_spin_lock(&dai->lock); - const struct dai_properties *props = dai_get_properties(dai->dev, direction, 0); - int stream_id = props->stream_id; + struct dai_properties props; + int ret; - k_spin_unlock(&dai->lock, key); + sof_umutex_lock(&dai->lock, K_FOREVER); + ret = dai_get_properties_copy(dai->dev, direction, 0, &props); + sof_umutex_unlock(&dai->lock); + if (ret < 0) + return ret; - return stream_id; + return props.stream_id; } static int dai_get_fifo(struct dai *dai, int direction, int stream_id) { - k_spinlock_key_t key = k_spin_lock(&dai->lock); - const struct dai_properties *props = dai_get_properties(dai->dev, direction, - stream_id); - int fifo_address = props->fifo_address; + struct dai_properties props; + int ret; - k_spin_unlock(&dai->lock, key); + sof_umutex_lock(&dai->lock, K_FOREVER); + ret = dai_get_properties_copy(dai->dev, direction, stream_id, &props); + sof_umutex_unlock(&dai->lock); + if (ret < 0) + return ret; - return fifo_address; + return props.fifo_address; } /* this is called by DMA driver every time descriptor has completed */ @@ -509,6 +516,7 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, const struct ipc_config_dai *dai_cfg) { uint32_t dir; + int ret; assert_can_be_cold(); @@ -532,7 +540,12 @@ __cold int dai_common_new(struct dai_data *dd, struct comp_dev *dev, return -ENODEV; } - k_spinlock_init(&dd->dai->lock); + ret = sof_umutex_init(&dd->dai->lock); + if (ret < 0) { + dai_put(dd->dai); + comp_err(dev, "sof_umutex_init() failed: %d", ret); + return ret; + } dma_sg_init(&dd->config.elem_array); dd->xrun = 0; @@ -658,6 +671,8 @@ __cold void dai_common_free(struct dai_data *dd) dai_release_llp_slot(dd); + sof_umutex_free(&dd->dai->lock); + dai_put(dd->dai); sof_heap_free(dd->alloc_ctx.heap, dd->dai_spec_config); @@ -1973,17 +1988,18 @@ static int dai_ts_stop_op(struct comp_dev *dev) uint32_t dai_get_init_delay_ms(struct dai *dai) { - const struct dai_properties *props; - k_spinlock_key_t key; - uint32_t init_delay; + struct dai_properties props; + uint32_t init_delay = 0; + int ret; if (!dai) return 0; - key = k_spin_lock(&dai->lock); - props = dai_get_properties(dai->dev, 0, 0); - init_delay = props->reg_init_delay; - k_spin_unlock(&dai->lock, key); + sof_umutex_lock(&dai->lock, K_FOREVER); + ret = dai_get_properties_copy(dai->dev, 0, 0, &props); + if (!ret) + init_delay = props.reg_init_delay; + sof_umutex_unlock(&dai->lock); return init_delay; } diff --git a/src/audio/data_blob.c b/src/audio/data_blob.c index 399244106f95..f62be15e2ed8 100644 --- a/src/audio/data_blob.c +++ b/src/audio/data_blob.c @@ -29,6 +29,7 @@ struct comp_data_blob_handler { uint32_t single_blob:1; /**< Allocate only one blob. Module can not * be active while reconfguring. */ + struct k_heap *heap; /**< heap for user-safe alloc, or NULL */ void *(*alloc)(size_t size); /**< alternate allocator, maybe null */ void (*free)(void *buf); /**< alternate free(), maybe null */ @@ -632,23 +633,52 @@ static void default_free(void *buf) rfree(buf); } +static void *default_heap_alloc(size_t size) +{ + return sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + size, 0); +} + +static void default_heap_free(void *buf) +{ + sof_heap_free(sof_sys_user_heap_get(), buf); +} + struct comp_data_blob_handler * comp_data_blob_handler_new_ext(struct comp_dev *dev, bool single_blob, void *(*alloc)(size_t size), - void (*free)(void *buf)) + void (*free)(void *buf), + struct k_heap *heap) { struct comp_data_blob_handler *handler; comp_dbg(dev, "entry"); - handler = rzalloc(SOF_MEM_FLAG_USER, - sizeof(struct comp_data_blob_handler)); + if (heap) + handler = sof_heap_alloc(heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct comp_data_blob_handler), 0); + else + handler = rzalloc(SOF_MEM_FLAG_USER, + sizeof(struct comp_data_blob_handler)); if (handler) { + if (heap) + memset(handler, 0, sizeof(*handler)); handler->dev = dev; handler->single_blob = single_blob; - handler->alloc = alloc ? alloc : default_alloc; - handler->free = free ? free : default_free; + handler->heap = heap; + if (alloc) { + handler->alloc = alloc; + handler->free = free ? free : default_free; + } else if (heap) { + handler->alloc = default_heap_alloc; + handler->free = default_heap_free; + } else { + handler->alloc = default_alloc; + handler->free = free ? free : default_free; + } } return handler; @@ -662,6 +692,9 @@ void comp_data_blob_handler_free(struct comp_data_blob_handler *blob_handler) comp_free_data_blob(blob_handler); - rfree(blob_handler); + if (blob_handler->heap) + sof_heap_free(blob_handler->heap, blob_handler); + else + rfree(blob_handler); } EXPORT_SYMBOL(comp_data_blob_handler_free); diff --git a/src/audio/google/google_hotword_detect.c b/src/audio/google/google_hotword_detect.c index f7cf44f026fd..2bb7953d06a7 100644 --- a/src/audio/google/google_hotword_detect.c +++ b/src/audio/google/google_hotword_detect.c @@ -112,7 +112,7 @@ static struct comp_dev *ghd_create(const struct comp_driver *drv, cd->event.event_type = SOF_CTRL_EVENT_KD; cd->event.num_elems = 0; - cd->msg = ipc_msg_init(cd->event.rhdr.hdr.cmd, cd->event.rhdr.hdr.size); + cd->msg = ipc_msg_init(NULL, cd->event.rhdr.hdr.cmd, cd->event.rhdr.hdr.size); if (!cd->msg) { comp_err(dev, "ipc_msg_init failed"); goto cd_fail; diff --git a/src/audio/host-legacy.c b/src/audio/host-legacy.c index 3d62e271f518..4523ab751276 100644 --- a/src/audio/host-legacy.c +++ b/src/audio/host-legacy.c @@ -553,7 +553,7 @@ int host_common_new(struct host_data *hd, struct comp_dev *dev, ipc_build_stream_posn(&hd->posn, SOF_IPC_STREAM_POSITION, config_id); - hd->msg = ipc_msg_init(hd->posn.rhdr.hdr.cmd, hd->posn.rhdr.hdr.size); + hd->msg = ipc_msg_init(NULL, hd->posn.rhdr.hdr.cmd, hd->posn.rhdr.hdr.size); if (!hd->msg) { comp_err(dev, "ipc_msg_init failed"); dma_put(hd->dma); diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 90b0821caf5a..5ba944f772e4 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -246,7 +246,9 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt struct comp_buffer *sink; int ret; bool update_mailbox = false; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES bool send_ipc = false; +#endif if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { source = hd->dma_buffer; @@ -285,6 +287,7 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt if (hd->cont_update_posn) update_mailbox = true; +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES /* Don't send stream position if no_stream_position == 1 */ if (!hd->no_stream_position) { hd->report_pos += bytes; @@ -304,13 +307,16 @@ void host_common_update(struct host_data *hd, struct comp_dev *dev, uint32_t byt send_ipc = true; } } +#endif if (update_mailbox) { pipeline_get_timestamp(dev->pipeline, dev, &hd->posn); mailbox_stream_write(dev->pipeline->posn_offset, &hd->posn, sizeof(hd->posn)); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES if (send_ipc) ipc_msg_send(hd->msg, &hd->posn, false); +#endif } } @@ -721,13 +727,15 @@ __cold int host_common_new(struct host_data *hd, struct comp_dev *dev, ipc_build_stream_posn(&hd->posn, SOF_IPC_STREAM_POSITION, config_id); - hd->msg = ipc_msg_init(hd->posn.rhdr.hdr.cmd, sizeof(hd->posn)); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES + hd->msg = ipc_msg_init(hd->heap, hd->posn.rhdr.hdr.cmd, sizeof(hd->posn)); if (!hd->msg) { comp_err(dev, "ipc_msg_init failed"); sof_dma_put(hd->dma); return -ENOMEM; } hd->chan_index = -EINVAL; +#endif hd->copy_type = COMP_COPY_NORMAL; #ifdef CONFIG_SOF_USERSPACE_LL @@ -808,7 +816,9 @@ __cold void host_common_free(struct host_data *hd) sof_dma_put(hd->dma); +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES ipc_msg_free(hd->msg); +#endif dma_sg_free(hd->alloc_ctx.heap, &hd->config.elem_array); } diff --git a/src/audio/mfcc/mfcc_ipc4.c b/src/audio/mfcc/mfcc_ipc4.c index bb20d85e413b..21850be8a5d6 100644 --- a/src/audio/mfcc/mfcc_ipc4.c +++ b/src/audio/mfcc/mfcc_ipc4.c @@ -49,10 +49,10 @@ int mfcc_ipc_notification_init(struct processing_module *mod) primary->r.type = SOF_IPC4_GLB_NOTIFICATION; primary->r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; primary->r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; - cd->msg = ipc_msg_w_ext_init(msg_proto.header, msg_proto.extension, - sizeof(struct sof_ipc4_notify_module_data) + - sizeof(struct sof_ipc4_control_msg_payload) + - sizeof(struct sof_ipc4_ctrl_value_chan)); + cd->msg = mod_ipc_msg_w_ext_init(mod, msg_proto.header, msg_proto.extension, + sizeof(struct sof_ipc4_notify_module_data) + + sizeof(struct sof_ipc4_control_msg_payload) + + sizeof(struct sof_ipc4_ctrl_value_chan)); if (!cd->msg) { comp_err(dev, "Failed to initialize VAD notification"); return -ENOMEM; diff --git a/src/audio/module_adapter/module/cadence_ipc4.c b/src/audio/module_adapter/module/cadence_ipc4.c index 9621d8ab0f1d..f1002a3d2e7b 100644 --- a/src/audio/module_adapter/module/cadence_ipc4.c +++ b/src/audio/module_adapter/module/cadence_ipc4.c @@ -484,8 +484,8 @@ static int cadence_codec_process(struct processing_module *mod, struct sof_sourc primary->r.type = SOF_IPC4_GLB_NOTIFICATION; primary->r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; primary->r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; - msg = ipc_msg_w_ext_init(msg_proto.header, msg_proto.extension, - sizeof(*msg_module_data)); + msg = mod_ipc_msg_w_ext_init(mod, msg_proto.header, msg_proto.extension, + sizeof(*msg_module_data)); if (msg) { msg_module_data = (struct sof_ipc4_notify_module_data *)msg->tx_data; msg_module_data->instance_id = IPC4_INST_ID(ipc_config->id); diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index f9531033dadb..6fdf9f77da70 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -58,13 +58,15 @@ int module_load_config(struct comp_dev *dev, const void *cfg, size_t size) if (!dst->data) { /* No space for config available yet, allocate now */ - dst->data = rballoc(SOF_MEM_FLAG_USER, size); + dst->data = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER, size, 0); } else if (dst->size != size) { /* The size allocated for previous config doesn't match the new one. * Free old container and allocate new one. */ - rfree(dst->data); - dst->data = rballoc(SOF_MEM_FLAG_USER, size); + sof_heap_free(sof_sys_user_heap_get(), dst->data); + dst->data = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER, size, 0); } if (!dst->data) { comp_err(dev, "failed to allocate space for setup config."); @@ -284,7 +286,7 @@ EXPORT_SYMBOL(z_impl_mod_alloc_ext); #if CONFIG_COMP_BLOB struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod) { - struct module_resources * __maybe_unused res = &mod->priv.resources; + struct module_resources *res = &mod->priv.resources; struct comp_data_blob_handler *bhp; struct module_resource *container; @@ -294,7 +296,7 @@ struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_modul if (!container) return NULL; - bhp = comp_data_blob_handler_new_ext(mod->dev, false, NULL, NULL); + bhp = comp_data_blob_handler_new_ext(mod->dev, false, NULL, NULL, res->alloc->heap); if (!bhp) { container_put(mod, container); return NULL; @@ -539,7 +541,7 @@ int module_prepare(struct processing_module *mod, * as it has been applied during the procedure - it is safe to * free it. */ - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.avail = false; md->cfg.data = NULL; @@ -674,7 +676,7 @@ int module_reset(struct processing_module *mod) md->cfg.avail = false; md->cfg.size = 0; - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.data = NULL; #if CONFIG_IPC_MAJOR_3 @@ -725,10 +727,10 @@ int module_free(struct processing_module *mod) /* Free all memory shared by module_adapter & module */ md->cfg.avail = false; md->cfg.size = 0; - rfree(md->cfg.data); + sof_heap_free(sof_sys_user_heap_get(), md->cfg.data); md->cfg.data = NULL; if (md->runtime_params) { - rfree(md->runtime_params); + sof_heap_free(sof_sys_user_heap_get(), md->runtime_params); md->runtime_params = NULL; } #if CONFIG_IPC_MAJOR_3 @@ -795,7 +797,9 @@ int module_set_configuration(struct processing_module *mod, } /* Allocate buffer for new params */ - md->runtime_params = rballoc(SOF_MEM_FLAG_USER, md->new_cfg_size); + md->runtime_params = sof_heap_alloc(sof_sys_user_heap_get(), + SOF_MEM_FLAG_USER, + md->new_cfg_size, 0); if (!md->runtime_params) { comp_err(dev, "space allocation for new params failed"); return -ENOMEM; @@ -836,7 +840,7 @@ int module_set_configuration(struct processing_module *mod, md->new_cfg_size = 0; if (md->runtime_params) - rfree(md->runtime_params); + sof_heap_free(sof_sys_user_heap_get(), md->runtime_params); md->runtime_params = NULL; return ret; diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 01126446860b..3554d39420bd 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -101,7 +101,12 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv } mod_heap = NULL; } else { +#ifdef CONFIG_SOF_USERSPACE_LL + mod_heap = zephyr_ll_user_heap(); + comp_cl_dbg(drv, "using ll user heap for module"); +#else mod_heap = drv->user_heap; +#endif heap_size = 0; mod_vreg = NULL; } @@ -118,7 +123,9 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv goto emod; } - struct mod_alloc_ctx *alloc = rmalloc(flags, sizeof(*alloc)); + struct mod_alloc_ctx *alloc; + + alloc = sof_heap_alloc(mod_heap, flags, sizeof(*alloc), 0); if (!alloc) goto ealloc; @@ -154,7 +161,7 @@ static struct processing_module *module_adapter_mem_alloc(const struct comp_driv return mod; edev: - rfree(alloc); + sof_heap_free(mod_heap, alloc); ealloc: if (mod_vreg) vregion_free(mod_vreg, mod); @@ -183,12 +190,19 @@ static void module_adapter_mem_free(struct processing_module *mod) vregion_free(mod_vreg, mod->dev); vregion_free(mod_vreg, mod); - if (!vregion_put(mod_vreg)) - rfree(alloc); + if (!vregion_put(mod_vreg)) { + sof_heap_free(alloc->heap, alloc); + } } else { + LOG_INF("mod"); +#ifdef CONFIG_SOF_USERSPACE_LL + mod_heap = zephyr_ll_user_heap(); + comp_cl_dbg(drv, "using ll user heap for module free"); +#endif + comp_cl_info(drv, "free mod %p with heap %p", mod, mod_heap); sof_heap_free(mod_heap, mod->dev); sof_heap_free(mod_heap, mod); - rfree(alloc); + sof_heap_free(mod_heap, alloc); } } @@ -527,11 +541,13 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for input buffers */ if (mod->max_sources) { mod->input_buffers = - rzalloc(memory_flags, sizeof(*mod->input_buffers) * mod->max_sources); + sof_heap_alloc(sof_sys_user_heap_get(), memory_flags, + sizeof(*mod->input_buffers) * mod->max_sources, 0); if (!mod->input_buffers) { comp_err(dev, "failed to allocate input buffers"); return -ENOMEM; } + memset(mod->input_buffers, 0, sizeof(*mod->input_buffers) * mod->max_sources); } else { mod->input_buffers = NULL; } @@ -539,12 +555,14 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for output buffers */ if (mod->max_sinks) { mod->output_buffers = - rzalloc(memory_flags, sizeof(*mod->output_buffers) * mod->max_sinks); + sof_heap_alloc(sof_sys_user_heap_get(), memory_flags, + sizeof(*mod->output_buffers) * mod->max_sources, 0); if (!mod->output_buffers) { comp_err(dev, "failed to allocate output buffers"); ret = -ENOMEM; goto in_out_free; } + memset(mod->input_buffers, 0, sizeof(*mod->output_buffers) * mod->max_sources); } else { mod->output_buffers = NULL; } @@ -605,7 +623,8 @@ int module_adapter_prepare(struct comp_dev *dev) size_t size = MAX(mod->deep_buff_bytes, mod->period_bytes); list_for_item(blist, &dev->bsource_list) { - mod->input_buffers[i].data = rballoc(memory_flags, size); + mod->input_buffers[i].data = sof_heap_alloc(sof_sys_user_heap_get(), + memory_flags, size, 0); if (!mod->input_buffers[i].data) { comp_err(mod->dev, "Failed to alloc input buffer data"); ret = -ENOMEM; @@ -617,7 +636,9 @@ int module_adapter_prepare(struct comp_dev *dev) /* allocate memory for output buffer data */ i = 0; list_for_item(blist, &dev->bsink_list) { - mod->output_buffers[i].data = rballoc(memory_flags, md->mpd.out_buff_size); + mod->output_buffers[i].data = sof_heap_alloc(sof_sys_user_heap_get(), + memory_flags, + md->mpd.out_buff_size, 0); if (!mod->output_buffers[i].data) { comp_err(mod->dev, "Failed to alloc output buffer data"); ret = -ENOMEM; @@ -634,7 +655,9 @@ int module_adapter_prepare(struct comp_dev *dev) buff_size, memory_flags, PLATFORM_DCACHE_ALIGN, BUFFER_USAGE_NOT_SHARED); +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif if (!buffer) { comp_err(dev, "failed to allocate local buffer"); @@ -644,9 +667,17 @@ int module_adapter_prepare(struct comp_dev *dev) vregion_get(md->resources.alloc->vreg); +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_sched(cpu_get_id()); +#else irq_local_disable(flags); +#endif list_item_prepend(&buffer->buffers_list, &mod->raw_data_buffers_list); +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(cpu_get_id()); +#else irq_local_enable(flags); +#endif buffer_set_params(buffer, mod->stream_params, BUFFER_UPDATE_FORCE); audio_buffer_reset(&buffer->audio_buffer); @@ -676,26 +707,36 @@ int module_adapter_prepare(struct comp_dev *dev) list_for_item_safe(blist, _blist, &mod->raw_data_buffers_list) { struct comp_buffer *buffer = container_of(blist, struct comp_buffer, buffers_list); +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_sched(cpu_get_id()); +#else irq_local_disable(flags); +#endif list_item_del(&buffer->buffers_list); +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(cpu_get_id()); +#else irq_local_enable(flags); +#endif buffer_free(buffer); } out_data_free: for (i = 0; i < mod->num_of_sinks; i++) - rfree(mod->output_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers[i].data); in_data_free: for (i = 0; i < mod->num_of_sources; i++) - rfree(mod->input_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers[i].data); in_out_free: - rfree(mod->output_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers); mod->output_buffers = NULL; - rfree(mod->input_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers); mod->input_buffers = NULL; return ret; } @@ -1407,14 +1448,16 @@ int module_adapter_reset(struct comp_dev *dev) if (IS_PROCESSING_MODE_RAW_DATA(mod)) { for (i = 0; i < mod->num_of_sinks; i++) - rfree((__sparse_force void *)mod->output_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), + (__sparse_force void *)mod->output_buffers[i].data); for (i = 0; i < mod->num_of_sources; i++) - rfree((__sparse_force void *)mod->input_buffers[i].data); + sof_heap_free(sof_sys_user_heap_get(), + (__sparse_force void *)mod->input_buffers[i].data); } if (IS_PROCESSING_MODE_RAW_DATA(mod) || IS_PROCESSING_MODE_AUDIO_STREAM(mod)) { - rfree(mod->output_buffers); - rfree(mod->input_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->output_buffers); + sof_heap_free(sof_sys_user_heap_get(), mod->input_buffers); mod->num_of_sources = 0; mod->num_of_sinks = 0; @@ -1466,11 +1509,21 @@ void module_adapter_free(struct comp_dev *dev) list_for_item_safe(blist, _blist, &mod->raw_data_buffers_list) { struct comp_buffer *buffer = container_of(blist, struct comp_buffer, buffers_list); +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_sched(cpu_get_id()); +#else irq_local_disable(flags); +#endif list_item_del(&buffer->buffers_list); +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(cpu_get_id()); +#else irq_local_enable(flags); +#endif buffer_free(buffer); } diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 47d5d0127fd0..78e461fd38ee 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -41,10 +42,20 @@ DECLARE_TR_CTX(pipe_tr, SOF_UUID(pipe_uuid), LOG_LEVEL_INFO); /* lookup table to determine busy/free pipeline metadata objects */ struct pipeline_posn { bool posn_offset[PPL_POSN_OFFSETS]; /**< available offsets */ +#ifndef CONFIG_SOF_USERSPACE_LL struct k_spinlock lock; /**< lock mechanism */ +#endif }; /* the pipeline position lookup table */ -static SHARED_DATA struct pipeline_posn pipeline_posn_shared; +static APP_SYSUSER_BSS SHARED_DATA struct pipeline_posn pipeline_posn_shared; + +#ifdef CONFIG_SOF_USERSPACE_LL +/* Mutex pointer in user-accessible partition so user-space threads + * can read the pointer for syscalls. Kept outside the SHARED_DATA + * struct to avoid kernel object tracking issues. + */ +static APP_SYSUSER_BSS struct k_mutex *pipeline_posn_lock; +#endif /** * \brief Retrieves pipeline position structure. @@ -52,7 +63,12 @@ static SHARED_DATA struct pipeline_posn pipeline_posn_shared; */ static inline struct pipeline_posn *pipeline_posn_get(void) { +#ifdef CONFIG_SOF_USERSPACE_LL + return platform_shared_get(&pipeline_posn_shared, + sizeof(pipeline_posn_shared)); +#else return sof_get()->pipeline_posn; +#endif } /** @@ -65,9 +81,14 @@ static inline int pipeline_posn_offset_get(uint32_t *posn_offset) struct pipeline_posn *pipeline_posn = pipeline_posn_get(); int ret = -EINVAL; uint32_t i; + +#ifdef CONFIG_SOF_USERSPACE_LL + k_mutex_lock(pipeline_posn_lock, K_FOREVER); +#else k_spinlock_key_t key; key = k_spin_lock(&pipeline_posn->lock); +#endif for (i = 0; i < PPL_POSN_OFFSETS; ++i) { if (!pipeline_posn->posn_offset[i]) { @@ -78,8 +99,11 @@ static inline int pipeline_posn_offset_get(uint32_t *posn_offset) } } - +#ifdef CONFIG_SOF_USERSPACE_LL + k_mutex_unlock(pipeline_posn_lock); +#else k_spin_unlock(&pipeline_posn->lock, key); +#endif return ret; } @@ -92,22 +116,43 @@ static inline void pipeline_posn_offset_put(uint32_t posn_offset) { struct pipeline_posn *pipeline_posn = pipeline_posn_get(); int i = posn_offset / sizeof(struct sof_ipc_stream_posn); + +#ifdef CONFIG_SOF_USERSPACE_LL + k_mutex_lock(pipeline_posn_lock, K_FOREVER); + pipeline_posn->posn_offset[i] = false; + k_mutex_unlock(pipeline_posn_lock); +#else k_spinlock_key_t key; key = k_spin_lock(&pipeline_posn->lock); - pipeline_posn->posn_offset[i] = false; - k_spin_unlock(&pipeline_posn->lock, key); +#endif } void pipeline_posn_init(struct sof *sof) { sof->pipeline_posn = platform_shared_get(&pipeline_posn_shared, sizeof(pipeline_posn_shared)); +#ifdef CONFIG_SOF_USERSPACE_LL + pipeline_posn_lock = k_object_alloc(K_OBJ_MUTEX); + if (!pipeline_posn_lock) { + pipe_cl_err("pipeline posn mutex alloc failed"); + k_panic(); + } + k_mutex_init(pipeline_posn_lock); +#else k_spinlock_init(&sof->pipeline_posn->lock); +#endif } +#ifdef CONFIG_SOF_USERSPACE_LL +void pipeline_posn_grant_access(struct k_thread *thread) +{ + k_thread_access_grant(thread, pipeline_posn_lock); +} +#endif + /* create new pipeline - returns pipeline id or negative error */ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_t priority, uint32_t comp_id, struct create_pipeline_params *pparams) @@ -138,12 +183,17 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ p->pipeline_id = pipeline_id; p->status = COMP_STATE_INIT; p->trigger.cmd = COMP_TRIGGER_NO_ACTION; + +#ifdef CONFIG_SOF_USERSPACE_LL + LOG_WRN("pipeline trace settings cannot be copied"); +#else ret = memcpy_s(&p->tctx, sizeof(struct tr_ctx), &pipe_tr, sizeof(struct tr_ctx)); if (ret < 0) { pipe_err(p, "failed to copy trace settings"); goto free; } +#endif ret = pipeline_posn_offset_get(&p->posn_offset); if (ret < 0) { @@ -156,7 +206,7 @@ struct pipeline *pipeline_new(struct k_heap *heap, uint32_t pipeline_id, uint32_ ipc_build_stream_posn(&posn, SOF_IPC_STREAM_TRIG_XRUN, p->comp_id); if (posn.rhdr.hdr.size) { - p->msg = ipc_msg_init(posn.rhdr.hdr.cmd, posn.rhdr.hdr.size); + p->msg = ipc_msg_init(heap, posn.rhdr.hdr.cmd, posn.rhdr.hdr.size); if (!p->msg) { pipe_err(p, "ipc_msg_init failed"); goto free; @@ -179,16 +229,19 @@ static void buffer_set_comp(struct comp_buffer *buffer, struct comp_dev *comp, } #ifdef CONFIG_SOF_USERSPACE_LL +/* + * User-space LL: IPC-level zephyr_ll_lock_sched() provides mutual + * exclusion with the LL thread. No per-pipeline lock needed. + */ #define PPL_LOCK_DECLARE -#define PPL_LOCK() do { \ - int ret = sys_mutex_lock(&comp->list_mutex, K_FOREVER); \ - assert(ret == 0); \ - } while (0) -#define PPL_UNLOCK() do { \ - int ret = sys_mutex_unlock(&comp->list_mutex); \ - assert(ret == 0); \ - } while (0) +#define PPL_LOCK() zephyr_ll_lock_sched(PLATFORM_PRIMARY_CORE_ID) +#define PPL_UNLOCK() zephyr_ll_unlock_sched(PLATFORM_PRIMARY_CORE_ID) #else +/* + * Kernel-space LL. When modifying pipeline connections, block IRQs + * and prevent LL from running. No locking needed when iterating + * the pipeline in the LL thread. + */ #define PPL_LOCK_DECLARE uint32_t flags #define PPL_LOCK() irq_local_disable(flags) #define PPL_UNLOCK() irq_local_enable(flags) @@ -503,6 +556,10 @@ struct comp_dev *pipeline_get_dai_comp(uint32_t pipeline_id, int dir) */ struct comp_dev *pipeline_get_dai_comp_latency(uint32_t pipeline_id, uint32_t *latency) { +#ifdef CONFIG_SOF_USERSPACE_LL + LOG_WRN("latency cannot be computed in user-space pipelines!"); + *latency = 0; +#else struct ipc_comp_dev *ipc_sink; struct ipc_comp_dev *ipc_source; struct comp_dev *source; @@ -570,7 +627,7 @@ struct comp_dev *pipeline_get_dai_comp_latency(uint32_t pipeline_id, uint32_t *l /* Get a next sink component */ ipc_sink = ipc_get_ppl_sink_comp(ipc, source->pipeline->pipeline_id); } - +#endif return NULL; } EXPORT_SYMBOL(pipeline_get_dai_comp_latency); diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index 45fd1eed639c..cddfdc7165db 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -282,6 +283,18 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, struct pipeline_data *ppl_data = ctx->comp_data; struct list_item *tlist; struct pipeline *p; + +#ifdef CONFIG_SOF_USERSPACE_LL + /* + * In user-space irq_local_disable() is not available. Use the LL + * scheduler 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. + */ + int sched_core = ppl_data->start->ipc_config.core; + + zephyr_ll_lock_sched(sched_core); +#else uint32_t flags; /* @@ -290,6 +303,7 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, * immediately before all pipelines achieved a consistent state. */ irq_local_disable(flags); +#endif switch (cmd) { case COMP_TRIGGER_PAUSE: @@ -345,8 +359,11 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, p->xrun_bytes = 1; } } - +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(sched_core); +#else irq_local_enable(flags); +#endif } int pipeline_comp_ll_task_init(struct pipeline *p) diff --git a/src/audio/pipeline/pipeline-stream.c b/src/audio/pipeline/pipeline-stream.c index 8bfdfc182912..24922717cb10 100644 --- a/src/audio/pipeline/pipeline-stream.c +++ b/src/audio/pipeline/pipeline-stream.c @@ -145,6 +145,20 @@ static int pipeline_comp_copy(struct comp_dev *current, return err; } +#ifdef CONFIG_SOF_USERSPACE_LL +#include +/* + * User-space LL: The LL thread runs at cooperative priority (-16) + * and cannot be preempted by IPC (priority 1). No per-pipeline + * lock needed in pipeline_copy() hot path. + */ +#define PPL_LOCK() zephyr_ll_lock_sched(PLATFORM_PRIMARY_CORE_ID) +#define PPL_UNLOCK() zephyr_ll_unlock_sched(PLATFORM_PRIMARY_CORE_ID) +#else +#define PPL_LOCK() +#define PPL_UNLOCK() +#endif + /* Copy data across all pipeline components. * For capture pipelines it always starts from source component * and continues downstream and for playback pipelines it first @@ -162,6 +176,8 @@ int pipeline_copy(struct pipeline *p) uint32_t dir; int ret; + PPL_LOCK(); + if (p->source_comp->direction == SOF_IPC_STREAM_PLAYBACK) { dir = PPL_DIR_UPSTREAM; start = p->sink_comp; @@ -178,6 +194,8 @@ int pipeline_copy(struct pipeline *p) pipe_err(p, "ret = %d, start->comp.id = %u, dir = %u", ret, dev_comp_id(start), dir); + PPL_UNLOCK(); + return ret; } diff --git a/src/audio/sound_dose/sound_dose-ipc4.c b/src/audio/sound_dose/sound_dose-ipc4.c index fd3a8151e984..3c3f537dc338 100644 --- a/src/audio/sound_dose/sound_dose-ipc4.c +++ b/src/audio/sound_dose/sound_dose-ipc4.c @@ -32,9 +32,9 @@ static struct ipc_msg *sound_dose_notification_init(struct processing_module *mo primary->r.type = SOF_IPC4_GLB_NOTIFICATION; primary->r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; primary->r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; - msg = ipc_msg_w_ext_init(msg_proto.header, msg_proto.extension, - sizeof(struct sof_ipc4_notify_module_data) + - sizeof(struct sof_ipc4_control_msg_payload)); + msg = mod_ipc_msg_w_ext_init(mod, msg_proto.header, msg_proto.extension, + sizeof(struct sof_ipc4_notify_module_data) + + sizeof(struct sof_ipc4_control_msg_payload)); if (!msg) return NULL; diff --git a/src/audio/tdfb/tdfb_ipc3.c b/src/audio/tdfb/tdfb_ipc3.c index 544acbe9a41f..cfcfbfd10eda 100644 --- a/src/audio/tdfb/tdfb_ipc3.c +++ b/src/audio/tdfb/tdfb_ipc3.c @@ -36,7 +36,7 @@ static int init_get_ctl_ipc(struct processing_module *mod) cd->ctrl_data->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | SOF_IPC_COMP_GET_VALUE | comp_id; cd->ctrl_data->rhdr.hdr.size = TDFB_GET_CTRL_DATA_SIZE; - cd->msg = ipc_msg_init(cd->ctrl_data->rhdr.hdr.cmd, cd->ctrl_data->rhdr.hdr.size); + cd->msg = mod_ipc_msg_init(mod, cd->ctrl_data->rhdr.hdr.cmd, cd->ctrl_data->rhdr.hdr.size); cd->ctrl_data->comp_id = comp_id; cd->ctrl_data->type = SOF_CTRL_TYPE_VALUE_CHAN_GET; diff --git a/src/audio/tdfb/tdfb_ipc4.c b/src/audio/tdfb/tdfb_ipc4.c index 2f7e7e865214..a7b4e34977c7 100644 --- a/src/audio/tdfb/tdfb_ipc4.c +++ b/src/audio/tdfb/tdfb_ipc4.c @@ -48,8 +48,8 @@ static struct ipc_msg *tdfb_notification_init(struct processing_module *mod, primary->r.type = SOF_IPC4_GLB_NOTIFICATION; primary->r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; primary->r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; - msg = ipc_msg_w_ext_init(msg_proto.header, msg_proto.extension, - sizeof(struct sof_ipc4_notify_module_data) + + msg = mod_ipc_msg_w_ext_init(mod, msg_proto.header, msg_proto.extension, + sizeof(struct sof_ipc4_notify_module_data) + sizeof(struct sof_ipc4_control_msg_payload) + sizeof(struct sof_ipc4_ctrl_value_chan)); if (!msg) diff --git a/src/include/ipc4/handler.h b/src/include/ipc4/handler.h index b25cb98e9427..6073e4dc83c0 100644 --- a/src/include/ipc4/handler.h +++ b/src/include/ipc4/handler.h @@ -16,6 +16,36 @@ struct ipc4_message_request; */ int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, struct ipc_msg *reply); +/** + * @brief Process MOD_CONFIG_GET or MOD_CONFIG_SET in any execution context. + * @param[in] ipc4 IPC4 message request. + * @param[in] set true for CONFIG_SET, false for CONFIG_GET. + * @param[out] reply_ext Receives extension value for CONFIG_GET (may be NULL). + * @return IPC4 status code (0 on success). + */ +int ipc4_process_module_config(struct ipc4_message_request *ipc4, + bool set, uint32_t *reply_ext); + +/** + * @brief Process MOD_LARGE_CONFIG_GET in any execution context. + * @param[in] ipc4 IPC4 message request. + * @param[out] reply_ext Receives extension value for reply. + * @param[out] reply_tx_size Receives TX data size for reply. + * @param[out] reply_tx_data Receives TX data pointer for reply. + * @return IPC4 status code (0 on success). + */ +int ipc4_process_large_config_get(struct ipc4_message_request *ipc4, + uint32_t *reply_ext, + uint32_t *reply_tx_size, + void **reply_tx_data); + +/** + * @brief Process MOD_LARGE_CONFIG_SET in any execution context. + * @param[in] ipc4 IPC4 message request. + * @return IPC4 status code (0 on success). + */ +int ipc4_process_large_config_set(struct ipc4_message_request *ipc4); + /** * \brief Processes IPC4 userspace global message. * @param[in] ipc4 IPC4 message request. @@ -25,30 +55,50 @@ int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, struct i int ipc4_user_process_glb_message(struct ipc4_message_request *ipc4, struct ipc_msg *reply); /** - * \brief Increment the IPC compound message pre-start counter. + * \brief Process SET_PIPELINE_STATE IPC4 message (prepare + trigger phases). + * @param[in] ipc4 IPC4 message request. + * @return 0 on success, IPC4 error code otherwise. + */ +int ipc4_set_pipeline_state(struct ipc4_message_request *ipc4); + +/** + * \brief Complete the IPC compound message. * @param[in] msg_id IPC message ID. + * @param[in] error Error code of the IPC command. */ -void ipc_compound_pre_start(int msg_id); +void ipc_compound_msg_done(uint32_t msg_id, int error); +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) /** - * \brief Decrement the IPC compound message pre-start counter on return value status. + * \brief Increment the IPC compound message pre-start counter. * @param[in] msg_id IPC message ID. - * @param[in] ret Return value of the IPC command. - * @param[in] delayed True if the reply is delayed. */ -void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); +__syscall void ipc_compound_pre_start(int msg_id); /** - * \brief Complete the IPC compound message. + * \brief Decrement the IPC compound message pre-start counter on return value status. * @param[in] msg_id IPC message ID. - * @param[in] error Error code of the IPC command. + * @param[in] ret Return value of the IPC command. + * @param[in] delayed True if the reply is delayed. */ -void ipc_compound_msg_done(uint32_t msg_id, int error); +__syscall void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); /** * \brief Wait for the IPC compound message to complete. * @return 0 on success, error code otherwise on timeout. */ -int ipc_wait_for_compound_msg(void); +__syscall int ipc_wait_for_compound_msg(void); +#else +void z_impl_ipc_compound_pre_start(int msg_id); +#define ipc_compound_pre_start z_impl_ipc_compound_pre_start +void z_impl_ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); +#define ipc_compound_post_start z_impl_ipc_compound_post_start +int z_impl_ipc_wait_for_compound_msg(void); +#define ipc_wait_for_compound_msg z_impl_ipc_wait_for_compound_msg +#endif + +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) +#include +#endif #endif /* __SOF_IPC4_HANDLER_H__ */ diff --git a/src/include/ipc4/notification.h b/src/include/ipc4/notification.h index 614a926d16ad..bad08a39082c 100644 --- a/src/include/ipc4/notification.h +++ b/src/include/ipc4/notification.h @@ -297,4 +297,21 @@ void send_mixer_underrun_notif_msg(uint32_t resource_id, uint32_t eos_flag, uint uint32_t expected_data_mixed); void ipc4_update_notification_mask(uint32_t ntfy_mask, uint32_t enabled_mask); +#ifdef CONFIG_SOF_USERSPACE_LL + +__syscall bool send_resource_notif(uint32_t resource_id, uint32_t event_type, + uint32_t resource_type, void *data, uint32_t data_size); + +bool z_impl_send_resource_notif(uint32_t resource_id, uint32_t event_type, + uint32_t resource_type, void *data, uint32_t data_size); + +#include + +#else + +bool send_resource_notif(uint32_t resource_id, uint32_t event_type, + uint32_t resource_type, void *data, uint32_t data_size); + +#endif /* CONFIG_SOF_USERSPACE_LL */ + #endif /* __IPC4_NOTIFICATION_H__ */ diff --git a/src/include/sof/audio/buffer.h b/src/include/sof/audio/buffer.h index 6e6b8a9caef8..02c62caf9809 100644 --- a/src/include/sof/audio/buffer.h +++ b/src/include/sof/audio/buffer.h @@ -34,6 +34,7 @@ struct comp_dev; struct buffer_cb_transact; + /** \name Trace macros * @{ */ diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index ebb88c8f79a1..1d79b336b208 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -681,10 +681,6 @@ struct comp_dev { struct list_item bsource_list; /**< list of source buffers */ struct list_item bsink_list; /**< list of sink buffers */ -#ifdef CONFIG_SOF_USERSPACE_LL - struct sys_mutex list_mutex; /**< protect lists of source/sinks */ -#endif - /* performance data*/ struct comp_perf_data perf_data; /* Input Buffer Size for pin 0, add array for other pins if needed */ @@ -869,9 +865,6 @@ static inline void comp_init(const struct comp_driver *drv, dev->state = COMP_STATE_INIT; list_init(&dev->bsink_list); list_init(&dev->bsource_list); -#ifdef CONFIG_SOF_USERSPACE_LL - sys_mutex_init(&dev->list_mutex); -#endif #ifndef __ZEPHYR__ memcpy_s(&dev->tctx, sizeof(dev->tctx), trace_comp_drv_get_tr_ctx(dev->drv), sizeof(struct tr_ctx)); diff --git a/src/include/sof/audio/component_ext.h b/src/include/sof/audio/component_ext.h index d2bbf87a7764..6d67b44014c2 100644 --- a/src/include/sof/audio/component_ext.h +++ b/src/include/sof/audio/component_ext.h @@ -425,10 +425,14 @@ static inline void comp_make_shared(struct comp_dev *dev) dev->is_shared = true; } +#ifdef CONFIG_SOF_USERSPACE_LL +struct comp_driver_list *comp_drivers_get(void); +#else static inline struct comp_driver_list *comp_drivers_get(void) { return sof_get()->comp_drivers; } +#endif #if CONFIG_IPC_MAJOR_4 static inline int comp_ipc4_bind_remote(struct comp_dev *dev, struct bind_info *bind_data) diff --git a/src/include/sof/audio/data_blob.h b/src/include/sof/audio/data_blob.h index d2eb8a74d66d..58505b2e199b 100644 --- a/src/include/sof/audio/data_blob.h +++ b/src/include/sof/audio/data_blob.h @@ -12,6 +12,7 @@ #include struct comp_dev; +struct k_heap; struct comp_data_blob_handler; @@ -113,11 +114,13 @@ int comp_data_blob_get_cmd(struct comp_data_blob_handler *blob_handler, * @param single_blob Set true for single configuration blob operation * @param alloc Optional blob memory allocator function pointer * @param free Optional blob memory free function pointer + * @param heap Optional heap for user-safe allocation, or NULL for default */ struct comp_data_blob_handler * comp_data_blob_handler_new_ext(struct comp_dev *dev, bool single_blob, void *(*alloc)(size_t size), - void (*free)(void *buf)); + void (*free)(void *buf), + struct k_heap *heap); /** * Returns new data blob handler. @@ -130,7 +133,7 @@ comp_data_blob_handler_new_ext(struct comp_dev *dev, bool single_blob, static inline struct comp_data_blob_handler *comp_data_blob_handler_new(struct comp_dev *dev) { - return comp_data_blob_handler_new_ext(dev, false, NULL, NULL); + return comp_data_blob_handler_new_ext(dev, false, NULL, NULL, NULL); } /** diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 94827d86cd9f..937d29f3da90 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "module_interface.h" /* The __ZEPHYR__ condition is to keep cmocka tests working */ @@ -240,6 +241,47 @@ static inline void *mod_zalloc(struct processing_module *mod, size_t size) return ret; } +/** + * \brief Initialize a new IPC message using the module allocator. + * @param mod Module to allocate from + * @param header Message header metadata + * @param extension Message header extension metadata + * @param size Message data size in bytes. + * @return New IPC message. + */ +static inline struct ipc_msg *mod_ipc_msg_w_ext_init(struct processing_module *mod, + uint32_t header, + uint32_t extension, + uint32_t size) +{ + struct ipc_msg *msg; + + msg = mod_zalloc(mod, sizeof(*msg)); + if (!msg) + return NULL; + + if (size) { + msg->tx_data = mod_zalloc(mod, size); + if (!msg->tx_data) { + mod_free(mod, msg); + return NULL; + } + } + + msg->header = header; + msg->extension = extension; + msg->tx_size = size; + list_init(&msg->list); + + return msg; +} + +static inline struct ipc_msg *mod_ipc_msg_init(struct processing_module *mod, + uint32_t header, uint32_t size) +{ + return mod_ipc_msg_w_ext_init(mod, header, 0, size); +} + #if CONFIG_COMP_BLOB struct comp_data_blob_handler *mod_data_blob_handler_new(struct processing_module *mod); void mod_data_blob_handler_free(struct processing_module *mod, struct comp_data_blob_handler *dbh); diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index 913a569c208c..f8be1a5a409a 100644 --- a/src/include/sof/audio/pipeline.h +++ b/src/include/sof/audio/pipeline.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -206,6 +207,14 @@ int pipeline_complete(struct pipeline *p, struct comp_dev *source, */ void pipeline_posn_init(struct sof *sof); +#ifdef CONFIG_SOF_USERSPACE_LL +/** + * \brief Grants user-space thread access to pipeline position mutex. + * \param[in] thread Thread to grant access to. + */ +void pipeline_posn_grant_access(struct k_thread *thread); +#endif + /** * \brief Resets the pipeline and free runtime resources. * \param[in] p pipeline. diff --git a/src/include/sof/boot_test.h b/src/include/sof/boot_test.h index 1af6e7f2d8e0..dfa8671ee55f 100644 --- a/src/include/sof/boot_test.h +++ b/src/include/sof/boot_test.h @@ -13,6 +13,8 @@ #endif #include +struct k_thread; + #if CONFIG_SOF_BOOT_TEST #define TEST_RUN_ONCE(fn, ...) do { \ static bool once; \ @@ -36,4 +38,18 @@ void sof_run_boot_tests(void); +/** + * Mark a boot-test thread as expected to trigger a fatal error. + * + * @param thread Thread that is allowed to fault once, or NULL to clear. + */ +#if CONFIG_SOF_BOOT_TEST +void sof_boot_test_set_fault_valid(struct k_thread *thread); +#else +static inline void sof_boot_test_set_fault_valid(struct k_thread *thread) +{ + (void)thread; +} +#endif + #endif diff --git a/src/include/sof/coherent.h b/src/include/sof/coherent.h index 172e45b4ed92..ba6b8d8c7e52 100644 --- a/src/include/sof/coherent.h +++ b/src/include/sof/coherent.h @@ -86,8 +86,8 @@ STATIC_ASSERT(sizeof(struct coherent) <= DCACHE_LINE_SIZE, DCACHE_LINE_SIZE_too #define ADDR_IS_COHERENT(_c) #endif -/* debug sharing amongst cores */ -#ifdef COHERENT_CHECK_NONSHARED_CORES +/* debug sharing amongst cores - not available in user-space builds */ +#if defined(COHERENT_CHECK_NONSHARED_CORES) && !defined(CONFIG_SOF_USERSPACE_LL) #define CORE_CHECK_STRUCT_FIELD uint32_t __core; bool __is_shared #define CORE_CHECK_STRUCT_INIT(_c, is_shared) { (_c)->__core = cpu_get_id(); \ diff --git a/src/include/sof/ipc/common.h b/src/include/sof/ipc/common.h index e46fc10b9521..fc749f0b33a9 100644 --- a/src/include/sof/ipc/common.h +++ b/src/include/sof/ipc/common.h @@ -22,8 +22,10 @@ #include #include +struct comp_driver; struct dma_sg_elem_array; struct ipc_msg; +struct ipc4_message_request; /* validates internal non tail structures within IPC command structure */ #define IPC_IS_SIZE_INVALID(object) \ @@ -53,6 +55,37 @@ extern struct tr_ctx ipc_tr; #define IPC_TASK_SECONDARY_CORE BIT(2) #define IPC_TASK_POWERDOWN BIT(3) +struct ipc_user { + struct k_thread *thread; + struct k_sem *sem; + struct k_event *event; + /** @brief Copy of IPC4 message primary word forwarded to user thread */ + uint32_t ipc_msg_pri; + /** @brief Copy of IPC4 message extension word forwarded to user thread */ + uint32_t ipc_msg_ext; + /** @brief Result code from user thread processing */ + int result; + /** @brief Reply extension word from user thread (e.g. CONFIG_GET result) */ + uint32_t reply_ext; + /** @brief Reply TX data size from user thread (e.g. LARGE_CONFIG_GET result) */ + uint32_t reply_tx_size; + /** @brief Reply TX data pointer from user thread (e.g. LARGE_CONFIG_GET result) */ + void *reply_tx_data; + struct ipc *ipc; + struct k_thread *audio_thread; + /** @brief Original kernel driver pointer for restoring dev->drv after create */ + const struct comp_driver *init_drv; + /** + * @brief User-accessible copy of comp_driver + tr_ctx for create(). + * + * The comp_driver and tr_ctx structs reside in kernel memory + * (.rodata/.data) which is not user-readable. The kernel handler + * copies them here before forwarding to the user thread. + * Size verified by BUILD_ASSERT in handler-user.c. + */ + uint8_t init_drv_data[160] __aligned(4); +}; + struct ipc { struct k_spinlock lock; /* locking mechanism */ void *comp_data; @@ -74,6 +107,11 @@ struct ipc { struct task ipc_task; #endif +#ifdef CONFIG_SOF_USERSPACE_LL + struct ipc_user *ipc_user_pdata; + struct mod_alloc_ctx *ll_alloc; +#endif + #ifdef CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS /* io performance measurement */ struct io_perf_data_item *io_perf_in_msg_count; @@ -95,6 +133,12 @@ struct ipc { extern struct task_ops ipc_task_ops; +#ifdef CONFIG_SOF_USERSPACE_LL + +struct ipc *ipc_get(void); + +#else + /** * \brief Get the IPC global context. * @return The global IPC context. @@ -104,6 +148,8 @@ static inline struct ipc *ipc_get(void) return sof_get()->ipc; } +#endif /* CONFIG_SOF_USERSPACE_LL */ + /** * \brief Initialise global IPC context. * @param[in,out] sof Global SOF context. @@ -166,6 +212,56 @@ struct dai_data; */ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev); +/** + * \brief Processes IPC4 userspace module message. + * @param[in] ipc4 IPC4 message request. + * @param[in] reply IPC message reply structure. + * @return IPC4_SUCCESS on success, error code otherwise. + */ +int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, struct ipc_msg *reply); + +/** + * \brief Processes IPC4 userspace global message. + * @param[in] ipc4 IPC4 message request. + * @param[in] reply IPC message reply structure. + * @return IPC4_SUCCESS on success, error code otherwise. + */ +int ipc4_user_process_glb_message(struct ipc4_message_request *ipc4, struct ipc_msg *reply); + +/* + * When CONFIG_SOF_USERSPACE_LL is enabled, compound message functions are + * declared as syscalls in ipc4/handler.h — do not re-declare here with + * external linkage as that conflicts with the static inline syscall wrappers. + */ +#if !(defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL)) +/** + * \brief Increment the IPC compound message pre-start counter. + * @param[in] msg_id IPC message ID. + */ +void ipc_compound_pre_start(int msg_id); + +/** + * \brief Decrement the IPC compound message pre-start counter on return value status. + * @param[in] msg_id IPC message ID. + * @param[in] ret Return value of the IPC command. + * @param[in] delayed True if the reply is delayed. + */ +void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed); + +/** + * \brief Wait for the IPC compound message to complete. + * @return 0 on success, error code otherwise on timeout. + */ +int ipc_wait_for_compound_msg(void); +#endif /* !CONFIG_SOF_USERSPACE_LL */ + +/** + * \brief Complete the IPC compound message. + * @param[in] msg_id IPC message ID. + * @param[in] error Error code of the IPC command. + */ +void ipc_compound_msg_done(uint32_t msg_id, int error); + /** * \brief create a IPC boot complete message. * @param[in] header header. @@ -240,7 +336,7 @@ int ipc_process_on_core(uint32_t core, bool blocking); * \brief reply to an IPC message. * @param[in] reply pointer to the reply structure. */ -void ipc_msg_reply(struct sof_ipc_reply *reply); +#include /** * \brief Call platform-specific IPC completion function. @@ -250,4 +346,14 @@ void ipc_complete_cmd(struct ipc *ipc); /* GDB stub: should enter GDB after completing the IPC processing */ extern bool ipc_enter_gdb; +#ifdef CONFIG_SOF_USERSPACE_LL +struct ipc4_message_request; +/** + * @brief Forward an IPC4 command to the user-space thread. + * @param ipc4 Pointer to the IPC4 message request + * @return Result from user thread processing + */ +int ipc_user_forward_cmd(struct ipc4_message_request *ipc4); +#endif + #endif /* __SOF_DRIVERS_IPC_H__ */ diff --git a/src/include/sof/ipc/ipc_msg_send.h b/src/include/sof/ipc/ipc_msg_send.h new file mode 100644 index 000000000000..c5b9b4a4448a --- /dev/null +++ b/src/include/sof/ipc/ipc_msg_send.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_IPC_IPC_MSG_SEND_H__ +#define __SOF_IPC_IPC_MSG_SEND_H__ + +#include + +struct ipc_msg; + +/** + * \brief Queues an IPC message for transmission. + * @param msg The IPC message. + * @param data The message data. + * @param high_priority True if a high priority message. + */ +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) +__syscall void ipc_msg_send(struct ipc_msg *msg, void *data, + bool high_priority); +#else +void z_impl_ipc_msg_send(struct ipc_msg *msg, void *data, + bool high_priority); +#define ipc_msg_send z_impl_ipc_msg_send +#endif + +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) +#include +#endif + +#endif /* __SOF_IPC_IPC_MSG_SEND_H__ */ diff --git a/src/include/sof/ipc/ipc_reply.h b/src/include/sof/ipc/ipc_reply.h new file mode 100644 index 000000000000..756fd535ca30 --- /dev/null +++ b/src/include/sof/ipc/ipc_reply.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_IPC_IPC_REPLY_H__ +#define __SOF_IPC_IPC_REPLY_H__ + +#include + +struct sof_ipc_reply; + +/** + * \brief reply to an IPC message. + * @param[in] reply pointer to the reply structure. + */ +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) +__syscall void ipc_msg_reply(struct sof_ipc_reply *reply); +#else +void z_impl_ipc_msg_reply(struct sof_ipc_reply *reply); +#define ipc_msg_reply z_impl_ipc_msg_reply +#endif + +#if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) +#include +#endif + +#endif /* __SOF_IPC_IPC_REPLY_H__ */ diff --git a/src/include/sof/ipc/msg.h b/src/include/sof/ipc/msg.h index 160a9be9ec7c..2471b8c2004b 100644 --- a/src/include/sof/ipc/msg.h +++ b/src/include/sof/ipc/msg.h @@ -29,6 +29,7 @@ struct dai_config; struct dma; struct dma_sg_elem_array; +struct k_heap; struct ipc_msg { uint32_t header; /* specific to platform */ @@ -40,46 +41,27 @@ struct ipc_msg { }; /** - * \brief Initialize a new IPC message. + * \brief Initialize a new IPC message using a specific heap. + * @param heap Heap to allocate from (NULL = default kernel heap) * @param header Message header metadata * @param extension Message header extension metadata * @param size Message data size in bytes. * @return New IPC message. */ -static inline struct ipc_msg *ipc_msg_w_ext_init(uint32_t header, uint32_t extension, - uint32_t size) -{ - struct ipc_msg *msg; - - msg = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(*msg)); - if (!msg) - return NULL; - - if (size) { - msg->tx_data = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, size); - if (!msg->tx_data) { - rfree(msg); - return NULL; - } - } - - msg->header = header; - msg->extension = extension; - msg->tx_size = size; - list_init(&msg->list); - - return msg; -} +struct ipc_msg *ipc_msg_w_ext_init(struct k_heap *heap, uint32_t header, + uint32_t extension, uint32_t size); /** - * \brief Initialise a new IPC message. + * \brief Initialize a new IPC message using a specific heap. + * @param heap Heap to allocate from (NULL = default kernel heap) * @param header Message header metadata * @param size Message data size in bytes. * @return New IPC message. */ -static inline struct ipc_msg *ipc_msg_init(uint32_t header, uint32_t size) +static inline struct ipc_msg *ipc_msg_init(struct k_heap *heap, + uint32_t header, uint32_t size) { - return ipc_msg_w_ext_init(header, 0, size); + return ipc_msg_w_ext_init(heap, header, 0, size); } /** @@ -108,13 +90,7 @@ static inline void ipc_msg_free(struct ipc_msg *msg) */ void ipc_send_queued_msg(void); -/** - * \brief Queues an IPC message for transmission. - * @param msg The IPC message to be freed. - * @param data The message data. - * @param high_priority True if a high priortity message. - */ -void ipc_msg_send(struct ipc_msg *msg, void *data, bool high_priority); +#include /** * \brief Send an IPC message directly for emergency. diff --git a/src/include/sof/ipc/topology.h b/src/include/sof/ipc/topology.h index 3503c7a407d8..590424ee8be9 100644 --- a/src/include/sof/ipc/topology.h +++ b/src/include/sof/ipc/topology.h @@ -49,6 +49,13 @@ typedef uint32_t ipc_comp; struct ipc_comp_dev; const struct comp_driver *ipc4_get_comp_drv(uint32_t module_id); struct comp_dev *ipc4_get_comp_dev(uint32_t comp_id); +int ipc4_add_comp_dev(struct comp_dev *dev); +#ifdef CONFIG_SOF_USERSPACE_LL +struct ipc4_message_request; +struct comp_driver; +struct comp_dev *comp_new_ipc4_user(struct ipc4_message_request *ipc4, + const struct comp_driver *drv); +#endif int ipc4_chain_manager_create(struct ipc4_chain_dma *cdma); int ipc4_chain_dma_state(struct comp_dev *dev, struct ipc4_chain_dma *cdma); int ipc4_create_chain_dma(struct ipc *ipc, struct ipc4_chain_dma *cdma); diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index 595d11de9b47..09fa0aacad5c 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,7 @@ struct dai { uint32_t dma_dev; const struct device *dev; const struct dai_data *dd; - struct k_spinlock lock; /* protect properties */ + struct sof_umutex lock; /* protect properties */ }; union hdalink_cfg { @@ -310,7 +311,9 @@ void dai_release_llp_slot(struct dai_data *dd); /** * \brief Retrieve a pointer to the Zephyr device structure for a DAI of a given type and index. */ -const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); +__syscall const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); + +const struct device *z_impl_dai_get_device(enum sof_ipc_dai_type type, uint32_t index); /** * \brief Retrieve the list of all DAI devices. @@ -320,4 +323,6 @@ const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); const struct device **dai_get_device_list(size_t *count); /** @}*/ +#include + #endif /* __SOF_LIB_DAI_ZEPHYR_H__ */ diff --git a/src/include/sof/lib/vregion.h b/src/include/sof/lib/vregion.h index 612443f5bc48..0f2363107893 100644 --- a/src/include/sof/lib/vregion.h +++ b/src/include/sof/lib/vregion.h @@ -6,6 +6,8 @@ #define __SOF_LIB_VREGION_H__ #include +#include +#include #ifdef __cplusplus extern "C" { @@ -70,12 +72,16 @@ struct vregion *vregion_put(struct vregion *vr); * @param[in] size Size of memory to allocate in bytes. * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size); +__syscall void *vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size); + +void *z_impl_vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size); /** * @brief like vregion_alloc() but allocates coherent memory */ -void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, size_t size); +__syscall void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, size_t size); + +void *z_impl_vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, size_t size); /** * @brief Allocate aligned memory from the specified virtual region. @@ -88,14 +94,20 @@ void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, siz * @param[in] alignment Alignment of memory to allocate in bytes. * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment); +__syscall void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, + size_t size, size_t alignment); + +void *z_impl_vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, + size_t size, size_t alignment); /** * @brief like vregion_alloc_align() but allocates coherent memory */ -void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment); +__syscall void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, + size_t size, size_t alignment); + +void *z_impl_vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, + size_t size, size_t alignment); /** * @brief Free memory allocated from the specified virtual region. @@ -105,7 +117,9 @@ void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type typ * @param[in] vr Pointer to the virtual region instance. * @param[in] ptr Pointer to the memory to free. */ -void vregion_free(struct vregion *vr, void *ptr); +__syscall void vregion_free(struct vregion *vr, void *ptr); + +void z_impl_vregion_free(struct vregion *vr, void *ptr); /** * @brief Log virtual region memory usage. @@ -183,4 +197,8 @@ static inline void vregion_mem_info(struct vregion *vr, size_t *size, uintptr_t } #endif +#if CONFIG_SOF_VREGIONS +#include +#endif + #endif /* __SOF_LIB_VREGION_H__ */ diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index 451ad7739f8f..7424b62bc124 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -47,6 +47,20 @@ struct ll_schedule_domain_ops { void (*handler)(void *arg), void *arg); int (*domain_unregister)(struct ll_schedule_domain *domain, struct task *task, uint32_t num_tasks); +#if CONFIG_SOF_USERSPACE_LL + /* + * Initialize the scheduling thread and perform all privileged setup + * (thread creation, timer init, access grants). Called once from + * kernel context before any user-space domain_register() calls. + */ + int (*domain_thread_init)(struct ll_schedule_domain *domain, + struct task *task); + /* Free resources acquired by domain_thread_init(). Called from + * kernel context when the scheduling context is being torn down. + */ + void (*domain_thread_free)(struct ll_schedule_domain *domain, + uint32_t num_tasks); +#endif void (*domain_enable)(struct ll_schedule_domain *domain, int core); void (*domain_disable)(struct ll_schedule_domain *domain, int core); #if CONFIG_CROSS_CORE_STREAM @@ -103,8 +117,13 @@ static inline struct ll_schedule_domain *dma_domain_get(void) #ifdef CONFIG_SOF_USERSPACE_LL struct task *zephyr_ll_task_alloc(void); +void zephyr_ll_task_free(struct task *task); struct k_heap *zephyr_ll_user_heap(void); +bool zephyr_ll_user_heap_verify(struct k_heap *heap); void zephyr_ll_user_resources_init(void); +void zephyr_ll_grant_access(struct k_thread *thread); +void zephyr_ll_lock_sched(int core); +void zephyr_ll_unlock_sched(int core); #endif /* CONFIG_SOF_USERSPACE_LL */ static inline struct ll_schedule_domain *domain_init @@ -181,6 +200,31 @@ static inline void domain_task_cancel(struct ll_schedule_domain *domain, domain->ops->domain_task_cancel(domain, task); } +#if CONFIG_SOF_USERSPACE_LL +/* + * Initialize the scheduling thread and do all privileged setup. + * Must be called from kernel context before user-space tasks register. + */ +static inline int domain_thread_init(struct ll_schedule_domain *domain, + struct task *task) +{ + assert(domain->ops->domain_thread_init); + + return domain->ops->domain_thread_init(domain, task); +} + +/* + * Free resources acquired by domain_thread_init(). + * Must be called from kernel context. + */ +static inline void domain_thread_free(struct ll_schedule_domain *domain, + uint32_t num_tasks) +{ + if (domain->ops->domain_thread_free) + domain->ops->domain_thread_free(domain, num_tasks); +} +#endif + static inline int domain_register(struct ll_schedule_domain *domain, struct task *task, void (*handler)(void *arg), void *arg) @@ -278,7 +322,7 @@ struct ll_schedule_domain *zephyr_ll_domain(void); struct ll_schedule_domain *zephyr_domain_init(int clk); #define timer_domain_init(timer, clk) zephyr_domain_init(clk) #ifdef CONFIG_SOF_USERSPACE_LL -struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain); +struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain, int core); struct k_mem_domain *zephyr_ll_mem_domain(void); #endif /* CONFIG_SOF_USERSPACE_LL */ #endif /* __ZEPHYR__ */ diff --git a/src/include/sof/schedule/schedule.h b/src/include/sof/schedule/schedule.h index bbdcbbecf3b4..1ff6180b962e 100644 --- a/src/include/sof/schedule/schedule.h +++ b/src/include/sof/schedule/schedule.h @@ -158,6 +158,16 @@ struct scheduler_ops { * This operation is optional. */ int (*scheduler_restore)(void *data); + + /** + * Initializes context + * @param data Private data of selected scheduler. + * @param task task that needs to be scheduled + * @return thread that will be used to run the scheduled task + * + * This operation is optional. + */ + struct k_thread *(*scheduler_init_context)(void *data, struct task *task); }; /** \brief Holds information about scheduler. */ @@ -179,6 +189,10 @@ struct schedulers { */ struct schedulers **arch_schedulers_get(void); +#if CONFIG_SOF_USERSPACE_LL +struct schedulers **arch_schedulers_get_for_core(int core); +#endif + /** * Retrieves scheduler's data. * @param type SOF_SCHEDULE_ type. @@ -199,10 +213,39 @@ static inline void *scheduler_get_data(uint16_t type) return NULL; } +#if CONFIG_SOF_USERSPACE_LL +/** + * Retrieves scheduler's data for a specific core. + * @param type SOF_SCHEDULE_ type. + * @param core Core ID to get scheduler data for. + * @return Pointer to scheduler's data. + * + * Safe to call from user-space context — does not use cpu_get_id(). + */ +static inline void *scheduler_get_data_for_core(uint16_t type, int core) +{ + struct schedulers *schedulers = *arch_schedulers_get_for_core(core); + struct schedule_data *sch; + struct list_item *slist; + + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (type == sch->type) + return sch->data; + } + + return NULL; +} +#endif + /** See scheduler_ops::schedule_task_running */ static inline int schedule_task_running(struct task *task) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -224,7 +267,11 @@ static inline int schedule_task_running(struct task *task) static inline int schedule_task(struct task *task, uint64_t start, uint64_t period) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -245,7 +292,11 @@ static inline int schedule_task(struct task *task, uint64_t start, static inline int schedule_task_before(struct task *task, uint64_t start, uint64_t period, struct task *before) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -271,7 +322,11 @@ static inline int schedule_task_before(struct task *task, uint64_t start, static inline int schedule_task_after(struct task *task, uint64_t start, uint64_t period, struct task *after) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -296,7 +351,11 @@ static inline int schedule_task_after(struct task *task, uint64_t start, /** See scheduler_ops::reschedule_task */ static inline int reschedule_task(struct task *task, uint64_t start) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -318,7 +377,11 @@ static inline int reschedule_task(struct task *task, uint64_t start) /** See scheduler_ops::schedule_task_cancel */ static inline int schedule_task_cancel(struct task *task) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -334,7 +397,11 @@ static inline int schedule_task_cancel(struct task *task) /** See scheduler_ops::schedule_task_free */ static inline int schedule_task_free(struct task *task) { +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else struct schedulers *schedulers = *arch_schedulers_get(); +#endif struct schedule_data *sch; struct list_item *slist; @@ -379,6 +446,28 @@ static inline int schedulers_restore(void) return 0; } +/** See scheduler_ops::scheduler_init_context */ +static inline struct k_thread *scheduler_init_context(struct task *task) +{ +#if CONFIG_SOF_USERSPACE_LL + struct schedulers *schedulers = *arch_schedulers_get_for_core(task->core); +#else + struct schedulers *schedulers = *arch_schedulers_get(); +#endif + struct schedule_data *sch; + struct list_item *slist; + + assert(schedulers); + + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (task->type == sch->type && sch->ops->scheduler_init_context) + return sch->ops->scheduler_init_context(sch->data, task); + } + + return NULL; +} + /** * Initializes scheduling task. * @param task Task to be initialized. diff --git a/src/init/init.c b/src/init/init.c index e8d374c810ad..546b1931db41 100644 --- a/src/init/init.c +++ b/src/init/init.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #if CONFIG_IPC_MAJOR_4 #include @@ -47,6 +48,10 @@ LOG_MODULE_REGISTER(init, CONFIG_SOF_LOG_LEVEL); +#if CONFIG_SOF_USERSPACE_LL +SOF_DEFINE_REG_UUID(sec_core_init); +#endif + /* main firmware context */ static struct sof sof; @@ -135,6 +140,18 @@ __cold int secondary_core_init(struct sof *sof) return err; #endif /* CONFIG_ZEPHYR_DP_SCHEDULER */ +#if CONFIG_SOF_USERSPACE_LL + /* Create domain thread for this secondary core's LL scheduler */ + { + struct task *task = zephyr_ll_task_alloc(); + + schedule_task_init_ll(task, SOF_UUID(sec_core_init_uuid), + SOF_SCHEDULE_LL_TIMER, + 0, NULL, NULL, cpu_get_id(), 0); + scheduler_init_context(task); + } +#endif + /* initialize IDC mechanism */ trace_point(TRACE_BOOT_PLATFORM_IDC); err = idc_init(); @@ -224,6 +241,11 @@ __cold static int primary_core_init(int argc, char *argv[], struct sof *sof) zephyr_ll_user_resources_init(); #endif + /* init pipeline position offsets - must be before platform_init() + * which calls ipc_init() -> ipc_user_init() that needs the posn mutex. + */ + pipeline_posn_init(sof); + /* init the platform */ if (platform_init(sof) < 0) sof_panic(SOF_IPC_PANIC_PLATFORM); diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index d0d248c9ec77..83e386f9b65e 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -35,6 +37,19 @@ #include #include +#ifdef __ZEPHYR__ +#include +#include +#endif + +#ifdef CONFIG_SOF_USERSPACE_LL +#include +#include +#include +#include +#include +#endif + #include LOG_MODULE_REGISTER(ipc, CONFIG_SOF_LOG_LEVEL); @@ -43,6 +58,62 @@ SOF_DEFINE_REG_UUID(ipc); DECLARE_TR_CTX(ipc_tr, SOF_UUID(ipc_uuid), LOG_LEVEL_INFO); +#ifdef CONFIG_SOF_USERSPACE_LL +K_APPMEM_PARTITION_DEFINE(ipc_context_part); + +K_APP_BMEM(ipc_context_part) static struct ipc ipc_context; + +struct ipc *ipc_get(void) +{ + return &ipc_context; +} +EXPORT_SYMBOL(ipc_get); +#endif + +struct ipc_msg *ipc_msg_w_ext_init(struct k_heap *heap, uint32_t header, + uint32_t extension, uint32_t size) +{ + struct ipc_msg *msg; + + if (heap) { + msg = sof_heap_alloc(heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(*msg), 0); + if (msg) + memset(msg, 0, sizeof(*msg)); + } else { + msg = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(*msg)); + } + if (!msg) + return NULL; + + if (size) { + if (heap) { + msg->tx_data = sof_heap_alloc(heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + size, 0); + if (msg->tx_data) + memset(msg->tx_data, 0, size); + } else { + msg->tx_data = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + size); + } + if (!msg->tx_data) { + if (heap) + sof_heap_free(heap, msg); + else + rfree(msg); + return NULL; + } + } + + msg->header = header; + msg->extension = extension; + msg->tx_size = size; + list_init(&msg->list); + + return msg; +} + int ipc_process_on_core(uint32_t core, bool blocking) { struct ipc *ipc = ipc_get(); @@ -216,7 +287,11 @@ __cold void ipc_msg_send_direct(struct ipc_msg *msg, void *data) k_spin_unlock(&ipc->lock, key); } +#ifdef CONFIG_SOF_USERSPACE_LL +void z_impl_ipc_msg_send(struct ipc_msg *msg, void *data, bool high_priority) +#else void ipc_msg_send(struct ipc_msg *msg, void *data, bool high_priority) +#endif { struct ipc *ipc = ipc_get(); k_spinlock_key_t key; @@ -256,12 +331,37 @@ void ipc_msg_send(struct ipc_msg *msg, void *data, bool high_priority) list_item_append(&msg->list, &ipc->msg_list); } +#if 0 /*def CONFIG_SOF_USERSPACE_LL */ + LOG_WRN("Skipping IPC worker schedule. TODO to fix\n"); +#else schedule_ipc_worker(); +#endif k_spin_unlock(&ipc->lock, key); } EXPORT_SYMBOL(ipc_msg_send); +#ifdef CONFIG_SOF_USERSPACE_LL +static inline bool z_vrfy_ipc_msg_send_check_data(struct ipc_msg *msg, void *data) +{ + /* If data != NULL and tx_size > 0, verify the data buffer */ + if (data && msg->tx_size > 0) + K_OOPS(K_SYSCALL_MEMORY_READ(data, msg->tx_size)); + + return true; +} + +void z_vrfy_ipc_msg_send(struct ipc_msg *msg, void *data, bool high_priority) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(msg, sizeof(*msg))); + + z_vrfy_ipc_msg_send_check_data(msg, data); + + z_impl_ipc_msg_send(msg, data, high_priority); +} +#include +#endif + #ifdef __ZEPHYR__ static void ipc_work_handler(struct k_work *work) { @@ -288,34 +388,397 @@ void ipc_schedule_process(struct ipc *ipc) #endif } +#ifdef CONFIG_SOF_USERSPACE_LL +/* User-space thread for pipeline_two_components test */ + +#define IPC_USER_EVENT_CMD BIT(0) +#define IPC_USER_EVENT_STOP BIT(1) + +static struct k_thread ipc_user_thread; +static K_THREAD_STACK_DEFINE(ipc_user_stack, CONFIG_SOF_IPC_USER_THREAD_STACK_SIZE); + +/** + * @brief Forward an IPC4 command to the user-space thread. + * + * Called from kernel context (IPC EDF task) to forward the IPC4 + * message to the user-space thread for processing. Sets + * IPC_TASK_IN_THREAD in task_mask so the host is not signaled + * until the user thread completes. Blocks until the user thread + * finishes processing and returns the result. + * + * @param ipc4 Pointer to the IPC4 message request + * @return Result from user thread processing + */ +int ipc_user_forward_cmd(struct ipc4_message_request *ipc4) +{ + struct ipc *ipc = ipc_get(); + struct ipc_user *pdata = ipc->ipc_user_pdata; + k_spinlock_key_t key; + int ret; + + LOG_DBG("IPC: forward cmd %08x", ipc4->primary.dat); + + /* Copy message words — original buffer may be reused */ + pdata->ipc_msg_pri = ipc4->primary.dat; + pdata->ipc_msg_ext = ipc4->extension.dat; + pdata->ipc = ipc; + + /* Prevent host completion until user thread finishes */ + key = k_spin_lock(&ipc->lock); + ipc->task_mask |= IPC_TASK_IN_THREAD; + k_spin_unlock(&ipc->lock, key); + + /* Wake the user thread */ + k_event_set(pdata->event, IPC_USER_EVENT_CMD); + + /* Wait for user thread to complete */ + ret = k_sem_take(pdata->sem, K_MSEC(10)); + if (ret) { + LOG_ERR("IPC user: sem error %d\n", ret); + return ret; + } + + /* Clear the task mask bit and check for completion */ + key = k_spin_lock(&ipc->lock); + ipc->task_mask &= ~IPC_TASK_IN_THREAD; + ipc_complete_cmd(ipc); + k_spin_unlock(&ipc->lock, key); + + return pdata->result; +} + +/** + * User-space thread entry point for pipeline_two_components test. + * p1 points to the ppl_test_ctx shared with the kernel launcher. + */ +static void ipc_user_thread_fn(void *p1, void *p2, void *p3) +{ + struct ipc_user *ipc_user = p1; + + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + __ASSERT(k_is_user_context(), "expected user context"); + + /* Signal startup complete — unblocks init waiting on semaphore */ + k_sem_give(ipc_user->sem); + LOG_INF("IPC user-space thread started"); + + for (;;) { + uint32_t mask = k_event_wait_safe(ipc_user->event, + IPC_USER_EVENT_CMD | IPC_USER_EVENT_STOP, + false, K_MSEC(5000)); + + LOG_DBG("IPC user wake, mask %u", mask); + + if (mask & IPC_USER_EVENT_CMD) { + struct ipc4_message_request msg; + + /* Reconstruct the IPC4 message from copied words */ + msg.primary.dat = ipc_user->ipc_msg_pri; + msg.extension.dat = ipc_user->ipc_msg_ext; + + ipc_user->reply_ext = 0; + + if (msg.primary.r.msg_tgt == SOF_IPC4_MESSAGE_TARGET_MODULE_MSG) { + /* Module message dispatch */ + switch (msg.primary.r.type) { + case SOF_IPC4_MOD_CONFIG_GET: + ipc_user->result = + ipc4_process_module_config( + &msg, false, + &ipc_user->reply_ext); + break; + case SOF_IPC4_MOD_CONFIG_SET: + ipc_user->result = + ipc4_process_module_config( + &msg, true, NULL); + break; + case SOF_IPC4_MOD_BIND: { + struct ipc4_module_bind_unbind bu; + + memcpy_s(&bu, sizeof(bu), &msg, sizeof(msg)); + ipc_user->result = ipc_comp_connect( + ipc_user->ipc, + (ipc_pipe_comp_connect *)&bu); + break; + } + case SOF_IPC4_MOD_UNBIND: { + struct ipc4_module_bind_unbind bu; + + memcpy_s(&bu, sizeof(bu), &msg, sizeof(msg)); + ipc_user->result = ipc_comp_disconnect( + ipc_user->ipc, + (ipc_pipe_comp_connect *)&bu); + break; + } + case SOF_IPC4_MOD_INIT_INSTANCE: { + /* User thread creates the component — + * drv->ops.create() runs in user-space so + * untrusted module code does not execute + * with kernel privileges. + * + * init_drv = original kernel pointer + * init_drv_data = user-accessible copy + */ + const struct comp_driver *orig_drv = + ipc_user->init_drv; + const struct comp_driver *drv_copy = + (const struct comp_driver *) + ipc_user->init_drv_data; + + ipc_user->init_drv = NULL; + if (!orig_drv) { + ipc_user->result = + IPC4_MOD_NOT_INITIALIZED; + break; + } + + struct comp_dev *dev = + comp_new_ipc4_user(&msg, drv_copy); + + if (!dev) { + ipc_user->result = + IPC4_MOD_NOT_INITIALIZED; + break; + } + + /* Restore original kernel driver pointer. + * comp_init() set dev->drv to the copy; + * runtime code expects the canonical + * kernel address. + */ + dev->drv = orig_drv; + + ipc_user->result = + ipc4_add_comp_dev(dev); + if (ipc_user->result != IPC4_SUCCESS) + break; + + comp_update_ibs_obs_cpc(dev); + ipc_user->result = 0; + break; + } + case SOF_IPC4_MOD_DELETE_INSTANCE: { + struct ipc4_module_delete_instance module; + + memcpy_s(&module, sizeof(module), &msg, sizeof(msg)); + uint32_t comp_id = IPC4_COMP_ID( + module.primary.r.module_id, + module.primary.r.instance_id); + ipc_user->result = ipc_comp_free( + ipc_user->ipc, comp_id); + if (ipc_user->result < 0) + ipc_user->result = + IPC4_INVALID_RESOURCE_ID; + break; + } + case SOF_IPC4_MOD_LARGE_CONFIG_GET: + ipc_user->result = + ipc4_process_large_config_get( + &msg, + &ipc_user->reply_ext, + &ipc_user->reply_tx_size, + &ipc_user->reply_tx_data); + break; + case SOF_IPC4_MOD_LARGE_CONFIG_SET: + ipc_user->result = + ipc4_process_large_config_set( + &msg); + break; + default: + LOG_ERR("IPC user: unsupported module cmd type %d", + msg.primary.r.type); + ipc_user->result = -EINVAL; + break; + } + } else { + /* Global message dispatch */ + switch (msg.primary.r.type) { + case SOF_IPC4_GLB_CREATE_PIPELINE: + ipc_user->result = + ipc_pipeline_new(ipc_user->ipc, + (ipc_pipe_new *)&msg); + break; + case SOF_IPC4_GLB_DELETE_PIPELINE: { + struct ipc4_pipeline_delete *pipe = + (struct ipc4_pipeline_delete *)&msg; + ipc_user->result = + ipc_pipeline_free( + ipc_user->ipc, + pipe->primary.r.instance_id); + break; + } + case SOF_IPC4_GLB_SET_PIPELINE_STATE: + ipc_user->result = + ipc4_set_pipeline_state(&msg); + break; + default: + LOG_ERR("IPC user: unsupported glb cmd type %d", + msg.primary.r.type); + ipc_user->result = -EINVAL; + break; + } + } + + /* Signal completion — kernel side will finish IPC */ + k_sem_give(ipc_user->sem); + } + + if (mask & IPC_USER_EVENT_STOP) + break; + } +} + +__cold int ipc_user_init(void) +{ + struct ipc *ipc = ipc_get(); + struct ipc_user *ipc_user = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER, + sizeof(*ipc_user), 0); + int ret; + + ipc_user->sem = k_object_alloc(K_OBJ_SEM); + if (!ipc_user->sem) { + LOG_ERR("user IPC sem alloc failed"); + k_panic(); + } + + ret = k_mem_domain_add_partition(zephyr_ll_mem_domain(), &ipc_context_part); + + /* + * Grant user-space access to .cold (execute) and .coldrodata (read) + * sections in IMR. The prepare path walks component code that may + * reference __cold functions and __cold_rodata data. + */ +#ifdef CONFIG_COLD_STORE_EXECUTE_DRAM + { + extern char __cold_start[], __cold_end[]; + extern char __coldrodata_start[]; + extern char _imr_end[]; + struct k_mem_partition cold_part; + + cold_part.start = (uintptr_t)__cold_start; + cold_part.size = ALIGN_UP((uintptr_t)__cold_end - + (uintptr_t)__cold_start, + CONFIG_MMU_PAGE_SIZE); + cold_part.attr = K_MEM_PARTITION_P_RX_U_RX; + ret = k_mem_domain_add_partition(zephyr_ll_mem_domain(), + &cold_part); + if (ret < 0) + LOG_WRN("cold text partition add failed: %d", ret); + + cold_part.start = (uintptr_t)__coldrodata_start; + cold_part.size = ALIGN_UP((uintptr_t)_imr_end - + (uintptr_t)__coldrodata_start, + CONFIG_MMU_PAGE_SIZE); + cold_part.attr = K_MEM_PARTITION_P_RO_U_RO; + ret = k_mem_domain_add_partition(zephyr_ll_mem_domain(), + &cold_part); + if (ret < 0) + LOG_WRN("cold rodata partition add failed: %d", ret); + } +#endif + + k_sem_init(ipc_user->sem, 0, 1); + + /* Allocate kernel objects for the user-space thread */ + ipc_user->event = k_object_alloc(K_OBJ_EVENT); + if (!ipc_user->event) { + LOG_ERR("user IPC event alloc failed"); + k_panic(); + } + k_event_init(ipc_user->event); + + k_thread_create(&ipc_user_thread, ipc_user_stack, + CONFIG_SOF_IPC_USER_THREAD_STACK_SIZE, + ipc_user_thread_fn, ipc_user, NULL, NULL, + -1, K_USER, K_FOREVER); + + ipc_user->thread = &ipc_user_thread; + k_thread_access_grant(&ipc_user_thread, ipc_user->sem, ipc_user->event); + user_grant_dai_access_all(&ipc_user_thread); + user_grant_dma_access_all(&ipc_user_thread); + user_access_to_mailbox(zephyr_ll_mem_domain(), &ipc_user_thread); + zephyr_ll_grant_access(&ipc_user_thread); + pipeline_posn_grant_access(&ipc_user_thread); + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &ipc_user_thread); + + k_thread_cpu_pin(&ipc_user_thread, PLATFORM_PRIMARY_CORE_ID); + k_thread_name_set(&ipc_user_thread, "ipc_user"); + + /* Store references in ipc struct so kernel handler can forward commands */ + ipc->ipc_user_pdata = ipc_user; + + k_thread_start(&ipc_user_thread); + + struct task *task = zephyr_ll_task_alloc(); + schedule_task_init_ll(task, SOF_UUID(ipc_uuid), SOF_SCHEDULE_LL_TIMER, + 0, NULL, NULL, cpu_get_id(), 0); + ipc_user->audio_thread = scheduler_init_context(task); + + /* Grant ipc_user thread permission on the audio thread object. + * Needed so user-space dai_common_new() can call + * k_thread_access_grant(audio_thread, dai_mutex) from user context. + */ + k_thread_access_grant(&ipc_user_thread, ipc_user->audio_thread); + + /* Wait for user thread startup — consumes the initial k_sem_give from thread */ + k_sem_take(ipc->ipc_user_pdata->sem, K_FOREVER); + + return 0; +} +#else +static int ipc_user_init(void) +{ + return 0; +} +#endif /* CONFIG_SOF_USERSPACE_LL */ + __cold int ipc_init(struct sof *sof) { + struct k_heap *heap; + struct ipc *ipc; + assert_can_be_cold(); tr_dbg(&ipc_tr, "entry"); -#if CONFIG_SOF_BOOT_TEST_STANDALONE - LOG_INF("SOF_BOOT_TEST_STANDALONE, disabling IPC."); - return 0; -#endif +#ifdef CONFIG_SOF_USERSPACE_LL + heap = zephyr_ll_user_heap(); + + ipc = ipc_get(); + memset(ipc, 0, sizeof(*ipc)); + ipc->ll_alloc = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*ipc->ll_alloc), 0); + if (!ipc->ll_alloc) { + tr_err(&ipc_tr, "Unable to allocate IPC ll_alloc"); + return -ENOMEM; + } + ipc->ll_alloc->heap = heap; + ipc->ll_alloc->vreg = NULL; +#else + heap = NULL; /* init ipc data */ - sof->ipc = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(*sof->ipc)); - if (!sof->ipc) { + ipc = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, sizeof(*ipc)); + if (!ipc) { tr_err(&ipc_tr, "Unable to allocate IPC data"); return -ENOMEM; } - sof->ipc->comp_data = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, - SOF_IPC_MSG_MAX_SIZE); - if (!sof->ipc->comp_data) { + sof->ipc = ipc; +#endif + + ipc->comp_data = sof_heap_alloc(heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + SOF_IPC_MSG_MAX_SIZE, 0); + if (!ipc->comp_data) { tr_err(&ipc_tr, "Unable to allocate IPC component data"); - rfree(sof->ipc); + sof_heap_free(heap, ipc); return -ENOMEM; } + memset(ipc->comp_data, 0, SOF_IPC_MSG_MAX_SIZE); - k_spinlock_init(&sof->ipc->lock); - list_init(&sof->ipc->msg_list); - list_init(&sof->ipc->comp_list); + k_spinlock_init(&ipc->lock); + list_init(&ipc->msg_list); + list_init(&ipc->comp_list); #ifdef CONFIG_SOF_TELEMETRY_IO_PERFORMANCE_MEASUREMENTS struct io_perf_data_item init_data = {IO_PERF_IPC_ID, @@ -324,15 +787,17 @@ __cold int ipc_init(struct sof *sof) IO_PERF_POWERED_UP_ENABLED, IO_PERF_D0IX_POWER_MODE, 0, 0, 0 }; - io_perf_monitor_init_data(&sof->ipc->io_perf_in_msg_count, &init_data); + io_perf_monitor_init_data(&ipc->io_perf_in_msg_count, &init_data); init_data.direction = IO_PERF_OUTPUT_DIRECTION; - io_perf_monitor_init_data(&sof->ipc->io_perf_out_msg_count, &init_data); + io_perf_monitor_init_data(&ipc->io_perf_out_msg_count, &init_data); #endif + #ifdef __ZEPHYR__ - struct k_thread *thread = &sof->ipc->ipc_send_wq.thread; + struct k_thread *thread = &ipc->ipc_send_wq.thread; - k_work_queue_start(&sof->ipc->ipc_send_wq, ipc_send_wq_stack, + k_work_queue_init(&ipc->ipc_send_wq); + k_work_queue_start(&ipc->ipc_send_wq, ipc_send_wq_stack, K_THREAD_STACK_SIZEOF(ipc_send_wq_stack), 1, NULL); k_thread_suspend(thread); @@ -344,10 +809,17 @@ __cold int ipc_init(struct sof *sof) k_thread_resume(thread); - k_work_init_delayable(&sof->ipc->z_delayed_work, ipc_work_handler); + k_work_init_delayable(&ipc->z_delayed_work, ipc_work_handler); +#endif + + ipc_user_init(); + +#if CONFIG_SOF_BOOT_TEST_STANDALONE + LOG_INF("SOF_BOOT_TEST_STANDALONE, skipping platform IPC init."); + return 0; #endif - return platform_ipc_init(sof->ipc); + return platform_ipc_init(ipc); } /* Locking: call with ipc->lock held and interrupts disabled */ diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index ad7b3771a16b..d1694a5b1930 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -86,8 +87,10 @@ __cold struct comp_buffer *buffer_new(struct mod_alloc_ctx *alloc, buffer->stream.runtime_stream_params.pipeline_id = desc->comp.pipeline_id; buffer->core = desc->comp.core; +#if !defined(CONFIG_SOF_USERSPACE_LL) memcpy_s(&buffer->tctx, sizeof(struct tr_ctx), &buffer_tr, sizeof(struct tr_ctx)); +#endif } return buffer; @@ -291,7 +294,9 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) struct ipc_comp_dev *icd; struct comp_buffer *buffer; struct comp_buffer *safe; +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif assert_can_be_cold(); @@ -333,7 +338,15 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) return -EINVAL; } + /* Lock buffer lists to prevent racing with the LL scheduler. + * In user-space builds, use the LL scheduler's sys_mutex + * (re-entrant, so safe if caller already holds it). + */ +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_lock_sched(icd->core); +#else irq_local_disable(flags); +#endif comp_dev_for_each_producer_safe(icd->cd, buffer, safe) { comp_buffer_set_sink_component(buffer, NULL); /* This breaks the list, but we anyway delete all buffers */ @@ -346,7 +359,11 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) comp_buffer_reset_source_list(buffer); } +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(icd->core); +#else irq_local_enable(flags); +#endif /* free component and remove from list */ comp_free(icd->cd); @@ -354,7 +371,7 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) icd->cd = NULL; list_item_del(&icd->list); - rfree(icd); + sof_heap_free(sof_sys_user_heap_get(), icd); return 0; } diff --git a/src/ipc/ipc3/helper.c b/src/ipc/ipc3/helper.c index 4d87f042dd1d..fd2af4603b29 100644 --- a/src/ipc/ipc3/helper.c +++ b/src/ipc/ipc3/helper.c @@ -710,7 +710,7 @@ int ipc_comp_new(struct ipc *ipc, ipc_comp *_comp) return 0; } -void ipc_msg_reply(struct sof_ipc_reply *reply) +void z_impl_ipc_msg_reply(struct sof_ipc_reply *reply) { struct ipc *ipc = ipc_get(); k_spinlock_key_t key; diff --git a/src/ipc/ipc4/CMakeLists.txt b/src/ipc/ipc4/CMakeLists.txt index 360a4e988650..c9f9fcb5710d 100644 --- a/src/ipc/ipc4/CMakeLists.txt +++ b/src/ipc/ipc4/CMakeLists.txt @@ -7,6 +7,7 @@ add_local_sources(sof helper.c logging.c notification.c + notification-user.c ) # The DAI interface is not implemented in library builds and diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 58c08d9ec04b..1540d95cf0f0 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -236,9 +236,9 @@ void dai_dma_release(struct dai_data *dd, struct comp_dev *dev) * TODO: refine power management when stream is paused */ /* if reset is after pause dma has already been stopped */ - dma_stop(dd->dma->z_dev, dd->chan_index); + sof_dma_stop(dd->dma, dd->chan_index); - dma_release_channel(dd->dma->z_dev, dd->chan_index); + sof_dma_release_channel(dd->dma, dd->chan_index); dd->chan_index = -EINVAL; } } @@ -450,7 +450,7 @@ void dai_dma_position_update(struct dai_data *dd, struct comp_dev *dev) if (!dd->slot_info.node_id) return; - ret = dma_get_status(dd->dma->z_dev, dd->chan_index, &status); + ret = sof_dma_get_status(dd->dma, dd->chan_index, &status); if (ret < 0) return; diff --git a/src/ipc/ipc4/handler-kernel.c b/src/ipc/ipc4/handler-kernel.c index d7d9f417806a..f58bf3787229 100644 --- a/src/ipc/ipc4/handler-kernel.c +++ b/src/ipc/ipc4/handler-kernel.c @@ -40,6 +40,11 @@ #include #include +#ifdef __ZEPHYR__ +#include +#include +#endif + #include #include #include @@ -128,7 +133,7 @@ __cold static bool is_any_ppl_active(void) return false; } -void ipc_compound_pre_start(int msg_id) +void z_impl_ipc_compound_pre_start(int msg_id) { /* ipc thread will wait for all scheduled tasks to be complete * Use a reference count to check status of these tasks. @@ -136,7 +141,23 @@ void ipc_compound_pre_start(int msg_id) atomic_add(&msg_data.delayed_reply, 1); } -void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed) +#ifdef CONFIG_USERSPACE +/** + * \brief Userspace verification wrapper for ipc_compound_pre_start(). + * + * Forwards the call to z_impl_ipc_compound_pre_start(). No pointer + * validation is needed as only primitive types are passed. + * + * @param[in] msg_id IPC message ID. + */ +void z_vrfy_ipc_compound_pre_start(int msg_id) +{ + z_impl_ipc_compound_pre_start(msg_id); +} +#include +#endif + +void z_impl_ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed) { if (ret) { ipc_cmd_err(&ipc_tr, "failed to process msg %d status %d", msg_id, ret); @@ -149,6 +170,24 @@ void ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed) atomic_sub(&msg_data.delayed_reply, 1); } +#ifdef CONFIG_USERSPACE +/** + * \brief Userspace verification wrapper for ipc_compound_post_start(). + * + * Forwards the call to z_impl_ipc_compound_post_start(). No pointer + * validation is needed as only primitive types are passed. + * + * @param[in] msg_id IPC message ID. + * @param[in] ret Return value of the IPC command. + * @param[in] delayed True if the reply is delayed. + */ +void z_vrfy_ipc_compound_post_start(uint32_t msg_id, int ret, bool delayed) +{ + z_impl_ipc_compound_post_start(msg_id, ret, delayed); +} +#include +#endif + void ipc_compound_msg_done(uint32_t msg_id, int error) { if (!atomic_read(&msg_data.delayed_reply)) { @@ -170,13 +209,13 @@ void ipc_compound_msg_done(uint32_t msg_id, int error) * be always IPC4_FAILURE. Therefore the compound messages handling is simplified. The pipeline * triggers will require an explicit scheduler call to get the components to desired state. */ -int ipc_wait_for_compound_msg(void) +int z_impl_ipc_wait_for_compound_msg(void) { atomic_set(&msg_data.delayed_reply, 0); return IPC4_SUCCESS; } #else -int ipc_wait_for_compound_msg(void) +int z_impl_ipc_wait_for_compound_msg(void) { int try_count = 30; @@ -192,6 +231,22 @@ int ipc_wait_for_compound_msg(void) return IPC4_SUCCESS; } + +#ifdef CONFIG_USERSPACE +/** + * \brief Userspace verification wrapper for ipc_wait_for_compound_msg(). + * + * Forwards the call to z_impl_ipc_wait_for_compound_msg(). No pointer + * validation is needed as no pointers are passed. + * + * @return IPC4_SUCCESS on success, IPC4_FAILURE on timeout. + */ +int z_vrfy_ipc_wait_for_compound_msg(void) +{ + return z_impl_ipc_wait_for_compound_msg(); +} +#include +#endif #endif #if CONFIG_LIBRARY_MANAGER @@ -509,7 +564,7 @@ void ipc_send_buffer_status_notify(void) } #endif -void ipc_msg_reply(struct sof_ipc_reply *reply) +void z_impl_ipc_msg_reply(struct sof_ipc_reply *reply) { struct ipc4_message_request in; @@ -517,6 +572,15 @@ void ipc_msg_reply(struct sof_ipc_reply *reply) ipc_compound_msg_done(in.primary.r.type, reply->error); } +#ifdef CONFIG_USERSPACE +void z_vrfy_ipc_msg_reply(struct sof_ipc_reply *reply) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(reply, sizeof(*reply))); + z_impl_ipc_msg_reply(reply); +} +#include +#endif + void ipc_cmd(struct ipc_cmd_hdr *_hdr) { struct ipc4_message_request *in = ipc4_get_message_request(); diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index df243fa6ff58..05a3e8d304ac 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -90,6 +90,7 @@ static inline const struct ipc4_pipeline_set_state_data *ipc4_get_pipeline_data( /* * Global IPC Operations. */ +#ifndef CONFIG_SOF_USERSPACE_LL __cold static int ipc4_new_pipeline(struct ipc4_message_request *ipc4) { struct ipc *ipc = ipc_get(); @@ -98,7 +99,9 @@ __cold static int ipc4_new_pipeline(struct ipc4_message_request *ipc4) return ipc_pipeline_new(ipc, (ipc_pipe_new *)ipc4); } +#endif +#ifndef CONFIG_SOF_USERSPACE_LL __cold static int ipc4_delete_pipeline(struct ipc4_message_request *ipc4) { struct ipc4_pipeline_delete *pipe; @@ -111,6 +114,7 @@ __cold static int ipc4_delete_pipeline(struct ipc4_message_request *ipc4) return ipc_pipeline_free(ipc, pipe->primary.r.instance_id); } +#endif static int ipc4_pcm_params(struct ipc_comp_dev *pcm_dev) { @@ -419,8 +423,12 @@ __cold const struct ipc4_pipeline_set_state_data *ipc4_get_pipeline_data_wrapper return ipc4_get_pipeline_data(); } -/* Entry point for ipc4_pipeline_trigger(), therefore cannot be cold */ -static int ipc4_set_pipeline_state(struct ipc4_message_request *ipc4) +/** + * \brief Process SET_PIPELINE_STATE IPC4 message (prepare + trigger phases). + * @param[in] ipc4 IPC4 message request. + * @return 0 on success, IPC4 error code otherwise. + */ +int ipc4_set_pipeline_state(struct ipc4_message_request *ipc4) { const struct ipc4_pipeline_set_state_data *ppl_data; struct ipc4_pipeline_set_state state; @@ -681,13 +689,26 @@ int ipc4_user_process_glb_message(struct ipc4_message_request *ipc4, /* pipeline settings */ case SOF_IPC4_GLB_CREATE_PIPELINE: + /* Implementation in progress: forward only CREATE_PIPELINE for now */ +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_new_pipeline(ipc4); +#endif break; case SOF_IPC4_GLB_DELETE_PIPELINE: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_delete_pipeline(ipc4); +#endif break; case SOF_IPC4_GLB_SET_PIPELINE_STATE: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_set_pipeline_state(ipc4); +#endif break; case SOF_IPC4_GLB_GET_PIPELINE_STATE: @@ -805,7 +826,22 @@ __cold static int ipc4_unbind_module_instance(struct ipc4_message_request *ipc4) return ipc_comp_disconnect(ipc, (ipc_pipe_comp_connect *)&bu); } -static int ipc4_set_get_config_module_instance(struct ipc4_message_request *ipc4, bool set) +/** + * @brief Process MOD_CONFIG_GET or MOD_CONFIG_SET in any execution context. + * + * Looks up the target component by module_id:instance_id, verifies the + * driver supports get_attribute/set_attribute, and dispatches the + * operation. For GET, the retrieved value is written to @p reply_ext. + * + * Callable from both the IPC kernel task and the IPC user thread. + * + * @param ipc4 Pointer to the IPC4 message request (primary + extension) + * @param set true for CONFIG_SET, false for CONFIG_GET + * @param reply_ext Output: receives the extension value for CONFIG_GET (may be NULL for SET) + * @return IPC4 status code (0 on success) + */ +__cold int ipc4_process_module_config(struct ipc4_message_request *ipc4, + bool set, uint32_t *reply_ext) { struct ipc4_module_config *config = (struct ipc4_module_config *)ipc4; int (*function)(struct comp_dev *dev, uint32_t type, void *value); @@ -855,8 +891,21 @@ static int ipc4_set_get_config_module_instance(struct ipc4_message_request *ipc4 ret = IPC4_INVALID_CONFIG_PARAM_ID; } + if (!set && reply_ext) + *reply_ext = config->extension.dat; + + return ret; +} + +static int ipc4_set_get_config_module_instance(struct ipc4_message_request *ipc4, bool set) +{ + uint32_t reply_ext; + int ret; + + ret = ipc4_process_module_config(ipc4, set, &reply_ext); + if (!set) - msg_reply->extension = config->extension.dat; + msg_reply->extension = reply_ext; return ret; } @@ -986,6 +1035,108 @@ __cold static int ipc4_get_vendor_config_module_instance(struct comp_dev *dev, return IPC4_SUCCESS; } +__cold int ipc4_process_large_config_get(struct ipc4_message_request *ipc4, + uint32_t *reply_ext, + uint32_t *reply_tx_size, + void **reply_tx_data) +{ + struct ipc4_module_large_config_reply reply; + struct ipc4_module_large_config config; + char *data = ipc_get()->comp_data; + const struct comp_driver *drv; + struct comp_dev *dev = NULL; + uint32_t data_offset; + + assert_can_be_cold(); + + int ret = memcpy_s(&config, sizeof(config), ipc4, sizeof(*ipc4)); + + if (ret < 0) + return IPC4_FAILURE; + + tr_dbg(&ipc_tr, "%x : %x", + (uint32_t)config.primary.r.module_id, (uint32_t)config.primary.r.instance_id); + + /* get component dev for non-basefw since there is no + * component dev for basefw + */ + if (config.primary.r.module_id) { + uint32_t comp_id; + + comp_id = IPC4_COMP_ID(config.primary.r.module_id, + config.primary.r.instance_id); + dev = ipc4_get_comp_dev(comp_id); + if (!dev) + return IPC4_MOD_INVALID_ID; + + drv = dev->drv; + + /* Multicore disabled for userspace forwarding; + * non-userspace path retains ipc4_process_on_core() + */ + } else { + drv = ipc4_get_comp_drv(config.primary.r.module_id); + } + + if (!drv) + return IPC4_MOD_INVALID_ID; + + if (!drv->ops.get_large_config) + return IPC4_INVALID_REQUEST; + + data_offset = config.extension.r.data_off_size; + + /* check for vendor param first */ + if (config.extension.r.large_param_id == VENDOR_CONFIG_PARAM) { + /* For now only vendor_config case uses payload from hostbox */ + dcache_invalidate_region((__sparse_force void __sparse_cache *)MAILBOX_HOSTBOX_BASE, + config.extension.r.data_off_size); + ret = ipc4_get_vendor_config_module_instance(dev, drv, + config.extension.r.init_block, + config.extension.r.final_block, + &data_offset, + data, + (const char *)MAILBOX_HOSTBOX_BASE); + } else { +#if CONFIG_LIBRARY + data += sizeof(reply); +#endif + ipc4_prepare_for_kcontrol_get(dev, config.extension.r.large_param_id, + data, data_offset); + + ret = drv->ops.get_large_config(dev, config.extension.r.large_param_id, + config.extension.r.init_block, + config.extension.r.final_block, + &data_offset, data); + } + + /* set up ipc4 error code for reply data */ + if (ret < 0) + ret = IPC4_MOD_INVALID_ID; + + /* Copy host config and overwrite */ + reply.extension.dat = config.extension.dat; + reply.extension.r.data_off_size = data_offset; + + /* The last block, no more data */ + if (!config.extension.r.final_block && data_offset < SOF_IPC_MSG_MAX_SIZE) + reply.extension.r.final_block = 1; + + /* Indicate last block if error occurs */ + if (ret) + reply.extension.r.final_block = 1; + + /* no need to allocate memory for reply msg */ + if (ret) + return ret; + + /* Output via parameters instead of msg_reply */ + *reply_ext = reply.extension.dat; + *reply_tx_size = data_offset; + *reply_tx_data = data; + return ret; +} + __cold static int ipc4_get_large_config_module_instance(struct ipc4_message_request *ipc4) { struct ipc4_module_large_config_reply reply; @@ -1164,6 +1315,77 @@ __cold static int ipc4_set_vendor_config_module_instance(struct comp_dev *dev, data_off_size, data); } +__cold int ipc4_process_large_config_set(struct ipc4_message_request *ipc4) +{ + struct ipc4_module_large_config config; + struct comp_dev *dev = NULL; + const struct comp_driver *drv; + + assert_can_be_cold(); + + int ret = memcpy_s(&config, sizeof(config), ipc4, sizeof(*ipc4)); + + if (ret < 0) + return IPC4_FAILURE; + + dcache_invalidate_region((__sparse_force void __sparse_cache *)MAILBOX_HOSTBOX_BASE, + config.extension.r.data_off_size); + tr_dbg(&ipc_tr, "%x : %x", + (uint32_t)config.primary.r.module_id, (uint32_t)config.primary.r.instance_id); + + if (config.primary.r.module_id) { + uint32_t comp_id; + + comp_id = IPC4_COMP_ID(config.primary.r.module_id, config.primary.r.instance_id); + dev = ipc4_get_comp_dev(comp_id); + if (!dev) + return IPC4_MOD_INVALID_ID; + + drv = dev->drv; + + /* Multicore disabled for userspace forwarding; + * non-userspace path retains ipc4_process_on_core() + */ + } else { + drv = ipc4_get_comp_drv(config.primary.r.module_id); + } + + if (!drv) + return IPC4_MOD_INVALID_ID; + + if (!drv->ops.set_large_config) + return IPC4_INVALID_REQUEST; + + /* check for vendor param first */ + if (config.extension.r.large_param_id == VENDOR_CONFIG_PARAM) { + ret = ipc4_set_vendor_config_module_instance(dev, drv, + (uint32_t)config.primary.r.module_id, + (uint32_t)config.primary.r.instance_id, + config.extension.r.init_block, + config.extension.r.final_block, + config.extension.r.data_off_size, + (const char *)MAILBOX_HOSTBOX_BASE); + } else { +#if CONFIG_LIBRARY + struct ipc *ipc = ipc_get(); + const char *data = (const char *)ipc->comp_data + sizeof(config); +#else + const char *data = (const char *)MAILBOX_HOSTBOX_BASE; +#endif + ret = drv->ops.set_large_config(dev, config.extension.r.large_param_id, + config.extension.r.init_block, config.extension.r.final_block, + config.extension.r.data_off_size, data); + if (ret < 0) { + ipc_cmd_err(&ipc_tr, "failed to set large_config_module_instance %x : %x", + (uint32_t)config.primary.r.module_id, + (uint32_t)config.primary.r.instance_id); + ret = IPC4_INVALID_RESOURCE_ID; + } + } + + return ret; +} + __cold static int ipc4_set_large_config_module_instance(struct ipc4_message_request *ipc4) { struct ipc4_module_large_config config; @@ -1280,28 +1502,151 @@ __cold int ipc4_user_process_module_message(struct ipc4_message_request *ipc4, switch (type) { case SOF_IPC4_MOD_INIT_INSTANCE: +#ifdef CONFIG_SOF_USERSPACE_LL + { + /* User-space init: kernel does driver lookup only (requires + * access to IMR manifest and driver list in kernel memory). + * Component creation (drv->ops.create) runs in user thread + * so untrusted module code does not execute in kernel context. + * Cross-core creation stays fully in kernel. + */ + struct ipc4_module_init_instance mi; + + BUILD_ASSERT(sizeof(struct comp_driver) + sizeof(struct tr_ctx) <= + sizeof(((struct ipc_user *)0)->init_drv_data), + "ipc_user.init_drv_data too small for driver copy"); + + memcpy_s(&mi, sizeof(mi), ipc4, sizeof(*ipc4)); + if (!cpu_is_me(mi.extension.r.core_id)) { + ret = ipc4_init_module_instance(ipc4); + } else { + struct ipc *ipc = ipc_get(); + uint32_t comp_id = IPC4_COMP_ID(mi.primary.r.module_id, + mi.primary.r.instance_id); + const struct comp_driver *drv = ipc4_get_comp_drv( + IPC4_MOD_ID(comp_id)); + + if (!drv) { + ret = IPC4_MOD_NOT_INITIALIZED; + } else { + struct ipc_user *pdata = ipc->ipc_user_pdata; + + /* Copy comp_driver and tr_ctx into + * user-accessible ipc_user buffer — + * originals are in kernel .rodata/.data + * and not readable from user mode. + */ + struct comp_driver *drv_copy = + (struct comp_driver *)pdata->init_drv_data; + struct tr_ctx *tctx_copy = + (struct tr_ctx *)(pdata->init_drv_data + + sizeof(struct comp_driver)); + + memcpy_s(drv_copy, sizeof(*drv_copy), + drv, sizeof(*drv)); + if (drv->tctx) { + memcpy_s(tctx_copy, sizeof(*tctx_copy), + drv->tctx, sizeof(*drv->tctx)); + drv_copy->tctx = tctx_copy; + } + + pdata->init_drv = drv; + ret = ipc_user_forward_cmd(ipc4); + } + } + } +#else ret = ipc4_init_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_CONFIG_GET: +#ifdef CONFIG_SOF_USERSPACE_LL + /* Forward to user thread for privilege-separated execution */ + ret = ipc_user_forward_cmd(ipc4); + if (!ret) { + struct ipc *ipc = ipc_get(); + struct ipc_user *pdata = ipc->ipc_user_pdata; + + msg_reply->extension = pdata->reply_ext; + } +#else ret = ipc4_set_get_config_module_instance(ipc4, false); +#endif break; case SOF_IPC4_MOD_CONFIG_SET: +#ifdef CONFIG_SOF_USERSPACE_LL + /* Forward to user thread for privilege-separated execution */ + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_set_get_config_module_instance(ipc4, true); +#endif break; case SOF_IPC4_MOD_LARGE_CONFIG_GET: +#ifdef CONFIG_SOF_USERSPACE_LL + { + struct ipc4_module_large_config config; + + memcpy_s(&config, sizeof(config), ipc4, sizeof(*ipc4)); + if (config.primary.r.module_id) { + /* Module case: forward to user thread */ + ret = ipc_user_forward_cmd(ipc4); + if (!ret) { + struct ipc *ipc = ipc_get(); + struct ipc_user *pdata = ipc->ipc_user_pdata; + + msg_reply->extension = pdata->reply_ext; + msg_reply->tx_size = pdata->reply_tx_size; + msg_reply->tx_data = pdata->reply_tx_data; + } + } else { + /* Base firmware (module_id==0): keep in kernel — + * ipc4_get_comp_drv() accesses IMR manifest which + * has no user-space partition. + */ + ret = ipc4_get_large_config_module_instance(ipc4); + } + } +#else ret = ipc4_get_large_config_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_LARGE_CONFIG_SET: +#ifdef CONFIG_SOF_USERSPACE_LL + { + struct ipc4_module_large_config config; + + memcpy_s(&config, sizeof(config), ipc4, sizeof(*ipc4)); + if (config.primary.r.module_id) { + ret = ipc_user_forward_cmd(ipc4); + } else { + /* Base firmware: keep in kernel (IMR access) */ + ret = ipc4_set_large_config_module_instance(ipc4); + } + } +#else ret = ipc4_set_large_config_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_BIND: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_bind_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_UNBIND: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_unbind_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_DELETE_INSTANCE: +#ifdef CONFIG_SOF_USERSPACE_LL + ret = ipc_user_forward_cmd(ipc4); +#else ret = ipc4_delete_module_instance(ipc4); +#endif break; case SOF_IPC4_MOD_ENTER_MODULE_RESTORE: case SOF_IPC4_MOD_EXIT_MODULE_RESTORE: diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 8e3073ab7797..bc12f1cd7914 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -64,7 +64,6 @@ LOG_MODULE_DECLARE(ipc, CONFIG_SOF_LOG_LEVEL); extern struct tr_ctx comp_tr; static const struct comp_driver *ipc4_get_drv(const void *uuid); -static int ipc4_add_comp_dev(struct comp_dev *dev); void ipc_build_stream_posn(struct sof_ipc_stream_posn *posn, uint32_t type, uint32_t id) @@ -209,6 +208,105 @@ __cold struct comp_dev *comp_new_ipc4(struct ipc4_module_init_instance *module_i return dev; } +#ifdef CONFIG_SOF_USERSPACE_LL +/** + * comp_new_ipc4_user - Create component in user-space IPC thread context. + * + * Called from the user-space IPC thread. Receives a pre-resolved driver + * pointer from the kernel handler. Performs IPC4 message parsing, HOSTBOX + * data read, and calls drv->ops.create() in user-space context. + * + * @param ipc4 IPC4 message request (reconstructed from ipc_user pri/ext words) + * @param drv Component driver resolved by kernel via ipc4_get_comp_drv() + * @return Created component device, or NULL on failure + */ +__cold struct comp_dev *comp_new_ipc4_user(struct ipc4_message_request *ipc4, + const struct comp_driver *drv) +{ + struct ipc4_module_init_instance module_init; + struct comp_ipc_config ipc_config; + struct comp_dev *dev; + uint32_t comp_id; + char *data; + int ret; + + assert_can_be_cold(); + + ret = memcpy_s(&module_init, sizeof(module_init), ipc4, sizeof(*ipc4)); + if (ret < 0) + return NULL; + + comp_id = IPC4_COMP_ID(module_init.primary.r.module_id, + module_init.primary.r.instance_id); + + if (ipc4_get_comp_dev(comp_id)) { + tr_err(&ipc_tr, "comp 0x%x exists", comp_id); + return NULL; + } + + if (module_init.extension.r.core_id >= CONFIG_CORE_COUNT) { + tr_err(&ipc_tr, "ipc: comp->core = %u", + (uint32_t)module_init.extension.r.core_id); + return NULL; + } + + memset(&ipc_config, 0, sizeof(ipc_config)); + ipc_config.id = comp_id; + ipc_config.pipeline_id = module_init.extension.r.ppl_instance_id; + ipc_config.core = module_init.extension.r.core_id; + ipc_config.ipc_config_size = + module_init.extension.r.param_block_size * sizeof(uint32_t); + ipc_config.ipc_extended_init = module_init.extension.r.extended_init; + if (ipc_config.ipc_config_size > MAILBOX_HOSTBOX_SIZE) { + tr_err(&ipc_tr, + "IPC payload size %u too big for the message window", + ipc_config.ipc_config_size); + return NULL; + } +#ifdef CONFIG_DCACHE_LINE_SIZE + if (!IS_ENABLED(CONFIG_LIBRARY)) + sys_cache_data_invd_range( + (__sparse_force void __sparse_cache *)MAILBOX_HOSTBOX_BASE, + ALIGN_UP(ipc_config.ipc_config_size, + CONFIG_DCACHE_LINE_SIZE)); +#endif + data = ipc4_get_comp_new_data(); + +#if CONFIG_ZEPHYR_DP_SCHEDULER + if (module_init.extension.r.proc_domain) + ipc_config.proc_domain = COMP_PROCESSING_DOMAIN_DP; + else + ipc_config.proc_domain = COMP_PROCESSING_DOMAIN_LL; +#else + if (module_init.extension.r.proc_domain) { + tr_err(&ipc_tr, + "ipc: DP scheduling is disabled, cannot create comp 0x%x", + comp_id); + return NULL; + } + ipc_config.proc_domain = COMP_PROCESSING_DOMAIN_LL; +#endif + + if (drv->type == SOF_COMP_MODULE_ADAPTER) { + const struct ipc_config_process spec = { + .data = (const unsigned char *)data, + .size = ipc_config.ipc_config_size, + }; + + dev = drv->ops.create(drv, &ipc_config, (const void *)&spec); + } else { + dev = drv->ops.create(drv, &ipc_config, (const void *)data); + } + if (!dev) + return NULL; + + list_init(&dev->bsource_list); + list_init(&dev->bsink_list); + + return dev; +} +#endif /* CONFIG_SOF_USERSPACE_LL */ + /* Called from ipc4_set_pipeline_state(), so cannot be cold */ struct ipc_comp_dev *ipc_get_comp_by_ppl_id(struct ipc *ipc, uint16_t type, uint32_t ppl_id, @@ -342,9 +440,12 @@ __cold static int ipc4_create_pipeline(struct ipc4_pipeline_create *pipe_desc, struct ipc_comp_dev *ipc_pipe; struct pipeline *pipe; struct ipc *ipc = ipc_get(); + struct k_heap *heap = sof_sys_user_heap_get(); assert_can_be_cold(); + LOG_INF("pipe_desc %x, instance %u", pipe_desc, pipe_desc->primary.r.instance_id); + /* check whether pipeline id is already taken or in use */ ipc_pipe = ipc_get_pipeline_by_id(ipc, pipe_desc->primary.r.instance_id); if (ipc_pipe) { @@ -354,8 +455,9 @@ __cold static int ipc4_create_pipeline(struct ipc4_pipeline_create *pipe_desc, } /* create the pipeline */ - pipe = pipeline_new(NULL, pipe_desc->primary.r.instance_id, + pipe = pipeline_new(heap, pipe_desc->primary.r.instance_id, pipe_desc->primary.r.ppl_priority, 0, pparams); + LOG_INF("pipeline_new() -> %p", pipe); if (!pipe) { tr_err(&ipc_tr, "ipc: pipeline_new() failed"); return IPC4_OUT_OF_MEMORY; @@ -370,12 +472,13 @@ __cold static int ipc4_create_pipeline(struct ipc4_pipeline_create *pipe_desc, pipe->core = pipe_desc->extension.r.core_id; /* allocate the IPC pipeline container */ - ipc_pipe = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, - sizeof(struct ipc_comp_dev)); + ipc_pipe = sof_heap_alloc(heap, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct ipc_comp_dev), 0); if (!ipc_pipe) { pipeline_free(pipe); return IPC4_OUT_OF_MEMORY; } + memset(ipc_pipe, 0, sizeof(*ipc_pipe)); ipc_pipe->pipeline = pipe; ipc_pipe->type = COMP_TYPE_PIPELINE; @@ -386,6 +489,8 @@ __cold static int ipc4_create_pipeline(struct ipc4_pipeline_create *pipe_desc, /* add new pipeline to the list */ list_item_append(&ipc_pipe->list, &ipc->comp_list); + LOG_INF("success"); + return IPC4_SUCCESS; } @@ -449,10 +554,21 @@ __cold static int ipc_pipeline_module_free(uint32_t pipeline_id) struct ipc *ipc = ipc_get(); struct ipc_comp_dev *icd; int ret; +#ifdef CONFIG_SOF_USERSPACE_LL + int ppl_core; +#endif assert_can_be_cold(); icd = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_COMPONENT, pipeline_id, IPC_COMP_ALL); + if (!icd) + return IPC4_SUCCESS; + +#ifdef CONFIG_SOF_USERSPACE_LL + ppl_core = icd->core; + zephyr_ll_lock_sched(ppl_core); +#endif + while (icd) { struct comp_buffer *buffer; struct comp_buffer *safe; @@ -482,12 +598,20 @@ __cold static int ipc_pipeline_module_free(uint32_t pipeline_id) else ret = ipc_comp_free(ipc, icd->id); - if (ret) + if (ret) { +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(ppl_core); +#endif return IPC4_INVALID_RESOURCE_STATE; + } icd = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_COMPONENT, pipeline_id, IPC_COMP_ALL); } +#ifdef CONFIG_SOF_USERSPACE_LL + zephyr_ll_unlock_sched(ppl_core); +#endif + return IPC4_SUCCESS; } @@ -523,7 +647,7 @@ __cold int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id) ipc_pipe->pipeline = NULL; list_item_del(&ipc_pipe->list); - rfree(ipc_pipe); + sof_heap_free(sof_sys_user_heap_get(), ipc_pipe); return IPC4_SUCCESS; } @@ -540,7 +664,10 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool ipc_buf.size = buf_size; ipc_buf.comp.id = IPC4_COMP_ID(src_queue, dst_queue); ipc_buf.comp.pipeline_id = src->ipc_config.pipeline_id; - ipc_buf.comp.core = cpu_get_id(); + + assert(IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || src->ipc_config.core == cpu_get_id()); + ipc_buf.comp.core = src->ipc_config.core; + return buffer_new(alloc, &ipc_buf, is_shared); } @@ -561,6 +688,23 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool * disable any interrupts. */ +#if CONFIG_SOF_USERSPACE_LL +#define ll_block(cross_core_bind, flags) \ + do { \ + if (cross_core_bind) \ + domain_block(sof_get()->platform_timer_domain); \ + else \ + zephyr_ll_lock_sched(cpu_get_id()); \ + } while (0) + +#define ll_unblock(cross_core_bind, flags) \ + do { \ + if (cross_core_bind) \ + domain_unblock(sof_get()->platform_timer_domain); \ + else \ + zephyr_ll_unlock_sched(cpu_get_id()); \ + } while (0) +#else #define ll_block(cross_core_bind, flags) \ do { \ if (cross_core_bind) \ @@ -576,6 +720,7 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool else \ irq_local_enable(flags); \ } while (0) +#endif /* CONFIG_SOF_USERSPACE_LL */ /* Calling both ll_block() and ll_wait_finished_on_core() makes sure LL will not start its * next cycle and its current cycle on specified core has finished. @@ -607,8 +752,13 @@ static int ll_wait_finished_on_core(struct comp_dev *dev) #else +#if CONFIG_SOF_USERSPACE_LL +#define ll_block(cross_core_bind, flags) zephyr_ll_lock_sched(0) +#define ll_unblock(cross_core_bind, flags) zephyr_ll_unlock_sched(0) +#else #define ll_block(cross_core_bind, flags) irq_local_disable(flags) #define ll_unblock(cross_core_bind, flags) irq_local_enable(flags) +#endif /* CONFIG_SOF_USERSPACE_LL */ #endif @@ -665,6 +815,11 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) #else alloc = NULL; #endif /* CONFIG_ZEPHYR_DP_SCHEDULER */ + +#ifdef CONFIG_SOF_USERSPACE_LL + if (!alloc) + alloc = ipc->ll_alloc; +#endif bool cross_core_bind = source->ipc_config.core != sink->ipc_config.core; /* If both components are on same core -- process IPC on that core, @@ -797,6 +952,12 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect) */ ll_block(cross_core_bind, flags); + /* + * TODO: for multicore user support, comp_bufffer_connect() + * needs to be converted to a syscall. for now, limit to core0 + */ + assert(IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || source->ipc_config.core == cpu_get_id()); + if (cross_core_bind) { #if CONFIG_CROSS_CORE_STREAM /* Make sure LL has finished on both cores */ @@ -1033,7 +1194,7 @@ __cold int ipc4_chain_dma_state(struct comp_dev *dev, struct ipc4_chain_dma *cdm if (icd->cd != dev) continue; list_item_del(&icd->list); - rfree(icd); + sof_heap_free(sof_sys_user_heap_get(), icd); break; } comp_free(dev); @@ -1131,11 +1292,19 @@ __cold static const struct comp_driver *ipc4_search_for_drv(const void *uuid) struct list_item *clist; const struct comp_driver *drv = NULL; struct comp_driver_info *info; +#ifndef CONFIG_SOF_USERSPACE_LL uint32_t flags; +#endif assert_can_be_cold(); + /* Driver list is populated at boot before IPC processing starts. + * In user-space builds irq_local_disable() is privileged, but the + * list is immutable by this point so no lock is needed. + */ +#ifndef CONFIG_SOF_USERSPACE_LL irq_local_disable(flags); +#endif /* search driver list with UUID */ list_for_item(clist, &drivers->list) { @@ -1151,7 +1320,9 @@ __cold static const struct comp_driver *ipc4_search_for_drv(const void *uuid) } } +#ifndef CONFIG_SOF_USERSPACE_LL irq_local_enable(flags); +#endif return drv; } @@ -1253,7 +1424,7 @@ struct comp_dev *ipc4_get_comp_dev(uint32_t comp_id) } EXPORT_SYMBOL(ipc4_get_comp_dev); -__cold static int ipc4_add_comp_dev(struct comp_dev *dev) +__cold int ipc4_add_comp_dev(struct comp_dev *dev) { struct ipc *ipc = ipc_get(); struct ipc_comp_dev *icd; @@ -1268,12 +1439,13 @@ __cold static int ipc4_add_comp_dev(struct comp_dev *dev) } /* allocate the IPC component container */ - icd = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, - sizeof(struct ipc_comp_dev)); + icd = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct ipc_comp_dev), 0); if (!icd) { tr_err(&ipc_tr, "alloc failed"); return IPC4_OUT_OF_MEMORY; } + memset(icd, 0, sizeof(*icd)); icd->cd = dev; icd->type = COMP_TYPE_COMPONENT; diff --git a/src/ipc/ipc4/notification-user.c b/src/ipc/ipc4/notification-user.c new file mode 100644 index 000000000000..37a881e32829 --- /dev/null +++ b/src/ipc/ipc4/notification-user.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright(c) 2023 Intel Corporation. All rights reserved. + * + * Author: Piotr Makaruk + * Adrian Warecki + */ + +#include +#include +#include + +#include + +static enum sof_ipc4_resource_event_type dir_to_xrun_event(enum sof_ipc_stream_direction dir) +{ + return (dir == SOF_IPC_STREAM_PLAYBACK) ? SOF_IPC4_GATEWAY_UNDERRUN_DETECTED : + SOF_IPC4_GATEWAY_OVERRUN_DETECTED; +} + +bool send_copier_gateway_xrun_notif_msg(uint32_t pipeline_id, enum sof_ipc_stream_direction dir) +{ + return send_resource_notif(pipeline_id, dir_to_xrun_event(dir), SOF_IPC4_PIPELINE, NULL, + 0); +} + +bool send_gateway_xrun_notif_msg(uint32_t resource_id, enum sof_ipc_stream_direction dir) +{ + return send_resource_notif(resource_id, dir_to_xrun_event(dir), SOF_IPC4_GATEWAY, NULL, 0); +} + +void send_mixer_underrun_notif_msg(uint32_t resource_id, uint32_t eos_flag, uint32_t data_mixed, + uint32_t expected_data_mixed) +{ + struct ipc4_mixer_underrun_event_data mixer_underrun_data; + + mixer_underrun_data.eos_flag = eos_flag; + mixer_underrun_data.data_mixed = data_mixed; + mixer_underrun_data.expected_data_mixed = expected_data_mixed; + + send_resource_notif(resource_id, SOF_IPC4_MIXER_UNDERRUN_DETECTED, SOF_IPC4_PIPELINE, + &mixer_underrun_data, sizeof(mixer_underrun_data)); +} +EXPORT_SYMBOL(send_mixer_underrun_notif_msg); + +void send_process_data_error_notif_msg(uint32_t resource_id, uint32_t error_code) +{ + struct ipc4_process_data_error_event_data error_data; + + error_data.error_code = error_code; + + send_resource_notif(resource_id, SOF_IPC4_PROCESS_DATA_ERROR, SOF_IPC4_MODULE_INSTANCE, + &error_data, sizeof(error_data)); +} diff --git a/src/ipc/ipc4/notification.c b/src/ipc/ipc4/notification.c index 9fd566ee1f93..485b875f8468 100644 --- a/src/ipc/ipc4/notification.c +++ b/src/ipc/ipc4/notification.c @@ -12,7 +12,9 @@ #include #include -#include +#ifdef CONFIG_SOF_USERSPACE_LL +#include +#endif static uint32_t notification_mask = 0xFFFFFFFF; @@ -37,8 +39,13 @@ static bool is_notif_filtered_out(uint32_t event_type) return (notification_mask & BIT(notif_idx)) == 0; } -static bool send_resource_notif(uint32_t resource_id, uint32_t event_type, uint32_t resource_type, - void *data, uint32_t data_size) +#ifdef CONFIG_SOF_USERSPACE_LL +bool z_impl_send_resource_notif(uint32_t resource_id, uint32_t event_type, + uint32_t resource_type, void *data, uint32_t data_size) +#else +bool send_resource_notif(uint32_t resource_id, uint32_t event_type, + uint32_t resource_type, void *data, uint32_t data_size) +#endif { struct ipc_msg *msg; @@ -80,49 +87,34 @@ static bool send_resource_notif(uint32_t resource_id, uint32_t event_type, uint3 return true; } -static enum sof_ipc4_resource_event_type dir_to_xrun_event(enum sof_ipc_stream_direction dir) -{ - return (dir == SOF_IPC_STREAM_PLAYBACK) ? SOF_IPC4_GATEWAY_UNDERRUN_DETECTED : - SOF_IPC4_GATEWAY_OVERRUN_DETECTED; -} - void ipc4_update_notification_mask(uint32_t ntfy_mask, uint32_t enabled_mask) { notification_mask &= enabled_mask | (~ntfy_mask); notification_mask |= enabled_mask & ntfy_mask; } -bool send_copier_gateway_xrun_notif_msg(uint32_t pipeline_id, enum sof_ipc_stream_direction dir) +#ifdef CONFIG_SOF_USERSPACE_LL +static inline bool z_vrfy_send_resource_notif(uint32_t resource_id, uint32_t event_type, + uint32_t resource_type, void *data, + uint32_t data_size) { - return send_resource_notif(pipeline_id, dir_to_xrun_event(dir), SOF_IPC4_PIPELINE, NULL, - 0); -} + /* Validate event_type is a known resource event */ + K_OOPS(K_SYSCALL_VERIFY(event_type < SOF_IPC4_INVALID_RESOURCE_EVENT_TYPE)); -bool send_gateway_xrun_notif_msg(uint32_t resource_id, enum sof_ipc_stream_direction dir) -{ - return send_resource_notif(resource_id, dir_to_xrun_event(dir), SOF_IPC4_GATEWAY, NULL, 0); -} + /* Validate resource_type is a known resource type */ + K_OOPS(K_SYSCALL_VERIFY(resource_type < SOF_IPC4_INVALID_RESOURCE_TYPE)); -void send_mixer_underrun_notif_msg(uint32_t resource_id, uint32_t eos_flag, uint32_t data_mixed, - uint32_t expected_data_mixed) -{ - struct ipc4_mixer_underrun_event_data mixer_underrun_data; - - mixer_underrun_data.eos_flag = eos_flag; - mixer_underrun_data.data_mixed = data_mixed; - mixer_underrun_data.expected_data_mixed = expected_data_mixed; + /* data and data_size must be consistent */ + K_OOPS(K_SYSCALL_VERIFY((!data && !data_size) || (data && data_size))); - send_resource_notif(resource_id, SOF_IPC4_MIXER_UNDERRUN_DETECTED, SOF_IPC4_PIPELINE, - &mixer_underrun_data, sizeof(mixer_underrun_data)); -} -EXPORT_SYMBOL(send_mixer_underrun_notif_msg); - -void send_process_data_error_notif_msg(uint32_t resource_id, uint32_t error_code) -{ - struct ipc4_process_data_error_event_data error_data; + /* Payload must fit in the event_data union and the IPC message */ + K_OOPS(K_SYSCALL_VERIFY(data_size <= sizeof(union ipc4_resource_event_data))); + K_OOPS(K_SYSCALL_VERIFY(data_size <= SOF_IPC_MSG_MAX_SIZE)); - error_data.error_code = error_code; + if (data && data_size) + K_OOPS(K_SYSCALL_MEMORY_READ(data, data_size)); - send_resource_notif(resource_id, SOF_IPC4_PROCESS_DATA_ERROR, SOF_IPC4_MODULE_INSTANCE, - &error_data, sizeof(error_data)); + return z_impl_send_resource_notif(resource_id, event_type, resource_type, data, data_size); } +#include +#endif diff --git a/src/lib/dai.c b/src/lib/dai.c index 7a8e44087a65..d51266aa345e 100644 --- a/src/lib/dai.c +++ b/src/lib/dai.c @@ -243,7 +243,7 @@ static int sof_dai_type_to_zephyr(uint32_t type) } } -const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index) +const struct device *z_impl_dai_get_device(enum sof_ipc_dai_type type, uint32_t index) { struct dai_config cfg; int z_type; diff --git a/src/library_manager/lib_notification.c b/src/library_manager/lib_notification.c index 36aa85835e70..0feab9660c3e 100644 --- a/src/library_manager/lib_notification.c +++ b/src/library_manager/lib_notification.c @@ -53,7 +53,7 @@ struct ipc_msg *lib_notif_msg_init(uint32_t header, uint32_t size) sizeof(*msg_pool_elem)); if (!msg_pool_elem) return NULL; - msg = ipc_msg_init(header, SRAM_OUTBOX_SIZE); + msg = ipc_msg_init(NULL, header, SRAM_OUTBOX_SIZE); if (!msg) { rfree(msg_pool_elem); return NULL; diff --git a/src/samples/audio/detect_test.c b/src/samples/audio/detect_test.c index 1e2b21ee9d9e..305211a07ac8 100644 --- a/src/samples/audio/detect_test.c +++ b/src/samples/audio/detect_test.c @@ -472,7 +472,7 @@ static struct ipc_msg *ipc4_kd_notification_init(uint32_t word_id, notif.extension.r.sv_score = score; - msg = ipc_msg_w_ext_init(notif.primary.dat, + msg = ipc_msg_w_ext_init(NULL, notif.primary.dat, notif.extension.dat, 0); if (!msg) @@ -731,7 +731,7 @@ static struct comp_dev *test_keyword_new(const struct comp_driver *drv, cd->msg = ipc4_kd_notification_init(NOTIFICATION_DEFAULT_WORD_ID, NOTIFICATION_DEFAULT_SCORE); #else - cd->msg = ipc_msg_init(cd->event.rhdr.hdr.cmd, sizeof(cd->event)); + cd->msg = ipc_msg_init(NULL, cd->event.rhdr.hdr.cmd, sizeof(cd->event)); #endif /* CONFIG_IPC_MAJOR_4 */ if (!cd->msg) { diff --git a/src/schedule/schedule.c b/src/schedule/schedule.c index 5e56b6c490e0..7bcc30d755aa 100644 --- a/src/schedule/schedule.c +++ b/src/schedule/schedule.c @@ -47,15 +47,19 @@ int schedule_task_init(struct task *task, static void scheduler_register(struct schedule_data *scheduler) { struct schedulers **sch = arch_schedulers_get(); + struct k_heap *heap = NULL; +#ifdef CONFIG_SOF_USERSPACE_LL + heap = sof_sys_user_heap_get(); +#endif if (!*sch) { /* init schedulers list */ - *sch = rzalloc(SOF_MEM_FLAG_KERNEL, - sizeof(**sch)); + *sch = sof_heap_alloc(heap, 0, sizeof(**sch), 0); if (!*sch) { tr_err(&sch_tr, "allocation failed"); return; } + memset(*sch, 0, sizeof(**sch)); list_init(&(*sch)->list); } @@ -65,16 +69,21 @@ static void scheduler_register(struct schedule_data *scheduler) void scheduler_init(int type, const struct scheduler_ops *ops, void *data) { struct schedule_data *sch; + struct k_heap *heap = NULL; +#ifdef CONFIG_SOF_USERSPACE_LL + heap = sof_sys_user_heap_get(); +#endif if (!ops || !ops->schedule_task || !ops->schedule_task_cancel || !ops->schedule_task_free) return; - sch = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(*sch)); + sch = sof_heap_alloc(heap, SOF_MEM_FLAG_KERNEL, sizeof(*sch), 0); if (!sch) { tr_err(&sch_tr, "allocation failed"); sof_panic(SOF_IPC_PANIC_IPC); } + memset(sch, 0, sizeof(*sch)); list_init(&sch->list); sch->type = type; sch->ops = ops; diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index ebf5aea8d4d2..f90f70ae4436 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -126,7 +126,8 @@ static void zephyr_domain_thread_fn(void *p1, void *p2, void *p3) } #endif - dt->handler(dt->arg); + if (dt->handler) + dt->handler(dt->arg); #ifdef CONFIG_SCHEDULE_LL_STATS_LOG cycles1 = k_cycle_get_32(); @@ -289,63 +290,65 @@ static int zephyr_domain_unregister(struct ll_schedule_domain *domain, #else /* CONFIG_SOF_USERSPACE_LL */ -/* User-space implementation for register/unregister */ - -static int zephyr_domain_register_user(struct ll_schedule_domain *domain, - struct task *task, - void (*handler)(void *arg), void *arg) +/* + * Privileged thread initialization for userspace LL scheduling. + * Creates the scheduling thread, sets up timer, grants access to kernel + * objects. Must be called from kernel context before any user-space + * domain_register() calls. + */ +static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, + struct task *task) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); - int core = cpu_get_id(); - struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; - char thread_name[] = "ll_thread0"; + struct zephyr_domain_thread *dt; + char thread_name[] = "userll_thread0"; k_tid_t thread; + int core = task->core; - tr_dbg(&ll_tr, "entry"); + tr_dbg(&ll_tr, "thread_init entry"); - /* domain work only needs registered once on each core */ - if (dt->handler) - return 0; + if (core >= CONFIG_CORE_COUNT) + return -EINVAL; - __ASSERT_NO_MSG(task->core == core); + dt = zephyr_domain->domain_thread + core; - dt->handler = handler; - dt->arg = arg; + /* thread only needs to be created once per core */ + if (dt->ll_thread) + return 0; + + dt->handler = NULL; + dt->arg = NULL; /* 10 is rather random, we better not accumulate 10 missed timer interrupts */ k_sem_init(dt->sem, 0, 10); thread_name[sizeof(thread_name) - 2] = '0' + core; + /* Allocate thread structure dynamically */ + dt->ll_thread = k_object_alloc(K_OBJ_THREAD); if (!dt->ll_thread) { - /* Allocate thread structure dynamically */ - dt->ll_thread = k_object_alloc(K_OBJ_THREAD); - if (!dt->ll_thread) { - tr_err(&ll_tr, "Failed to allocate thread object for core %d", core); - dt->handler = NULL; - dt->arg = NULL; - return -ENOMEM; - } + tr_err(&ll_tr, "Failed to allocate thread object for core %d", core); + return -ENOMEM; + } - thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE, - zephyr_domain_thread_fn, zephyr_domain, - INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY, - K_USER, K_FOREVER); + thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE, + zephyr_domain_thread_fn, zephyr_domain, + INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY, + K_USER, K_FOREVER); #ifdef CONFIG_SCHED_CPU_MASK - k_thread_cpu_mask_clear(thread); - k_thread_cpu_mask_enable(thread, core); + k_thread_cpu_mask_clear(thread); + k_thread_cpu_mask_enable(thread, core); #endif - k_thread_name_set(thread, thread_name); + k_thread_name_set(thread, thread_name); - k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread); - k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); - user_grant_dai_access_all(thread); - user_grant_dma_access_all(thread); - tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core); + k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread); + k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); + user_grant_dai_access_all(thread); + user_grant_dma_access_all(thread); + tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core); - k_thread_start(thread); - } + k_thread_start(thread); k_mutex_lock(domain->lock, K_FOREVER); if (!k_timer_user_data_get(zephyr_domain->timer)) { @@ -368,6 +371,43 @@ static int zephyr_domain_register_user(struct ll_schedule_domain *domain, return 0; } +/* + * User-space register: bookkeeping only. The privileged thread setup has + * already been done by domain_thread_init() called from kernel context. + */ +static int zephyr_domain_register_user(struct ll_schedule_domain *domain, + struct task *task, + void (*handler)(void *arg), void *arg) +{ + struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); + struct zephyr_domain_thread *dt; + int core; + + tr_dbg(&ll_tr, "register_user entry"); + + if (task->core >= CONFIG_CORE_COUNT) + return -EINVAL; + + core = task->core; + dt = zephyr_domain->domain_thread + core; + + if (!dt->ll_thread) { + tr_err(&ll_tr, "domain_thread_init() not called for core %d", core); + return -EINVAL; + } + + __ASSERT_NO_MSG(!dt->handler || dt->handler == handler); + if (dt->handler) + return 0; + + dt->handler = handler; + dt->arg = arg; + + tr_info(&ll_tr, "task registered on core %d", core); + + return 0; +} + static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, struct task *task, uint32_t num_tasks) { @@ -382,25 +422,14 @@ static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, k_mutex_lock(domain->lock, K_FOREVER); - if (!atomic_read(&domain->total_num_tasks)) { - /* Disable the watchdog */ - watchdog_disable(core); - - k_timer_stop(zephyr_domain->timer); - k_timer_user_data_set(zephyr_domain->timer, NULL); - } - zephyr_domain->domain_thread[core].handler = NULL; k_mutex_unlock(domain->lock); - tr_info(&ll_tr, "domain->type %d domain->clk %d", - domain->type, domain->clk); - - /* Thread not removed here, only the timer is stopped. - * Thread object cleanup would require k_thread_abort() which cannot - * be safely called from this context. The thread remains allocated - * but dormant until next registration or system shutdown. + /* + * In this user thread implementation, the timer is left + * running until privileged domain_thread_free() is called + * to clean up resources. */ tr_dbg(&ll_tr, "exit"); @@ -408,13 +437,51 @@ static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain, return 0; } -struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain) +/* + * Free resources acquired by zephyr_domain_thread_init(). + * Stops the timer, aborts the scheduling thread and frees the thread object. + * Must be called from kernel context. + */ +static void zephyr_domain_thread_free(struct ll_schedule_domain *domain, + uint32_t num_tasks) { struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); int core = cpu_get_id(); struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; - tr_dbg(&ll_tr, "entry"); + tr_dbg(&ll_tr, "thread_free entry, core %d, num_tasks %u", core, num_tasks); + + /* Still tasks on other cores, only clean up this core's thread */ + k_mutex_lock(domain->lock, K_FOREVER); + + if (!num_tasks && !atomic_read(&domain->total_num_tasks)) { + /* Last task globally: stop the timer and watchdog */ + watchdog_disable(core); + + k_timer_stop(zephyr_domain->timer); + k_timer_user_data_set(zephyr_domain->timer, NULL); + } + + dt->handler = NULL; + dt->arg = NULL; + + k_mutex_unlock(domain->lock); + + if (dt->ll_thread) { + k_thread_abort(dt->ll_thread); + k_object_free(dt->ll_thread); + dt->ll_thread = NULL; + } + + tr_info(&ll_tr, "thread_free done, core %d", core); +} + +struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain, int core) +{ + struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); + struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + + tr_dbg(&ll_tr, "entry core %d", core); return dt->ll_thread; } @@ -450,6 +517,8 @@ APP_TASK_DATA static const struct ll_schedule_domain_ops zephyr_domain_ops = { #ifdef CONFIG_SOF_USERSPACE_LL .domain_register = zephyr_domain_register_user, .domain_unregister = zephyr_domain_unregister_user, + .domain_thread_init = zephyr_domain_thread_init, + .domain_thread_free = zephyr_domain_thread_free, #else .domain_register = zephyr_domain_register, .domain_unregister = zephyr_domain_unregister, diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index 575a82d91dda..359053786a88 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -5,6 +5,7 @@ // Author: Guennadi Liakhovetski #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include @@ -31,7 +33,7 @@ struct zephyr_ll { struct ll_schedule_domain *ll_domain; /* scheduling domain */ unsigned int core; /* core ID of this instance */ #if CONFIG_SOF_USERSPACE_LL - struct k_mutex *lock; /* mutex for userspace */ + struct sof_umutex lock; /* mutex for userspace */ #endif struct k_heap *heap; }; @@ -40,13 +42,13 @@ struct zephyr_ll { struct zephyr_ll_pdata { bool run; bool freeing; - struct k_sem sem; + struct sys_sem sem; }; static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) { #if CONFIG_SOF_USERSPACE_LL - k_mutex_lock(sch->lock, K_FOREVER); + sof_umutex_lock(&sch->lock, K_FOREVER); #else irq_local_disable(*flags); #endif @@ -55,7 +57,7 @@ static void zephyr_ll_lock(struct zephyr_ll *sch, uint32_t *flags) static void zephyr_ll_unlock(struct zephyr_ll *sch, uint32_t *flags) { #if CONFIG_SOF_USERSPACE_LL - k_mutex_unlock(sch->lock); + sof_umutex_unlock(&sch->lock); #else irq_local_enable(*flags); #endif @@ -63,8 +65,15 @@ static void zephyr_ll_unlock(struct zephyr_ll *sch, uint32_t *flags) static void zephyr_ll_assert_core(const struct zephyr_ll *sch) { - assert(CONFIG_CORE_COUNT == 1 || IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || - sch->core == cpu_get_id()); +#if CONFIG_SOF_USERSPACE_LL + /* In user-space mode, cpu_get_id() is not available. + * Core correctness is ensured by task->core routing in + * schedule.h and verified at task schedule time. + */ + (void)sch; +#else + assert(CONFIG_CORE_COUNT == 1 || sch->core == cpu_get_id()); +#endif } /* Locking: caller should hold the domain lock */ @@ -87,7 +96,7 @@ static void zephyr_ll_task_done(struct zephyr_ll *sch, * zephyr_ll_task_free() is trying to free this task. Complete * it and signal the semaphore to let the function proceed */ - k_sem_give(&pdata->sem); + sys_sem_give(&pdata->sem); tr_info(&ll_tr, "task complete %p %pU", task, task->uid); tr_info(&ll_tr, "num_tasks %d total_num_tasks %ld", @@ -360,17 +369,7 @@ static int zephyr_ll_task_schedule_common(struct zephyr_ll *sch, struct task *ta ret = domain_register(sch->ll_domain, task, &schedule_ll_callback, sch); if (ret < 0) - tr_err(&ll_tr, "cannot register domain %d", - ret); - -#if CONFIG_SOF_USERSPACE_LL - k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock); - - tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock, - zephyr_domain_thread_tid(sch->ll_domain)); - tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock, - zephyr_domain_thread_tid(sch->ll_domain)); -#endif + tr_err(&ll_tr, "cannot register domain %d", ret); return 0; } @@ -403,7 +402,7 @@ static int zephyr_ll_task_schedule_after(void *data, struct task *task, uint64_t * This is synchronous - after this returns the object can be destroyed! * Assertion: under Zephyr this is always called from a thread context! */ -static int zephyr_ll_task_free(void *data, struct task *task) +static int zephyr_ll_task_sched_free(void *data, struct task *task) { struct zephyr_ll *sch = data; uint32_t flags; @@ -412,10 +411,12 @@ static int zephyr_ll_task_free(void *data, struct task *task) zephyr_ll_assert_core(sch); +#ifndef CONFIG_SOF_USERSPACE_LL if (k_is_in_isr()) { tr_err(&ll_tr, "cannot free tasks from interrupt context!"); return -EDEADLK; } +#endif zephyr_ll_lock(sch, &flags); @@ -454,7 +455,7 @@ static int zephyr_ll_task_free(void *data, struct task *task) if (must_wait) /* Wait for up to 100 periods */ - k_sem_take(&pdata->sem, K_USEC(LL_TIMER_PERIOD_US * 100)); + sys_sem_take(&pdata->sem, K_USEC(LL_TIMER_PERIOD_US * 100)); /* Protect against racing with schedule_task() */ zephyr_ll_lock(sch, &flags); @@ -509,15 +510,45 @@ static void zephyr_ll_scheduler_free(void *data, uint32_t flags) if (sch->n_tasks) tr_err(&ll_tr, "%u tasks are still active!", sch->n_tasks); + +#if CONFIG_SOF_USERSPACE_LL + domain_thread_free(sch->ll_domain, sch->n_tasks); +#endif +} + +#if CONFIG_SOF_USERSPACE_LL +struct k_thread *zephyr_ll_init_context(void *data, struct task *task) +{ + struct zephyr_ll *sch = data; + int ret; + + /* + * Use domain_thread_init() for privileged setup (thread creation, + * timer, access grants). domain_register() is now bookkeeping only + * and will be called later from user context when scheduling tasks. + */ + ret = domain_thread_init(sch->ll_domain, task); + if (ret < 0) { + tr_err(&ll_tr, "cannot init_context %d", ret); + return NULL; + } + + assert(!k_is_user_context()); + + return zephyr_domain_thread_tid(sch->ll_domain, task->core); } +#endif static const struct scheduler_ops zephyr_ll_ops = { .schedule_task = zephyr_ll_task_schedule, .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, .schedule_task_cancel = zephyr_ll_task_cancel, .scheduler_free = zephyr_ll_scheduler_free, +#if CONFIG_SOF_USERSPACE_LL + .scheduler_init_context = zephyr_ll_init_context, +#endif }; #if CONFIG_SOF_USERSPACE_LL @@ -526,6 +557,43 @@ struct task *zephyr_ll_task_alloc(void) return sof_heap_alloc(zephyr_ll_user_heap(), SOF_MEM_FLAG_USER, sizeof(struct task), sizeof(void *)); } + +void zephyr_ll_task_free(struct task *task) +{ + sof_heap_free(zephyr_ll_user_heap(), task); +} + +void zephyr_ll_grant_access(struct k_thread *thread) +{ + /* sof_umutex does not require access grants */ +} + +/** + * Lock the LL scheduler to prevent it from processing tasks. + * + * Uses the LL scheduler's own sof_umutex which is re-entrant, so + * schedule_task() calls within the locked section will not deadlock. + * Must be paired with zephyr_ll_unlock_sched(). + */ +void zephyr_ll_lock_sched(int core) +{ + struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data_for_core(SOF_SCHEDULE_LL_TIMER, + core); + + sof_umutex_lock(&sch->lock, K_FOREVER); +} + +/** + * Unlock the LL scheduler after a previous zephyr_ll_lock_sched() call. + */ +void zephyr_ll_unlock_sched(int core) +{ + struct zephyr_ll *sch = (struct zephyr_ll *)scheduler_get_data_for_core(SOF_SCHEDULE_LL_TIMER, + core); + + sof_umutex_unlock(&sch->lock); +} + #endif /* CONFIG_SOF_USERSPACE_LL */ int zephyr_ll_task_init(struct task *task, @@ -560,7 +628,7 @@ int zephyr_ll_task_init(struct task *task, memset(pdata, 0, sizeof(*pdata)); - k_sem_init(&pdata->sem, 0, 1); + sys_sem_init(&pdata->sem, 0, 1); task->priv_data = pdata; @@ -599,16 +667,12 @@ int zephyr_ll_scheduler_init(struct ll_schedule_domain *domain) sch->heap = heap; #if CONFIG_SOF_USERSPACE_LL - /* Allocate mutex dynamically for userspace access */ - sch->lock = k_object_alloc(K_OBJ_MUTEX); - if (!sch->lock) { + if (sof_umutex_init(&sch->lock)) { tr_err(&ll_tr, "mutex allocation failed"); sof_heap_free(sch->heap, sch); return -ENOMEM; } - k_mutex_init(sch->lock); - - tr_dbg(&ll_tr, "ll-scheduler init done, sch %p sch->lock %p", sch, sch->lock); + tr_dbg(&ll_tr, "ll-scheduler init done, sch %p", sch); #endif scheduler_init(domain->type, &zephyr_ll_ops, sch); diff --git a/src/schedule/zephyr_ll_user.c b/src/schedule/zephyr_ll_user.c index aa33807b4aa3..bad2f6c9cbb6 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -17,25 +17,51 @@ LOG_MODULE_DECLARE(ll_schedule, CONFIG_SOF_LOG_LEVEL); * * This structure encapsulates the memory management resources required for the * low-latency (LL) scheduler in userspace mode. It provides memory isolation - * and heap management for LL scheduler threads. + * and heap management for LL scheduler threads. Only kernel accessible. */ struct zephyr_ll_mem_resources { struct k_mem_domain mem_domain; /**< Memory domain for LL thread isolation */ - struct k_heap *heap; /**< Heap allocator for LL scheduler memory */ + struct k_heap heap; /**< Heap allocator for LL scheduler memory */ }; static struct zephyr_ll_mem_resources ll_mem_resources; -static struct k_heap *zephyr_ll_heap_init(void) +/** + * Heap allocator for LL scheduler memory (user accessible pointer) + * + * Note: this is also user-writable, so kernel must not rely on this to + * be correct and must always validate it separately. + */ +APP_SYSUSER_DATA static struct k_heap *zephyr_ll_heap; + +static struct k_heap *ll_heap_alloc(void) { - struct k_heap *heap = module_driver_heap_init(); - struct k_mem_partition mem_partition; - int ret; + const size_t alloc_size = CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE; + + BUILD_ASSERT(CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE % CONFIG_MM_DRV_PAGE_SIZE == 0); + + void *mem = rballoc_align(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, alloc_size, + CONFIG_MM_DRV_PAGE_SIZE); + if (!mem) + return NULL; + + k_heap_init(&ll_mem_resources.heap, mem, alloc_size); /* - * TODO: the size of LL heap should be independently configurable and - * not tied to CONFIG_SOF_ZEPHYR_USERSPACE_MODULE_HEAP_SIZE + * k_heap_init() does not set these, so set the values + * manually here */ + ll_mem_resources.heap.heap.init_mem = mem; + ll_mem_resources.heap.heap.init_bytes = alloc_size; + + return &ll_mem_resources.heap; +} + +static void ll_heap_init(void) +{ + struct k_heap *heap = ll_heap_alloc(); + struct k_mem_partition mem_partition; + int ret; if (!heap) { tr_err(&ll_tr, "heap alloc fail"); @@ -53,6 +79,7 @@ static struct k_heap *zephyr_ll_heap_init(void) if (ret) k_panic(); +#ifdef CONFIG_CACHE_HAS_MIRRORED_MEMORY_REGIONS mem_partition.start = (uintptr_t)sys_cache_uncached_ptr_get(heap->heap.init_mem); mem_partition.attr = K_MEM_PARTITION_P_RW_U_RW; ret = k_mem_domain_add_partition(&ll_mem_resources.mem_domain, &mem_partition); @@ -60,25 +87,59 @@ static struct k_heap *zephyr_ll_heap_init(void) (void *)mem_partition.start, heap->heap.init_bytes, ret); if (ret) k_panic(); - - return heap; +#endif } void zephyr_ll_user_resources_init(void) { + int ret; + k_mem_domain_init(&ll_mem_resources.mem_domain, 0, NULL); - ll_mem_resources.heap = zephyr_ll_heap_init(); + ll_heap_init(); + + /* store a user-accessible pointer */ + zephyr_ll_heap = &ll_mem_resources.heap; /* attach common partition to LL domain */ user_memory_attach_common_partition(zephyr_ll_mem_domain()); + + ret = user_memory_attach_system_user_partition(zephyr_ll_mem_domain()); + if (ret) + k_panic(); +} + +/** + * Check if 'heap' is a valid heap pointer. + * + * Available only in kernel mode. + * + * @return true if valid + */ +bool zephyr_ll_user_heap_verify(struct k_heap *heap) +{ + return heap == &ll_mem_resources.heap; } +/** + * Returns heap object to use in user-space LL code. + * + * Can be called from user-space. + * + * @return heap pointer that can be passed to sof_heap_alloc() + */ struct k_heap *zephyr_ll_user_heap(void) { - return ll_mem_resources.heap; + return zephyr_ll_heap; } +/** + * Returns pointer to LL user-space memory domain. + * + * Available only in kernel mode. + * + * @return pointer to memory domain + */ struct k_mem_domain *zephyr_ll_mem_domain(void) { return &ll_mem_resources.mem_domain; diff --git a/src/trace/dma-trace.c b/src/trace/dma-trace.c index 554204ac5c70..fc7e7b2e953c 100644 --- a/src/trace/dma-trace.c +++ b/src/trace/dma-trace.c @@ -160,7 +160,7 @@ int dma_trace_init_early(struct sof *sof) k_spinlock_init(&sof->dmat->lock); ipc_build_trace_posn(&sof->dmat->posn); - sof->dmat->msg = ipc_msg_init(sof->dmat->posn.rhdr.hdr.cmd, + sof->dmat->msg = ipc_msg_init(NULL, sof->dmat->posn.rhdr.hdr.cmd, sof->dmat->posn.rhdr.hdr.size); if (!sof->dmat->msg) { ret = -ENOMEM; diff --git a/uuid-registry.txt b/uuid-registry.txt index dc5cf5e15297..b4c27f1c895d 100644 --- a/uuid-registry.txt +++ b/uuid-registry.txt @@ -146,6 +146,7 @@ d7f6712d-131c-45a7-82ed6aa9dc2291ea pm_runtime 9302adf5-88be-4234-a0a7dca538ef81f4 sai 3dee06de-f25a-4e10-ae1fabc9573873ea schedule 70d223ef-2b91-4aac-b444d89a0db2793a sdma +bdcb1461-34f5-4047-b9cc70fdf8dfb234 sec_core_init 55a88ed5-3d18-46ca-88f10ee6eae9930f selector 32fe92c1-1e17-4fc2-9758c7f3542e980a selector4 cf90d851-68a2-4987-a2de85aed0c8531c sgen_mt8186 diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 16575d5e4eec..a2eb9b60c9a1 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -577,6 +577,12 @@ if(CONFIG_SOF_USERSPACE_INTERFACE_DMA) zephyr_syscall_header(include/sof/lib/sof_dma.h) endif() +if(CONFIG_SOF_USERSPACE_INTERFACE_MUTEX) + zephyr_library_sources(syscall/umutex.c) + zephyr_library_sources(lib/umutex.c) + zephyr_syscall_header(include/rtos/umutex.h) +endif() + # Mandatory Files used on all platforms. # Commented files will be added/removed as integration dictates. zephyr_library_sources( @@ -620,6 +626,17 @@ zephyr_library_sources_ifdef(CONFIG_SHELL zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/audio/module_adapter/module/generic.h) zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/fast-get.h) +zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/ipc/ipc_reply.h) +zephyr_syscall_header(${SOF_SRC_PATH}/include/ipc4/handler.h) +zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/ipc/ipc_msg_send.h) +zephyr_syscall_header(include/rtos/alloc.h) +zephyr_library_sources_ifdef(CONFIG_SOF_USERSPACE_INTERFACE_ALLOC syscall/alloc.c) +zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/vregion.h) +zephyr_library_sources(syscall/vregion.c) +zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/dai-zephyr.h) +zephyr_library_sources(syscall/dai.c) + +zephyr_syscall_header(${SOF_SRC_PATH}/include/ipc4/notification.h) zephyr_library_link_libraries(SOF) target_link_libraries(SOF INTERFACE zephyr_interface) diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 998abae8a969..208428a095df 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -29,10 +29,35 @@ config SOF_USERSPACE_INTERFACE_DMA help Allow user-space threads to use the SOF DMA interface. +config SOF_USERSPACE_INTERFACE_ALLOC + bool "Enable SOF heap alloc interface to userspace threads" + depends on USERSPACE + help + Allow user-space threads to use sof_heap_alloc/sof_heap_free + as Zephyr system calls. + +config SOF_USERSPACE_INTERFACE_MUTEX + bool "User-space mutex interface" + depends on USERSPACE + help + Enables the sof_umutex API for dynamically-allocated + user-space accessible mutexes backed by k_object_alloc. + +config SOF_USERSPACE_INTERFACE_VREGION + bool "Enable SOF vregion interface to userspace threads" + depends on USERSPACE + depends on SOF_VREGIONS + help + Allow user-space threads to use vregion_alloc/vregion_free + and their variants as Zephyr system calls. + config SOF_USERSPACE_LL bool "Run Low-Latency pipelines in userspace threads" depends on USERSPACE + select SOF_USERSPACE_INTERFACE_ALLOC select SOF_USERSPACE_INTERFACE_DMA + select SOF_USERSPACE_INTERFACE_MUTEX + select SOF_USERSPACE_INTERFACE_VREGION if SOF_VREGIONS help Run Low-Latency (LL) pipelines in userspace threads. This adds memory protection between operating system resources and @@ -116,6 +141,17 @@ config SOF_ZEPHYR_USERSPACE_MODULE_HEAP_SIZE module has its own independent heap to which only it has access. This heap is shared between instances of the same module. +config SOF_ZEPHYR_SYS_USER_HEAP_SIZE + hex "Size of the shared LL user-space heap" + default 0x20000 + depends on SOF_USERSPACE_LL + help + The size of the shared heap used by the low-latency (LL) scheduler + user-space thread. This heap is shared across all pipelines running + on the LL thread and must be large enough to hold all host DMA + buffers, chain DMA data, SG elements, and module adapter buffers + for all simultaneously active pipelines. + config SOF_USERSPACE_PROXY bool "Use userspace proxy to support userspace modules" select SOF_USERSPACE_USE_DRIVER_HEAP @@ -291,3 +327,11 @@ config STACK_SIZE_IPC_TX default 2048 help IPC sender work-queue thread stack size. Keep a power of 2. + +config SOF_IPC_USER_THREAD_STACK_SIZE + int "IPC user thread stack size" + default 8192 + depends on SOF_USERSPACE_LL + help + Stack size for the userspace IPC thread. + Keep a power of 2. diff --git a/zephyr/include/rtos/alloc.h b/zephyr/include/rtos/alloc.h index e21e498f0471..658fc94c9819 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -99,9 +99,26 @@ void rfree(void *ptr); */ void l3_heap_save(void); +void *z_impl_sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment); + +void z_impl_sof_heap_free(struct k_heap *heap, void *addr); + +/* + * This is ugly to define the signatures twice, but this + * is required to support userspace builds that do not export alloc. + */ +#ifdef CONFIG_SOF_USERSPACE_INTERFACE_ALLOC +__syscall void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment); +__syscall void sof_heap_free(struct k_heap *heap, void *addr); +#include +#else void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, size_t alignment); void sof_heap_free(struct k_heap *heap, void *addr); +#endif + #if CONFIG_SOF_FULL_ZEPHYR_APPLICATION struct k_heap *sof_sys_heap_get(void); #else diff --git a/zephyr/include/rtos/mutex.h b/zephyr/include/rtos/mutex.h index a8886f768d1e..bf1f25813888 100644 --- a/zephyr/include/rtos/mutex.h +++ b/zephyr/include/rtos/mutex.h @@ -8,5 +8,6 @@ #include /* k_mutex_*() */ #include /* for sys_mutex */ +#include /* for sof_umutex */ #endif /* __ZEPHYR_RTOS_MUTEX_H__ */ diff --git a/zephyr/include/rtos/sof.h b/zephyr/include/rtos/sof.h index 573e6ac63d77..efe0c1e6acab 100644 --- a/zephyr/include/rtos/sof.h +++ b/zephyr/include/rtos/sof.h @@ -46,8 +46,10 @@ struct sof { int argc; char **argv; +#ifndef CONFIG_SOF_USERSPACE_LL /* ipc */ struct ipc *ipc; +#endif /* system agent */ struct sa *sa; diff --git a/zephyr/include/rtos/umutex.h b/zephyr/include/rtos/umutex.h new file mode 100644 index 000000000000..2695bfe4b0f5 --- /dev/null +++ b/zephyr/include/rtos/umutex.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. +// + +#ifndef __ZEPHYR_RTOS_UMUTEX_H__ +#define __ZEPHYR_RTOS_UMUTEX_H__ + +#include + +/** + * @brief User-space accessible mutex with dynamic allocation. + * + * This mutex variant can be dynamically allocated at runtime. The state + * struct resides in user-accessible memory; access to the mutex is granted + * to any thread that can access this memory region (no per-thread + * k_thread_access_grant needed). + * + * The backing k_mutex is allocated via k_object_alloc(K_OBJ_MUTEX) during + * init and freed via k_object_free() during free. + */ +struct sof_umutex { + struct k_mutex *mutex; /**< Pointer to dynamically-allocated backing k_mutex */ +}; + +__syscall int z_sof_umutex_init(struct sof_umutex *umutex); +__syscall int z_sof_umutex_lock(struct sof_umutex *umutex, k_timeout_t timeout); +__syscall int z_sof_umutex_unlock(struct sof_umutex *umutex); +__syscall void z_sof_umutex_free(struct sof_umutex *umutex); + +/** + * @brief Initialize a dynamic user-space mutex. + * + * Allocates the backing k_mutex kernel object. The sof_umutex struct + * must reside in memory accessible to the calling thread. + * + * @param umutex Pointer to the sof_umutex state (in user-accessible memory) + * @return 0 on success, -ENOMEM if allocation fails + */ +static inline int sof_umutex_init(struct sof_umutex *umutex) +{ + return z_sof_umutex_init(umutex); +} + +/** + * @brief Lock a dynamic user-space mutex. + * + * @param umutex Pointer to the sof_umutex state + * @param timeout Timeout value (K_FOREVER for indefinite wait) + * @return 0 on success, -EAGAIN on timeout, -EINVAL if not initialized + */ +static inline int sof_umutex_lock(struct sof_umutex *umutex, k_timeout_t timeout) +{ + return z_sof_umutex_lock(umutex, timeout); +} + +/** + * @brief Unlock a dynamic user-space mutex. + * + * @param umutex Pointer to the sof_umutex state + * @return 0 on success, -EINVAL if not initialized, -EPERM if not owner + */ +static inline int sof_umutex_unlock(struct sof_umutex *umutex) +{ + return z_sof_umutex_unlock(umutex); +} + +/** + * @brief Free a dynamic user-space mutex. + * + * Releases the backing k_mutex kernel object via k_object_free(). + * The sof_umutex state must not be used after this call. + * + * @param umutex Pointer to the sof_umutex state + */ +static inline void sof_umutex_free(struct sof_umutex *umutex) +{ + z_sof_umutex_free(umutex); +} + +#include + +#endif /* __ZEPHYR_RTOS_UMUTEX_H__ */ diff --git a/zephyr/include/sof/lib/cpu.h b/zephyr/include/sof/lib/cpu.h index c23405e85121..533cb29f3602 100644 --- a/zephyr/include/sof/lib/cpu.h +++ b/zephyr/include/sof/lib/cpu.h @@ -55,7 +55,11 @@ static inline bool cpu_is_primary(int id) static inline bool cpu_is_me(int id) { +#ifdef CONFIG_SOF_USERSPACE_LL + return true; +#else return id == cpu_get_id(); +#endif } int cpu_enable_core(int id); diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index 8192c2caff0f..6aae73db38ad 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -628,8 +628,8 @@ EXPORT_SYMBOL(rfree); * To match the fall-back SOF main heap all private heaps should also be in the * uncached address range. */ -void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, - size_t alignment) +void *z_impl_sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment) { if (flags & (SOF_MEM_FLAG_LARGE_BUFFER | SOF_MEM_FLAG_USER_SHARED_BUFFER)) return rballoc_align(flags, bytes, alignment); @@ -643,7 +643,7 @@ void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, return (__sparse_force void *)heap_alloc_aligned_cached(heap, alignment, bytes); } -void sof_heap_free(struct k_heap *heap, void *addr) +void z_impl_sof_heap_free(struct k_heap *heap, void *addr) { if (heap && addr && is_heap_pointer(heap, addr)) heap_free(heap, addr); @@ -651,6 +651,28 @@ void sof_heap_free(struct k_heap *heap, void *addr) rfree(addr); } +#ifndef CONFIG_SOF_USERSPACE_INTERFACE_ALLOC + +/* + * Putting these as inlines in alloc.h breaks Zephyr native + * ztests like sof/test/ztest/unit/fast-get that include rtos/alloc.h + * but do not link lib/alloc.c . To to keep the tests happy, + * implement the functions here. + */ + +void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment) +{ + return z_impl_sof_heap_alloc(heap, falgs, bytes, alignment); +} + +void sof_heap_free(struct k_heap *heap, void *addr) +{ + return z_impl_sof_heap_free(heap, addr); +} + +#endif /* CONFIG_SOF_USERSPACE_INTERFACE_ALLOC */ + static int heap_init(void) { sys_heap_init(&sof_heap.heap, heapmem, HEAPMEM_SIZE - SHARED_BUFFER_HEAP_MEM_SIZE); diff --git a/zephyr/lib/umutex.c b/zephyr/lib/umutex.c new file mode 100644 index 000000000000..70cb7ca06426 --- /dev/null +++ b/zephyr/lib/umutex.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +/** + * @file + * @brief SOF dynamic user-space mutex implementation. + * + * Provides the kernel-side implementation of sof_umutex operations. + * The backing k_mutex is dynamically allocated via k_object_alloc + * and freed via k_object_free. + */ + +#include +#include +#include + +int z_impl_z_sof_umutex_init(struct sof_umutex *umutex) +{ + struct k_mutex *m; + int ret; + + m = k_object_alloc(K_OBJ_MUTEX); + if (m == NULL) { + return -ENOMEM; + } + + ret = k_mutex_init(m); + if (ret) { + k_object_free(m); + return ret; + } + + umutex->mutex = m; + return 0; +} + +int z_impl_z_sof_umutex_lock(struct sof_umutex *umutex, k_timeout_t timeout) +{ + if (umutex->mutex == NULL) { + return -EINVAL; + } + + return k_mutex_lock(umutex->mutex, timeout); +} + +int z_impl_z_sof_umutex_unlock(struct sof_umutex *umutex) +{ + if (umutex->mutex == NULL) { + return -EINVAL; + } + + return k_mutex_unlock(umutex->mutex); +} + +void z_impl_z_sof_umutex_free(struct sof_umutex *umutex) +{ + if (umutex->mutex != NULL) { + k_object_free(umutex->mutex); + umutex->mutex = NULL; + } +} diff --git a/zephyr/lib/userspace_helper.c b/zephyr/lib/userspace_helper.c index ce4368adc077..2abc2fa0e2f2 100644 --- a/zephyr/lib/userspace_helper.c +++ b/zephyr/lib/userspace_helper.c @@ -110,6 +110,43 @@ int user_access_to_mailbox(struct k_mem_domain *domain, k_tid_t thread_id) if (ret < 0) return ret; +#if defined(CONFIG_SOF_USERSPACE_LL) && defined(CONFIG_IPC_MAJOR_4) + /* HOSTBOX partitions for IPC4 module init parameter block reads. + * comp_new_ipc4() accesses MAILBOX_HOSTBOX_BASE directly to get + * the module configuration data sent by the host. + */ + { + struct k_mem_partition hostbox_partition; + + /* Uncached HOSTBOX partition */ + hostbox_partition.start = + (uintptr_t)sys_cache_uncached_ptr_get( + (void __sparse_cache *)MAILBOX_HOSTBOX_BASE); + hostbox_partition.size = ALIGN_UP(MAILBOX_HOSTBOX_SIZE, + CONFIG_MMU_PAGE_SIZE); + hostbox_partition.attr = K_MEM_PARTITION_P_RO_U_RO; + + ret = k_mem_domain_add_partition(domain, &hostbox_partition); + if (ret < 0) + return ret; + + /* Cached HOSTBOX partition for cache invalidation path. + * sys_cache_data_invd_range() syscall verification requires + * write access to the region, so use RW instead of RO. + */ + hostbox_partition.start = + (uintptr_t)sys_cache_cached_ptr_get( + (void *)MAILBOX_HOSTBOX_BASE); + hostbox_partition.size = ALIGN_UP(MAILBOX_HOSTBOX_SIZE, + CONFIG_MMU_PAGE_SIZE); + hostbox_partition.attr = K_MEM_PARTITION_P_RW_U_RW; + + ret = k_mem_domain_add_partition(domain, &hostbox_partition); + if (ret < 0) + return ret; + } +#endif /* CONFIG_IPC_MAJOR_4 */ + #ifndef CONFIG_IPC_MAJOR_4 /* * Next mailbox_stream (not available in IPC4). Stream access is cached, diff --git a/zephyr/lib/vregion.c b/zephyr/lib/vregion.c index 15683925196d..66c10b8bcc42 100644 --- a/zephyr/lib/vregion.c +++ b/zephyr/lib/vregion.c @@ -305,7 +305,7 @@ static void lifetime_free(struct vlinear_heap *heap, void *ptr) * @param vr Pointer to the virtual region instance. * @param ptr Pointer to the memory to free. */ -void vregion_free(struct vregion *vr, void *ptr) +void z_impl_vregion_free(struct vregion *vr, void *ptr) { if (!vr || !ptr) return; @@ -329,7 +329,7 @@ void vregion_free(struct vregion *vr, void *ptr) k_mutex_unlock(&vr->lock); } -EXPORT_SYMBOL(vregion_free); +EXPORT_SYMBOL(z_impl_vregion_free); /** * @brief Allocate memory type from the virtual region. @@ -341,8 +341,8 @@ EXPORT_SYMBOL(vregion_free); * * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment) +void *z_impl_vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, + size_t size, size_t alignment) { void *p; @@ -370,7 +370,7 @@ void *vregion_alloc_align(struct vregion *vr, enum vregion_mem_type type, return p; } -EXPORT_SYMBOL(vregion_alloc_align); +EXPORT_SYMBOL(z_impl_vregion_alloc_align); /** * @brief Allocate memory from the virtual region. @@ -379,17 +379,17 @@ EXPORT_SYMBOL(vregion_alloc_align); * @param[in] size Size of the allocation. * @return void* Pointer to the allocated memory, or NULL on failure. */ -void *vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size) +void *z_impl_vregion_alloc(struct vregion *vr, enum vregion_mem_type type, size_t size) { - return vregion_alloc_align(vr, type, size, 0); + return z_impl_vregion_alloc_align(vr, type, size, 0); } -EXPORT_SYMBOL(vregion_alloc); +EXPORT_SYMBOL(z_impl_vregion_alloc); -void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, size_t size) +void *z_impl_vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, size_t size) { size = ALIGN_UP(size, CONFIG_DCACHE_LINE_SIZE); - void *p = vregion_alloc_align(vr, type, size, CONFIG_DCACHE_LINE_SIZE); + void *p = z_impl_vregion_alloc_align(vr, type, size, CONFIG_DCACHE_LINE_SIZE); if (!p) return NULL; @@ -398,15 +398,16 @@ void *vregion_alloc_coherent(struct vregion *vr, enum vregion_mem_type type, siz return sys_cache_uncached_ptr_get(p); } +EXPORT_SYMBOL(z_impl_vregion_alloc_coherent); -void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, - size_t size, size_t alignment) +void *z_impl_vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type type, + size_t size, size_t alignment) { if (alignment < CONFIG_DCACHE_LINE_SIZE) alignment = CONFIG_DCACHE_LINE_SIZE; size = ALIGN_UP(size, CONFIG_DCACHE_LINE_SIZE); - void *p = vregion_alloc_align(vr, type, size, alignment); + void *p = z_impl_vregion_alloc_align(vr, type, size, alignment); if (!p) return NULL; @@ -415,6 +416,7 @@ void *vregion_alloc_coherent_align(struct vregion *vr, enum vregion_mem_type typ return sys_cache_uncached_ptr_get(p); } +EXPORT_SYMBOL(z_impl_vregion_alloc_coherent_align); /** * @brief Log virtual region memory usage. diff --git a/zephyr/schedule.c b/zephyr/schedule.c index 1e3971a33682..885c482c7b52 100644 --- a/zephyr/schedule.c +++ b/zephyr/schedule.c @@ -22,6 +22,26 @@ static APP_SYSUSER_BSS struct schedulers *_schedulers[CONFIG_CORE_COUNT]; */ struct schedulers **arch_schedulers_get(void) { + if (k_is_user_context()) { + printk("FIXME: using core0 scheduler\n"); + return _schedulers; + } + return _schedulers + cpu_get_id(); } EXPORT_SYMBOL(arch_schedulers_get); + +#if CONFIG_SOF_USERSPACE_LL +/** + * Retrieves registered schedulers for a specific core. + * @param core Core ID to get schedulers for. + * @return List of registered schedulers for the specified core. + * + * Safe to call from user-space context — does not use cpu_get_id(). + */ +struct schedulers **arch_schedulers_get_for_core(int core) +{ + return _schedulers + core; +} +EXPORT_SYMBOL(arch_schedulers_get_for_core); +#endif diff --git a/zephyr/syscall/alloc.c b/zephyr/syscall/alloc.c new file mode 100644 index 000000000000..fd3e486018fb --- /dev/null +++ b/zephyr/syscall/alloc.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include + +static inline void *z_vrfy_sof_heap_alloc(struct k_heap *heap, uint32_t flags, + size_t bytes, size_t alignment) +{ + /* reject flags that bypass heap isolation */ + K_OOPS(flags & (SOF_MEM_FLAG_LARGE_BUFFER | SOF_MEM_FLAG_USER_SHARED_BUFFER)); + + /* user-space use of sof_heap_alloc() limited to this single heap */ + K_OOPS(!zephyr_ll_user_heap_verify(heap)); + + return z_impl_sof_heap_alloc(heap, flags, bytes, alignment); +} +#include + +static inline void z_vrfy_sof_heap_free(struct k_heap *heap, void *addr) +{ + /* user-space use of sof_heap_alloc() limited to this single heap */ + K_OOPS(!zephyr_ll_user_heap_verify(heap)); + + if (addr) { + uintptr_t start = (uintptr_t)heap->heap.init_mem; + uintptr_t addr_uc = (uintptr_t)sys_cache_uncached_ptr_get(addr); + K_OOPS(addr_uc < start || addr_uc >= start + heap->heap.init_bytes); + K_OOPS(K_SYSCALL_MEMORY_WRITE(addr, 1)); + } + z_impl_sof_heap_free(heap, addr); +} +#include diff --git a/zephyr/syscall/dai.c b/zephyr/syscall/dai.c new file mode 100644 index 000000000000..ea4f233fa40d --- /dev/null +++ b/zephyr/syscall/dai.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include + +static inline const struct device *z_vrfy_dai_get_device(enum sof_ipc_dai_type type, + uint32_t index) +{ + return z_impl_dai_get_device(type, index); +} +#include diff --git a/zephyr/syscall/umutex.c b/zephyr/syscall/umutex.c new file mode 100644 index 000000000000..9e25da1bb7a8 --- /dev/null +++ b/zephyr/syscall/umutex.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +/** + * @file + * @brief SOF dynamic user-space mutex syscall verification. + * + * Verify handlers ensure the calling user thread has write access to + * the sof_umutex state struct before forwarding to the implementation. + */ + +#include +#include + +static inline int z_vrfy_z_sof_umutex_init(struct sof_umutex *umutex) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(umutex, sizeof(struct sof_umutex))); + return z_impl_z_sof_umutex_init(umutex); +} +#include + +static inline int z_vrfy_z_sof_umutex_lock(struct sof_umutex *umutex, + k_timeout_t timeout) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(umutex, sizeof(struct sof_umutex))); + return z_impl_z_sof_umutex_lock(umutex, timeout); +} +#include + +static inline int z_vrfy_z_sof_umutex_unlock(struct sof_umutex *umutex) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(umutex, sizeof(struct sof_umutex))); + return z_impl_z_sof_umutex_unlock(umutex); +} +#include + +static inline void z_vrfy_z_sof_umutex_free(struct sof_umutex *umutex) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(umutex, sizeof(struct sof_umutex))); + z_impl_z_sof_umutex_free(umutex); +} +#include diff --git a/zephyr/syscall/vregion.c b/zephyr/syscall/vregion.c new file mode 100644 index 000000000000..777cf656161d --- /dev/null +++ b/zephyr/syscall/vregion.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include + +static inline void *z_vrfy_vregion_alloc(struct vregion *vr, + enum vregion_mem_type type, size_t size) +{ + size_t vr_size = 0; + uintptr_t vr_start; + + vregion_mem_info(vr, &vr_size, &vr_start); + if (vr_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE((void *)vr_start, vr_size)); + + return z_impl_vregion_alloc(vr, type, size); +} +#include + +static inline void *z_vrfy_vregion_alloc_coherent(struct vregion *vr, + enum vregion_mem_type type, size_t size) +{ + size_t vr_size = 0; + uintptr_t vr_start; + + vregion_mem_info(vr, &vr_size, &vr_start); + if (vr_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE((void *)vr_start, vr_size)); + + return z_impl_vregion_alloc_coherent(vr, type, size); +} +#include + +static inline void *z_vrfy_vregion_alloc_align(struct vregion *vr, + enum vregion_mem_type type, + size_t size, size_t alignment) +{ + size_t vr_size = 0; + uintptr_t vr_start; + + vregion_mem_info(vr, &vr_size, &vr_start); + if (vr_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE((void *)vr_start, vr_size)); + + return z_impl_vregion_alloc_align(vr, type, size, alignment); +} +#include + +static inline void *z_vrfy_vregion_alloc_coherent_align(struct vregion *vr, + enum vregion_mem_type type, + size_t size, size_t alignment) +{ + size_t vr_size = 0; + uintptr_t vr_start; + + vregion_mem_info(vr, &vr_size, &vr_start); + if (vr_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE((void *)vr_start, vr_size)); + + return z_impl_vregion_alloc_coherent_align(vr, type, size, alignment); +} +#include + +static inline void z_vrfy_vregion_free(struct vregion *vr, void *ptr) +{ + size_t vr_size = 0; + uintptr_t vr_start; + + vregion_mem_info(vr, &vr_size, &vr_start); + if (vr_size) + K_OOPS(K_SYSCALL_MEMORY_WRITE((void *)vr_start, vr_size)); + + z_impl_vregion_free(vr, ptr); +} +#include diff --git a/zephyr/test/CMakeLists.txt b/zephyr/test/CMakeLists.txt index 6edf90ec840a..5cae3c7f2f5f 100644 --- a/zephyr/test/CMakeLists.txt +++ b/zephyr/test/CMakeLists.txt @@ -11,6 +11,13 @@ if(CONFIG_SOF_BOOT_TEST) zephyr_library_sources_ifdef(CONFIG_USERSPACE userspace/ksem.c ) + if(CONFIG_USERSPACE AND CONFIG_SOF_USERSPACE_INTERFACE_ALLOC) + zephyr_library_sources(userspace/test_heap_alloc.c) + endif() + + if(CONFIG_SOF_USERSPACE_INTERFACE_MUTEX) + zephyr_library_sources(userspace/test_umutex.c) + endif() endif() if(CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_SOF_USERSPACE_INTERFACE_DMA) diff --git a/zephyr/test/userspace/test_heap_alloc.c b/zephyr/test/userspace/test_heap_alloc.c new file mode 100644 index 000000000000..54f9aa7e6d43 --- /dev/null +++ b/zephyr/test/userspace/test_heap_alloc.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright(c) 2026 Intel Corporation. + */ + +/* + * Test cases for sof_heap_alloc() / sof_heap_free() use from a Zephyr + * user-space thread. + */ + +#include +#include +#include + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); + +#define USER_STACKSIZE 2048 + +static struct k_thread user_thread; +static K_THREAD_STACK_DEFINE(user_stack, USER_STACKSIZE); + +K_APPMEM_PARTITION_DEFINE(heap_alloc_test_part); +K_APP_BMEM(heap_alloc_test_part) static uint8_t non_heap_byte; + +enum heap_alloc_test_case { + HEAP_ALLOC_VALID, + HEAP_ALLOC_NULL_HEAP, + HEAP_ALLOC_FORBIDDEN_FLAGS, + HEAP_FREE_NON_HEAP_POINTER, + HEAP_FREE_INACCESSIBLE_POINTER, +}; + +static void user_function(void *p1, void *p2, void *p3) +{ + struct k_heap *heap = p1; + enum heap_alloc_test_case test_case = (enum heap_alloc_test_case)(uintptr_t)p2; + void *free_ptr = p3; + void *ptr; + + __ASSERT(k_is_user_context(), "isn't user"); + + LOG_INF("SOF thread %s (%s)", + k_is_user_context() ? "UserSpace!" : "privileged mode.", + CONFIG_BOARD_TARGET); + + switch (test_case) { + case HEAP_ALLOC_VALID: + ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); + zassert_not_null(ptr, "sof_heap_alloc returned NULL"); + sof_heap_free(heap, ptr); + return; + case HEAP_ALLOC_NULL_HEAP: + (void)sof_heap_alloc(NULL, SOF_MEM_FLAG_USER, 128, 0); + break; + case HEAP_ALLOC_FORBIDDEN_FLAGS: + (void)sof_heap_alloc(heap, SOF_MEM_FLAG_LARGE_BUFFER, 128, 0); + break; + case HEAP_FREE_NON_HEAP_POINTER: + sof_heap_free(heap, &non_heap_byte); + break; + case HEAP_FREE_INACCESSIBLE_POINTER: + sof_heap_free(heap, free_ptr); + break; + default: + zassert_unreachable("unknown heap allocation test case"); + } + + zassert_unreachable("syscall security check did not fault"); +} + +static void run_user_heap_alloc_case(enum heap_alloc_test_case test_case, struct k_heap *heap, + void *ptr, bool grant_ll_domain, bool expect_fault) +{ + int ret; + + k_thread_create(&user_thread, user_stack, USER_STACKSIZE, + user_function, heap, (void *)(uintptr_t)test_case, ptr, + -1, K_USER, K_FOREVER); + + if (grant_ll_domain) + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &user_thread); + + if (expect_fault) + sof_boot_test_set_fault_valid(&user_thread); + + k_thread_start(&user_thread); + ret = k_thread_join(&user_thread, K_FOREVER); + zassert_equal(ret, 0, "user thread join failed: %d", ret); +} + +static void test_user_thread_heap_alloc(enum heap_alloc_test_case test_case) +{ + bool grant_ll_domain = true; + bool expect_fault = false; + void *ptr = NULL; + struct k_heap *heap; + + heap = zephyr_ll_user_heap(); + zassert_not_null(heap, "user heap not found"); + + switch (test_case) { + case HEAP_ALLOC_VALID: + break; + case HEAP_ALLOC_NULL_HEAP: + case HEAP_ALLOC_FORBIDDEN_FLAGS: + case HEAP_FREE_NON_HEAP_POINTER: + expect_fault = true; + break; + case HEAP_FREE_INACCESSIBLE_POINTER: + ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); + zassert_not_null(ptr, "kernel heap allocation failed"); + grant_ll_domain = false; + expect_fault = true; + break; + default: + zassert_unreachable("unknown heap allocation test case"); + } + + run_user_heap_alloc_case(test_case, heap, ptr, grant_ll_domain, expect_fault); + + if (ptr) + sof_heap_free(heap, ptr); +} + +ZTEST(sof_boot, user_space_heap_alloc) +{ + test_user_thread_heap_alloc(HEAP_ALLOC_VALID); + + ztest_test_pass(); +} + +ZTEST(sof_boot, user_space_heap_alloc_rejects_null_heap) +{ + test_user_thread_heap_alloc(HEAP_ALLOC_NULL_HEAP); + + ztest_test_pass(); +} + +ZTEST(sof_boot, user_space_heap_alloc_rejects_forbidden_flags) +{ + test_user_thread_heap_alloc(HEAP_ALLOC_FORBIDDEN_FLAGS); + + ztest_test_pass(); +} + +ZTEST(sof_boot, user_space_heap_free_rejects_non_heap_pointer) +{ + k_mem_domain_add_partition(zephyr_ll_mem_domain(), &heap_alloc_test_part); + + test_user_thread_heap_alloc(HEAP_FREE_NON_HEAP_POINTER); + + k_mem_domain_remove_partition(zephyr_ll_mem_domain(), &heap_alloc_test_part); + + ztest_test_pass(); +} + +ZTEST(sof_boot, user_space_heap_free_rejects_inaccessible_pointer) +{ + test_user_thread_heap_alloc(HEAP_FREE_INACCESSIBLE_POINTER); + + ztest_test_pass(); +} diff --git a/zephyr/test/userspace/test_ll_task.c b/zephyr/test/userspace/test_ll_task.c index 234423defc60..5ac0280205c4 100644 --- a/zephyr/test/userspace/test_ll_task.c +++ b/zephyr/test/userspace/test_ll_task.c @@ -14,9 +14,20 @@ #include #include #include +#include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -24,6 +35,7 @@ #include #include /* offsetof() */ +#include LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); @@ -36,10 +48,15 @@ K_APPMEM_PARTITION_DEFINE(userspace_ll_part); /* Global variable for test runs counter, accessible from user-space */ K_APP_BMEM(userspace_ll_part) static int test_runs; +/* User-space thread for pipeline_two_components test */ +#define PPL_USER_STACKSIZE 4096 + +static struct k_thread ppl_user_thread; +static K_THREAD_STACK_DEFINE(ppl_user_stack, PPL_USER_STACKSIZE); + static enum task_state task_callback(void *arg) { LOG_INF("entry"); - if (++test_runs > 3) return SOF_TASK_STATE_COMPLETED; @@ -77,7 +94,7 @@ static void ll_task_test(void) LOG_INF("task scheduled and running"); /* Let the task run for a bit */ - k_sleep(K_MSEC(10)); + k_sleep(K_MSEC(100)); /* Cancel the task to stop any scheduled execution */ ret = schedule_task_cancel(task); @@ -87,6 +104,9 @@ static void ll_task_test(void) ret = schedule_task_free(task); zassert_equal(ret, 0); + k_mem_domain_remove_partition(zephyr_ll_mem_domain(), &userspace_ll_part); + zephyr_ll_task_free(task); + LOG_INF("test complete"); } @@ -129,6 +149,374 @@ ZTEST(userspace_ll, pipeline_check) pipeline_check(); } +/* Copier UUID: 9ba00c83-ca12-4a83-943c-1fa2e82f9dda */ +static const uint8_t copier_uuid[16] = { + 0x83, 0x0c, 0xa0, 0x9b, 0x12, 0xca, 0x83, 0x4a, + 0x94, 0x3c, 0x1f, 0xa2, 0xe8, 0x2f, 0x9d, 0xda +}; + +/** + * Find the module_id (manifest entry index) for the copier module + * by iterating the firmware manifest and matching the copier UUID. + */ +static int find_copier_module_id(void) +{ + const struct sof_man_fw_desc *desc = basefw_vendor_get_manifest(); + const struct sof_man_module *mod; + uint32_t i; + + if (!desc) + return -1; + + for (i = 0; i < desc->header.num_module_entries; i++) { + mod = (const struct sof_man_module *)((const char *)desc + + SOF_MAN_MODULE_OFFSET(i)); + if (!memcmp(&mod->uuid, copier_uuid, sizeof(copier_uuid))) + return (int)i; + } + + return -1; +} + +/** + * IPC4 copier module config - used as payload for comp_new_ipc4(). + * Placed at MAILBOX_HOSTBOX_BASE before calling comp_new_ipc4(). + * Layout matches struct ipc4_copier_module_cfg from copier.h. + */ +struct copier_init_data { + struct ipc4_base_module_cfg base; + struct ipc4_audio_format out_fmt; + uint32_t copier_feature_mask; + /* Gateway config (matches struct ipc4_copier_gateway_cfg) */ + union ipc4_connector_node_id node_id; + uint32_t dma_buffer_size; + uint32_t config_length; +} __packed __aligned(4); + +static void fill_audio_format(struct ipc4_audio_format *fmt) +{ + memset(fmt, 0, sizeof(*fmt)); + fmt->sampling_frequency = IPC4_FS_48000HZ; + fmt->depth = IPC4_DEPTH_32BIT; + fmt->ch_cfg = IPC4_CHANNEL_CONFIG_STEREO; + fmt->channels_count = 2; + fmt->valid_bit_depth = 32; + fmt->s_type = IPC4_TYPE_MSB_INTEGER; + fmt->interleaving_style = IPC4_CHANNELS_INTERLEAVED; +} + +/** + * Create a copier component via IPC4. + * + * @param module_id Copier module_id from manifest + * @param instance_id Instance ID for this component + * @param pipeline_id Parent pipeline ID + * @param node_id Gateway node ID (type + virtual DMA index) + */ +static struct comp_dev *create_copier(int module_id, int instance_id, + int pipeline_id, + union ipc4_connector_node_id node_id) +{ + struct ipc4_module_init_instance module_init; + struct copier_init_data cfg; + struct comp_dev *dev; + + /* Prepare copier config payload */ + memset(&cfg, 0, sizeof(cfg)); + fill_audio_format(&cfg.base.audio_fmt); + /* 2 channels * 4 bytes * 48 frames = 384 bytes */ + cfg.base.ibs = 384; + cfg.base.obs = 384; + cfg.base.is_pages = 0; + cfg.base.cpc = 0; + cfg.out_fmt = cfg.base.audio_fmt; + cfg.copier_feature_mask = 0; + cfg.node_id = node_id; + cfg.dma_buffer_size = 768; + cfg.config_length = 0; + + /* Write config data to mailbox hostbox (where comp_new_ipc4 reads it). + * Flush cache so that data is visible in SRAM before comp_new_ipc4() + * invalidates the cache line (in normal IPC flow, host writes via DMA + * directly to SRAM, so the invalidation reads fresh data; here the DSP + * core itself writes, so an explicit flush is needed). + */ + memcpy((void *)MAILBOX_HOSTBOX_BASE, &cfg, sizeof(cfg)); + sys_cache_data_flush_range((void *)MAILBOX_HOSTBOX_BASE, sizeof(cfg)); + + /* Prepare IPC4 module init header */ + memset(&module_init, 0, sizeof(module_init)); + module_init.primary.r.module_id = module_id; + module_init.primary.r.instance_id = instance_id; + module_init.primary.r.type = SOF_IPC4_MOD_INIT_INSTANCE; + module_init.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG; + module_init.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + + module_init.extension.r.param_block_size = sizeof(cfg) / sizeof(uint32_t); + module_init.extension.r.ppl_instance_id = pipeline_id; + module_init.extension.r.core_id = 0; + module_init.extension.r.proc_domain = 0; /* LL */ + + dev = comp_new_ipc4(&module_init); + + return dev; +} + +/** + * Context shared between kernel setup and the user-space pipeline thread. + */ +struct ppl_test_ctx { + struct pipeline *p; + struct k_heap *heap; + struct mod_alloc_ctx *alloc; + struct comp_dev *host_comp; + struct comp_dev *dai_comp; + struct comp_buffer *buf; + struct ipc *ipc; + struct ipc_comp_dev *ipc_pipe; +}; + +/** + * Pipeline operations: connect, complete, prepare, copy, verify, and clean up. + * This function is called either directly (kernel mode) or from a user-space + * thread, exercising pipeline_*() calls from the requested context. + */ +static void pipeline_ops(struct ppl_test_ctx *ctx) +{ + struct pipeline *p = ctx->p; + struct comp_dev *host_comp = ctx->host_comp; + struct comp_dev *dai_comp = ctx->dai_comp; + struct comp_buffer *buf = ctx->buf; + int ret; + + LOG_INF("pipeline_ops: user_context=%d", k_is_user_context()); + + /* Step: Connect host -> buffer -> DAI */ + ret = pipeline_connect(host_comp, buf, PPL_CONN_DIR_COMP_TO_BUFFER); + zassert_equal(ret, 0, "connect host to buffer failed"); + + ret = pipeline_connect(dai_comp, buf, PPL_CONN_DIR_BUFFER_TO_COMP); + zassert_equal(ret, 0, "connect buffer to DAI failed"); + + LOG_INF("host -> buffer -> DAI connected"); + + /* Step: Complete the pipeline */ + ret = pipeline_complete(p, host_comp, dai_comp); + zassert_equal(ret, 0, "pipeline complete failed"); + + /* Step: Prepare the pipeline */ + p->sched_comp = host_comp; + + ret = pipeline_prepare(p, host_comp); + zassert_equal(ret, 0, "pipeline prepare failed"); + + ret = pipeline_trigger(p, host_comp, COMP_TRIGGER_PRE_START); + //zassert_equal(ret, 0, "pipeline TRIGGER_START failed"); + + LOG_INF("pipeline complete, status = %d", p->status); + + /* Step: Run copies */ + pipeline_schedule_copy(p, 1000); + + /* Step: let run for 3 msec */ + k_sleep(K_MSEC(3)); + + /* Verify pipeline source and sink assignments */ + zassert_equal(p->source_comp, host_comp, "source comp mismatch"); + zassert_equal(p->sink_comp, dai_comp, "sink comp mismatch"); + + LOG_INF("pipeline_ops done"); +} + +/** + * User-space thread entry point for pipeline_two_components test. + * p1 points to the ppl_test_ctx shared with the kernel launcher. + */ +static void pipeline_user_thread(void *p1, void *p2, void *p3) +{ + struct ppl_test_ctx *ctx = (struct ppl_test_ctx *)p1; + + zassert_true(k_is_user_context(), "expected user context"); + pipeline_ops(ctx); +} + +/** + * Test creating a pipeline with a host copier and a DAI (link) copier, + * connected through a shared buffer. + * + * When run_in_user is true, all pipeline_*() calls are made from a + * separate user-space thread. + */ +static void pipeline_two_components(void) +{ + struct ppl_test_ctx *ctx; + struct k_heap *heap = NULL; + uint32_t pipeline_id = 2; + uint32_t priority = 0; + struct task *task; + uint32_t comp_id; + int copier_module_id; + int host_instance_id = 0; + int dai_instance_id = 1; + int core = 0; + int ret; + + /* Step: Find the copier module_id from the firmware manifest */ + copier_module_id = find_copier_module_id(); + zassert_true(copier_module_id >= 0, "copier module not found in manifest"); + LOG_INF("copier module_id = %d", copier_module_id); + + /* Step: Create pipeline */ + LOG_INF("running test with user memory domain"); + heap = zephyr_ll_user_heap(); + zassert_not_null(heap, "user heap not found"); + + task = zephyr_ll_task_alloc(); + zassert_not_null(task, "task allocation failed"); + + ctx = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*ctx), 0); + ctx->alloc = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(struct mod_alloc_ctx), 0); + ctx->alloc->heap = heap; + ctx->alloc->vreg = NULL; + ctx->heap = heap; + ctx->ipc = ipc_get(); + + comp_id = IPC4_COMP_ID(copier_module_id, host_instance_id); + ctx->p = pipeline_new(ctx->heap, pipeline_id, priority, comp_id, NULL); + zassert_not_null(ctx->p, "pipeline creation failed"); + + /* create the LL scheduler thread by initializing one task */ + k_mem_domain_add_partition(zephyr_ll_mem_domain(), &userspace_ll_part); + + test_runs = 0; + ret = schedule_task_init_ll(task, SOF_UUID(test_task_uuid), SOF_SCHEDULE_LL_TIMER, + priority, task_callback, + (void *)&test_runs, core, 0); + zassert_equal(ret, 0); + + LOG_INF("task init done"); + + /* Set pipeline period so components get correct dev->period and dev->frames. + * This mirrors what ipc4_create_pipeline() does in normal IPC flow. + */ + ctx->p->time_domain = SOF_TIME_DOMAIN_TIMER; + ctx->p->period = LL_TIMER_PERIOD_US; + + /* Register pipeline in IPC component list so comp_new_ipc4() can + * find it via ipc_get_comp_by_ppl_id() and set dev->period. + */ + ctx->ipc_pipe = rzalloc(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct ipc_comp_dev)); + zassert_not_null(ctx->ipc_pipe, "ipc_comp_dev alloc failed"); + ctx->ipc_pipe->pipeline = ctx->p; + ctx->ipc_pipe->type = COMP_TYPE_PIPELINE; + ctx->ipc_pipe->id = pipeline_id; + ctx->ipc_pipe->core = 0; + list_item_append(&ctx->ipc_pipe->list, &ctx->ipc->comp_list); + + /* Step: Create host copier with HDA host output gateway */ + union ipc4_connector_node_id host_node_id = { .f = { + .dma_type = ipc4_hda_host_output_class, + .v_index = 0 + }}; + ctx->host_comp = create_copier(copier_module_id, host_instance_id, pipeline_id, + host_node_id); + zassert_not_null(ctx->host_comp, "host copier creation failed"); + + /* Assign pipeline to host component */ + ctx->host_comp->pipeline = ctx->p; + ctx->host_comp->ipc_config.type = SOF_COMP_HOST; + + LOG_INF("host copier created, comp_id = 0x%x", ctx->host_comp->ipc_config.id); + + /* Step: Create link copier with HDA link output gateway */ + union ipc4_connector_node_id link_node_id = { .f = { + .dma_type = ipc4_hda_link_output_class, + .v_index = 0 + }}; + ctx->dai_comp = create_copier(copier_module_id, dai_instance_id, pipeline_id, + link_node_id); + zassert_not_null(ctx->dai_comp, "DAI copier creation failed"); + + /* Assign pipeline to DAI component */ + ctx->dai_comp->pipeline = ctx->p; + ctx->dai_comp->ipc_config.type = SOF_COMP_DAI; + + LOG_INF("DAI copier created, comp_id = 0x%x", ctx->dai_comp->ipc_config.id); + + /* Step: Allocate a buffer to connect host -> DAI */ + ctx->buf = buffer_alloc(ctx->alloc, 384, 0, 0, false); + zassert_not_null(ctx->buf, "buffer allocation failed"); + + struct k_thread *task_thread; + + /* Create a user-space thread to execute pipeline operations */ + k_thread_create(&ppl_user_thread, ppl_user_stack, PPL_USER_STACKSIZE, + pipeline_user_thread, ctx, NULL, NULL, + -1, K_USER, K_FOREVER); + + /* Add thread to LL memory domain so it can access pipeline memory */ + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &ppl_user_thread); + + user_grant_dai_access_all(&ppl_user_thread); + user_grant_dma_access_all(&ppl_user_thread); + user_access_to_mailbox(zephyr_ll_mem_domain(), &ppl_user_thread); + zephyr_ll_grant_access(&ppl_user_thread); + + task_thread = scheduler_init_context(task); + zassert_not_null(task_thread); + + k_thread_start(&ppl_user_thread); + + LOG_INF("user thread started, waiting for completion"); + + k_thread_join(&ppl_user_thread, K_FOREVER); + + /* Step: Clean up - reset, disconnect, free buffer, free components, free pipeline */ + /* Reset pipeline to bring components back to COMP_STATE_READY, + * required before ipc_comp_free() which rejects non-READY components. + */ + ret = pipeline_reset(ctx->p, ctx->host_comp); + zassert_equal(ret, 0, "pipeline reset failed"); + + pipeline_disconnect(ctx->host_comp, ctx->buf, PPL_CONN_DIR_COMP_TO_BUFFER); + pipeline_disconnect(ctx->dai_comp, ctx->buf, PPL_CONN_DIR_BUFFER_TO_COMP); + + buffer_free(ctx->buf); + + /* Free components through IPC to properly remove from IPC device list */ + ret = ipc_comp_free(ctx->ipc, ctx->host_comp->ipc_config.id); + zassert_equal(ret, 0, "host comp free failed"); + + ret = ipc_comp_free(ctx->ipc, ctx->dai_comp->ipc_config.id); + zassert_equal(ret, 0, "DAI comp free failed"); + + /* Unregister pipeline from IPC component list */ + list_item_del(&ctx->ipc_pipe->list); + rfree(ctx->ipc_pipe); + + ret = pipeline_free(ctx->p); + zassert_equal(ret, 0, "pipeline free failed"); + + schedule_free(0); + + ret = schedule_task_free(task); + zassert_equal(ret, 0); + + sof_heap_free(heap, ctx->alloc); + sof_heap_free(heap, ctx); + + zephyr_ll_task_free(task); + k_mem_domain_remove_partition(zephyr_ll_mem_domain(), &userspace_ll_part); + + LOG_INF("two component pipeline test complete"); +} + +ZTEST(userspace_ll, pipeline_two_components_user) +{ + pipeline_two_components(); +} + ZTEST_SUITE(userspace_ll, NULL, NULL, NULL, NULL, NULL); /** diff --git a/zephyr/test/userspace/test_umutex.c b/zephyr/test/userspace/test_umutex.c new file mode 100644 index 000000000000..5f655f1063c2 --- /dev/null +++ b/zephyr/test/userspace/test_umutex.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright(c) 2026 Intel Corporation. + */ + +/** + * @file + * @brief Test case for sof_umutex API from a Zephyr user-space thread. + * + * Validates that sof_umutex_init/lock/unlock/free work correctly when + * called from user-space context. + */ + +#include +#include +#include + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); + +#define USER_STACKSIZE 2048 + +static struct k_thread umutex_user_thread; +static K_THREAD_STACK_DEFINE(umutex_user_stack, USER_STACKSIZE); + +/* Memory partition for test data accessible from user-space */ +K_APPMEM_PARTITION_DEFINE(umutex_test_part); + +/* Place the sof_umutex state in the user-accessible partition */ +K_APP_BMEM(umutex_test_part) static struct sof_umutex test_umutex; + +static void umutex_user_function(void *p1, void *p2, void *p3) +{ + int ret; + + __ASSERT(k_is_user_context(), "isn't user"); + + LOG_INF("umutex test thread %s (%s)", + k_is_user_context() ? "UserSpace!" : "privileged mode.", + CONFIG_BOARD_TARGET); + + /* Initialize the umutex — allocates backing k_mutex */ + ret = sof_umutex_init(&test_umutex); + zassert_equal(ret, 0, "sof_umutex_init failed: %d", ret); + + LOG_INF("sof_umutex_init succeeded"); + + /* Lock the mutex */ + ret = sof_umutex_lock(&test_umutex, K_FOREVER); + zassert_equal(ret, 0, "sof_umutex_lock failed: %d", ret); + + LOG_INF("sof_umutex_lock succeeded"); + + /* Unlock the mutex */ + ret = sof_umutex_unlock(&test_umutex); + zassert_equal(ret, 0, "sof_umutex_unlock failed: %d", ret); + + LOG_INF("sof_umutex_unlock succeeded"); + + /* Free the mutex — releases backing k_mutex */ + sof_umutex_free(&test_umutex); + + LOG_INF("sof_umutex_free done"); +} + +static void test_user_thread_umutex(void) +{ + /* Add test partition to LL memory domain so user thread can access test_umutex */ + k_mem_domain_add_partition(zephyr_ll_mem_domain(), &umutex_test_part); + + k_thread_create(&umutex_user_thread, umutex_user_stack, USER_STACKSIZE, + umutex_user_function, NULL, NULL, NULL, + -1, K_USER, K_FOREVER); + + /* Add thread to LL memory domain so it can access the partition */ + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &umutex_user_thread); + + k_thread_start(&umutex_user_thread); + k_thread_join(&umutex_user_thread, K_FOREVER); + + k_mem_domain_remove_partition(zephyr_ll_mem_domain(), &umutex_test_part); +} + +ZTEST(sof_boot, user_space_umutex) +{ + test_user_thread_umutex(); + + ztest_test_pass(); +} diff --git a/zephyr/wrapper.c b/zephyr/wrapper.c index c0c167b930fe..8c732fc9092e 100644 --- a/zephyr/wrapper.c +++ b/zephyr/wrapper.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -176,9 +177,6 @@ int task_main_start(struct sof *sof) /* init default audio components */ sys_comp_init(sof); - /* init pipeline position offsets */ - pipeline_posn_init(sof); - return 0; } @@ -265,7 +263,11 @@ void platform_dai_timestamp(struct comp_dev *dai, /* get current wallclock for componnent */ void platform_dai_wallclock(struct comp_dev *dai, uint64_t *wallclock) { +#ifndef CONFIG_SOF_USERSPACE_LL *wallclock = sof_cycle_get_64(); +#else + *wallclock = k_uptime_get(); +#endif } /* @@ -325,11 +327,36 @@ volatile int *_sof_fatal_null = NULL; struct arch_esf; +#if CONFIG_SOF_BOOT_TEST +static struct k_thread *sof_boot_test_fault_thread; + +void sof_boot_test_set_fault_valid(struct k_thread *thread) +{ + sof_boot_test_fault_thread = thread; +} + +static bool sof_boot_test_fault_expected(void) +{ + if (sof_boot_test_fault_thread != k_current_get()) + return false; + + sof_boot_test_fault_thread = NULL; + return true; +} +#endif + void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *esf) { ARG_UNUSED(esf); +#if CONFIG_SOF_BOOT_TEST + if (sof_boot_test_fault_expected()) { + LOG_ERR("Expected fatal error as part of boot test"); + return; + } +#endif + /* flush and switch to immediate mode */ LOG_PANIC();