diff --git a/src/audio/kpb.c b/src/audio/kpb.c index a2739b14e821..a778b0ccfb19 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -119,7 +119,14 @@ struct comp_data { #if CONFIG_KPB_CLI_Q #include -static struct k_queue *kpb_q; +/* + * Only a single KPB instance is currently supported: the queue, thread and + * dev below are global. Requests are copied by value, so user space never + * passes pointers to the kernel. + */ +K_MSGQ_DEFINE(kpb_cli_msgq, sizeof(struct kpb_client), KPB_MAX_NO_OF_CLIENTS, + __alignof__(struct kpb_client)); +static struct comp_dev *kpb_cli_q_dev; static struct k_thread kpb_thread; K_KERNEL_STACK_DEFINE(kpb_stack, 4096); #endif @@ -479,37 +486,34 @@ static int kpb_params(struct comp_dev *dev, struct sof_ipc_stream_params *params #if CONFIG_KPB_CLI_Q static void kpb_thread_fn(void *p1, void *p2, void *p3) { - struct k_queue *q = p1; + struct comp_dev *dev = p1; + struct kpb_client cli; for (;;) { - struct kpb_client *cli = k_queue_get(q, K_FOREVER); - - kpb_init_draining(cli->dev, cli); + k_msgq_get(&kpb_cli_msgq, &cli, K_FOREVER); + /* keep the LL thread (kpb_copy()) from preempting draining setup */ + k_sched_lock(); + kpb_init_draining(dev, &cli); + k_sched_unlock(); } } -void kpb_notifier_schedule(struct kpb_client *cli) +int z_impl_kpb_notifier_schedule(uint32_t id, uint32_t drain_req) { - k_queue_alloc_append(cli->queue, cli); -} -EXPORT_SYMBOL(kpb_notifier_schedule); + struct kpb_client cli = { + .id = id, + .drain_req = drain_req, + }; -void z_impl_kpb_notifier_init(struct comp_dev *dev, struct kpb_client *cli) -{ - comp_dbg(dev, "adding new client to %p", kpb_q); - k_thread_access_grant(k_current_get(), kpb_q); - cli->queue = kpb_q; - cli->dev = dev; -} + if (id >= KPB_MAX_NO_OF_CLIENTS || drain_req > KPB_MAX_DRAINING_REQ) + return -EINVAL; -#include -void z_vrfy_kpb_notifier_init(struct comp_dev *dev, struct kpb_client *cli) -{ - K_OOPS(K_SYSCALL_MEMORY_WRITE(dev, sizeof(*dev))); - K_OOPS(K_SYSCALL_MEMORY_WRITE(cli, sizeof(*cli))); - z_impl_kpb_notifier_init(dev, cli); + if (!kpb_cli_q_dev) + return -ENODEV; + + return k_msgq_put(&kpb_cli_msgq, &cli, K_NO_WAIT); } -#include +EXPORT_SYMBOL(z_impl_kpb_notifier_schedule); #endif /* @@ -551,6 +555,13 @@ __cold static struct comp_dev *kpb_new(const struct comp_driver *drv, return NULL; } +#if CONFIG_KPB_CLI_Q + if (kpb_cli_q_dev) { + comp_cl_err(&comp_kpb, "only one KPB instance is supported with KPB_CLI_Q"); + return NULL; + } +#endif + dev = comp_alloc(drv, sizeof(*dev)); if (!dev) return NULL; @@ -608,18 +619,13 @@ __cold static struct comp_dev *kpb_new(const struct comp_driver *drv, #endif #if CONFIG_KPB_CLI_Q - kpb_q = k_object_alloc(K_OBJ_QUEUE); - if (!kpb_q) { - comp_free_device(dev); - return NULL; - } - - k_queue_init(kpb_q); - k_thread_create(&kpb_thread, kpb_stack, 4096, kpb_thread_fn, kpb_q, NULL, NULL, + k_msgq_purge(&kpb_cli_msgq); + k_thread_create(&kpb_thread, kpb_stack, 4096, kpb_thread_fn, dev, NULL, NULL, 1, 0, K_FOREVER); k_thread_cpu_pin(&kpb_thread, cpu_get_id()); k_thread_start(&kpb_thread); + kpb_cli_q_dev = dev; #endif return dev; @@ -770,8 +776,9 @@ __cold static void kpb_free(struct comp_dev *dev) comp_info(dev, "entry"); #if CONFIG_KPB_CLI_Q + kpb_cli_q_dev = NULL; k_thread_abort(&kpb_thread); - k_object_free(kpb_q); + k_msgq_purge(&kpb_cli_msgq); #elif CONFIG_AMS /* Unregister KPB as AMS consumer */ int ret; @@ -1705,7 +1712,9 @@ static int kpb_register_client(struct comp_data *kpb, struct kpb_client *cli) static void kpb_init_draining(struct comp_dev *dev, struct kpb_client *cli) { struct comp_data *kpb = comp_get_drvdata(dev); - bool is_sink_ready = (comp_buffer_get_sink_state(kpb->host_sink) == COMP_STATE_ACTIVE); + /* sinks are NULL before bind and after reset/unbind */ + bool is_sink_ready = kpb->host_sink && kpb->sel_sink && + comp_buffer_get_sink_state(kpb->host_sink) == COMP_STATE_ACTIVE; size_t sample_width = kpb->config.sampling_width; size_t drain_req = (size_t)cli->drain_req * kpb->config.channels * (kpb->config.sampling_freq / 1000) * @@ -2015,28 +2024,29 @@ static enum task_state kpb_draining_task(void *arg) } else { draining_data->next_copy_time = 0; } + } - if (draining_data->drain_req == 0) { - /* We have finished draining of requested data however - * while we were draining real time stream could provided - * new data which needs to be copy to host. + /* also reached with a zero-length (no history) draining request */ + if (draining_data->drain_req == 0) { + /* We have finished draining of requested data however + * while we were draining real time stream could provided + * new data which needs to be copy to host. + */ + comp_cl_info(&comp_kpb, "kpb: update drain_req by %zu", + *rt_stream_update); + kpb_lock(kpb); + draining_data->drain_req += *rt_stream_update; + *rt_stream_update = 0; + if (!draining_data->drain_req && kpb->state == KPB_STATE_DRAINING) { + /* Draining is done. Now switch KPB to copy real time + * stream to client's sink. This state is called + * "draining on demand" + * Note! If KPB state changed during draining due to + * i.e reset request we should not change that state. */ - comp_cl_info(&comp_kpb, "kpb: update drain_req by %zu", - *rt_stream_update); - kpb_lock(kpb); - draining_data->drain_req += *rt_stream_update; - *rt_stream_update = 0; - if (!draining_data->drain_req && kpb->state == KPB_STATE_DRAINING) { - /* Draining is done. Now switch KPB to copy real time - * stream to client's sink. This state is called - * "draining on demand" - * Note! If KPB state changed during draining due to - * i.e reset request we should not change that state. - */ - kpb_change_state(kpb, KPB_STATE_HOST_COPY); - } - kpb_unlock(kpb); + kpb_change_state(kpb, KPB_STATE_HOST_COPY); } + kpb_unlock(kpb); } out: diff --git a/src/audio/module_adapter/Kconfig b/src/audio/module_adapter/Kconfig index f8e40a8cda15..04026dcb5b14 100644 --- a/src/audio/module_adapter/Kconfig +++ b/src/audio/module_adapter/Kconfig @@ -226,7 +226,7 @@ endif # Cadence config INTEL_MODULES bool "Intel modules" depends on LIBRARY_MANAGER - select KPB_NOTIFIER if COMP_KPB + select KPB_NOTIFIER if COMP_KPB && !KPB_CLI_Q help Select for a Intel modules API implementation. This will cause module adapter component to include IADK module diff --git a/src/audio/module_adapter/library/kpb_service_bridge.c b/src/audio/module_adapter/library/kpb_service_bridge.c index 574ce73431ce..b2d7c2f717f1 100644 --- a/src/audio/module_adapter/library/kpb_service_bridge.c +++ b/src/audio/module_adapter/library/kpb_service_bridge.c @@ -7,16 +7,21 @@ * do not provide extern "C" blocks; C++ is used as a wrapper. */ +#include #include #include #include #include #include #include +#if !CONFIG_KPB_CLI_Q #include +#endif struct kpb_service_client { +#if !CONFIG_KPB_CLI_Q struct kpb_event_data event; +#endif struct kpb_client client; size_t current_history_depth; size_t max_history_depth; @@ -37,15 +42,37 @@ static bool kpb_service_client_is_valid(const struct kpb_service_client *client) return false; } -static void kpb_service_send_event(struct kpb_service_client *client, - enum kpb_event event_id) +#if CONFIG_KPB_CLI_Q +static AdspErrorCode kpb_service_send_event(struct kpb_service_client *client, + enum kpb_event event_id) +{ + /* KPB draining does not depend on registration, so only draining is queued */ + if (event_id != KPB_EVENT_BEGIN_DRAINING) + return ADSP_NO_ERROR; + + switch (kpb_notifier_schedule(client->client.id, client->client.drain_req)) { + case 0: + return ADSP_NO_ERROR; + case -ENODEV: + return ADSP_SERVICE_UNAVAILABLE; + case -ENOMSG: + return ADSP_BUSY_RESOURCE; + default: + return ADSP_INVALID_PARAMETERS; + } +} +#else +static AdspErrorCode kpb_service_send_event(struct kpb_service_client *client, + enum kpb_event event_id) { client->event.event_id = event_id; client->event.client_data = &client->client; notifier_event(client, NOTIFIER_ID_KPB_CLIENT_EVT, NOTIFIER_TARGET_CORE_ALL_MASK, &client->event, sizeof(client->event)); + return ADSP_NO_ERROR; } +#endif AdspErrorCode kpb_service_register_client(size_t current_history_depth, size_t max_history_depth, uint32_t output_pin, @@ -86,8 +113,7 @@ AdspErrorCode kpb_service_signal_detection(struct kpb_service_client *client, client->client.drain_req = MIN(client->current_history_depth + phrase_length, client->max_history_depth); - kpb_service_send_event(client, KPB_EVENT_BEGIN_DRAINING); - return ADSP_NO_ERROR; + return kpb_service_send_event(client, KPB_EVENT_BEGIN_DRAINING); } AdspErrorCode kpb_service_unregister_client(struct kpb_service_client *client) diff --git a/src/include/sof/audio/kpb.h b/src/include/sof/audio/kpb.h index b17e7eb9bd55..7698395578df 100644 --- a/src/include/sof/audio/kpb.h +++ b/src/include/sof/audio/kpb.h @@ -113,8 +113,6 @@ enum kpb_client_state { }; struct kpb_client { - struct comp_dev *dev; - struct k_queue *queue; uint8_t id; /**< id associated with output sink */ uint32_t drain_req; /**< normalized value of buffered bytes */ enum kpb_client_state state; /**< current state of a client */ @@ -256,13 +254,24 @@ struct kpb_fmt_dev_list { }; #if CONFIG_KPB_CLI_Q -void kpb_notifier_schedule(struct kpb_client *cli); #if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) #include -__syscall void kpb_notifier_init(struct comp_dev *dev, struct kpb_client *cli); +/** + * \brief Request draining of the KPB history buffer via the client queue. + * + * Callable from user space. The request is handled asynchronously by the + * KPB client queue thread. + * + * \param[in] id Client ID, i.e. the KPB output pin. + * \param[in] drain_req Requested draining length in milliseconds. + * \return 0 on success, -EINVAL on invalid arguments, -ENODEV if no KPB + * instance exists, -ENOMSG if the queue is full. + */ +__syscall int kpb_notifier_schedule(uint32_t id, uint32_t drain_req); #include #else -#define kpb_notifier_init z_impl_kpb_notifier_init +int z_impl_kpb_notifier_schedule(uint32_t id, uint32_t drain_req); +#define kpb_notifier_schedule z_impl_kpb_notifier_schedule #endif #endif diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 8da2dc47fdc5..ab4cfc00d2b2 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -613,6 +613,9 @@ zephyr_syscall_header(include/sof/lib/cpu.h) zephyr_library_sources_ifdef(CONFIG_USERSPACE syscall/cpu.c) zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/audio/module_adapter/module/generic.h) zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/audio/kpb.h) +if(CONFIG_USERSPACE AND CONFIG_KPB_CLI_Q) + zephyr_library_sources(syscall/kpb.c) +endif() 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/sof/schedule/dp_schedule.h) diff --git a/zephyr/syscall/kpb.c b/zephyr/syscall/kpb.c new file mode 100644 index 000000000000..11837baefcf2 --- /dev/null +++ b/zephyr/syscall/kpb.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright 2026 Intel Corporation. + +#include +#include +#include +#include + +/** + * \brief Userspace verification wrapper for kpb_notifier_schedule(). + * + * Only scalar arguments are passed, their ranges are checked by the + * implementation, so no memory access validation is required. + * + * \param[in] id Client ID, i.e. the KPB output pin. + * \param[in] drain_req Requested draining length in milliseconds. + * \return 0 on success, negative error code otherwise. + */ +static inline int z_vrfy_kpb_notifier_schedule(uint32_t id, uint32_t drain_req) +{ + return z_impl_kpb_notifier_schedule(id, drain_req); +} +#include