diff --git a/posix/include/sof/lib/cpu.h b/posix/include/sof/lib/cpu.h index b6e0fadb22c0..2eabb77cae09 100644 --- a/posix/include/sof/lib/cpu.h +++ b/posix/include/sof/lib/cpu.h @@ -54,7 +54,11 @@ static inline bool cpu_is_primary(int id) static inline bool cpu_is_me(int id) { +#if CONFIG_LIBRARY + return true; +#else return id == cpu_get_id(); +#endif } #endif diff --git a/src/arch/host/CMakeLists.txt b/src/arch/host/CMakeLists.txt index 75314f00f556..d52bd13b8d69 100644 --- a/src/arch/host/CMakeLists.txt +++ b/src/arch/host/CMakeLists.txt @@ -22,6 +22,12 @@ target_compile_options(sof_options INTERFACE -g -O3 -fPIC -DPIC -std=c99 -std=gn -Wall -Werror -Wmissing-prototypes ${implicit_fallthrough} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -Wpointer-arith -DCONFIG_LIBRARY "-imacros${CONFIG_H_PATH}") +if(CMAKE_C_COMPILER MATCHES "xt-|xtensa-") + target_compile_options(sof_options INTERFACE + -U__INT32_TYPE__ -D__INT32_TYPE__=int + -U__UINT32_TYPE__ "-D__UINT32_TYPE__=unsigned int" + -Wno-format) +endif() if(NOT BUILD_UNIT_TESTS_HOST) add_subdirectory(lib) diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig index 3a72dc85c79a..61e0567d5626 100644 --- a/src/arch/host/configs/library_defconfig +++ b/src/arch/host/configs/library_defconfig @@ -19,6 +19,7 @@ CONFIG_COMP_RTNR=y CONFIG_COMP_SEL=y CONFIG_COMP_SOUND_DOSE=y CONFIG_COMP_SRC=y +CONFIG_COMP_SRC_LITE=y CONFIG_COMP_SRC_IPC4_FULL_MATRIX=y CONFIG_COMP_STFT_PROCESS=y CONFIG_COMP_STUBS=y @@ -27,10 +28,15 @@ CONFIG_COMP_TONE=y CONFIG_COMP_VOLUME=y CONFIG_COMP_VOLUME_LINEAR_RAMP=y CONFIG_COMP_VOLUME_WINDOWS_FADE=y +CONFIG_CORE_COUNT=8 +CONFIG_CROSS_CORE_STREAM=y CONFIG_DEBUG_MEMORY_USAGE_SCAN=n CONFIG_IPC_MAJOR_3=n CONFIG_IPC_MAJOR_4=y CONFIG_LIBRARY=y CONFIG_MATH_IIR_DF2T=y +CONFIG_MP_MAX_NUM_CPUS=8 +CONFIG_PIPELINE_2_0=y CONFIG_TRACEV=y CONFIG_XT_RUN=y +CONFIG_ZEPHYR_DP_SCHEDULER=y diff --git a/src/arch/host/include/arch/lib/cpu.h b/src/arch/host/include/arch/lib/cpu.h index e2cfb76260b7..6284eaa75bdc 100644 --- a/src/arch/host/include/arch/lib/cpu.h +++ b/src/arch/host/include/arch/lib/cpu.h @@ -10,29 +10,56 @@ #ifndef __ARCH_LIB_CPU_H__ #define __ARCH_LIB_CPU_H__ +int __attribute__((weak)) host_cpu_num_cores(void); +int __attribute__((weak)) host_cpu_enable_core(int id); +void __attribute__((weak)) host_cpu_disable_core(int id); +int __attribute__((weak)) host_cpu_is_core_enabled(int id); +int __attribute__((weak)) host_cpu_enabled_cores(void); + +static inline int arch_cpu_num_cores(void) +{ + if (host_cpu_num_cores) + return host_cpu_num_cores(); +#ifdef CONFIG_CORE_COUNT + return CONFIG_CORE_COUNT; +#else + return 1; +#endif +} + static inline int arch_cpu_enable_core(int id) { + if (host_cpu_enable_core) + return host_cpu_enable_core(id); return 0; } static inline void arch_cpu_disable_core(int id) { + if (host_cpu_disable_core) + host_cpu_disable_core(id); } static inline int arch_cpu_is_core_enabled(int id) { - return 0; + if (host_cpu_is_core_enabled) + return host_cpu_is_core_enabled(id); + return 1; } static inline int arch_cpu_enabled_cores(void) { + if (host_cpu_enabled_cores) + return host_cpu_enabled_cores(); +#ifdef CONFIG_CORE_COUNT + return (1 << CONFIG_CORE_COUNT) - 1; +#else return 1; +#endif } -static inline int arch_cpu_get_id(void) -{ - return 0; -} +int arch_cpu_get_id(void); +void host_cpu_set_id(int id); static inline int arch_cpu_restore_secondary_cores(void) { diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 0dd1b1694381..c2c227a0c14e 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -157,7 +157,9 @@ if(CONFIG_ZEPHYR_NATIVE_DRIVERS) sof_list_append_ifdef(CONFIG_COMP_DAI base_files dai-zephyr.c) else() list(APPEND base_files host-legacy.c) - sof_list_append_ifdef(CONFIG_COMP_DAI base_files dai-legacy.c) + if(CONFIG_COMP_DAI AND NOT CONFIG_LIBRARY) + list(APPEND base_files dai-legacy.c) + endif() endif() ### Common files @@ -180,7 +182,7 @@ else() ### library, e.g. testbench ### ${base_files} ) - if(CONFIG_COMP_DAI) + if(CONFIG_COMP_DAI AND NOT CONFIG_LIBRARY) add_local_sources(sof dai-legacy.c ) diff --git a/src/audio/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index f2b3c1c05e96..81720c733cc3 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -31,11 +31,43 @@ SOF_DEFINE_REG_UUID(buffer); /* unused with Zephyr, generates no output */ DECLARE_TR_CTX(buffer_tr, SOF_UUID(buffer_uuid), LOG_LEVEL_INFO); +#if defined(CONFIG_LIBRARY) && !defined(__XTENSA__) && !defined(CONFIG_XT_SIMULATOR) && \ + !defined(UNIT_TEST) +static volatile int shared_comp_buf_lock; +static __thread int shared_comp_buf_lock_depth; + +void comp_buffer_shared_lock(struct comp_buffer *buffer) +{ + if (buffer && audio_buffer_is_shared(&buffer->audio_buffer)) { + if (shared_comp_buf_lock_depth++ == 0) { + while (__atomic_test_and_set(&shared_comp_buf_lock, __ATOMIC_ACQUIRE)) + ; + } + } +} + +void comp_buffer_shared_unlock(struct comp_buffer *buffer) +{ + if (buffer && audio_buffer_is_shared(&buffer->audio_buffer)) { + if (--shared_comp_buf_lock_depth == 0) + __atomic_clear(&shared_comp_buf_lock, __ATOMIC_RELEASE); + } +} +#endif + +#define shared_buf_lock(buffer) comp_buffer_shared_lock(buffer) +#define shared_buf_unlock(buffer) comp_buffer_shared_unlock(buffer) + static size_t comp_buffer_get_data_available(struct sof_source *source) { struct comp_buffer *buffer = comp_buffer_get_from_source(source); + size_t avail; + + shared_buf_lock(buffer); + avail = audio_stream_get_avail_bytes(&buffer->stream); + shared_buf_unlock(buffer); - return audio_stream_get_avail_bytes(&buffer->stream); + return avail; } static int comp_buffer_get_data(struct sof_source *source, size_t req_size, @@ -44,8 +76,11 @@ static int comp_buffer_get_data(struct sof_source *source, size_t req_size, { struct comp_buffer *buffer = comp_buffer_get_from_source(source); - if (req_size > audio_stream_get_avail_bytes(&buffer->stream)) + shared_buf_lock(buffer); + if (req_size > audio_stream_get_avail_bytes(&buffer->stream)) { + shared_buf_unlock(buffer); return -ENODATA; + } buffer_stream_invalidate(buffer, req_size); @@ -53,6 +88,7 @@ static int comp_buffer_get_data(struct sof_source *source, size_t req_size, *data_ptr = buffer->stream.r_ptr; *buffer_start = buffer->stream.addr; *buffer_size = buffer->stream.size; + shared_buf_unlock(buffer); return 0; } @@ -60,8 +96,11 @@ static int comp_buffer_release_data(struct sof_source *source, size_t free_size) { struct comp_buffer *buffer = comp_buffer_get_from_source(source); - if (free_size) + if (free_size) { + shared_buf_lock(buffer); audio_stream_consume(&buffer->stream, free_size); + shared_buf_unlock(buffer); + } return 0; } @@ -69,8 +108,13 @@ static int comp_buffer_release_data(struct sof_source *source, size_t free_size) static size_t comp_buffer_get_free_size(struct sof_sink *sink) { struct comp_buffer *buffer = comp_buffer_get_from_sink(sink); + size_t free_size; + + shared_buf_lock(buffer); + free_size = audio_stream_get_free_bytes(&buffer->stream); + shared_buf_unlock(buffer); - return audio_stream_get_free_bytes(&buffer->stream); + return free_size; } static int comp_buffer_get_buffer(struct sof_sink *sink, size_t req_size, @@ -78,13 +122,17 @@ static int comp_buffer_get_buffer(struct sof_sink *sink, size_t req_size, { struct comp_buffer *buffer = comp_buffer_get_from_sink(sink); - if (req_size > audio_stream_get_free_bytes(&buffer->stream)) + shared_buf_lock(buffer); + if (req_size > audio_stream_get_free_bytes(&buffer->stream)) { + shared_buf_unlock(buffer); return -ENODATA; + } /* get circular buffer parameters */ *data_ptr = buffer->stream.w_ptr; *buffer_start = buffer->stream.addr; *buffer_size = buffer->stream.size; + shared_buf_unlock(buffer); return 0; } @@ -93,8 +141,10 @@ static int comp_buffer_commit_buffer(struct sof_sink *sink, size_t commit_size) struct comp_buffer *buffer = comp_buffer_get_from_sink(sink); if (commit_size) { + shared_buf_lock(buffer); buffer_stream_writeback(buffer, commit_size); comp_update_buffer_produce(buffer, commit_size); + shared_buf_unlock(buffer); } return 0; @@ -560,6 +610,8 @@ void comp_update_buffer_produce(struct comp_buffer *buffer, uint32_t bytes) return; } + shared_buf_lock(buffer); + #if CONFIG_PROBE void *produce_begin = audio_stream_get_wptr(&buffer->stream); #endif @@ -589,6 +641,8 @@ void comp_update_buffer_produce(struct comp_buffer *buffer, uint32_t bytes) ((char *)audio_stream_get_wptr(&buffer->stream) - (char *)audio_stream_get_addr(&buffer->stream))); #endif + + shared_buf_unlock(buffer); } void comp_update_buffer_consume(struct comp_buffer *buffer, uint32_t bytes) @@ -610,6 +664,8 @@ void comp_update_buffer_consume(struct comp_buffer *buffer, uint32_t bytes) return; } + shared_buf_lock(buffer); + audio_stream_consume(&buffer->stream, bytes); #if CONFIG_SOF_LOG_DBG_BUFFER @@ -622,6 +678,8 @@ void comp_update_buffer_consume(struct comp_buffer *buffer, uint32_t bytes) ((char *)audio_stream_get_wptr(&buffer->stream) - (char *)audio_stream_get_addr(&buffer->stream))); #endif + + shared_buf_unlock(buffer); } static inline struct list_item *buffer_comp_list(struct comp_buffer *buffer, diff --git a/src/audio/buffers/ring_buffer.c b/src/audio/buffers/ring_buffer.c index 3d32ec4af204..1a8dde39ac93 100644 --- a/src/audio/buffers/ring_buffer.c +++ b/src/audio/buffers/ring_buffer.c @@ -144,7 +144,13 @@ size_t ring_buffer_inc_offset(struct ring_buffer *ring_buffer, size_t offset, si static inline size_t _ring_buffer_get_data_available(struct ring_buffer *ring_buffer) { +#if defined(CONFIG_LIBRARY) && !defined(__XTENSA__) && !defined(CONFIG_XT_SIMULATOR) + int32_t w_off = __atomic_load_n(&ring_buffer->_write_offset, __ATOMIC_ACQUIRE); + int32_t r_off = __atomic_load_n(&ring_buffer->_read_offset, __ATOMIC_ACQUIRE); + int32_t avail_data = w_off - r_off; +#else int32_t avail_data = ring_buffer->_write_offset - ring_buffer->_read_offset; +#endif /* wrap around ? 2*size because of "double area" */ if (avail_data < 0) avail_data = 2 * ring_buffer->data_buffer_size + avail_data; @@ -199,9 +205,17 @@ static int ring_buffer_commit_buffer(struct sof_sink *sink, size_t commit_size) commit_size); /* move write pointer */ +#if defined(CONFIG_LIBRARY) && !defined(__XTENSA__) && !defined(CONFIG_XT_SIMULATOR) + __atomic_store_n(&ring_buffer->_write_offset, + ring_buffer_inc_offset(ring_buffer, + ring_buffer->_write_offset, + commit_size), + __ATOMIC_RELEASE); +#else ring_buffer->_write_offset = ring_buffer_inc_offset(ring_buffer, ring_buffer->_write_offset, commit_size); +#endif } return 0; @@ -237,15 +251,23 @@ static int ring_buffer_release_data(struct sof_source *source, size_t free_size) CORE_CHECK_STRUCT(&ring_buffer->audio_buffer); if (free_size) { /* data consumed, free buffer space, no need for any special cache operations */ +#if defined(CONFIG_LIBRARY) && !defined(__XTENSA__) && !defined(CONFIG_XT_SIMULATOR) + __atomic_store_n(&ring_buffer->_read_offset, + ring_buffer_inc_offset(ring_buffer, + ring_buffer->_read_offset, + free_size), + __ATOMIC_RELEASE); +#else ring_buffer->_read_offset = ring_buffer_inc_offset(ring_buffer, ring_buffer->_read_offset, free_size); +#endif } return 0; } -int ring_buffer_module_unbind(struct sof_sink *sink) +static int ring_buffer_module_unbind(struct sof_sink *sink) { struct ring_buffer *ring_buffer = ring_buffer_from_sink(sink); diff --git a/src/audio/component.c b/src/audio/component.c index 9a1623099fef..62370ebd2d34 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -25,6 +25,10 @@ #include #include +#if CONFIG_LIBRARY && !defined(UNIT_TEST) +#include +#endif + #if defined(__XCC__) #include # if XCHAL_HAVE_HIFI5 @@ -619,7 +623,13 @@ int comp_copy(struct comp_dev *dev) if (comp_check_eos(dev)) return 0; +#if CONFIG_LIBRARY && !defined(UNIT_TEST) + uint64_t telem_start = host_telem_mod_entry(dev, false); +#endif ret = dev->drv->ops.copy(dev); +#if CONFIG_LIBRARY && !defined(UNIT_TEST) + host_telem_mod_exit(dev, false, telem_start, ret); +#endif #ifdef CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS const uint32_t cycles_consumed = (uint32_t)telemetry_timestamp() - begin_stamp; diff --git a/src/audio/copier/CMakeLists.txt b/src/audio/copier/CMakeLists.txt index 7cead10dabe0..ee23f22fa10f 100644 --- a/src/audio/copier/CMakeLists.txt +++ b/src/audio/copier/CMakeLists.txt @@ -1,4 +1,7 @@ -add_local_sources(sof copier.c copier_hifi.c copier_generic.c copier_host.c copier_dai.c) +add_local_sources(sof copier.c copier_hifi.c copier_generic.c copier_host.c) +if(NOT CONFIG_LIBRARY) + add_local_sources(sof copier_dai.c) +endif() if(CONFIG_IPC4_GATEWAY) add_local_sources(sof copier_ipcgtw.c diff --git a/src/audio/copier/dai_copier.h b/src/audio/copier/dai_copier.h index eb540efb86c4..9bf9e50bc055 100644 --- a/src/audio/copier/dai_copier.h +++ b/src/audio/copier/dai_copier.h @@ -17,6 +17,123 @@ struct ipc_config_dai; struct comp_dev; struct dai_data; +struct ipc4_copier_module_cfg; +struct copier_data; + +#if CONFIG_LIBRARY +static inline int dai_common_new(struct dai_data *dd, struct comp_dev *dev, + const struct ipc_config_dai *dai_cfg) +{ + return 0; +} + +static inline void dai_common_free(struct dai_data *dd) {} + +static inline int dai_common_config_prepare(struct dai_data *dd, struct comp_dev *dev) +{ + return 0; +} + +static inline int dai_common_prepare(struct dai_data *dd, struct comp_dev *dev) +{ + return 0; +} + +static inline void dai_common_reset(struct dai_data *dd, struct comp_dev *dev) {} + +static inline int dai_common_trigger(struct dai_data *dd, struct comp_dev *dev, int cmd) +{ + return 0; +} + +static inline int dai_common_position(struct dai_data *dd, struct comp_dev *dev, + struct sof_ipc_stream_posn *posn) +{ + return 0; +} + +static inline int dai_common_params(struct dai_data *dd, struct comp_dev *dev, + struct sof_ipc_stream_params *params) +{ + return 0; +} + +static inline int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, + pcm_converter_func *converter) +{ + return 0; +} + +static inline int dai_common_ts_config_op(struct dai_data *dd, struct comp_dev *dev) +{ + return 0; +} + +static inline int dai_common_ts_start(struct dai_data *dd, struct comp_dev *dev) +{ + return 0; +} + +static inline int dai_common_ts_stop(struct dai_data *dd, struct comp_dev *dev) +{ + return 0; +} + +static inline int dai_common_ts_get(struct dai_data *dd, struct comp_dev *dev, + struct timestamp_data *tsd) +{ + return 0; +} + +static inline int dai_common_get_hw_params(struct dai_data *dd, struct comp_dev *dev, + struct sof_ipc_stream_params *params, int dir) +{ + return 0; +} + +static inline int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, + struct comp_buffer *multi_endpoint_buffer, + int num_endpoints) +{ + return 0; +} + +static inline int dai_zephyr_unbind(struct dai_data *dd, struct comp_dev *dev, const void *data) +{ + return 0; +} + +static inline int copier_dai_create(struct comp_dev *dev, struct copier_data *cd, + const struct ipc4_copier_module_cfg *copier, + struct pipeline *pipeline) +{ + return 0; +} + +static inline void copier_dai_free(struct processing_module *mod) {} + +static inline int copier_dai_prepare(struct comp_dev *dev, struct copier_data *cd) +{ + return 0; +} + +static inline void copier_dai_adjust_params(const struct copier_data *cd, + struct ipc4_audio_format *in_fmt, + struct ipc4_audio_format *out_fmt) {} + +static inline int copier_dai_params(struct copier_data *cd, struct comp_dev *dev, + struct sof_ipc_stream_params *params, int dai_index) +{ + return 0; +} + +static inline void copier_dai_reset(struct copier_data *cd, struct comp_dev *dev) {} + +static inline int copier_dai_trigger(struct copier_data *cd, struct comp_dev *dev, int cmd) +{ + return 0; +} +#else int dai_common_new(struct dai_data *dd, struct comp_dev *dev, const struct ipc_config_dai *dai_cfg); @@ -53,29 +170,12 @@ int dai_common_ts_get(struct dai_data *dd, struct comp_dev *dev, struct timestam int dai_common_get_hw_params(struct dai_data *dd, struct comp_dev *dev, struct sof_ipc_stream_params *params, int dir); -#if CONFIG_LIBRARY -static inline int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, - struct comp_buffer *multi_endpoint_buffer, - int num_endpoints) -{ - return 0; -} - -static inline int dai_zephyr_unbind(struct dai_data *dd, struct comp_dev *dev, const void *data) -{ - return 0; -} -#else int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, struct comp_buffer *multi_endpoint_buffer, int num_endpoints); int dai_zephyr_unbind(struct dai_data *dd, struct comp_dev *dev, const struct bind_info *unbind_data); -#endif - -struct ipc4_copier_module_cfg; -struct copier_data; int copier_dai_create(struct comp_dev *dev, struct copier_data *cd, const struct ipc4_copier_module_cfg *copier, struct pipeline *pipeline); @@ -94,4 +194,5 @@ int copier_dai_params(struct copier_data *cd, struct comp_dev *dev, void copier_dai_reset(struct copier_data *cd, struct comp_dev *dev); int copier_dai_trigger(struct copier_data *cd, struct comp_dev *dev, int cmd); +#endif #endif /* __SOF_LIB_DAI_COPIER_H__ */ diff --git a/src/audio/google/Kconfig b/src/audio/google/Kconfig index 61d1608b6891..692eef1c2d44 100644 --- a/src/audio/google/Kconfig +++ b/src/audio/google/Kconfig @@ -86,7 +86,7 @@ config COMP_GOOGLE_CTC_AUDIO_PROCESSING select COMP_BLOB select GOOGLE_CTC_AUDIO_PROCESSING_MOCK if COMP_STUBS select STATIC_INIT_GNU if ZEPHYR_SOF_MODULE - depends on ZEPHYR_SOF_MODULE + depends on ZEPHYR_SOF_MODULE || LIBRARY help Select for Google crosstalk cancellation audio processing. It uses the Google real-time audio processing library to perform diff --git a/src/audio/google/google_ctc_audio_processing.c b/src/audio/google/google_ctc_audio_processing.c index d7d7f389a9bc..1c7ad2344ce9 100644 --- a/src/audio/google/google_ctc_audio_processing.c +++ b/src/audio/google/google_ctc_audio_processing.c @@ -380,7 +380,7 @@ static int ctc_prepare(struct processing_module *mod, struct sof_source *source; unsigned int num_channels; uint8_t *config; - int config_size; + size_t config_size; comp_info(mod->dev, "entry"); @@ -422,7 +422,7 @@ static int ctc_prepare(struct processing_module *mod, config = comp_get_data_blob(cd->tuning_handler, &config_size, NULL); if (config_size != CTC_BLOB_CONFIG_SIZE) { - comp_info(mod->dev, "config_size not expected: %d", config_size); + comp_info(mod->dev, "config_size not expected: %zu", config_size); config = NULL; config_size = 0; } diff --git a/src/audio/google/google_ctc_audio_processing_ipc4.c b/src/audio/google/google_ctc_audio_processing_ipc4.c index fa5f265e9d0f..25835a22a70a 100644 --- a/src/audio/google/google_ctc_audio_processing_ipc4.c +++ b/src/audio/google/google_ctc_audio_processing_ipc4.c @@ -29,7 +29,7 @@ int ctc_set_config(struct processing_module *mod, uint32_t param_id, struct google_ctc_audio_processing_comp_data *cd = module_get_private_data(mod); int ret; struct google_ctc_config *config; - int size; + size_t size; switch (param_id) { case 0: @@ -58,7 +58,7 @@ int ctc_set_config(struct processing_module *mod, uint32_t param_id, config = comp_get_data_blob(cd->tuning_handler, &size, NULL); if (size != CTC_BLOB_CONFIG_SIZE) { comp_err(mod->dev, - "Invalid config size = %d", + "Invalid config size = %zu", size); return -EINVAL; } diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index 3ccae18cbe09..5f37fff58c8c 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -1074,6 +1074,9 @@ static int module_adapter_audio_stream_copy_1to1(struct comp_dev *dev) uint32_t frames; int ret; + comp_buffer_shared_lock(mod->source_comp_buffer); + comp_buffer_shared_lock(mod->sink_comp_buffer); + frames = audio_stream_avail_frames_aligned(&mod->source_comp_buffer->stream, &mod->sink_comp_buffer->stream); mod->input_buffers[0].size = frames; @@ -1102,8 +1105,8 @@ static int module_adapter_audio_stream_copy_1to1(struct comp_dev *dev) /* consume from the input buffer */ mod->total_data_consumed += mod->input_buffers[0].consumed; if (mod->input_buffers[0].consumed) - audio_stream_consume(&mod->source_comp_buffer->stream, - mod->input_buffers[0].consumed); + comp_update_buffer_consume(mod->source_comp_buffer, + mod->input_buffers[0].consumed); /* produce data into the output buffer */ mod->total_data_produced += mod->output_buffers[0].size; @@ -1113,6 +1116,9 @@ static int module_adapter_audio_stream_copy_1to1(struct comp_dev *dev) if (mod->output_buffers[0].size) comp_update_buffer_produce(mod->sink_comp_buffer, mod->output_buffers[0].size); + comp_buffer_shared_unlock(mod->sink_comp_buffer); + comp_buffer_shared_unlock(mod->source_comp_buffer); + return ret; } @@ -1124,6 +1130,7 @@ static int module_adapter_audio_stream_type_copy(struct comp_dev *dev) struct comp_buffer *source; struct processing_module *mod = comp_mod(dev); uint32_t num_input_buffers, num_output_buffers; + uint32_t locked_inputs, locked_outputs; int ret, i; /* handle special case of HOST/DAI type components */ @@ -1152,6 +1159,13 @@ static int module_adapter_audio_stream_type_copy(struct comp_dev *dev) return -EINVAL; } + locked_inputs = num_input_buffers; + locked_outputs = num_output_buffers; + for (i = 0; i < locked_inputs; i++) + comp_buffer_shared_lock(sources[i]); + for (i = 0; i < locked_outputs; i++) + comp_buffer_shared_lock(sinks[i]); + /* setup active input/output buffers for processing */ if (num_output_buffers == 1) { module_single_sink_setup(dev, sources, sinks); @@ -1184,7 +1198,7 @@ static int module_adapter_audio_stream_type_copy(struct comp_dev *dev) container_of(mod->input_buffers[i].data, struct comp_buffer, stream); if (mod->input_buffers[i].consumed) - audio_stream_consume(&src->stream, mod->input_buffers[i].consumed); + comp_update_buffer_consume(src, mod->input_buffers[i].consumed); } /* compute data consumed based on pin 0 since it is processed with base config @@ -1216,6 +1230,11 @@ static int module_adapter_audio_stream_type_copy(struct comp_dev *dev) mod->output_buffers[i].size = 0; } + for (i = 0; i < locked_outputs; i++) + comp_buffer_shared_unlock(sinks[i]); + for (i = 0; i < locked_inputs; i++) + comp_buffer_shared_unlock(sources[i]); + return 0; out: for (i = 0; i < num_output_buffers; i++) { @@ -1227,6 +1246,11 @@ static int module_adapter_audio_stream_type_copy(struct comp_dev *dev) mod->input_buffers[i].consumed = 0; } + for (i = 0; i < locked_outputs; i++) + comp_buffer_shared_unlock(sinks[i]); + for (i = 0; i < locked_inputs; i++) + comp_buffer_shared_unlock(sources[i]); + return ret; } diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index fc8daad6e378..b33cf9aa4661 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -31,6 +31,10 @@ #include #include +#if CONFIG_LIBRARY && !defined(UNIT_TEST) +#include +#endif + LOG_MODULE_DECLARE(pipe, CONFIG_SOF_LOG_LEVEL); SOF_DEFINE_REG_UUID(pipe_task); @@ -577,9 +581,23 @@ void pipeline_comp_ll_task_free(struct pipeline *p) static enum task_state dp_task_run(void *data) { struct processing_module *mod = data; +#if CONFIG_LIBRARY + uint32_t i; + uint64_t telem_start; + + for (i = 0; i < mod->num_of_sources; i++) + source_reset_num_of_processed_bytes(mod->sources[i]); + for (i = 0; i < mod->num_of_sinks; i++) + sink_reset_num_of_processed_bytes(mod->sinks[i]); + + telem_start = host_telem_mod_entry(mod->dev, true); +#endif int ret = module_process_sink_src(mod, mod->sources, mod->num_of_sources, mod->sinks, mod->num_of_sinks); +#if CONFIG_LIBRARY + host_telem_mod_exit(mod->dev, true, telem_start, ret); +#endif if (ret) pipeline_comp_copy_error_notify(mod->dev, ret); @@ -602,8 +620,10 @@ int pipeline_comp_dp_task_init(struct comp_dev *comp) #if CONFIG_SOF_USERSPACE_PROXY unsigned int flags = mod->user_ctx ? K_USER : 0; +#elif CONFIG_USERSPACE + unsigned int flags = K_USER; #else - unsigned int flags = IS_ENABLED(CONFIG_USERSPACE) ? K_USER : 0; + unsigned int flags = 0; #endif if (mod->priv.cfg.ext_data && mod->priv.cfg.ext_data->dp_data && diff --git a/src/audio/pipeline/pipeline-stream.c b/src/audio/pipeline/pipeline-stream.c index 4bdd0c93b9c9..c8adbe0bb780 100644 --- a/src/audio/pipeline/pipeline-stream.c +++ b/src/audio/pipeline/pipeline-stream.c @@ -35,6 +35,10 @@ #include "../audio/copier/copier.h" +#if CONFIG_LIBRARY && !defined(UNIT_TEST) +#include +#endif + LOG_MODULE_DECLARE(pipe, CONFIG_SOF_LOG_LEVEL); /* @@ -176,6 +180,9 @@ int pipeline_copy(struct pipeline *p) struct comp_dev *start; uint32_t dir; int ret; +#if CONFIG_LIBRARY && !defined(UNIT_TEST) + uint64_t telem_start = host_telem_pipe_begin(p); +#endif PPL_LOCK(p->core); @@ -197,6 +204,10 @@ int pipeline_copy(struct pipeline *p) PPL_UNLOCK(); +#if CONFIG_LIBRARY && !defined(UNIT_TEST) + host_telem_pipe_end(p, telem_start, ret); +#endif + return ret; } diff --git a/src/include/kernel/tokens.h b/src/include/kernel/tokens.h index 93c4754f831c..9183f2644e11 100644 --- a/src/include/kernel/tokens.h +++ b/src/include/kernel/tokens.h @@ -96,6 +96,11 @@ #define SOF_TKN_COMP_OUTPUT_PIN_BINDING_WNAME 414 #define SOF_TKN_COMP_NUM_INPUT_AUDIO_FORMATS 415 #define SOF_TKN_COMP_NUM_OUTPUT_AUDIO_FORMATS 416 +#define SOF_TKN_COMP_NO_WNAME_IN_KCONTROL_NAME 417 +#define SOF_TKN_COMP_SCHED_DOMAIN 418 +#define SOF_TKN_COMP_DOMAIN_ID 419 +#define SOF_TKN_COMP_STACK_BYTES 420 +#define SOF_TKN_COMP_HEAP_BYTES 421 /* SSP */ #define SOF_TKN_INTEL_SSP_CLKS_CONTROL 500 diff --git a/src/include/sof/audio/buffer.h b/src/include/sof/audio/buffer.h index 9f9f7df672b5..abcb7c6d1b4b 100644 --- a/src/include/sof/audio/buffer.h +++ b/src/include/sof/audio/buffer.h @@ -500,6 +500,22 @@ static inline void buffer_init_stream(struct comp_buffer *buffer, size_t size) audio_stream_init(&buffer->stream, buffer->stream.addr, size); } +#if defined(CONFIG_LIBRARY) && !defined(__XTENSA__) && !defined(CONFIG_XT_SIMULATOR) && \ + !defined(UNIT_TEST) +void comp_buffer_shared_lock(struct comp_buffer *buffer); +void comp_buffer_shared_unlock(struct comp_buffer *buffer); +#else +static inline void comp_buffer_shared_lock(struct comp_buffer *buffer) +{ + (void)buffer; +} + +static inline void comp_buffer_shared_unlock(struct comp_buffer *buffer) +{ + (void)buffer; +} +#endif + /** @}*/ #endif /* __SOF_AUDIO_BUFFER_H__ */ diff --git a/src/include/sof/schedule/dp_schedule.h b/src/include/sof/schedule/dp_schedule.h index 1dac74b600ef..cfcc1b365599 100644 --- a/src/include/sof/schedule/dp_schedule.h +++ b/src/include/sof/schedule/dp_schedule.h @@ -91,6 +91,8 @@ __syscall void scheduler_dp_internal_free(struct task *task); */ __syscall void scheduler_dp_ll_tick(void); #include +#else +void scheduler_dp_ll_tick(void); #endif /** diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index c135f43a4e97..8f6d972d02a4 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -124,10 +124,17 @@ __cold static int ipc4_comp_new_config(struct comp_ipc_config *ipc_config, return -EEXIST; } +#if CONFIG_LIBRARY + if (module_init->extension.r.core_id >= (uint32_t)arch_cpu_num_cores()) { +#else if (module_init->extension.r.core_id >= CONFIG_CORE_COUNT) { +#endif tr_err(&ipc_tr, "ipc: comp->core = %u", (uint32_t)module_init->extension.r.core_id); return -EINVAL; } +#if CONFIG_LIBRARY + cpu_enable_core(module_init->extension.r.core_id); +#endif memset(ipc_config, 0, sizeof(*ipc_config)); ipc_config->id = comp_id; @@ -541,11 +548,18 @@ __cold int ipc_pipeline_new(struct ipc *ipc, ipc_pipe_new *_pipe_desc) tr_dbg(&ipc_tr, "ipc: pipeline id = %u", (uint32_t)pipe_desc->primary.r.instance_id); +#if CONFIG_LIBRARY + if (pipe_desc->extension.r.core_id >= (uint32_t)arch_cpu_num_cores()) { +#else if (pipe_desc->extension.r.core_id >= CONFIG_CORE_COUNT) { +#endif tr_err(&ipc_tr, "invalid pipeline core ID: %u", (uint32_t)pipe_desc->extension.r.core_id); return IPC4_INVALID_CORE_ID; } +#if CONFIG_LIBRARY + cpu_enable_core(pipe_desc->extension.r.core_id); +#endif /* pass IPC to target core */ if (!cpu_is_me(pipe_desc->extension.r.core_id)) @@ -697,7 +711,8 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool * is privileged and so avoided here. */ ipc_buf.comp.core = is_shared ? PLATFORM_PRIMARY_CORE_ID : src->ipc_config.core; - assert(IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || ipc_buf.comp.core == cpu_get_id()); + assert(IS_ENABLED(CONFIG_SOF_USERSPACE_LL) || IS_ENABLED(CONFIG_LIBRARY) || + ipc_buf.comp.core == cpu_get_id()); return buffer_new(alloc, &ipc_buf, is_shared); } @@ -721,6 +736,9 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool #if CONFIG_SOF_USERSPACE_LL #error "CONFIG_SOF_USERSPACE_LL not compatible with cross-core streams" +#elif CONFIG_LIBRARY +#define ll_block(src_core, dst_core, flags) irq_local_disable(flags) +#define ll_unblock(src_core, dst_core, flags) irq_local_enable(flags) #else #define ll_block(src_core, dst_core, flags) \ do { \ diff --git a/src/platform/Kconfig b/src/platform/Kconfig index 46758df1c1dd..19bbef664123 100644 --- a/src/platform/Kconfig +++ b/src/platform/Kconfig @@ -233,6 +233,7 @@ if !ZEPHYR_SOF_MODULE config MP_MAX_NUM_CPUS int + default 8 if LIBRARY default 1 help Maximum number of cores per configuration diff --git a/src/platform/library/include/platform/lib/memory.h b/src/platform/library/include/platform/lib/memory.h index 4fe40783573c..f9b6a9179122 100644 --- a/src/platform/library/include/platform/lib/memory.h +++ b/src/platform/library/include/platform/lib/memory.h @@ -28,7 +28,7 @@ uint8_t *get_library_mailbox(void); #define MAILBOX_BASE get_library_mailbox() -#define PLATFORM_HEAP_SYSTEM 2 +#define PLATFORM_HEAP_SYSTEM CONFIG_CORE_COUNT #define PLATFORM_HEAP_SYSTEM_RUNTIME CONFIG_CORE_COUNT #define PLATFORM_HEAP_RUNTIME 1 #define PLATFORM_HEAP_BUFFER 2 diff --git a/src/platform/library/include/platform/lib/telemetry_sched.h b/src/platform/library/include/platform/lib/telemetry_sched.h new file mode 100644 index 000000000000..60941de80050 --- /dev/null +++ b/src/platform/library/include/platform/lib/telemetry_sched.h @@ -0,0 +1,425 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. All rights reserved. + * + * Per-Core, Per-Pipeline, and Per-Module (LL + DP) Scheduling & Buffer Telemetry + * for SOF Testbench (Host Pthread and Xtensa Simulator). + */ + +#ifndef __PLATFORM_LIB_TELEMETRY_SCHED_H__ +#define __PLATFORM_LIB_TELEMETRY_SCHED_H__ + +#include +#include +#include +#include + +#ifndef CONFIG_CORE_COUNT +#define CONFIG_CORE_COUNT 4 +#endif + +#define TB_TELEM_MAX_CORES CONFIG_CORE_COUNT +#define TB_TELEM_MAX_PIPES 32 +#define TB_TELEM_MAX_MODS 128 +#define TB_TELEM_MAX_PINS 4 +#define TB_TELEM_MAX_OVERRIDES 64 +#define TB_MOD_TAP_SAMPLES 64 +#define TB_TELEM_TRACE_CAPACITY 4096 +#define TB_DSP_NOMINAL_HZ 400000000ULL /* 400 MHz nominal DSP clock */ + +/* Per-iteration module scheduling timeline event type */ +enum host_sched_event_type { + HOST_SCHED_EVT_LL_COPY = 0, /* LL module comp_copy() execution */ + HOST_SCHED_EVT_DP_SYNC = 1, /* DP module LL-tick ring buffer sync */ + HOST_SCHED_EVT_DP_RUN = 2, /* DP worker thread dp_task_run() execution */ +}; + +/* Per-iteration LL/DP module scheduling timeline trace event */ +struct host_sched_trace_event { + uint32_t seq_id; /* 1-based monotonic sequence ID */ + uint32_t ll_tick_nr; /* 1-based 1ms LL tick number */ + uint32_t mod_iter_nr; /* 1-based iteration count for this module */ + uint32_t comp_id; /* IPC4 component ID ((inst << 16) | mod_id) */ + uint16_t pipeline_id; + uint8_t core_id; /* DSP core ID (0..7) */ + uint8_t event_type; /* enum host_sched_event_type */ + uint8_t mem_domain; /* HOST_MEM_DOMAIN_SRAM (0) or DRAM (1) */ + uint8_t cpc_violation; /* 1 if duration_cycles > cpc_budget */ + uint16_t period_us; /* Module scheduling period in us */ + uint64_t start_cycles; /* Virtual DSP cycle count at module start */ + uint64_t complete_cycles; /* Virtual DSP cycle count at module completion */ + uint32_t duration_cycles; /* Execution cycles (complete_cycles - start_cycles) */ + uint32_t cpc_budget; /* Effective CPC budget */ + uint64_t start_ns; /* Virtual DSP timeline start time (ns from t=0) */ + uint64_t complete_ns; /* Virtual DSP timeline completion time (ns from t=0) */ + uint64_t deadline_ns; /* Scheduling deadline timestamp (ns from t=0) */ + uint64_t wall_start_ns; /* Host monotonic start timestamp (ns from run start) */ + uint64_t wall_complete_ns; /* Host monotonic completion timestamp (ns from run start) */ + uint32_t bytes_in; /* Total bytes consumed across input pins */ + uint32_t bytes_out; /* Total bytes produced across output pins */ + int32_t ret_status; /* Return code from module processing */ +}; + +struct pipeline; +struct comp_dev; + +/* Workload execution time simulation mode */ +enum host_workload_sim_mode { + HOST_WORKLOAD_SIM_OFF = 0, /* Measure host CPU thread time */ + HOST_WORKLOAD_SIM_VIRTUAL = 1, /* Simulate cycles & exec time from CPC/freq/mem */ + HOST_WORKLOAD_SIM_REALTIME = 2, /* Simulate cycles & pace wall-clock execution */ +}; + +/* Memory execution domain (SRAM vs DRAM residency) */ +enum host_mem_exec_domain { + HOST_MEM_DOMAIN_SRAM = 0, /* L2 HPSRAM/LPSRAM (1.00x base CPC) */ + HOST_MEM_DOMAIN_DRAM = 1, /* L3/System DRAM (cache/bus stall penalty) */ + HOST_MEM_DOMAIN_AUTO = 0xFF, /* Inherit global/pipeline default */ +}; + +/* Per-module CPC and memory domain override entry */ +struct host_workload_mod_override { + bool active; + uint32_t comp_id; + uint32_t cpc_override; /* 0 = use topology/default CPC */ + uint32_t mem_domain_override; /* HOST_MEM_DOMAIN_SRAM, DRAM, or AUTO */ +}; + +/* Global workload execution time simulation configuration */ +struct host_workload_sim_cfg { + uint32_t mode; /* enum host_workload_sim_mode */ + uint32_t default_mem_domain; /* HOST_MEM_DOMAIN_SRAM (0) or DRAM (1) */ + uint32_t sram_factor_x100; /* Default 100 (1.00x) */ + uint32_t dram_factor_x100; /* Default 160 (1.60x cache/bus stall factor) */ + uint32_t dram_cycles_per_word; /* Default 2 cycles per 32-bit word in DRAM */ + uint32_t core_freq_hz[TB_TELEM_MAX_CORES]; /* Target clock frequency in Hz per core */ + uint32_t num_overrides; + struct host_workload_mod_override overrides[TB_TELEM_MAX_OVERRIDES]; +}; + +/* Per-core LL and DP scheduler telemetry */ +struct host_core_telem { + bool active; + uint32_t freq_hz; /* Active target core clock frequency in Hz */ + uint32_t ll_budget_cycles; /* Available cycles per 1ms LL tick (freq_hz/1000) */ + uint32_t ll_deadline_violations; /* Count of LL ticks exceeding ll_budget_cycles */ + + uint32_t ll_tick_count; + uint32_t ll_active_tasks; + uint64_t ll_last_cycles; + uint64_t ll_avg_cycles; + uint64_t ll_peak_cycles; + uint64_t ll_total_cycles; + uint32_t ll_avg_kcps; + uint32_t ll_peak_kcps; + uint64_t ll_last_exec_ns; /* Execution time in ns at target core freq */ + uint64_t ll_avg_exec_ns; + uint64_t ll_peak_exec_ns; + uint64_t ll_total_exec_ns; + + uint32_t dp_run_count; + uint32_t dp_active_threads; + uint64_t dp_last_cycles; + uint64_t dp_avg_cycles; + uint64_t dp_peak_cycles; + uint64_t dp_total_cycles; + uint32_t dp_avg_kcps; + uint32_t dp_peak_kcps; + uint64_t dp_last_exec_ns; /* Execution time in ns at target core freq */ + uint64_t dp_avg_exec_ns; + uint64_t dp_peak_exec_ns; + uint64_t dp_total_exec_ns; + + uint32_t total_util_x100; /* Combined LL+DP utilization vs freq_hz (0.01% units) */ + + /* Internal scratch accumulators for workload simulation & timeline cursors */ + uint64_t _sim_ll_tick_cycles; + uint64_t _sim_dp_run_cycles; + uint64_t _tick_ll_offset_cycles; + uint64_t _dp_cursor_cycles; +}; + +/* Per-pipeline scheduling telemetry */ +struct host_pipe_telem { + bool active; + uint32_t pipeline_id; + uint32_t comp_id; + uint32_t core_id; + uint32_t priority; + uint32_t period_us; + uint32_t direction; + uint32_t status; + uint32_t mem_domain; /* HOST_MEM_DOMAIN_SRAM (0) or DRAM (1) */ + uint64_t copy_iterations; + uint64_t last_cycles; + uint64_t avg_cycles; + uint64_t peak_cycles; + uint64_t total_cycles; + uint32_t avg_kcps; + uint32_t peak_kcps; + uint64_t last_exec_ns; /* Pipeline execution time in ns at core freq */ + uint64_t avg_exec_ns; + uint64_t peak_exec_ns; + uint64_t total_exec_ns; + uint32_t xrun_count; + uint32_t num_ll_modules; + uint32_t num_dp_modules; + + /* Internal scratch accumulator for workload simulation */ + uint64_t _sim_tick_cycles; +}; + +/* Per-pin buffer telemetry captured on module entry and exit */ +struct host_mod_pin_telem { + bool active; + uint32_t buf_id; + uint32_t peer_comp_id; /* Upstream producer or downstream consumer ID */ + uint32_t buf_size; /* Total primary buffer capacity in bytes */ + uint32_t rate; + uint16_t channels; + uint16_t frame_fmt; + + /* Primary LL comp_buffer levels (bytes) */ + uint32_t entry_avail_bytes; + uint32_t entry_free_bytes; + uint32_t exit_avail_bytes; + uint32_t exit_free_bytes; + + /* Secondary DP ring_buffer levels (bytes, non-zero for DP connections) */ + uint32_t dp_ring_size; + uint32_t dp_ring_entry_avail; + uint32_t dp_ring_exit_avail; + uint32_t dp_ring_entry_free; + uint32_t dp_ring_exit_free; + + /* Bytes consumed (input pin) or produced (output pin) */ + uint32_t last_bytes_transferred; + uint64_t total_bytes_transferred; + uint32_t xrun_count; + + /* Signal level telemetry on entry and exit (0.01 dBFS, e.g. -301 = -3.01 dBFS) */ + int16_t entry_rms_dbfs_x100; + int16_t entry_peak_dbfs_x100; + int16_t exit_rms_dbfs_x100; + int16_t exit_peak_dbfs_x100; + + /* Rolling S16-normalized sample snapshots at module entry and exit */ + uint16_t entry_num_samples; + uint16_t exit_num_samples; + int16_t entry_samples[TB_MOD_TAP_SAMPLES]; + int16_t exit_samples[TB_MOD_TAP_SAMPLES]; + + /* Internal scratch pointers saved at entry for exit delta calculation */ + uintptr_t _entry_ptr; + uint32_t _entry_avail; + uint32_t _dp_entry_woff; +}; + +/* Per-module (LL or DP) scheduling and buffer telemetry */ +struct host_mod_telem { + bool active; + uint32_t comp_id; + uint32_t pipeline_id; + uint32_t core_id; + uint32_t proc_domain; /* COMP_PROCESSING_DOMAIN_LL (0) or DP (1) */ + uint32_t type; /* enum sof_comp_type */ + uint32_t period_us; + uint32_t ibs; + uint32_t obs; + uint32_t cpc; /* Cycles Per Chunk budget from topology */ + uint32_t effective_cpc; /* Effective base CPC (override -> topology -> default) */ + uint32_t mem_domain; /* Effective memory domain: SRAM (0) or DRAM (1) */ + + /* Processing execution telemetry (comp_copy for LL, dp_task_run for DP) */ + uint64_t iterations; + uint64_t last_cycles; + uint64_t avg_cycles; + uint64_t peak_cycles; + uint64_t total_cycles; + uint32_t avg_kcps; + uint32_t peak_kcps; + uint64_t last_exec_ns; /* Execution time in ns at core's target frequency */ + uint64_t avg_exec_ns; + uint64_t peak_exec_ns; + uint64_t total_exec_ns; + uint64_t last_start_cycles; /* Virtual DSP start cycle of most recent iteration */ + uint64_t last_complete_cycles; /* Virtual DSP completion cycle of most recent iteration */ + uint64_t last_start_ns; /* Virtual DSP start time (ns) of most recent iteration */ + uint64_t last_complete_ns; /* Virtual DSP completion time (ns) of most recent iter */ + uint64_t last_deadline_ns; /* Scheduling deadline (ns) of most recent iteration */ + uint64_t last_wall_start_ns; /* Host monotonic start time (ns) of most recent iter */ + uint64_t last_wall_complete_ns; /* Host monotonic completion time (ns) of most recent iter */ + uint32_t cpc_violations; + uint32_t error_count; + + /* Internal entry wall-clock scratch timestamps */ + uint64_t _ll_entry_wall_ns; + uint64_t _dp_entry_wall_ns; + + /* For DP modules: LL tick ring-buffer synchronization stats */ + uint64_t ll_ring_sync_iterations; + uint64_t ll_ring_sync_last_cycles; + uint64_t ll_ring_sync_total_cycles; + + /* Input (producer) and Output (consumer) pin buffer telemetry */ + uint32_t num_in_pins; + uint32_t num_out_pins; + struct host_mod_pin_telem in_pins[TB_TELEM_MAX_PINS]; + struct host_mod_pin_telem out_pins[TB_TELEM_MAX_PINS]; +}; + +/* Hardware platform preset descriptor */ +struct host_platform_preset { + const char *name; + const char *aliases; + const char *desc; + uint32_t hw_ip_version; /* 2=cAVS2.5, 4=ACE1.5, 5=ACE2.0, 6=ACE3.0, 7=ACE4.0 */ + uint32_t num_cores; + uint32_t freq_hz; + size_t hpsram_size_bytes; + size_t lpsram_size_bytes; + size_t dram_size_bytes; +}; + +/* Runtime hardware platform configuration & live memory allocation state */ +struct host_platform_hw_cfg { + char platform_name[32]; + char arch_desc[48]; + uint32_t hw_ip_version; + uint32_t num_cores; /* Active platform core count (1..TB_TELEM_MAX_CORES) */ + uint32_t enabled_cores_mask; /* Bitmask of enabled cores */ + uint32_t default_freq_hz; /* Nominal DSP core clock frequency in Hz */ + size_t hpsram_size_bytes; /* Total L2 HPSRAM capacity in bytes */ + size_t lpsram_size_bytes; /* Total L2 LPSRAM capacity in bytes */ + size_t dram_size_bytes; /* Total L3 IMR/DRAM capacity in bytes */ + uint32_t sram_bank_size; /* SRAM EBB bank size in bytes (65536) */ + bool enforce_mem_limit; /* Reject allocations exceeding configured capacity */ + + /* Live heap allocation tracking across rmalloc/rzalloc/rballoc/sof_heap_alloc */ + size_t hpsram_used_bytes; + size_t hpsram_peak_bytes; + size_t lpsram_used_bytes; + size_t lpsram_peak_bytes; + size_t dram_used_bytes; + size_t dram_peak_bytes; + uint64_t total_alloc_calls; + uint64_t total_free_calls; + uint32_t alloc_failures; +}; + +/* Global testbench scheduling and buffer telemetry state */ +struct host_sched_telemetry { + bool enabled; + uint32_t num_cores; + uint32_t num_pipes; + uint32_t num_mods; + uint32_t current_ll_tick; + uint64_t run_start_wall_ns; + uint32_t trace_head; + uint32_t trace_count; + uint32_t trace_total_events; + struct host_platform_hw_cfg hw_cfg; + struct host_workload_sim_cfg sim_cfg; + struct host_core_telem cores[TB_TELEM_MAX_CORES]; + struct host_pipe_telem pipes[TB_TELEM_MAX_PIPES]; + struct host_mod_telem mods[TB_TELEM_MAX_MODS]; + struct host_sched_trace_event trace_events[TB_TELEM_TRACE_CAPACITY]; +}; + +/* Reset all telemetry counters (preserves workload simulation & platform configuration) */ +void host_telem_reset(void); + +/* Enable or disable telemetry collection (enabled by default) */ +void host_telem_set_enabled(bool enabled); + +/* Retrieve pointer to global telemetry structure */ +struct host_sched_telemetry *host_telem_get(void); + +/* Hardware platform configuration & preset APIs */ +const struct host_platform_preset *host_platform_get_presets(size_t *out_count); +const struct host_platform_preset *host_platform_find_preset(const char *name); +int host_platform_apply_preset(const char *name); +int host_platform_set_core_count(uint32_t num_cores); +uint32_t host_platform_get_core_count(void); +void host_platform_set_memory(size_t hpsram_bytes, size_t lpsram_bytes, + size_t dram_bytes, bool enforce_limit); +size_t host_platform_get_hpsram_size(void); +size_t host_platform_get_lpsram_size(void); +size_t host_platform_get_dram_size(void); +void host_platform_reset_mem_stats(void); +bool host_platform_account_alloc(const void *ptr, uint32_t flags, size_t bytes); +void host_platform_account_free(const void *ptr, size_t bytes); + +/* Host arch_cpu_* runtime backing functions */ +int host_cpu_num_cores(void); +int host_cpu_enable_core(int id); +void host_cpu_disable_core(int id); +int host_cpu_is_core_enabled(int id); +int host_cpu_enabled_cores(void); + +/* Workload execution time simulation configuration APIs */ +void host_workload_sim_set_mode(uint32_t mode); +uint32_t host_workload_sim_get_mode(void); +void host_workload_sim_set_core_freq(int core, uint32_t freq_hz); +uint32_t host_workload_sim_get_core_freq(int core); +void host_workload_sim_set_mem_domain(uint32_t mem_domain, uint32_t dram_factor_x100, + uint32_t dram_cycles_per_word); +int host_workload_sim_set_mod_override(uint32_t comp_id, uint32_t cpc_override, + uint32_t mem_domain_override); +void host_workload_sim_clear_overrides(void); + +/* Read current thread/CPU execution timestamp in nominal DSP cycles */ +uint64_t host_telem_timestamp_cycles(void); + +/* Core LL and DP scheduler hooks */ +void host_telem_ll_tick_begin(void); +void host_telem_core_ll_tick(int core, uint32_t tasks_run, uint64_t cycles); +void host_telem_core_dp_run(int core, uint32_t active_dp_threads, uint64_t cycles); + +/* Per-iteration scheduling timeline trace ring buffer APIs */ +void host_telem_trace_clear(void); +uint32_t host_telem_trace_read(struct host_sched_trace_event *out_events, + uint32_t max_events, uint32_t since_seq_id, + int core_filter, int pipe_filter, + int comp_filter, int domain_filter); + +#ifdef UNIT_TEST +static inline uint64_t host_telem_pipe_begin(struct pipeline *p) +{ + (void)p; + return 0; +} + +static inline void host_telem_pipe_end(struct pipeline *p, uint64_t start_cycles, int ret) +{ + (void)p; + (void)start_cycles; + (void)ret; +} + +static inline uint64_t host_telem_mod_entry(struct comp_dev *dev, bool is_dp_thread) +{ + (void)dev; + (void)is_dp_thread; + return 0; +} + +static inline void host_telem_mod_exit(struct comp_dev *dev, bool is_dp_thread, + uint64_t start_cycles, int ret) +{ + (void)dev; + (void)is_dp_thread; + (void)start_cycles; + (void)ret; +} +#else +/* Pipeline copy entry/exit hooks */ +uint64_t host_telem_pipe_begin(struct pipeline *p); +void host_telem_pipe_end(struct pipeline *p, uint64_t start_cycles, int ret); + +/* Module processing entry/exit hooks (is_dp_thread = false in comp_copy, true in dp_task_run) */ +uint64_t host_telem_mod_entry(struct comp_dev *dev, bool is_dp_thread); +void host_telem_mod_exit(struct comp_dev *dev, bool is_dp_thread, uint64_t start_cycles, int ret); +#endif + +#endif /* __PLATFORM_LIB_TELEMETRY_SCHED_H__ */ diff --git a/src/platform/library/lib/CMakeLists.txt b/src/platform/library/lib/CMakeLists.txt index 05965cd482ee..fa8c4532599c 100644 --- a/src/platform/library/lib/CMakeLists.txt +++ b/src/platform/library/lib/CMakeLists.txt @@ -6,6 +6,7 @@ add_local_sources(sof dai.c pm_runtime.c memory.c + telemetry_sched.c ) if (CONFIG_TRACE) diff --git a/src/platform/library/lib/alloc.c b/src/platform/library/lib/alloc.c index 953267ab5265..ba339ca14865 100644 --- a/src/platform/library/lib/alloc.c +++ b/src/platform/library/lib/alloc.c @@ -13,44 +13,87 @@ #include #include #include +#include + +bool __attribute__((weak)) host_platform_account_alloc(const void *ptr, uint32_t flags, + size_t bytes); +void __attribute__((weak)) host_platform_account_free(const void *ptr, size_t bytes); /* testbench mem alloc definition */ +static void *tb_tracked_alloc(uint32_t flags, size_t bytes, bool zero_init) +{ + void *ptr; + size_t actual; + + if (bytes == 0) + bytes = 1; + + ptr = zero_init ? calloc(bytes, 1) : malloc(bytes); + if (!ptr) + return NULL; + + actual = malloc_usable_size(ptr); + if (actual < bytes) + actual = bytes; + + if (host_platform_account_alloc && !host_platform_account_alloc(ptr, flags, actual)) { + free(ptr); + return NULL; + } + + return ptr; +} + +static void tb_tracked_free(void *ptr) +{ + if (!ptr) + return; + if (host_platform_account_free) + host_platform_account_free(ptr, malloc_usable_size(ptr)); + free(ptr); +} + void *rmalloc_align(uint32_t flags, size_t bytes, uint32_t alignment) { - return malloc(bytes); + (void)alignment; + return tb_tracked_alloc(flags, bytes, false); } void *rmalloc(uint32_t flags, size_t bytes) { - return malloc(bytes); + return tb_tracked_alloc(flags, bytes, false); } void *rzalloc(uint32_t flags, size_t bytes) { - return calloc(bytes, 1); + return tb_tracked_alloc(flags, bytes, true); } void rfree(void *ptr) { - free(ptr); + tb_tracked_free(ptr); } void *rballoc_align(uint32_t flags, size_t bytes, uint32_t alignment) { - return malloc(bytes); + (void)alignment; + return tb_tracked_alloc(flags, bytes, false); } void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, size_t alignment) { - return malloc(bytes); + (void)heap; + (void)alignment; + return tb_tracked_alloc(flags, bytes, false); } void sof_heap_free(struct k_heap *heap, void *addr) { - free(addr); + (void)heap; + tb_tracked_free(addr); } void heap_trace(struct mm_heap *heap, int size) diff --git a/src/platform/library/lib/clk.c b/src/platform/library/lib/clk.c index aa7c86cd2f91..5a9cd20299e3 100644 --- a/src/platform/library/lib/clk.c +++ b/src/platform/library/lib/clk.c @@ -1,18 +1,34 @@ // SPDX-License-Identifier: BSD-3-Clause #include +#include +#include + +uint32_t __attribute__((weak)) host_workload_sim_get_core_freq(int core); + +static uint64_t get_active_core_freq_hz(int clock) +{ + (void)clock; + if (host_workload_sim_get_core_freq) { + uint32_t hz = host_workload_sim_get_core_freq(cpu_get_id()); + + if (hz > 0) + return (uint64_t)hz; + } + return TB_DSP_NOMINAL_HZ; +} uint64_t clock_ms_to_ticks(int clock, uint64_t ms) { - return 0; + return (get_active_core_freq_hz(clock) * ms) / 1000ULL; } uint64_t clock_us_to_ticks(int clock, uint64_t us) { - return 0; + return (get_active_core_freq_hz(clock) * us) / 1000000ULL; } uint64_t clock_ns_to_ticks(int clock, uint64_t ns) { - return 0; + return (get_active_core_freq_hz(clock) * ns) / 1000000000ULL; } diff --git a/src/platform/library/lib/memory.c b/src/platform/library/lib/memory.c index 3f92c62889d7..be83fbb4c757 100644 --- a/src/platform/library/lib/memory.c +++ b/src/platform/library/lib/memory.c @@ -130,14 +130,31 @@ static struct block_map lp_buf_heap_map[] = { static struct mm memmap; +size_t __attribute__((weak)) host_platform_get_hpsram_size(void); +size_t __attribute__((weak)) host_platform_get_lpsram_size(void); + void platform_init_memmap(struct sof *sof) { - uint32_t heap_buffer_size = SOF_FW_END - HEAP_BUFFER_BASE; + uint32_t hp_sram_total = HP_SRAM_SIZE; + uint32_t lp_buffer_size = HEAP_LP_BUFFER_SIZE; + uint32_t heap_buffer_size; uint32_t buffer_count; int i; + if (host_platform_get_hpsram_size && host_platform_get_hpsram_size() >= 0x40000U) + hp_sram_total = (uint32_t)host_platform_get_hpsram_size(); + if (host_platform_get_lpsram_size && host_platform_get_lpsram_size() >= 0x1000U) + lp_buffer_size = (uint32_t)host_platform_get_lpsram_size(); + + if (hp_sram_total > (HEAP_BUFFER_BASE - HP_SRAM_BASE)) + heap_buffer_size = hp_sram_total - (HEAP_BUFFER_BASE - HP_SRAM_BASE); + else + heap_buffer_size = hp_sram_total / 2; + /* calculate the buffer heap size */ buffer_count = heap_buffer_size / HEAP_BUFFER_BLOCK_SIZE; + if (buffer_count > HEAP_BUFFER_COUNT_MAX) + buffer_count = HEAP_BUFFER_COUNT_MAX; heap_buffer_size = buffer_count * HEAP_BUFFER_BLOCK_SIZE; for (i = 0; i < ARRAY_SIZE(buf_heap_map); i++) { @@ -172,10 +189,20 @@ void platform_init_memmap(struct sof *sof) /* .system and .system_runtime secondary core initialization */ for (i = 1; i < CONFIG_CORE_COUNT; i++) { +#if CONFIG_CORE_COUNT > 1 + sys_rt_heap_map[i][0] = (struct block_map) + BLOCK_DEF(64, HEAP_SYS_RT_X_COUNT64, + uncached_block_hdr(sys_rt_x_block64[i - 1])); + sys_rt_heap_map[i][1] = (struct block_map) + BLOCK_DEF(512, HEAP_SYS_RT_X_COUNT512, + uncached_block_hdr(sys_rt_x_block512[i - 1])); + sys_rt_heap_map[i][2] = (struct block_map) + BLOCK_DEF(1024, HEAP_SYS_RT_X_COUNT1024, + uncached_block_hdr(sys_rt_x_block1024[i - 1])); +#endif /* .system init */ sof->memory_map->system[i].heap = - (unsigned long)malloc(HEAP_SYSTEM_S_SIZE) + - ((i - 1) * SOF_CORE_S_SIZE); + (unsigned long)malloc(HEAP_SYSTEM_S_SIZE); sof->memory_map->system[i].size = HEAP_SYSTEM_S_SIZE; sof->memory_map->system[i].info.free = HEAP_SYSTEM_S_SIZE; sof->memory_map->system[i].caps = SOF_MEM_CAPS_RAM | @@ -187,8 +214,7 @@ void platform_init_memmap(struct sof *sof) sof->memory_map->system_runtime[i].map = uncached_block_map(sys_rt_heap_map[i]); sof->memory_map->system_runtime[i].heap = - (unsigned long)malloc(HEAP_SYS_RUNTIME_S_SIZE) + - ((i - 1) * SOF_CORE_S_SIZE) + HEAP_SYSTEM_S_SIZE; + (unsigned long)malloc(HEAP_SYS_RUNTIME_S_SIZE); sof->memory_map->system_runtime[i].size = HEAP_SYS_RUNTIME_S_SIZE; sof->memory_map->system_runtime[i].info.free = @@ -240,15 +266,15 @@ void platform_init_memmap(struct sof *sof) /* heap lp buffer init */ sof->memory_map->buffer[1].blocks = ARRAY_SIZE(lp_buf_heap_map); sof->memory_map->buffer[1].map = uncached_block_map(lp_buf_heap_map); - sof->memory_map->buffer[1].heap = (unsigned long)malloc(HEAP_LP_BUFFER_SIZE); - sof->memory_map->buffer[1].size = HEAP_LP_BUFFER_SIZE; - sof->memory_map->buffer[1].info.free = HEAP_LP_BUFFER_SIZE; + sof->memory_map->buffer[1].heap = (unsigned long)malloc(lp_buffer_size); + sof->memory_map->buffer[1].size = lp_buffer_size; + sof->memory_map->buffer[1].info.free = lp_buffer_size; sof->memory_map->buffer[1].caps = SOF_MEM_CAPS_RAM | SOF_MEM_CAPS_LP | SOF_MEM_CAPS_CACHE | SOF_MEM_CAPS_DMA; /* .total init */ sof->memory_map->total.free = HEAP_SYSTEM_T_SIZE + HEAP_SYS_RUNTIME_T_SIZE + HEAP_RUNTIME_SIZE + heap_buffer_size + - HEAP_LP_BUFFER_SIZE; + lp_buffer_size; } diff --git a/src/platform/library/lib/telemetry_sched.c b/src/platform/library/lib/telemetry_sched.c new file mode 100644 index 000000000000..1aa8471ac5f8 --- /dev/null +++ b/src/platform/library/lib/telemetry_sched.c @@ -0,0 +1,2050 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Per-Core, Per-Pipeline, and Per-Module (LL + DP) Scheduling & Buffer Telemetry +// for SOF Testbench (Host Pthread and Xtensa Simulator). + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if !defined(__XTENSA__) && !defined(CONFIG_XT_SIMULATOR) +static volatile char g_telem_lock; +#define TELEM_LOCK() \ + do { \ + while (__atomic_test_and_set(&g_telem_lock, __ATOMIC_ACQUIRE)) { \ + } \ + } while (0) +#define TELEM_UNLOCK() __atomic_clear(&g_telem_lock, __ATOMIC_RELEASE) +#else +#define TELEM_LOCK() do {} while (0) +#define TELEM_UNLOCK() do {} while (0) +#endif + +static const struct host_platform_preset g_platform_presets[] = { + { + .name = "tgl", + .aliases = "cavs25,adl,rpl,tigerlake,alderlake,raptorlake", + .desc = "Intel Tiger Lake / Alder Lake cAVS 2.5 (Xtensa LX6)", + .hw_ip_version = 2, + .num_cores = 4, + .freq_hz = 400000000U, + .hpsram_size_bytes = 2944UL * 1024UL, /* 46 x 64 KB EBB banks */ + .lpsram_size_bytes = 64UL * 1024UL, /* 1 x 64 KB EBB bank */ + .dram_size_bytes = 16384UL * 1024UL, /* 16 MB IMR */ + }, + { + .name = "tgl-h", + .aliases = "cavs25_tgph,tgph", + .desc = "Intel Tiger Lake-H cAVS 2.5 (Xtensa LX6, 2-Core)", + .hw_ip_version = 2, + .num_cores = 2, + .freq_hz = 400000000U, + .hpsram_size_bytes = 2944UL * 1024UL, + .lpsram_size_bytes = 64UL * 1024UL, + .dram_size_bytes = 16384UL * 1024UL, + }, + { + .name = "mtl", + .aliases = "arl,arls,ace15,ace15_mtpm,meteorlake,arrowlake", + .desc = "Intel Meteor Lake / Arrow Lake ACE 1.5 (Xtensa LX7)", + .hw_ip_version = 4, + .num_cores = 3, + .freq_hz = 393216000U, + .hpsram_size_bytes = 2816UL * 1024UL, /* 44 x 64 KB EBB banks */ + .lpsram_size_bytes = 64UL * 1024UL, /* 1 x 64 KB EBB bank */ + .dram_size_bytes = 16384UL * 1024UL, /* 16 MB IMR */ + }, + { + .name = "lnl", + .aliases = "ace20,ace20_lnl,lunarlake", + .desc = "Intel Lunar Lake ACE 2.0 (Xtensa LX7)", + .hw_ip_version = 5, + .num_cores = 5, + .freq_hz = 393216000U, + .hpsram_size_bytes = 2816UL * 1024UL, /* 44 x 64 KB EBB banks */ + .lpsram_size_bytes = 64UL * 1024UL, /* 1 x 64 KB EBB bank */ + .dram_size_bytes = 16384UL * 1024UL, /* 16 MB IMR */ + }, + { + .name = "ptl", + .aliases = "ace30,ace30_ptl,pantherlake", + .desc = "Intel Panther Lake ACE 3.0 (Xtensa LX7)", + .hw_ip_version = 6, + .num_cores = 5, + .freq_hz = 442368000U, + .hpsram_size_bytes = 4608UL * 1024UL, /* 72 x 64 KB EBB banks */ + .lpsram_size_bytes = 64UL * 1024UL, /* 1 x 64 KB EBB bank */ + .dram_size_bytes = 16384UL * 1024UL, /* 16 MB IMR */ + }, + { + .name = "wcl", + .aliases = "ace30_wcl,wildcatlake", + .desc = "Intel Wildcat Lake ACE 3.0 (Xtensa LX7)", + .hw_ip_version = 6, + .num_cores = 3, + .freq_hz = 614400000U, + .hpsram_size_bytes = 3456UL * 1024UL, /* 54 x 64 KB EBB banks */ + .lpsram_size_bytes = 64UL * 1024UL, /* 1 x 64 KB EBB bank */ + .dram_size_bytes = 16384UL * 1024UL, /* 16 MB IMR */ + }, + { + .name = "nvl", + .aliases = "ace40,ace40_nvl,novalake", + .desc = "Intel Nova Lake ACE 4.0 (Xtensa LX7)", + .hw_ip_version = 7, + .num_cores = 4, + .freq_hz = 614400000U, + .hpsram_size_bytes = 4608UL * 1024UL, /* 72 x 64 KB EBB banks */ + .lpsram_size_bytes = 64UL * 1024UL, /* 1 x 64 KB EBB bank */ + .dram_size_bytes = 16384UL * 1024UL, /* 16 MB IMR */ + }, + { + .name = "nvl-s", + .aliases = "ace40_nvls,nvls", + .desc = "Intel Nova Lake-S ACE 4.0 (Xtensa LX7, 2-Core)", + .hw_ip_version = 7, + .num_cores = 2, + .freq_hz = 438857000U, + .hpsram_size_bytes = 4608UL * 1024UL, /* 72 x 64 KB EBB banks */ + .lpsram_size_bytes = 64UL * 1024UL, /* 1 x 64 KB EBB bank */ + .dram_size_bytes = 16384UL * 1024UL, /* 16 MB IMR */ + }, + { + .name = "default", + .aliases = "host,sof-testbench4,generic", + .desc = "SOF Host Testbench Default (8-Core Virtual DSP)", + .hw_ip_version = 4, + .num_cores = TB_TELEM_MAX_CORES, + .freq_hz = (uint32_t)TB_DSP_NOMINAL_HZ, + .hpsram_size_bytes = 47UL * 65536UL, /* 3008 KB (47 x 64 KB) */ + .lpsram_size_bytes = 64UL * 1024UL, /* 64 KB */ + .dram_size_bytes = 16384UL * 1024UL, /* 16 MB */ + }, +}; + +static struct host_sched_telemetry g_sched_telem = { + .enabled = true, + .num_cores = TB_TELEM_MAX_CORES, + .hw_cfg = { + .platform_name = "sof-testbench4", + .arch_desc = "SOF Host Testbench Default (8-Core Virtual DSP)", + .hw_ip_version = 4, + .num_cores = TB_TELEM_MAX_CORES, + .enabled_cores_mask = (1U << TB_TELEM_MAX_CORES) - 1U, + .default_freq_hz = (uint32_t)TB_DSP_NOMINAL_HZ, + .hpsram_size_bytes = 47UL * 65536UL, + .lpsram_size_bytes = 64UL * 1024UL, + .dram_size_bytes = 16384UL * 1024UL, + .sram_bank_size = 65536U, + .enforce_mem_limit = false, + }, + .sim_cfg = { + .mode = HOST_WORKLOAD_SIM_OFF, + .default_mem_domain = HOST_MEM_DOMAIN_SRAM, + .sram_factor_x100 = 100, + .dram_factor_x100 = 160, + .dram_cycles_per_word = 2, + }, +}; + +static void ensure_sim_cfg_defaults(void) +{ + uint32_t max_mask; + int c; + + if (g_sched_telem.hw_cfg.num_cores == 0 || + g_sched_telem.hw_cfg.num_cores > TB_TELEM_MAX_CORES) + g_sched_telem.hw_cfg.num_cores = TB_TELEM_MAX_CORES; + g_sched_telem.num_cores = g_sched_telem.hw_cfg.num_cores; + + max_mask = (1U << g_sched_telem.hw_cfg.num_cores) - 1U; + if ((g_sched_telem.hw_cfg.enabled_cores_mask & max_mask) == 0) + g_sched_telem.hw_cfg.enabled_cores_mask = max_mask; + else + g_sched_telem.hw_cfg.enabled_cores_mask &= max_mask; + g_sched_telem.hw_cfg.enabled_cores_mask |= 1U; /* Core 0 is always enabled */ + + if (g_sched_telem.hw_cfg.default_freq_hz == 0) + g_sched_telem.hw_cfg.default_freq_hz = (uint32_t)TB_DSP_NOMINAL_HZ; + if (g_sched_telem.hw_cfg.hpsram_size_bytes == 0) + g_sched_telem.hw_cfg.hpsram_size_bytes = 47UL * 65536UL; + if (g_sched_telem.hw_cfg.lpsram_size_bytes == 0) + g_sched_telem.hw_cfg.lpsram_size_bytes = 64UL * 1024UL; + if (g_sched_telem.hw_cfg.dram_size_bytes == 0) + g_sched_telem.hw_cfg.dram_size_bytes = 16384UL * 1024UL; + if (g_sched_telem.hw_cfg.sram_bank_size == 0) + g_sched_telem.hw_cfg.sram_bank_size = 65536U; + if (g_sched_telem.hw_cfg.platform_name[0] == '\0') + snprintf(g_sched_telem.hw_cfg.platform_name, + sizeof(g_sched_telem.hw_cfg.platform_name), "sof-testbench4"); + + if (g_sched_telem.sim_cfg.sram_factor_x100 == 0) + g_sched_telem.sim_cfg.sram_factor_x100 = 100; + if (g_sched_telem.sim_cfg.dram_factor_x100 == 0) + g_sched_telem.sim_cfg.dram_factor_x100 = 160; + if (g_sched_telem.sim_cfg.dram_cycles_per_word == 0) + g_sched_telem.sim_cfg.dram_cycles_per_word = 2; + + for (c = 0; c < TB_TELEM_MAX_CORES; c++) { + if (g_sched_telem.sim_cfg.core_freq_hz[c] == 0) + g_sched_telem.sim_cfg.core_freq_hz[c] = + g_sched_telem.hw_cfg.default_freq_hz; + g_sched_telem.cores[c].freq_hz = g_sched_telem.sim_cfg.core_freq_hz[c]; + g_sched_telem.cores[c].ll_budget_cycles = + g_sched_telem.cores[c].freq_hz / 1000U; + } +} + +void host_telem_reset(void) +{ + struct host_workload_sim_cfg saved_cfg; + struct host_platform_hw_cfg saved_hw; + + TELEM_LOCK(); + ensure_sim_cfg_defaults(); + saved_cfg = g_sched_telem.sim_cfg; + saved_hw = g_sched_telem.hw_cfg; + memset(&g_sched_telem, 0, sizeof(g_sched_telem)); + g_sched_telem.enabled = true; + g_sched_telem.hw_cfg = saved_hw; + g_sched_telem.num_cores = saved_hw.num_cores; + g_sched_telem.sim_cfg = saved_cfg; + ensure_sim_cfg_defaults(); + TELEM_UNLOCK(); +} + +const struct host_platform_preset *host_platform_get_presets(size_t *out_count) +{ + if (out_count) + *out_count = ARRAY_SIZE(g_platform_presets); + return g_platform_presets; +} + +const struct host_platform_preset *host_platform_find_preset(const char *name) +{ + size_t i, nlen; + + if (!name || !*name) + return NULL; + nlen = strlen(name); + + for (i = 0; i < ARRAY_SIZE(g_platform_presets); i++) { + const struct host_platform_preset *p = &g_platform_presets[i]; + const char *cur = p->aliases; + + if (strcasecmp(p->name, name) == 0) + return p; + + while (cur && *cur) { + const char *comma = strchr(cur, ','); + size_t alen = comma ? (size_t)(comma - cur) : strlen(cur); + + if (alen == nlen && strncasecmp(cur, name, nlen) == 0) + return p; + cur = comma ? (comma + 1) : NULL; + } + } + return NULL; +} + +int host_platform_apply_preset(const char *name) +{ + const struct host_platform_preset *p = host_platform_find_preset(name); + int c; + + if (!p) + return -1; + + TELEM_LOCK(); + ensure_sim_cfg_defaults(); + snprintf(g_sched_telem.hw_cfg.platform_name, + sizeof(g_sched_telem.hw_cfg.platform_name), "%s", p->name); + snprintf(g_sched_telem.hw_cfg.arch_desc, + sizeof(g_sched_telem.hw_cfg.arch_desc), "%s", p->desc); + g_sched_telem.hw_cfg.hw_ip_version = p->hw_ip_version; + g_sched_telem.hw_cfg.num_cores = + (p->num_cores <= TB_TELEM_MAX_CORES) ? p->num_cores : TB_TELEM_MAX_CORES; + g_sched_telem.num_cores = g_sched_telem.hw_cfg.num_cores; + g_sched_telem.hw_cfg.enabled_cores_mask = + (1U << g_sched_telem.hw_cfg.num_cores) - 1U; + g_sched_telem.hw_cfg.default_freq_hz = p->freq_hz; + g_sched_telem.hw_cfg.hpsram_size_bytes = p->hpsram_size_bytes; + g_sched_telem.hw_cfg.lpsram_size_bytes = p->lpsram_size_bytes; + g_sched_telem.hw_cfg.dram_size_bytes = p->dram_size_bytes; + g_sched_telem.hw_cfg.sram_bank_size = 65536U; + g_sched_telem.hw_cfg.enforce_mem_limit = + (strcasecmp(p->name, "default") != 0); + + for (c = 0; c < TB_TELEM_MAX_CORES; c++) { + g_sched_telem.sim_cfg.core_freq_hz[c] = p->freq_hz; + g_sched_telem.cores[c].freq_hz = p->freq_hz; + g_sched_telem.cores[c].ll_budget_cycles = p->freq_hz / 1000U; + } + TELEM_UNLOCK(); + return 0; +} + +int host_platform_set_core_count(uint32_t num_cores) +{ + if (num_cores == 0 || num_cores > TB_TELEM_MAX_CORES) + return -1; + + TELEM_LOCK(); + ensure_sim_cfg_defaults(); + g_sched_telem.hw_cfg.num_cores = num_cores; + g_sched_telem.num_cores = num_cores; + g_sched_telem.hw_cfg.enabled_cores_mask = (1U << num_cores) - 1U; + TELEM_UNLOCK(); + return 0; +} + +uint32_t host_platform_get_core_count(void) +{ + ensure_sim_cfg_defaults(); + return g_sched_telem.hw_cfg.num_cores; +} + +void host_platform_set_memory(size_t hpsram_bytes, size_t lpsram_bytes, + size_t dram_bytes, bool enforce_limit) +{ + TELEM_LOCK(); + ensure_sim_cfg_defaults(); + if (hpsram_bytes > 0) + g_sched_telem.hw_cfg.hpsram_size_bytes = hpsram_bytes; + if (lpsram_bytes > 0) + g_sched_telem.hw_cfg.lpsram_size_bytes = lpsram_bytes; + if (dram_bytes > 0) + g_sched_telem.hw_cfg.dram_size_bytes = dram_bytes; + g_sched_telem.hw_cfg.enforce_mem_limit = enforce_limit; + TELEM_UNLOCK(); +} + +size_t host_platform_get_hpsram_size(void) +{ + ensure_sim_cfg_defaults(); + return g_sched_telem.hw_cfg.hpsram_size_bytes; +} + +size_t host_platform_get_lpsram_size(void) +{ + ensure_sim_cfg_defaults(); + return g_sched_telem.hw_cfg.lpsram_size_bytes; +} + +size_t host_platform_get_dram_size(void) +{ + ensure_sim_cfg_defaults(); + return g_sched_telem.hw_cfg.dram_size_bytes; +} + +#define TB_ALLOC_TRACK_SLOTS 8192U + +enum host_alloc_pool { + HOST_ALLOC_POOL_NONE = 0, + HOST_ALLOC_POOL_HPSRAM = 1, + HOST_ALLOC_POOL_LPSRAM = 2, + HOST_ALLOC_POOL_DRAM = 3, +}; + +struct host_alloc_record { + const void *ptr; + size_t bytes; + uint8_t pool; +}; + +static struct host_alloc_record g_alloc_table[TB_ALLOC_TRACK_SLOTS]; + +static void record_tracked_alloc_locked(const void *ptr, size_t bytes, uint8_t pool) +{ + uint32_t h, i; + + if (!ptr || pool == HOST_ALLOC_POOL_NONE) + return; + + h = (uint32_t)(((uintptr_t)ptr >> 4) ^ ((uintptr_t)ptr >> 16)); + for (i = 0; i < TB_ALLOC_TRACK_SLOTS; i++) { + uint32_t idx = (h + i) & (TB_ALLOC_TRACK_SLOTS - 1U); + + if (!g_alloc_table[idx].ptr || g_alloc_table[idx].ptr == ptr) { + g_alloc_table[idx].ptr = ptr; + g_alloc_table[idx].bytes = bytes; + g_alloc_table[idx].pool = pool; + return; + } + } +} + +static bool pop_tracked_alloc_locked(const void *ptr, size_t *out_bytes, uint8_t *out_pool) +{ + uint32_t h, i; + + if (!ptr) + return false; + + h = (uint32_t)(((uintptr_t)ptr >> 4) ^ ((uintptr_t)ptr >> 16)); + for (i = 0; i < TB_ALLOC_TRACK_SLOTS; i++) { + uint32_t idx = (h + i) & (TB_ALLOC_TRACK_SLOTS - 1U); + + if (g_alloc_table[idx].ptr == ptr) { + *out_bytes = g_alloc_table[idx].bytes; + *out_pool = g_alloc_table[idx].pool; + g_alloc_table[idx].ptr = NULL; + g_alloc_table[idx].bytes = 0; + g_alloc_table[idx].pool = HOST_ALLOC_POOL_NONE; + return true; + } + } + return false; +} + +void host_platform_reset_mem_stats(void) +{ + TELEM_LOCK(); + g_sched_telem.hw_cfg.hpsram_used_bytes = 0; + g_sched_telem.hw_cfg.hpsram_peak_bytes = 0; + g_sched_telem.hw_cfg.lpsram_used_bytes = 0; + g_sched_telem.hw_cfg.lpsram_peak_bytes = 0; + g_sched_telem.hw_cfg.dram_used_bytes = 0; + g_sched_telem.hw_cfg.dram_peak_bytes = 0; + g_sched_telem.hw_cfg.total_alloc_calls = 0; + g_sched_telem.hw_cfg.total_free_calls = 0; + g_sched_telem.hw_cfg.alloc_failures = 0; + memset(g_alloc_table, 0, sizeof(g_alloc_table)); + TELEM_UNLOCK(); +} + +bool host_platform_account_alloc(const void *ptr, uint32_t flags, size_t bytes) +{ + uint8_t pool = HOST_ALLOC_POOL_NONE; + bool ok = true; + + TELEM_LOCK(); + ensure_sim_cfg_defaults(); + + if ((flags & SOF_MEM_FLAG_LOW_POWER) != 0) { + if (g_sched_telem.hw_cfg.enforce_mem_limit && + g_sched_telem.hw_cfg.lpsram_used_bytes + bytes > + g_sched_telem.hw_cfg.lpsram_size_bytes) { + g_sched_telem.hw_cfg.alloc_failures++; + ok = false; + } else { + g_sched_telem.hw_cfg.lpsram_used_bytes += bytes; + if (g_sched_telem.hw_cfg.lpsram_used_bytes > + g_sched_telem.hw_cfg.lpsram_peak_bytes) + g_sched_telem.hw_cfg.lpsram_peak_bytes = + g_sched_telem.hw_cfg.lpsram_used_bytes; + g_sched_telem.hw_cfg.total_alloc_calls++; + pool = HOST_ALLOC_POOL_LPSRAM; + } + } else if ((flags & SOF_MEM_FLAG_L3) != 0 || + g_sched_telem.sim_cfg.default_mem_domain == HOST_MEM_DOMAIN_DRAM) { + if (g_sched_telem.hw_cfg.enforce_mem_limit && + g_sched_telem.hw_cfg.dram_used_bytes + bytes > + g_sched_telem.hw_cfg.dram_size_bytes) { + g_sched_telem.hw_cfg.alloc_failures++; + ok = false; + } else { + g_sched_telem.hw_cfg.dram_used_bytes += bytes; + if (g_sched_telem.hw_cfg.dram_used_bytes > + g_sched_telem.hw_cfg.dram_peak_bytes) + g_sched_telem.hw_cfg.dram_peak_bytes = + g_sched_telem.hw_cfg.dram_used_bytes; + /* Also mirror in HPSRAM peak if running from SRAM/DRAM unified pool */ + g_sched_telem.hw_cfg.hpsram_used_bytes += bytes; + if (g_sched_telem.hw_cfg.hpsram_used_bytes > + g_sched_telem.hw_cfg.hpsram_peak_bytes) + g_sched_telem.hw_cfg.hpsram_peak_bytes = + g_sched_telem.hw_cfg.hpsram_used_bytes; + g_sched_telem.hw_cfg.total_alloc_calls++; + pool = HOST_ALLOC_POOL_DRAM; + } + } else { + if (g_sched_telem.hw_cfg.enforce_mem_limit && + g_sched_telem.hw_cfg.hpsram_used_bytes + bytes > + g_sched_telem.hw_cfg.hpsram_size_bytes) { + g_sched_telem.hw_cfg.alloc_failures++; + ok = false; + } else { + g_sched_telem.hw_cfg.hpsram_used_bytes += bytes; + if (g_sched_telem.hw_cfg.hpsram_used_bytes > + g_sched_telem.hw_cfg.hpsram_peak_bytes) + g_sched_telem.hw_cfg.hpsram_peak_bytes = + g_sched_telem.hw_cfg.hpsram_used_bytes; + g_sched_telem.hw_cfg.total_alloc_calls++; + pool = HOST_ALLOC_POOL_HPSRAM; + } + } + + if (ok) + record_tracked_alloc_locked(ptr, bytes, pool); + TELEM_UNLOCK(); + return ok; +} + +void host_platform_account_free(const void *ptr, size_t bytes) +{ + size_t tracked_bytes = bytes; + uint8_t pool = HOST_ALLOC_POOL_HPSRAM; + + TELEM_LOCK(); + (void)pop_tracked_alloc_locked(ptr, &tracked_bytes, &pool); + + if (pool == HOST_ALLOC_POOL_LPSRAM) { + if (g_sched_telem.hw_cfg.lpsram_used_bytes >= tracked_bytes) + g_sched_telem.hw_cfg.lpsram_used_bytes -= tracked_bytes; + else + g_sched_telem.hw_cfg.lpsram_used_bytes = 0; + } else if (pool == HOST_ALLOC_POOL_DRAM) { + if (g_sched_telem.hw_cfg.dram_used_bytes >= tracked_bytes) + g_sched_telem.hw_cfg.dram_used_bytes -= tracked_bytes; + else + g_sched_telem.hw_cfg.dram_used_bytes = 0; + + if (g_sched_telem.hw_cfg.hpsram_used_bytes >= tracked_bytes) + g_sched_telem.hw_cfg.hpsram_used_bytes -= tracked_bytes; + else + g_sched_telem.hw_cfg.hpsram_used_bytes = 0; + } else { + if (g_sched_telem.hw_cfg.hpsram_used_bytes >= tracked_bytes) + g_sched_telem.hw_cfg.hpsram_used_bytes -= tracked_bytes; + else + g_sched_telem.hw_cfg.hpsram_used_bytes = 0; + } + + g_sched_telem.hw_cfg.total_free_calls++; + TELEM_UNLOCK(); +} + +int host_cpu_num_cores(void) +{ + ensure_sim_cfg_defaults(); + return (int)g_sched_telem.hw_cfg.num_cores; +} + +int host_cpu_enable_core(int id) +{ + ensure_sim_cfg_defaults(); + if (id < 0 || (uint32_t)id >= g_sched_telem.hw_cfg.num_cores) + return -1; + g_sched_telem.hw_cfg.enabled_cores_mask |= (1U << id); + return 0; +} + +void host_cpu_disable_core(int id) +{ + ensure_sim_cfg_defaults(); + if (id <= 0 || (uint32_t)id >= g_sched_telem.hw_cfg.num_cores) + return; + g_sched_telem.hw_cfg.enabled_cores_mask &= ~(1U << id); +} + +int host_cpu_is_core_enabled(int id) +{ + ensure_sim_cfg_defaults(); + if (id < 0 || (uint32_t)id >= g_sched_telem.hw_cfg.num_cores) + return 0; + return (g_sched_telem.hw_cfg.enabled_cores_mask & (1U << id)) ? 1 : 0; +} + +int host_cpu_enabled_cores(void) +{ + ensure_sim_cfg_defaults(); + return (int)g_sched_telem.hw_cfg.enabled_cores_mask; +} + +void host_telem_set_enabled(bool enabled) +{ + g_sched_telem.enabled = enabled; +} + +struct host_sched_telemetry *host_telem_get(void) +{ + ensure_sim_cfg_defaults(); + return &g_sched_telem; +} + +void host_workload_sim_set_mode(uint32_t mode) +{ + TELEM_LOCK(); + ensure_sim_cfg_defaults(); + if (mode > HOST_WORKLOAD_SIM_REALTIME) + mode = HOST_WORKLOAD_SIM_REALTIME; + g_sched_telem.sim_cfg.mode = mode; + TELEM_UNLOCK(); +} + +uint32_t host_workload_sim_get_mode(void) +{ + ensure_sim_cfg_defaults(); + return g_sched_telem.sim_cfg.mode; +} + +void host_workload_sim_set_core_freq(int core, uint32_t freq_hz) +{ + int c; + + if (freq_hz < 1000000U) + freq_hz = (uint32_t)TB_DSP_NOMINAL_HZ; + + TELEM_LOCK(); + ensure_sim_cfg_defaults(); + if (core < 0) { + g_sched_telem.hw_cfg.default_freq_hz = freq_hz; + for (c = 0; c < TB_TELEM_MAX_CORES; c++) { + g_sched_telem.sim_cfg.core_freq_hz[c] = freq_hz; + g_sched_telem.cores[c].freq_hz = freq_hz; + g_sched_telem.cores[c].ll_budget_cycles = freq_hz / 1000U; + } + } else if (core < TB_TELEM_MAX_CORES) { + if (core == 0) + g_sched_telem.hw_cfg.default_freq_hz = freq_hz; + g_sched_telem.sim_cfg.core_freq_hz[core] = freq_hz; + g_sched_telem.cores[core].freq_hz = freq_hz; + g_sched_telem.cores[core].ll_budget_cycles = freq_hz / 1000U; + } + TELEM_UNLOCK(); +} + +uint32_t host_workload_sim_get_core_freq(int core) +{ + ensure_sim_cfg_defaults(); + if (core < 0 || core >= TB_TELEM_MAX_CORES) + core = 0; + return g_sched_telem.sim_cfg.core_freq_hz[core]; +} + +void host_workload_sim_set_mem_domain(uint32_t mem_domain, uint32_t dram_factor_x100, + uint32_t dram_cycles_per_word) +{ + TELEM_LOCK(); + ensure_sim_cfg_defaults(); + g_sched_telem.sim_cfg.default_mem_domain = + (mem_domain == HOST_MEM_DOMAIN_DRAM) ? HOST_MEM_DOMAIN_DRAM : HOST_MEM_DOMAIN_SRAM; + if (dram_factor_x100 >= 100U && dram_factor_x100 <= 1000U) + g_sched_telem.sim_cfg.dram_factor_x100 = dram_factor_x100; + if (dram_cycles_per_word <= 64U) + g_sched_telem.sim_cfg.dram_cycles_per_word = dram_cycles_per_word; + TELEM_UNLOCK(); +} + +int host_workload_sim_set_mod_override(uint32_t comp_id, uint32_t cpc_override, + uint32_t mem_domain_override) +{ + uint32_t i; + int rc = -1; + + TELEM_LOCK(); + ensure_sim_cfg_defaults(); + for (i = 0; i < g_sched_telem.sim_cfg.num_overrides; i++) { + if (g_sched_telem.sim_cfg.overrides[i].active && + g_sched_telem.sim_cfg.overrides[i].comp_id == comp_id) { + g_sched_telem.sim_cfg.overrides[i].cpc_override = cpc_override; + g_sched_telem.sim_cfg.overrides[i].mem_domain_override = + mem_domain_override; + rc = 0; + break; + } + } + if (rc < 0 && g_sched_telem.sim_cfg.num_overrides < TB_TELEM_MAX_OVERRIDES) { + struct host_workload_mod_override *ov = + &g_sched_telem.sim_cfg.overrides[g_sched_telem.sim_cfg.num_overrides++]; + ov->active = true; + ov->comp_id = comp_id; + ov->cpc_override = cpc_override; + ov->mem_domain_override = mem_domain_override; + rc = 0; + } + TELEM_UNLOCK(); + return rc; +} + +void host_workload_sim_clear_overrides(void) +{ + TELEM_LOCK(); + g_sched_telem.sim_cfg.num_overrides = 0; + memset(g_sched_telem.sim_cfg.overrides, 0, sizeof(g_sched_telem.sim_cfg.overrides)); + TELEM_UNLOCK(); +} + +static uint32_t get_core_freq_hz(uint32_t core) +{ + ensure_sim_cfg_defaults(); + if (core >= TB_TELEM_MAX_CORES) + core = 0; + return g_sched_telem.sim_cfg.core_freq_hz[core]; +} + +static uint64_t cycles_to_ns(uint64_t cycles, uint32_t freq_hz) +{ + uint64_t f_khz = (freq_hz >= 1000U) ? (freq_hz / 1000U) : (TB_DSP_NOMINAL_HZ / 1000ULL); + + return (cycles * 1000000ULL) / f_khz; +} + +static void pace_execution_ns(uint64_t delay_ns) +{ +#if !defined(__XTENSA__) && !defined(CONFIG_XT_SIMULATOR) + struct timespec t0, now, req; + uint64_t start_ns, target_ns, cur_ns; + + if (delay_ns == 0) + return; + if (delay_ns > 50000000ULL) + delay_ns = 50000000ULL; /* Cap single-step sleep at 50 ms */ + + if (clock_gettime(CLOCK_MONOTONIC, &t0) != 0) + return; + start_ns = (uint64_t)t0.tv_sec * 1000000000ULL + (uint64_t)t0.tv_nsec; + target_ns = start_ns + delay_ns; + + if (delay_ns >= 80000ULL) { + uint64_t sleep_ns = delay_ns - 40000ULL; + + req.tv_sec = (time_t)(sleep_ns / 1000000000ULL); + req.tv_nsec = (long)(sleep_ns % 1000000000ULL); + nanosleep(&req, NULL); + } + + do { + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) + break; + cur_ns = (uint64_t)now.tv_sec * 1000000000ULL + (uint64_t)now.tv_nsec; + } while (cur_ns < target_ns); +#else + (void)delay_ns; +#endif +} + +uint64_t host_telem_timestamp_cycles(void) +{ +#if defined(__XTENSA__) + uint32_t ccount = 0; + + __asm__ __volatile__("rsr.ccount %0" : "=a"(ccount)); + return (uint64_t)ccount; +#elif defined(CONFIG_XT_SIMULATOR) + clock_t c = clock(); + + return (uint64_t)c * ((TB_DSP_NOMINAL_HZ / (uint64_t)CLOCKS_PER_SEC)); +#else + struct timespec ts; + uint64_t ns; + + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) != 0) + clock_gettime(CLOCK_MONOTONIC, &ts); + + ns = (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; + /* Convert nanoseconds to nominal 400 MHz DSP cycles (0.4 cycles/ns) */ + return (ns * (TB_DSP_NOMINAL_HZ / 1000000ULL)) / 1000ULL; +#endif +} + +static uint64_t host_telem_wall_ns(void) +{ +#if !defined(__XTENSA__) && !defined(CONFIG_XT_SIMULATOR) + struct timespec ts; + uint64_t now_ns; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + return 0; + now_ns = (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; + if (g_sched_telem.run_start_wall_ns == 0) + g_sched_telem.run_start_wall_ns = now_ns; + return (now_ns >= g_sched_telem.run_start_wall_ns) ? + (now_ns - g_sched_telem.run_start_wall_ns) : 0; +#else + return 0; +#endif +} + +void host_telem_ll_tick_begin(void) +{ + int c; + + if (!g_sched_telem.enabled) + return; + + TELEM_LOCK(); + if (g_sched_telem.run_start_wall_ns == 0) + (void)host_telem_wall_ns(); + g_sched_telem.current_ll_tick++; + for (c = 0; c < TB_TELEM_MAX_CORES; c++) + g_sched_telem.cores[c]._tick_ll_offset_cycles = 0; + TELEM_UNLOCK(); +} + +void host_telem_trace_clear(void) +{ + TELEM_LOCK(); + g_sched_telem.trace_head = 0; + g_sched_telem.trace_count = 0; + g_sched_telem.trace_total_events = 0; + memset(g_sched_telem.trace_events, 0, sizeof(g_sched_telem.trace_events)); + TELEM_UNLOCK(); +} + +uint32_t host_telem_trace_read(struct host_sched_trace_event *out_events, + uint32_t max_events, uint32_t since_seq_id, + int core_filter, int pipe_filter, + int comp_filter, int domain_filter) +{ + uint32_t count, start_idx, i, copied = 0; + + if (!out_events || max_events == 0) + return 0; + + TELEM_LOCK(); + count = g_sched_telem.trace_count; + if (count > TB_TELEM_TRACE_CAPACITY) + count = TB_TELEM_TRACE_CAPACITY; + start_idx = (g_sched_telem.trace_head + TB_TELEM_TRACE_CAPACITY - count) % + TB_TELEM_TRACE_CAPACITY; + + for (i = 0; i < count && copied < max_events; i++) { + const struct host_sched_trace_event *ev = + &g_sched_telem.trace_events[(start_idx + i) % TB_TELEM_TRACE_CAPACITY]; + + if (since_seq_id > 0 && ev->seq_id <= since_seq_id) + continue; + if (core_filter >= 0 && ev->core_id != (uint8_t)core_filter) + continue; + if (pipe_filter >= 0 && ev->pipeline_id != (uint16_t)pipe_filter) + continue; + if (comp_filter >= 0 && ev->comp_id != (uint32_t)comp_filter) + continue; + if (domain_filter >= 0 && ev->event_type != (uint8_t)domain_filter) + continue; + + out_events[copied++] = *ev; + } + TELEM_UNLOCK(); + + return copied; +} + +static void record_mod_sched_event(struct host_mod_telem *m, uint32_t core_id, + uint32_t freq_hz, uint8_t event_type, + uint32_t mod_iter_nr, uint64_t delta_cycles, + uint32_t cpc_budget, uint32_t bytes_in, + uint32_t bytes_out, uint64_t wall_start_ns, + uint64_t wall_complete_ns, int ret_status) +{ + struct host_core_telem *c; + struct host_sched_trace_event *ev; + uint32_t tick_nr; + uint64_t tick_budget_cyc, tick_base_cyc, tick_base_ns; + uint64_t virt_start_cyc, virt_complete_cyc, virt_start_ns, virt_complete_ns, deadline_ns; + uint64_t exec_ns; + + if (core_id >= TB_TELEM_MAX_CORES) + core_id = 0; + + TELEM_LOCK(); + c = &g_sched_telem.cores[core_id]; + tick_nr = g_sched_telem.current_ll_tick ? g_sched_telem.current_ll_tick : 1U; + tick_budget_cyc = (freq_hz >= 1000U) ? (freq_hz / 1000U) : 400000ULL; + tick_base_cyc = (uint64_t)(tick_nr - 1U) * tick_budget_cyc; + tick_base_ns = (uint64_t)(tick_nr - 1U) * 1000000ULL; + exec_ns = cycles_to_ns(delta_cycles, freq_hz); + + if (event_type != HOST_SCHED_EVT_DP_RUN) { + virt_start_cyc = tick_base_cyc + c->_tick_ll_offset_cycles; + virt_complete_cyc = virt_start_cyc + delta_cycles; + c->_tick_ll_offset_cycles += delta_cycles; + virt_start_ns = tick_base_ns + + cycles_to_ns(virt_start_cyc - tick_base_cyc, freq_hz); + virt_complete_ns = virt_start_ns + exec_ns; + deadline_ns = (uint64_t)tick_nr * 1000000ULL; + } else { + uint64_t ll_end_cyc = tick_base_cyc + c->_tick_ll_offset_cycles; + uint64_t dp_period_ns = (uint64_t)(m->period_us ? m->period_us : 1000U) * 1000ULL; + + if (c->_dp_cursor_cycles < ll_end_cyc) + c->_dp_cursor_cycles = ll_end_cyc; + virt_start_cyc = c->_dp_cursor_cycles; + virt_complete_cyc = virt_start_cyc + delta_cycles; + c->_dp_cursor_cycles = virt_complete_cyc; + virt_start_ns = cycles_to_ns(virt_start_cyc, freq_hz); + virt_complete_ns = virt_start_ns + exec_ns; + deadline_ns = tick_base_ns + dp_period_ns; + } + + if (event_type != HOST_SCHED_EVT_DP_SYNC) { + m->last_start_cycles = virt_start_cyc; + m->last_complete_cycles = virt_complete_cyc; + m->last_start_ns = virt_start_ns; + m->last_complete_ns = virt_complete_ns; + m->last_deadline_ns = deadline_ns; + m->last_wall_start_ns = wall_start_ns; + m->last_wall_complete_ns = wall_complete_ns; + } + + ev = &g_sched_telem.trace_events[g_sched_telem.trace_head]; + ev->seq_id = ++g_sched_telem.trace_total_events; + ev->ll_tick_nr = tick_nr; + ev->mod_iter_nr = mod_iter_nr; + ev->comp_id = m->comp_id; + ev->pipeline_id = (uint16_t)m->pipeline_id; + ev->core_id = (uint8_t)core_id; + ev->event_type = event_type; + ev->mem_domain = (uint8_t)m->mem_domain; + ev->cpc_violation = (cpc_budget > 0 && delta_cycles > cpc_budget) ? 1U : 0U; + ev->period_us = (uint16_t)(m->period_us ? m->period_us : 1000U); + ev->start_cycles = virt_start_cyc; + ev->complete_cycles = virt_complete_cyc; + ev->duration_cycles = (uint32_t)delta_cycles; + ev->cpc_budget = cpc_budget; + ev->start_ns = virt_start_ns; + ev->complete_ns = virt_complete_ns; + ev->deadline_ns = deadline_ns; + ev->wall_start_ns = wall_start_ns; + ev->wall_complete_ns = wall_complete_ns; + ev->bytes_in = bytes_in; + ev->bytes_out = bytes_out; + ev->ret_status = ret_status; + + g_sched_telem.trace_head = (g_sched_telem.trace_head + 1U) % TB_TELEM_TRACE_CAPACITY; + if (g_sched_telem.trace_count < TB_TELEM_TRACE_CAPACITY) + g_sched_telem.trace_count++; + + TELEM_UNLOCK(); +} + +/* + * Fast integer log2 in Q8 fixed-point (256 * log2(val)) for val > 0. + * Zero floating-point or libm dependency for Xtensa simulator compatibility. + */ +static int32_t fast_log2_q8(uint64_t val) +{ + int k = 0; + uint64_t tmp = val; + uint32_t m, frac_q8; + + if (val == 0) + return 0; + + while (tmp >>= 1) + k++; + + if (k >= 8) + m = (uint32_t)((val >> (k - 8)) & 0xFFU); + else + m = (uint32_t)((val << (8 - k)) & 0xFFU); + + /* Polynomial approximation log2(1 + f) ~= f + f*(1-f)*3/8 for f = m/256 */ + frac_q8 = m + ((m * (256U - m)) >> 9) + ((m * (256U - m)) >> 10); + return (k << 8) + (int32_t)frac_q8; +} + +/* Convert 16-bit peak amplitude [0..32768] to 0.01 dBFS [-12000..0] */ +static int16_t peak_s16_to_dbfs_x100(uint32_t peak) +{ + int32_t log2_q8, db_x100; + + if (peak == 0) + return -12000; + if (peak >= 32768U) + return 0; + + /* 20 * 100 * log10(2) ~= 602.06 */ + log2_q8 = fast_log2_q8(peak) - (15 << 8); + db_x100 = (log2_q8 * 602) / 256; + if (db_x100 < -12000) + db_x100 = -12000; + if (db_x100 > 0) + db_x100 = 0; + return (int16_t)db_x100; +} + +/* Convert 16-bit mean square [0..2^30] to 0.01 dBFS [-12000..0] */ +static int16_t ms_s16_to_dbfs_x100(uint64_t mean_sq) +{ + int32_t log2_q8, db_x100; + + if (mean_sq == 0) + return -12000; + if (mean_sq >= (1ULL << 30)) + return 0; + + /* 10 * 100 * log10(2) ~= 301.03 */ + log2_q8 = fast_log2_q8(mean_sq) - (30 << 8); + db_x100 = (log2_q8 * 301) / 256; + if (db_x100 < -12000) + db_x100 = -12000; + if (db_x100 > 0) + db_x100 = 0; + return (int16_t)db_x100; +} + +/* + * Capture up to TB_MOD_TAP_SAMPLES normalized S16 samples from a circular buffer + * and compute RMS and Peak in 0.01 dBFS. + */ +static void capture_circular_samples(const void *base_addr, size_t buf_size, + size_t start_offset, size_t avail_bytes, + uint16_t frame_fmt, + int16_t *out_samples, uint16_t *out_num_samples, + int16_t *out_rms_dbfs_x100, + int16_t *out_peak_dbfs_x100) +{ + const uint8_t *buf = (const uint8_t *)base_addr; + size_t bps = 2; + size_t n_avail, n_tap, i; + uint32_t peak = 0; + uint64_t sum_sq = 0; + + if (!buf || buf_size == 0 || avail_bytes == 0) { + *out_num_samples = 0; + *out_rms_dbfs_x100 = -12000; + *out_peak_dbfs_x100 = -12000; + return; + } + + switch (frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + bps = 2; + break; + case SOF_IPC_FRAME_S24_4LE: + case SOF_IPC_FRAME_S32_LE: + case SOF_IPC_FRAME_FLOAT: + bps = 4; + break; + case SOF_IPC_FRAME_S24_3LE: + bps = 3; + break; + default: + bps = 2; + break; + } + + if (avail_bytes > buf_size) + avail_bytes = buf_size; + + n_avail = avail_bytes / bps; + n_tap = (n_avail < TB_MOD_TAP_SAMPLES) ? n_avail : TB_MOD_TAP_SAMPLES; + if (n_tap == 0) { + *out_num_samples = 0; + *out_rms_dbfs_x100 = -12000; + *out_peak_dbfs_x100 = -12000; + return; + } + + for (i = 0; i < n_tap; i++) { + size_t off = (start_offset + i * bps) % buf_size; + int16_t s = 0; + uint32_t abs_s; + + if (bps == 2 && off + 2 <= buf_size) { + s = (int16_t)((uint16_t)buf[off] | ((uint16_t)buf[off + 1] << 8)); + } else if (bps == 4 && off + 4 <= buf_size) { + int32_t v = (int32_t)((uint32_t)buf[off] | + ((uint32_t)buf[off + 1] << 8) | + ((uint32_t)buf[off + 2] << 16) | + ((uint32_t)buf[off + 3] << 24)); + if (frame_fmt == SOF_IPC_FRAME_S24_4LE) { + v = (v << 8) >> 8; + s = (int16_t)(v >> 8); + } else { + s = (int16_t)(v >> 16); + } + } else if (bps == 3 && off + 3 <= buf_size) { + int32_t v = (int32_t)buf[off] | + ((int32_t)buf[off + 1] << 8) | + ((int32_t)(int8_t)buf[off + 2] << 16); + s = (int16_t)(v >> 8); + } + + out_samples[i] = s; + abs_s = (s < 0) ? (uint32_t)(-(int32_t)s) : (uint32_t)s; + if (abs_s > peak) + peak = abs_s; + sum_sq += (uint64_t)abs_s * (uint64_t)abs_s; + } + + *out_num_samples = (uint16_t)n_tap; + *out_peak_dbfs_x100 = peak_s16_to_dbfs_x100(peak); + *out_rms_dbfs_x100 = ms_s16_to_dbfs_x100(sum_sq / n_tap); +} + +static void update_core_util(int core, struct host_core_telem *c) +{ + uint32_t freq_hz = get_core_freq_hz((uint32_t)core); + uint64_t total_kcps = (uint64_t)c->ll_avg_kcps + (uint64_t)c->dp_avg_kcps; + uint64_t max_kcps = freq_hz / 1000ULL; + + c->freq_hz = freq_hz; + c->ll_budget_cycles = freq_hz / 1000U; + + if (max_kcps > 0) { + uint64_t u = (total_kcps * 10000ULL) / max_kcps; + + if (u == 0 && (c->ll_tick_count > 0 || c->dp_run_count > 0)) + u = 1; + if (u > 10000ULL) + u = 10000ULL; + c->total_util_x100 = (uint32_t)u; + } +} + +void host_telem_core_ll_tick(int core, uint32_t tasks_run, uint64_t cycles) +{ + struct host_core_telem *c; + uint32_t freq_hz; + uint64_t exec_ns; + + if (!g_sched_telem.enabled || core < 0 || core >= TB_TELEM_MAX_CORES) + return; + if (tasks_run == 0) + return; + + TELEM_LOCK(); + c = &g_sched_telem.cores[core]; + if (g_sched_telem.sim_cfg.mode != HOST_WORKLOAD_SIM_OFF && c->_sim_ll_tick_cycles > 0) { + cycles = c->_sim_ll_tick_cycles; + c->_sim_ll_tick_cycles = 0; + } + + if (cycles == 0) + cycles = 1; + + freq_hz = get_core_freq_hz((uint32_t)core); + exec_ns = cycles_to_ns(cycles, freq_hz); + + c->active = true; + c->freq_hz = freq_hz; + c->ll_budget_cycles = freq_hz / 1000U; + c->ll_tick_count++; + c->ll_active_tasks = tasks_run; + c->ll_last_cycles = cycles; + c->ll_total_cycles += cycles; + c->ll_avg_cycles = c->ll_total_cycles / c->ll_tick_count; + if (cycles > c->ll_peak_cycles) + c->ll_peak_cycles = cycles; + + c->ll_last_exec_ns = exec_ns; + c->ll_total_exec_ns += exec_ns; + c->ll_avg_exec_ns = c->ll_total_exec_ns / c->ll_tick_count; + if (exec_ns > c->ll_peak_exec_ns) + c->ll_peak_exec_ns = exec_ns; + + if (c->ll_budget_cycles > 0 && cycles > c->ll_budget_cycles) + c->ll_deadline_violations++; + + /* Assume 1 ms nominal LL tick period -> 1000 ticks/sec -> KCPS = cycles */ + c->ll_avg_kcps = (uint32_t)c->ll_avg_cycles; + c->ll_peak_kcps = (uint32_t)c->ll_peak_cycles; + update_core_util(core, c); + TELEM_UNLOCK(); +} + +void host_telem_core_dp_run(int core, uint32_t active_dp_threads, uint64_t cycles) +{ + struct host_core_telem *c; + uint32_t freq_hz, i, dp_avg_k = 0, dp_peak_k = 0; + uint64_t exec_ns; + + if (!g_sched_telem.enabled || core < 0 || core >= TB_TELEM_MAX_CORES) + return; + + TELEM_LOCK(); + c = &g_sched_telem.cores[core]; + if (g_sched_telem.sim_cfg.mode != HOST_WORKLOAD_SIM_OFF && c->_sim_dp_run_cycles > 0) { + cycles = c->_sim_dp_run_cycles; + c->_sim_dp_run_cycles = 0; + } + + if (cycles == 0) + cycles = 1; + + freq_hz = get_core_freq_hz((uint32_t)core); + exec_ns = cycles_to_ns(cycles, freq_hz); + + c->active = true; + c->freq_hz = freq_hz; + c->ll_budget_cycles = freq_hz / 1000U; + c->dp_run_count++; + if (active_dp_threads > 0) + c->dp_active_threads = active_dp_threads; + c->dp_last_cycles = cycles; + c->dp_total_cycles += cycles; + c->dp_avg_cycles = c->dp_total_cycles / c->dp_run_count; + if (cycles > c->dp_peak_cycles) + c->dp_peak_cycles = cycles; + + c->dp_last_exec_ns = exec_ns; + c->dp_total_exec_ns += exec_ns; + c->dp_avg_exec_ns = c->dp_total_exec_ns / c->dp_run_count; + if (exec_ns > c->dp_peak_exec_ns) + c->dp_peak_exec_ns = exec_ns; + + /* Sum amortized KCPS across active DP modules on this core */ + for (i = 0; i < g_sched_telem.num_mods; i++) { + if (g_sched_telem.mods[i].active && + g_sched_telem.mods[i].core_id == (uint32_t)core && + g_sched_telem.mods[i].proc_domain == COMP_PROCESSING_DOMAIN_DP) { + dp_avg_k += g_sched_telem.mods[i].avg_kcps; + dp_peak_k += g_sched_telem.mods[i].peak_kcps; + } + } + c->dp_avg_kcps = dp_avg_k ? dp_avg_k : (uint32_t)c->dp_avg_cycles; + c->dp_peak_kcps = dp_peak_k ? dp_peak_k : (uint32_t)c->dp_peak_cycles; + update_core_util(core, c); + TELEM_UNLOCK(); +} + +static struct host_pipe_telem *find_or_alloc_pipe(uint32_t pipeline_id) +{ + uint32_t i; + struct host_pipe_telem *p = NULL; + + for (i = 0; i < g_sched_telem.num_pipes; i++) { + if (g_sched_telem.pipes[i].active && + g_sched_telem.pipes[i].pipeline_id == pipeline_id) + return &g_sched_telem.pipes[i]; + } + + TELEM_LOCK(); + for (i = 0; i < g_sched_telem.num_pipes; i++) { + if (g_sched_telem.pipes[i].active && + g_sched_telem.pipes[i].pipeline_id == pipeline_id) { + p = &g_sched_telem.pipes[i]; + break; + } + } + if (!p && g_sched_telem.num_pipes < TB_TELEM_MAX_PIPES) { + p = &g_sched_telem.pipes[g_sched_telem.num_pipes++]; + memset(p, 0, sizeof(*p)); + p->pipeline_id = pipeline_id; + p->active = true; + } + TELEM_UNLOCK(); + + return p; +} + +static struct host_mod_telem *find_or_alloc_mod(uint32_t comp_id) +{ + uint32_t i; + struct host_mod_telem *m = NULL; + + for (i = 0; i < g_sched_telem.num_mods; i++) { + if (g_sched_telem.mods[i].active && + g_sched_telem.mods[i].comp_id == comp_id) + return &g_sched_telem.mods[i]; + } + + TELEM_LOCK(); + for (i = 0; i < g_sched_telem.num_mods; i++) { + if (g_sched_telem.mods[i].active && + g_sched_telem.mods[i].comp_id == comp_id) { + m = &g_sched_telem.mods[i]; + break; + } + } + if (!m && g_sched_telem.num_mods < TB_TELEM_MAX_MODS) { + m = &g_sched_telem.mods[g_sched_telem.num_mods++]; + memset(m, 0, sizeof(*m)); + m->comp_id = comp_id; + m->active = true; + } + TELEM_UNLOCK(); + + return m; +} + +static uint32_t default_cpc_for_comp_type(uint32_t comp_type, uint32_t proc_domain, + uint32_t period_us, uint32_t ibs, uint32_t obs) +{ + uint32_t base_cpc; + uint32_t chunk_bytes = ibs ? ibs : obs; + + switch (comp_type) { + case SOF_COMP_HOST: + case SOF_COMP_DAI: + case SOF_COMP_SG_HOST: + case SOF_COMP_SG_DAI: + case SOF_COMP_FILEREAD: + case SOF_COMP_FILEWRITE: + base_cpc = 1647; + break; + case SOF_COMP_VOLUME: + base_cpc = 10183; + break; + case SOF_COMP_MIXER: + base_cpc = 2950; + break; + case SOF_COMP_MUX: + case SOF_COMP_DEMUX: + case SOF_COMP_SELECTOR: + base_cpc = 2900; + break; + case SOF_COMP_SRC: + base_cpc = 24000; + break; + case SOF_COMP_ASRC: + base_cpc = 32000; + break; + case SOF_COMP_TONE: + base_cpc = 4200; + break; + case SOF_COMP_BUFFER: + base_cpc = 400; + break; + case SOF_COMP_EQ_IIR: + base_cpc = 18500; + break; + case SOF_COMP_EQ_FIR: + base_cpc = 26000; + break; + case SOF_COMP_KEYWORD_DETECT: + base_cpc = 45000; + break; + case SOF_COMP_KPB: + base_cpc = 3500; + break; + case SOF_COMP_DCBLOCK: + base_cpc = 6400; + break; + case SOF_COMP_SMART_AMP: + base_cpc = 38000; + break; + default: + base_cpc = (proc_domain == COMP_PROCESSING_DOMAIN_DP) ? 48000 : 12000; + break; + } + + if (proc_domain == COMP_PROCESSING_DOMAIN_DP && period_us > 1000) + base_cpc = base_cpc * (period_us / 1000U); + else if (chunk_bytes > 384U) + base_cpc = (uint32_t)(((uint64_t)base_cpc * chunk_bytes) / 384ULL); + + return base_cpc; +} + +static void resolve_mod_sim_params(struct host_mod_telem *m) +{ + uint32_t i; + uint32_t cpc_ov = 0; + uint32_t mem_ov = HOST_MEM_DOMAIN_AUTO; + + ensure_sim_cfg_defaults(); + + for (i = 0; i < g_sched_telem.sim_cfg.num_overrides; i++) { + const struct host_workload_mod_override *ov = + &g_sched_telem.sim_cfg.overrides[i]; + + if (!ov->active) + continue; + if (ov->comp_id == m->comp_id || + ((ov->comp_id & 0xFFFF0000U) == 0xFFFF0000U && + (ov->comp_id & 0xFFFFU) == (m->comp_id & 0xFFFFU))) { + if (ov->cpc_override > 0) + cpc_ov = ov->cpc_override; + if (ov->mem_domain_override != HOST_MEM_DOMAIN_AUTO) + mem_ov = ov->mem_domain_override; + break; + } + } + + if (mem_ov != HOST_MEM_DOMAIN_AUTO) + m->mem_domain = (mem_ov == HOST_MEM_DOMAIN_DRAM) ? + HOST_MEM_DOMAIN_DRAM : HOST_MEM_DOMAIN_SRAM; + else + m->mem_domain = g_sched_telem.sim_cfg.default_mem_domain; + + if (cpc_ov > 0) { + m->effective_cpc = cpc_ov; + } else if (m->cpc > 0) { + m->effective_cpc = m->cpc; + } else { + m->effective_cpc = default_cpc_for_comp_type(m->type, m->proc_domain, + m->period_us, m->ibs, m->obs); + } + if (m->cpc == 0) + m->cpc = m->effective_cpc; +} + +static uint64_t compute_simulated_mod_cycles(const struct host_mod_telem *m, + uint32_t bytes_in, uint32_t bytes_out) +{ + uint64_t base = m->effective_cpc ? m->effective_cpc : 2000ULL; + uint64_t cycles; + + /* If module has connected pins but transferred 0 bytes on this tick, charge idle check */ + if (bytes_in == 0 && bytes_out == 0 && (m->num_in_pins > 0 || m->num_out_pins > 0)) + base = 64ULL; + + if (m->mem_domain == HOST_MEM_DOMAIN_DRAM) { + uint32_t factor = g_sched_telem.sim_cfg.dram_factor_x100 ? + g_sched_telem.sim_cfg.dram_factor_x100 : 160U; + uint32_t c_per_word = g_sched_telem.sim_cfg.dram_cycles_per_word; + uint64_t xfer_words = (uint64_t)(bytes_in + bytes_out) / 4ULL; + + cycles = (base * (uint64_t)factor) / 100ULL + xfer_words * (uint64_t)c_per_word; + } else { + uint32_t factor = g_sched_telem.sim_cfg.sram_factor_x100 ? + g_sched_telem.sim_cfg.sram_factor_x100 : 100U; + + cycles = (base * (uint64_t)factor) / 100ULL; + } + + return cycles ? cycles : 1ULL; +} + +uint64_t host_telem_pipe_begin(struct pipeline *p) +{ + struct host_pipe_telem *pt; + uint32_t core; + + if (!g_sched_telem.enabled || !p) + return 0; + + pt = find_or_alloc_pipe(p->pipeline_id); + if (pt) { + uint64_t overhead; + + TELEM_LOCK(); + /* Base LL pipeline dispatch overhead (120 cycles in SRAM, scaled in DRAM) */ + uint32_t factor = (g_sched_telem.sim_cfg.default_mem_domain == + HOST_MEM_DOMAIN_DRAM) ? + g_sched_telem.sim_cfg.dram_factor_x100 : + g_sched_telem.sim_cfg.sram_factor_x100; + if (!factor) + factor = 100U; + overhead = (120ULL * factor) / 100ULL; + pt->_sim_tick_cycles = overhead; + core = (p->core < TB_TELEM_MAX_CORES) ? p->core : 0; + g_sched_telem.cores[core]._sim_ll_tick_cycles += overhead; + g_sched_telem.cores[core]._tick_ll_offset_cycles += overhead; + TELEM_UNLOCK(); + } + + return host_telem_timestamp_cycles(); +} + +void host_telem_pipe_end(struct pipeline *p, uint64_t start_cycles, int ret) +{ + struct host_pipe_telem *pt; + uint64_t end_cycles, delta, exec_ns; + uint32_t period_us, freq_hz, i, ll_cnt = 0, dp_cnt = 0; + + if (!g_sched_telem.enabled || !p) + return; + + end_cycles = host_telem_timestamp_cycles(); + delta = (end_cycles > start_cycles) ? (end_cycles - start_cycles) : 1; + + pt = find_or_alloc_pipe(p->pipeline_id); + if (!pt) + return; + + TELEM_LOCK(); + if (g_sched_telem.sim_cfg.mode != HOST_WORKLOAD_SIM_OFF && pt->_sim_tick_cycles > 0) { + delta = pt->_sim_tick_cycles; + pt->_sim_tick_cycles = 0; + } + + pt->comp_id = p->comp_id; + pt->core_id = p->core; + pt->priority = p->priority; + pt->period_us = p->period ? p->period : 1000; + pt->status = p->status; + pt->mem_domain = g_sched_telem.sim_cfg.default_mem_domain; + if (p->sched_comp) + pt->direction = p->sched_comp->direction; + + freq_hz = get_core_freq_hz(pt->core_id); + exec_ns = cycles_to_ns(delta, freq_hz); + + pt->copy_iterations++; + pt->last_cycles = delta; + pt->total_cycles += delta; + pt->avg_cycles = pt->total_cycles / pt->copy_iterations; + if (delta > pt->peak_cycles) + pt->peak_cycles = delta; + + pt->last_exec_ns = exec_ns; + pt->total_exec_ns += exec_ns; + pt->avg_exec_ns = pt->total_exec_ns / pt->copy_iterations; + if (exec_ns > pt->peak_exec_ns) + pt->peak_exec_ns = exec_ns; + + period_us = pt->period_us ? pt->period_us : 1000; + pt->avg_kcps = (uint32_t)((pt->avg_cycles * 1000ULL) / period_us); + pt->peak_kcps = (uint32_t)((pt->peak_cycles * 1000ULL) / period_us); + + if (ret < 0 || p->xrun_bytes != 0) + pt->xrun_count++; + + for (i = 0; i < g_sched_telem.num_mods; i++) { + if (g_sched_telem.mods[i].active && + g_sched_telem.mods[i].pipeline_id == p->pipeline_id) { + if (g_sched_telem.mods[i].proc_domain == COMP_PROCESSING_DOMAIN_DP) + dp_cnt++; + else + ll_cnt++; + } + } + pt->num_ll_modules = ll_cnt; + pt->num_dp_modules = dp_cnt; + TELEM_UNLOCK(); +} + +uint64_t host_telem_mod_entry(struct comp_dev *dev, bool is_dp_thread) +{ + struct host_mod_telem *m; + struct comp_buffer *buffer; + uint32_t idx; + + if (!g_sched_telem.enabled || !dev) + return 0; + + m = find_or_alloc_mod(dev->ipc_config.id); + if (!m) + return host_telem_timestamp_cycles(); + + m->pipeline_id = dev->ipc_config.pipeline_id; + m->core_id = dev->ipc_config.core; + m->proc_domain = dev->ipc_config.proc_domain; + m->type = dev->ipc_config.type; + m->period_us = dev->period ? dev->period : + (dev->pipeline && dev->pipeline->period ? dev->pipeline->period : 1000); + m->ibs = (uint32_t)dev->ibs; + m->obs = (uint32_t)dev->obs; + m->cpc = (uint32_t)dev->cpc; + +#if CONFIG_IPC_MAJOR_4 + if (dev->mod) { + if (!m->ibs) + m->ibs = dev->mod->priv.cfg.base_cfg.ibs; + if (!m->obs) + m->obs = dev->mod->priv.cfg.base_cfg.obs; + if (!m->cpc) + m->cpc = dev->mod->priv.cfg.base_cfg.cpc; + } +#endif + + if (!is_dp_thread) { + m->_ll_entry_wall_ns = host_telem_wall_ns(); + /* LL tick path (comp_copy) */ + idx = 0; + comp_dev_for_each_producer(dev, buffer) { + struct host_mod_pin_telem *pin; + struct comp_dev *prod; + uintptr_t base, rptr; + uint32_t avail_b, free_b; + + if (idx >= TB_TELEM_MAX_PINS) + break; + pin = &m->in_pins[idx++]; + pin->active = true; + pin->buf_id = buf_get_id(buffer); + prod = comp_buffer_get_source_component(buffer); + pin->peer_comp_id = prod ? prod->ipc_config.id : 0xFFFFFFFFU; + pin->buf_size = (uint32_t)audio_stream_get_size(&buffer->stream); + pin->rate = audio_stream_get_rate(&buffer->stream); + pin->channels = audio_stream_get_channels(&buffer->stream); + pin->frame_fmt = audio_stream_get_frm_fmt(&buffer->stream); + + avail_b = (uint32_t)audio_stream_get_avail_bytes(&buffer->stream); + free_b = (uint32_t)audio_stream_get_free_bytes(&buffer->stream); + pin->_entry_avail = avail_b; + + base = (uintptr_t)audio_stream_get_addr(&buffer->stream); + rptr = (uintptr_t)audio_stream_get_rptr(&buffer->stream); + pin->_entry_ptr = rptr; + + if (avail_b > 0 || pin->total_bytes_transferred == 0) { + pin->entry_avail_bytes = avail_b; + pin->entry_free_bytes = free_b; + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL && + pin->buf_size > 0 && rptr >= base) { + capture_circular_samples((const void *)base, pin->buf_size, + (size_t)(rptr - base), + avail_b, + pin->frame_fmt, + pin->entry_samples, + &pin->entry_num_samples, + &pin->entry_rms_dbfs_x100, + &pin->entry_peak_dbfs_x100); + } + } + +#if CONFIG_PIPELINE_2_0 + if (buffer->audio_buffer.secondary_buffer_source) { + struct sof_audio_buffer *sec = + buffer->audio_buffer.secondary_buffer_source; + uint32_t avail = (uint32_t)source_get_data_available( + audio_buffer_get_source(sec)); + uint32_t free_b = (uint32_t)sink_get_free_size( + audio_buffer_get_sink(sec)); + pin->dp_ring_entry_avail = avail; + pin->dp_ring_entry_free = free_b; + pin->dp_ring_size = avail + free_b; + } +#endif + } + if (idx > m->num_in_pins) + m->num_in_pins = idx; + + idx = 0; + comp_dev_for_each_consumer(dev, buffer) { + struct host_mod_pin_telem *pin; + struct comp_dev *cons; + uintptr_t base, rptr, wptr; + uint32_t avail_b, free_b; + + if (idx >= TB_TELEM_MAX_PINS) + break; + pin = &m->out_pins[idx++]; + pin->active = true; + pin->buf_id = buf_get_id(buffer); + cons = comp_buffer_get_sink_component(buffer); + pin->peer_comp_id = cons ? cons->ipc_config.id : 0xFFFFFFFFU; + pin->buf_size = (uint32_t)audio_stream_get_size(&buffer->stream); + pin->rate = audio_stream_get_rate(&buffer->stream); + pin->channels = audio_stream_get_channels(&buffer->stream); + pin->frame_fmt = audio_stream_get_frm_fmt(&buffer->stream); + + avail_b = (uint32_t)audio_stream_get_avail_bytes(&buffer->stream); + free_b = (uint32_t)audio_stream_get_free_bytes(&buffer->stream); + pin->_entry_avail = avail_b; + + base = (uintptr_t)audio_stream_get_addr(&buffer->stream); + rptr = (uintptr_t)audio_stream_get_rptr(&buffer->stream); + wptr = (uintptr_t)audio_stream_get_wptr(&buffer->stream); + pin->_entry_ptr = wptr; + + if (avail_b > 0 || pin->total_bytes_transferred == 0) { + pin->entry_avail_bytes = avail_b; + pin->entry_free_bytes = free_b; + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_LL && + pin->buf_size > 0 && rptr >= base) { + capture_circular_samples((const void *)base, pin->buf_size, + (size_t)(rptr - base), + avail_b, + pin->frame_fmt, + pin->entry_samples, + &pin->entry_num_samples, + &pin->entry_rms_dbfs_x100, + &pin->entry_peak_dbfs_x100); + } + } + +#if CONFIG_PIPELINE_2_0 + if (buffer->audio_buffer.secondary_buffer_sink) { + struct sof_audio_buffer *sec = + buffer->audio_buffer.secondary_buffer_sink; + uint32_t avail = (uint32_t)source_get_data_available( + audio_buffer_get_source(sec)); + uint32_t free_b = (uint32_t)sink_get_free_size( + audio_buffer_get_sink(sec)); + pin->dp_ring_entry_avail = avail; + pin->dp_ring_entry_free = free_b; + pin->dp_ring_size = avail + free_b; + } +#endif + } + if (idx > m->num_out_pins) + m->num_out_pins = idx; + } else if (dev->mod) { + /* DP worker thread path (dp_task_run) */ + struct processing_module *mod = dev->mod; + + m->_dp_entry_wall_ns = host_telem_wall_ns(); + + for (idx = 0; idx < mod->num_of_sources && idx < TB_TELEM_MAX_PINS; idx++) { + struct sof_source *src = mod->sources[idx]; + struct sof_audio_buffer *ab; + struct ring_buffer *rb; + struct host_mod_pin_telem *pin = &m->in_pins[idx]; + size_t roff; + + if (!src) + continue; + ab = sof_audio_buffer_from_source(src); + rb = container_of(ab, struct ring_buffer, audio_buffer); + pin->active = true; + pin->rate = source_get_rate(src); + pin->channels = source_get_channels(src); + pin->frame_fmt = source_get_frm_fmt(src); + pin->dp_ring_size = (uint32_t)rb->data_buffer_size; + pin->dp_ring_entry_avail = (uint32_t)source_get_data_available(src); + pin->dp_ring_entry_free = + (pin->dp_ring_size >= pin->dp_ring_entry_avail) ? + (pin->dp_ring_size - pin->dp_ring_entry_avail) : 0; + + roff = (rb->data_buffer_size > 0) ? + (rb->_read_offset % rb->data_buffer_size) : 0; + capture_circular_samples((__sparse_force const void *)rb->_data_buffer, + rb->data_buffer_size, roff, + pin->dp_ring_entry_avail, + pin->frame_fmt, + pin->entry_samples, + &pin->entry_num_samples, + &pin->entry_rms_dbfs_x100, + &pin->entry_peak_dbfs_x100); + } + if (idx > m->num_in_pins) + m->num_in_pins = idx; + + for (idx = 0; idx < mod->num_of_sinks && idx < TB_TELEM_MAX_PINS; idx++) { + struct sof_sink *snk = mod->sinks[idx]; + struct sof_audio_buffer *ab; + struct ring_buffer *rb; + struct host_mod_pin_telem *pin = &m->out_pins[idx]; + size_t roff, woff; + + if (!snk) + continue; + ab = sof_audio_buffer_from_sink(snk); + rb = container_of(ab, struct ring_buffer, audio_buffer); + pin->active = true; + pin->rate = sink_get_rate(snk); + pin->channels = sink_get_channels(snk); + pin->frame_fmt = sink_get_frm_fmt(snk); + pin->dp_ring_size = (uint32_t)rb->data_buffer_size; + pin->dp_ring_entry_free = (uint32_t)sink_get_free_size(snk); + pin->dp_ring_entry_avail = + (pin->dp_ring_size >= pin->dp_ring_entry_free) ? + (pin->dp_ring_size - pin->dp_ring_entry_free) : 0; + + woff = (rb->data_buffer_size > 0) ? + (rb->_write_offset % rb->data_buffer_size) : 0; + roff = (rb->data_buffer_size > 0) ? + (rb->_read_offset % rb->data_buffer_size) : 0; + pin->_dp_entry_woff = (uint32_t)woff; + + capture_circular_samples((__sparse_force const void *)rb->_data_buffer, + rb->data_buffer_size, roff, + pin->dp_ring_entry_avail, + pin->frame_fmt, + pin->entry_samples, + &pin->entry_num_samples, + &pin->entry_rms_dbfs_x100, + &pin->entry_peak_dbfs_x100); + } + if (idx > m->num_out_pins) + m->num_out_pins = idx; + } + + return host_telem_timestamp_cycles(); +} + +void host_telem_mod_exit(struct comp_dev *dev, bool is_dp_thread, uint64_t start_cycles, int ret) +{ + struct host_mod_telem *m; + struct host_pipe_telem *pt; + struct comp_buffer *buffer; + uint64_t end_cycles, delta, exec_ns; + uint32_t idx, period_us, freq_hz, core_id; + uint32_t total_in_bytes = 0, total_out_bytes = 0; + + if (!g_sched_telem.enabled || !dev) + return; + + end_cycles = host_telem_timestamp_cycles(); + delta = (end_cycles > start_cycles) ? (end_cycles - start_cycles) : 1; + + m = find_or_alloc_mod(dev->ipc_config.id); + if (!m) + return; + + resolve_mod_sim_params(m); + core_id = (m->core_id < TB_TELEM_MAX_CORES) ? m->core_id : 0; + + if (ret < 0) + m->error_count++; + + if (!is_dp_thread && dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) { + uint32_t sync_iter; + + /* DP module during LL tick: only synced secondary ring buffers */ + pt = find_or_alloc_pipe(m->pipeline_id); + TELEM_LOCK(); + if (g_sched_telem.sim_cfg.mode != HOST_WORKLOAD_SIM_OFF) { + uint32_t factor = (m->mem_domain == HOST_MEM_DOMAIN_DRAM) ? + g_sched_telem.sim_cfg.dram_factor_x100 : + g_sched_telem.sim_cfg.sram_factor_x100; + if (!factor) + factor = 100U; + delta = (160ULL * factor) / 100ULL; + if (pt) + pt->_sim_tick_cycles += delta; + g_sched_telem.cores[core_id]._sim_ll_tick_cycles += delta; + } + m->ll_ring_sync_iterations++; + sync_iter = (uint32_t)m->ll_ring_sync_iterations; + m->ll_ring_sync_last_cycles = delta; + m->ll_ring_sync_total_cycles += delta; + TELEM_UNLOCK(); + + idx = 0; + comp_dev_for_each_producer(dev, buffer) { + if (idx >= TB_TELEM_MAX_PINS) + break; + m->in_pins[idx].exit_avail_bytes = + (uint32_t)audio_stream_get_avail_bytes(&buffer->stream); + m->in_pins[idx].exit_free_bytes = + (uint32_t)audio_stream_get_free_bytes(&buffer->stream); + idx++; + } + + idx = 0; + comp_dev_for_each_consumer(dev, buffer) { + if (idx >= TB_TELEM_MAX_PINS) + break; + m->out_pins[idx].exit_avail_bytes = + (uint32_t)audio_stream_get_avail_bytes(&buffer->stream); + m->out_pins[idx].exit_free_bytes = + (uint32_t)audio_stream_get_free_bytes(&buffer->stream); + idx++; + } + + freq_hz = get_core_freq_hz(core_id); + record_mod_sched_event(m, core_id, freq_hz, HOST_SCHED_EVT_DP_SYNC, + sync_iter, delta, 0, + 0, 0, m->_ll_entry_wall_ns, + host_telem_wall_ns(), ret); + return; + } + + if (!is_dp_thread) { + /* LL module exit: inspect comp_buffer pointers and capture output samples */ + idx = 0; + comp_dev_for_each_producer(dev, buffer) { + struct host_mod_pin_telem *pin; + uintptr_t base, exit_r; + uint32_t sz, exit_avail, exit_free, consumed = 0; + + if (idx >= TB_TELEM_MAX_PINS) + break; + pin = &m->in_pins[idx++]; + exit_avail = (uint32_t)audio_stream_get_avail_bytes(&buffer->stream); + exit_free = (uint32_t)audio_stream_get_free_bytes(&buffer->stream); + + base = (uintptr_t)audio_stream_get_addr(&buffer->stream); + exit_r = (uintptr_t)audio_stream_get_rptr(&buffer->stream); + sz = pin->buf_size; + + if (sz > 0 && pin->_entry_ptr >= base && exit_r >= base) { + consumed = (uint32_t)((exit_r + sz - pin->_entry_ptr) % sz); + if (consumed == 0 && pin->_entry_avail > exit_avail) + consumed = pin->_entry_avail - exit_avail; + } + total_in_bytes += consumed; + + if (consumed > 0 || pin->total_bytes_transferred == 0) { + pin->exit_avail_bytes = exit_avail; + pin->exit_free_bytes = exit_free; + pin->last_bytes_transferred = consumed; + pin->total_bytes_transferred += consumed; + + if (sz > 0 && exit_r >= base) { + size_t tap_off = (exit_avail > 0) ? + (size_t)(exit_r - base) : + (size_t)(pin->_entry_ptr - base); + size_t tap_bytes = (exit_avail > 0) ? + exit_avail : consumed; + capture_circular_samples((const void *)base, sz, + tap_off, tap_bytes, + pin->frame_fmt, + pin->exit_samples, + &pin->exit_num_samples, + &pin->exit_rms_dbfs_x100, + &pin->exit_peak_dbfs_x100); + } + } + } + + idx = 0; + comp_dev_for_each_consumer(dev, buffer) { + struct host_mod_pin_telem *pin; + uintptr_t base, exit_w, rptr; + uint32_t sz, exit_avail, exit_free, produced = 0; + + if (idx >= TB_TELEM_MAX_PINS) + break; + pin = &m->out_pins[idx++]; + exit_avail = (uint32_t)audio_stream_get_avail_bytes(&buffer->stream); + exit_free = (uint32_t)audio_stream_get_free_bytes(&buffer->stream); + + base = (uintptr_t)audio_stream_get_addr(&buffer->stream); + exit_w = (uintptr_t)audio_stream_get_wptr(&buffer->stream); + rptr = (uintptr_t)audio_stream_get_rptr(&buffer->stream); + sz = pin->buf_size; + + if (sz > 0 && pin->_entry_ptr >= base && exit_w >= base) { + produced = (uint32_t)((exit_w + sz - pin->_entry_ptr) % sz); + if (produced == 0 && exit_avail > pin->_entry_avail) + produced = exit_avail - pin->_entry_avail; + } + total_out_bytes += produced; + + if (produced > 0 || pin->total_bytes_transferred == 0) { + pin->entry_avail_bytes = pin->_entry_avail; + pin->entry_free_bytes = (sz >= pin->_entry_avail) ? + (sz - pin->_entry_avail) : 0; + pin->exit_avail_bytes = exit_avail; + pin->exit_free_bytes = exit_free; + pin->last_bytes_transferred = produced; + pin->total_bytes_transferred += produced; + + if (sz > 0 && pin->_entry_ptr >= base) { + size_t tap_off = (produced > 0) ? + (size_t)(pin->_entry_ptr - base) : + (size_t)(rptr - base); + size_t tap_bytes = (produced > 0) ? + produced : exit_avail; + capture_circular_samples((const void *)base, sz, + tap_off, tap_bytes, + pin->frame_fmt, + pin->exit_samples, + &pin->exit_num_samples, + &pin->exit_rms_dbfs_x100, + &pin->exit_peak_dbfs_x100); + } + } + } + } else if (dev->mod) { + /* DP worker thread exit: inspect ring_buffer processed bytes and output samples */ + struct processing_module *mod = dev->mod; + + for (idx = 0; idx < mod->num_of_sources && idx < TB_TELEM_MAX_PINS; idx++) { + struct sof_source *src = mod->sources[idx]; + struct sof_audio_buffer *ab; + struct ring_buffer *rb; + struct host_mod_pin_telem *pin = &m->in_pins[idx]; + uint32_t consumed; + size_t roff; + + if (!src) + continue; + ab = sof_audio_buffer_from_source(src); + rb = container_of(ab, struct ring_buffer, audio_buffer); + consumed = (uint32_t)source_get_num_of_processed_bytes(src); + total_in_bytes += consumed; + pin->last_bytes_transferred = consumed; + pin->total_bytes_transferred += consumed; + pin->dp_ring_exit_avail = (uint32_t)source_get_data_available(src); + pin->dp_ring_exit_free = + (pin->dp_ring_size >= pin->dp_ring_exit_avail) ? + (pin->dp_ring_size - pin->dp_ring_exit_avail) : 0; + + roff = (rb->data_buffer_size > 0) ? + (rb->_read_offset % rb->data_buffer_size) : 0; + capture_circular_samples((__sparse_force const void *)rb->_data_buffer, + rb->data_buffer_size, roff, + pin->dp_ring_exit_avail ? + pin->dp_ring_exit_avail : consumed, + pin->frame_fmt, + pin->exit_samples, + &pin->exit_num_samples, + &pin->exit_rms_dbfs_x100, + &pin->exit_peak_dbfs_x100); + } + + for (idx = 0; idx < mod->num_of_sinks && idx < TB_TELEM_MAX_PINS; idx++) { + struct sof_sink *snk = mod->sinks[idx]; + struct sof_audio_buffer *ab; + struct ring_buffer *rb; + struct host_mod_pin_telem *pin = &m->out_pins[idx]; + uint32_t produced; + + if (!snk) + continue; + ab = sof_audio_buffer_from_sink(snk); + rb = container_of(ab, struct ring_buffer, audio_buffer); + produced = (uint32_t)sink_get_num_of_processed_bytes(snk); + total_out_bytes += produced; + pin->last_bytes_transferred = produced; + pin->total_bytes_transferred += produced; + pin->dp_ring_exit_free = (uint32_t)sink_get_free_size(snk); + pin->dp_ring_exit_avail = + (pin->dp_ring_size >= pin->dp_ring_exit_free) ? + (pin->dp_ring_size - pin->dp_ring_exit_free) : 0; + + capture_circular_samples((__sparse_force const void *)rb->_data_buffer, + rb->data_buffer_size, + pin->_dp_entry_woff, + produced ? produced : pin->dp_ring_exit_avail, + pin->frame_fmt, + pin->exit_samples, + &pin->exit_num_samples, + &pin->exit_rms_dbfs_x100, + &pin->exit_peak_dbfs_x100); + } + } + + /* Apply accurate workload execution simulation when enabled */ + pt = !is_dp_thread ? find_or_alloc_pipe(m->pipeline_id) : NULL; + if (g_sched_telem.sim_cfg.mode != HOST_WORKLOAD_SIM_OFF) + delta = compute_simulated_mod_cycles(m, total_in_bytes, total_out_bytes); + + freq_hz = get_core_freq_hz(core_id); + exec_ns = cycles_to_ns(delta, freq_hz); + + if (g_sched_telem.sim_cfg.mode == HOST_WORKLOAD_SIM_REALTIME) + pace_execution_ns(exec_ns); + + TELEM_LOCK(); + if (g_sched_telem.sim_cfg.mode != HOST_WORKLOAD_SIM_OFF) { + if (!is_dp_thread) { + if (pt) + pt->_sim_tick_cycles += delta; + g_sched_telem.cores[core_id]._sim_ll_tick_cycles += delta; + } else { + g_sched_telem.cores[core_id]._sim_dp_run_cycles += delta; + } + } + + /* Record module processing execution cycles & simulated execution time */ + m->iterations++; + uint32_t mod_iter_nr = (uint32_t)m->iterations; + + m->last_cycles = delta; + m->total_cycles += delta; + m->avg_cycles = m->total_cycles / m->iterations; + if (delta > m->peak_cycles) + m->peak_cycles = delta; + + m->last_exec_ns = exec_ns; + m->total_exec_ns += exec_ns; + m->avg_exec_ns = m->total_exec_ns / m->iterations; + if (exec_ns > m->peak_exec_ns) + m->peak_exec_ns = exec_ns; + + period_us = m->period_us ? m->period_us : 1000; + m->avg_kcps = (uint32_t)((m->avg_cycles * 1000ULL) / period_us); + m->peak_kcps = (uint32_t)((m->peak_cycles * 1000ULL) / period_us); + + if (m->effective_cpc > 0 && delta > m->effective_cpc) + m->cpc_violations++; + TELEM_UNLOCK(); + + record_mod_sched_event(m, core_id, freq_hz, + is_dp_thread ? HOST_SCHED_EVT_DP_RUN : HOST_SCHED_EVT_LL_COPY, + mod_iter_nr, delta, m->effective_cpc, + total_in_bytes, total_out_bytes, + is_dp_thread ? m->_dp_entry_wall_ns : m->_ll_entry_wall_ns, + host_telem_wall_ns(), ret); +} diff --git a/src/platform/library/schedule/CMakeLists.txt b/src/platform/library/schedule/CMakeLists.txt index 31d325038217..613bfadf659d 100644 --- a/src/platform/library/schedule/CMakeLists.txt +++ b/src/platform/library/schedule/CMakeLists.txt @@ -1,7 +1,18 @@ # SPDX-License-Identifier: BSD-3-Clause -add_local_sources(sof - schedule.c - ll_schedule.c - edf_schedule.c -) +if(TESTBENCH_XTENSA_SIM OR CMAKE_C_COMPILER MATCHES "xt-|xtensa-" OR DEFINED ENV{XTENSA_CORE}) + target_compile_definitions(sof PRIVATE CONFIG_XT_SIMULATOR=1) + add_local_sources(sof + schedule.c + ll_schedule.c + dp_schedule.c + edf_schedule.c + ) +else() + add_local_sources(sof + schedule.c + ll_schedule_pthread.c + dp_schedule_pthread.c + edf_schedule.c + ) +endif() diff --git a/src/platform/library/schedule/dp_schedule.c b/src/platform/library/schedule/dp_schedule.c new file mode 100644 index 000000000000..28d984411c46 --- /dev/null +++ b/src/platform/library/schedule/dp_schedule.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Synchronous standard-C DP scheduler for Xtensa simulator (xt-run) */ + +SOF_DEFINE_REG_UUID(dp_sched); + +DECLARE_TR_CTX(dp_tr, SOF_UUID(dp_sched_uuid), LOG_LEVEL_INFO); + +struct scheduler_dp_data { + struct list_item tasks; /* list of active DP tasks */ +}; + +struct task_dp_pdata { + struct processing_module *mod; + uint32_t ll_cycles_to_start; +}; + +void scheduler_dp_ll_tick(void) +{ + struct scheduler_dp_data *dp_sch = scheduler_get_data(SOF_SCHEDULE_DP); + struct list_item *tlist, *tlist_; + struct task *curr_task; + struct task_dp_pdata *pdata; + enum task_state state; + + if (!dp_sch) + return; + + list_for_item_safe(tlist, tlist_, &dp_sch->tasks) { + curr_task = container_of(tlist, struct task, list); + pdata = curr_task->priv_data; + struct processing_module *mod = pdata->mod; + + /* decrease number of LL ticks/cycles left till the module reaches its deadline */ + if (mod->dp_startup_delay && pdata->ll_cycles_to_start) { + pdata->ll_cycles_to_start--; + if (!pdata->ll_cycles_to_start) + mod->dp_startup_delay = false; + } + + if (curr_task->state == SOF_TASK_STATE_QUEUED && + mod->dev->state == COMP_STATE_ACTIVE) { + bool mod_ready; + + mod_ready = module_is_ready_to_process(mod, mod->sources, + mod->num_of_sources, + mod->sinks, + mod->num_of_sinks); + if (mod_ready) { + curr_task->state = SOF_TASK_STATE_RUNNING; + if (mod->dp_startup_delay && !pdata->ll_cycles_to_start) { + pdata->ll_cycles_to_start = + module_get_lpt(pdata->mod) / LL_TIMER_PERIOD_US; + if (!pdata->ll_cycles_to_start) + pdata->ll_cycles_to_start = 1; + } + + host_cpu_set_id(curr_task->core); + { + uint64_t t0 = host_telem_timestamp_cycles(); + uint64_t t1; + + state = task_run(curr_task); + t1 = host_telem_timestamp_cycles(); + host_telem_core_dp_run(curr_task->core, 1, + (t1 > t0) ? (t1 - t0) : 1); + } + host_cpu_set_id(0); + + if (curr_task->state == SOF_TASK_STATE_RUNNING) { + curr_task->state = state; + switch (state) { + case SOF_TASK_STATE_RESCHEDULE: + curr_task->state = SOF_TASK_STATE_QUEUED; + break; + case SOF_TASK_STATE_COMPLETED: + list_item_del(&curr_task->list); + task_complete(curr_task); + break; + case SOF_TASK_STATE_CANCEL: + list_item_del(&curr_task->list); + break; + default: + list_item_del(&curr_task->list); + curr_task->state = SOF_TASK_STATE_CANCEL; + break; + } + } + } + } + } +} + +static int scheduler_dp_task_stop(void *data, struct task *task) +{ + (void)data; + + if (task->state == SOF_TASK_STATE_QUEUED || + task->state == SOF_TASK_STATE_RUNNING) + list_item_del(&task->list); + + task->state = SOF_TASK_STATE_CANCEL; + + return 0; +} + +static int scheduler_dp_task_free(void *data, struct task *task) +{ + scheduler_dp_task_stop(data, task); + task->state = SOF_TASK_STATE_FREE; + rfree(task); + + return 0; +} + +static int scheduler_dp_task_shedule(void *data, struct task *task, uint64_t start, + uint64_t period) +{ + struct scheduler_dp_data *dp_sch = data; + struct task_dp_pdata *pdata = task->priv_data; + + (void)start; + (void)period; + + if (task_is_active(task)) + return 0; + + if (task->state != SOF_TASK_STATE_INIT && + task->state != SOF_TASK_STATE_CANCEL && + task->state != SOF_TASK_STATE_COMPLETED) + return -EINVAL; + + task->state = SOF_TASK_STATE_QUEUED; + list_item_prepend(&task->list, &dp_sch->tasks); + pdata->mod->dp_startup_delay = true; + pdata->ll_cycles_to_start = 0; + + return 0; +} + +static void dp_scheduler_free(void *data, uint32_t flags) +{ + struct scheduler_dp_data *dp_sch = data; + + (void)flags; + free(dp_sch); +} + +static struct scheduler_ops schedule_dp_ops = { + .schedule_task = scheduler_dp_task_shedule, + .schedule_task_cancel = scheduler_dp_task_stop, + .schedule_task_free = scheduler_dp_task_free, + .scheduler_free = dp_scheduler_free, +}; + +int scheduler_dp_init(void) +{ + struct scheduler_dp_data *dp_sch; + + dp_sch = calloc(1, sizeof(*dp_sch)); + if (!dp_sch) + return -ENOMEM; + + list_init(&dp_sch->tasks); + scheduler_init(SOF_SCHEDULE_DP, &schedule_dp_ops, dp_sch); + + return 0; +} + +int scheduler_dp_task_init(struct task **task, + const struct sof_uuid_entry *uid, + const struct task_ops *ops, + struct processing_module *mod, + uint16_t core, + size_t stack_size, + uint32_t options) +{ + struct { + struct task task; + struct task_dp_pdata pdata; + } *task_memory; + struct task_dp_pdata *pdata; + int ret; + + (void)stack_size; + + task_memory = rzalloc(SOF_MEM_FLAG_USER, sizeof(*task_memory)); + if (!task_memory) { + tr_err(&dp_tr, "scheduler_dp_task_init: memory alloc failed"); + return -ENOMEM; + } + + ret = schedule_task_init(&task_memory->task, uid, SOF_SCHEDULE_DP, 0, ops->run, + mod, core, options); + if (ret < 0) { + tr_err(&dp_tr, "scheduler_dp_task_init: schedule_task_init failed"); + rfree(task_memory); + return ret; + } + + pdata = &task_memory->pdata; + pdata->mod = mod; + + task_memory->task.ops.complete = ops->complete; + task_memory->task.ops.get_deadline = ops->get_deadline; + task_memory->task.state = SOF_TASK_STATE_INIT; + task_memory->task.core = core; + task_memory->task.priv_data = pdata; + + *task = &task_memory->task; + return 0; +} + +void scheduler_get_task_info_dp(struct scheduler_props *scheduler_props, + uint32_t *data_off_size) +{ + (void)scheduler_props; + (void)data_off_size; +} diff --git a/src/platform/library/schedule/dp_schedule_pthread.c b/src/platform/library/schedule/dp_schedule_pthread.c new file mode 100644 index 000000000000..4ad97eb337fa --- /dev/null +++ b/src/platform/library/schedule/dp_schedule_pthread.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "host_schedule.h" + +SOF_DEFINE_REG_UUID(dp_sched); + +DECLARE_TR_CTX(dp_tr, SOF_UUID(dp_sched_uuid), LOG_LEVEL_INFO); + +struct scheduler_dp_data { + struct list_item tasks; /* list of active DP tasks */ + pthread_mutex_t lock; /* mutex protecting tasks list and events */ + pthread_cond_t tick_done_cond; /* signalled when running DP tasks finish a tick */ + uint32_t running_count; /* number of DP tasks currently running in this tick */ +}; + +struct task_dp_pdata { + pthread_t thread_id; + bool thread_created; + pthread_cond_t cond; + uint32_t event; + struct processing_module *mod; + uint32_t ll_cycles_to_start; +}; + +/* + * Go through all DP tasks and evaluate their readiness. + * Must be called with dp_sch->lock held. + */ +static void scheduler_dp_recalculate_thread(struct scheduler_dp_data *dp_sch) +{ + struct list_item *tlist; + struct task *curr_task; + struct task_dp_pdata *pdata; + + list_for_item(tlist, &dp_sch->tasks) { + curr_task = container_of(tlist, struct task, list); + pdata = curr_task->priv_data; + struct processing_module *mod = pdata->mod; + + /* decrease number of LL ticks/cycles left till the module reaches its deadline */ + if (mod->dp_startup_delay && pdata->ll_cycles_to_start) { + pdata->ll_cycles_to_start--; + if (!pdata->ll_cycles_to_start) + mod->dp_startup_delay = false; + } + + if (curr_task->state == SOF_TASK_STATE_QUEUED && + mod->dev->state == COMP_STATE_ACTIVE) { + bool mod_ready; + + mod_ready = module_is_ready_to_process(mod, mod->sources, + mod->num_of_sources, + mod->sinks, + mod->num_of_sinks); + if (mod_ready) { + curr_task->state = SOF_TASK_STATE_RUNNING; + if (mod->dp_startup_delay && !pdata->ll_cycles_to_start) { + pdata->ll_cycles_to_start = + module_get_lpt(pdata->mod) / LL_TIMER_PERIOD_US; + if (!pdata->ll_cycles_to_start) + pdata->ll_cycles_to_start = 1; + } + pdata->event |= DP_TASK_EVENT_PROCESS; + dp_sch->running_count++; + pthread_cond_signal(&pdata->cond); + } + } + } +} + +void scheduler_dp_ll_tick(void) +{ + struct scheduler_dp_data *dp_sch = scheduler_get_data(SOF_SCHEDULE_DP); + + if (!dp_sch) + return; + + pthread_mutex_lock(&dp_sch->lock); + scheduler_dp_recalculate_thread(dp_sch); + while (dp_sch->running_count > 0) + pthread_cond_wait(&dp_sch->tick_done_cond, &dp_sch->lock); + pthread_mutex_unlock(&dp_sch->lock); +} + +static void *dp_thread_fn(void *arg) +{ + struct task *task = arg; + struct task_dp_pdata *pdata = task->priv_data; + struct scheduler_dp_data *dp_sch = scheduler_get_data(SOF_SCHEDULE_DP); + enum task_state state; + bool task_stop = false; + uint32_t event; + + host_cpu_set_id(task->core); + + pthread_mutex_lock(&dp_sch->lock); + + do { + while (pdata->event == 0) + pthread_cond_wait(&pdata->cond, &dp_sch->lock); + + event = pdata->event; + pdata->event = 0; + + if (event & DP_TASK_EVENT_PROCESS) { + state = task->state; + if (task->state == SOF_TASK_STATE_RUNNING) { + uint64_t t0, t1; + uint32_t core_dp_cnt = 0; + struct list_item *it; + + list_for_item(it, &dp_sch->tasks) { + struct task *t = container_of(it, struct task, list); + + if (t->core == task->core) + core_dp_cnt++; + } + pthread_mutex_unlock(&dp_sch->lock); + t0 = host_telem_timestamp_cycles(); + state = task_run(task); + t1 = host_telem_timestamp_cycles(); + host_telem_core_dp_run(task->core, core_dp_cnt, + (t1 > t0) ? (t1 - t0) : 1); + pthread_mutex_lock(&dp_sch->lock); + } + + if (task->state == SOF_TASK_STATE_RUNNING) { + task->state = state; + switch (state) { + case SOF_TASK_STATE_RESCHEDULE: + task->state = SOF_TASK_STATE_QUEUED; + break; + case SOF_TASK_STATE_CANCEL: + case SOF_TASK_STATE_COMPLETED: + list_item_del(&task->list); + break; + default: + list_item_del(&task->list); + task->state = SOF_TASK_STATE_CANCEL; + break; + } + } + + task_stop = task->state == SOF_TASK_STATE_COMPLETED || + task->state == SOF_TASK_STATE_CANCEL; + + if (dp_sch->running_count > 0) { + dp_sch->running_count--; + if (dp_sch->running_count == 0) + pthread_cond_broadcast(&dp_sch->tick_done_cond); + } + } + + if (event & DP_TASK_EVENT_CANCEL) + task_stop = true; + } while (!task_stop); + + pthread_mutex_unlock(&dp_sch->lock); + + if (task->state == SOF_TASK_STATE_COMPLETED) + task_complete(task); + + return NULL; +} + +static int scheduler_dp_task_stop(void *data, struct task *task) +{ + struct scheduler_dp_data *dp_sch = data; + struct task_dp_pdata *pdata = task->priv_data; + pthread_t tid = 0; + bool join_thread = false; + + pthread_mutex_lock(&dp_sch->lock); + + while (dp_sch->running_count > 0) + pthread_cond_wait(&dp_sch->tick_done_cond, &dp_sch->lock); + + if (task->state == SOF_TASK_STATE_QUEUED || + task->state == SOF_TASK_STATE_RUNNING) + list_item_del(&task->list); + + task->state = SOF_TASK_STATE_CANCEL; + pdata->event |= DP_TASK_EVENT_CANCEL; + pthread_cond_signal(&pdata->cond); + + if (pdata->thread_created) { + tid = pdata->thread_id; + pdata->thread_created = false; + join_thread = true; + } + + pthread_mutex_unlock(&dp_sch->lock); + + if (join_thread) + pthread_join(tid, NULL); + + return 0; +} + +static int scheduler_dp_task_free(void *data, struct task *task) +{ + struct task_dp_pdata *pdata = task->priv_data; + + scheduler_dp_task_stop(data, task); + pthread_cond_destroy(&pdata->cond); + task->state = SOF_TASK_STATE_FREE; + rfree(task); + + return 0; +} + +static int scheduler_dp_task_shedule(void *data, struct task *task, uint64_t start, + uint64_t period) +{ + struct scheduler_dp_data *dp_sch = data; + struct task_dp_pdata *pdata = task->priv_data; + int ret; + + pthread_mutex_lock(&dp_sch->lock); + + if (task_is_active(task)) { + pthread_mutex_unlock(&dp_sch->lock); + return 0; + } + + if (task->state != SOF_TASK_STATE_INIT && + task->state != SOF_TASK_STATE_CANCEL && + task->state != SOF_TASK_STATE_COMPLETED) { + pthread_mutex_unlock(&dp_sch->lock); + return -EINVAL; + } + + if (!pdata->thread_created) { + int host_cpu; + + pdata->event = 0; + ret = pthread_create(&pdata->thread_id, NULL, dp_thread_fn, task); + if (ret != 0) { + pthread_mutex_unlock(&dp_sch->lock); + return -ret; + } + host_cpu = host_pin_thread_to_dsp_core(pdata->thread_id, task->core); + pdata->thread_created = true; + tr_info(&dp_tr, "DP thread created for task %p on DSP core %d (host CPU %d)", + task, task->core, host_cpu); + } + + task->state = SOF_TASK_STATE_QUEUED; + list_item_prepend(&task->list, &dp_sch->tasks); + pdata->mod->dp_startup_delay = true; + pdata->ll_cycles_to_start = 0; + + pthread_mutex_unlock(&dp_sch->lock); + return 0; +} + +static void dp_scheduler_free(void *data, uint32_t flags) +{ + struct scheduler_dp_data *dp_sch = data; + + if (!dp_sch) + return; + + pthread_mutex_destroy(&dp_sch->lock); + pthread_cond_destroy(&dp_sch->tick_done_cond); + free(dp_sch); +} + +static struct scheduler_ops schedule_dp_ops = { + .schedule_task = scheduler_dp_task_shedule, + .schedule_task_cancel = scheduler_dp_task_stop, + .schedule_task_free = scheduler_dp_task_free, + .scheduler_free = dp_scheduler_free, +}; + +int scheduler_dp_init(void) +{ + struct scheduler_dp_data *dp_sch; + + dp_sch = calloc(1, sizeof(*dp_sch)); + if (!dp_sch) + return -ENOMEM; + + list_init(&dp_sch->tasks); + pthread_mutex_init(&dp_sch->lock, NULL); + pthread_cond_init(&dp_sch->tick_done_cond, NULL); + + scheduler_init(SOF_SCHEDULE_DP, &schedule_dp_ops, dp_sch); + + return 0; +} + +int scheduler_dp_task_init(struct task **task, + const struct sof_uuid_entry *uid, + const struct task_ops *ops, + struct processing_module *mod, + uint16_t core, + size_t stack_size, + uint32_t options) +{ + struct { + struct task task; + struct task_dp_pdata pdata; + } *task_memory; + struct task_dp_pdata *pdata; + int ret; + + (void)stack_size; + + task_memory = rzalloc(SOF_MEM_FLAG_USER, sizeof(*task_memory)); + if (!task_memory) { + tr_err(&dp_tr, "scheduler_dp_task_init: memory alloc failed"); + return -ENOMEM; + } + + ret = schedule_task_init(&task_memory->task, uid, SOF_SCHEDULE_DP, 0, ops->run, + mod, core, options); + if (ret < 0) { + tr_err(&dp_tr, "scheduler_dp_task_init: schedule_task_init failed"); + rfree(task_memory); + return ret; + } + + pdata = &task_memory->pdata; + pthread_cond_init(&pdata->cond, NULL); + pdata->mod = mod; + + task_memory->task.ops.complete = ops->complete; + task_memory->task.ops.get_deadline = ops->get_deadline; + task_memory->task.state = SOF_TASK_STATE_INIT; + task_memory->task.core = core; + task_memory->task.priv_data = pdata; + + ret = pthread_create(&pdata->thread_id, NULL, dp_thread_fn, &task_memory->task); + if (ret != 0) { + tr_err(&dp_tr, "scheduler_dp_task_init: pthread_create failed: %d", ret); + pthread_cond_destroy(&pdata->cond); + rfree(task_memory); + return -ret; + } + ret = host_pin_thread_to_dsp_core(pdata->thread_id, core); + pdata->thread_created = true; + tr_info(&dp_tr, "DP thread initialized for module %#x on DSP core %d (host CPU %d)", + dev_comp_id(mod->dev), core, ret); + + *task = &task_memory->task; + return 0; +} + +void scheduler_get_task_info_dp(struct scheduler_props *scheduler_props, + uint32_t *data_off_size) +{ + (void)scheduler_props; + (void)data_off_size; +} diff --git a/src/platform/library/schedule/host_schedule.h b/src/platform/library/schedule/host_schedule.h new file mode 100644 index 000000000000..3376f90f028f --- /dev/null +++ b/src/platform/library/schedule/host_schedule.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. All rights reserved. + */ + +#ifndef __LIBRARY_SCHEDULE_HOST_SCHEDULE_H__ +#define __LIBRARY_SCHEDULE_HOST_SCHEDULE_H__ + +#include + +int host_pin_thread_to_dsp_core(pthread_t thread, int dsp_core); + +#endif /* __LIBRARY_SCHEDULE_HOST_SCHEDULE_H__ */ diff --git a/src/platform/library/schedule/ll_schedule.c b/src/platform/library/schedule/ll_schedule.c index f865f4db18e9..079ca00d53f1 100644 --- a/src/platform/library/schedule/ll_schedule.c +++ b/src/platform/library/schedule/ll_schedule.c @@ -1,41 +1,57 @@ // SPDX-License-Identifier: BSD-3-Clause // -// Copyright(c) 2019 Intel Corporation. All rights reserved. +// Copyright(c) 2019-2026 Intel Corporation. All rights reserved. // // Author: Tomasz Lauda -#define _GNU_SOURCE - #include #include #include +#include #include #include +#include +#include #include #include #include -#include -#include #include - /* scheduler testbench definition */ +/* Single-threaded cooperative LL scheduler for Xtensa simulator (xt-run) */ SOF_DEFINE_REG_UUID(ll_sched_lib); DECLARE_TR_CTX(ll_tr, SOF_UUID(ll_sched_lib_uuid), LOG_LEVEL_INFO); +static int host_current_core_id; + /* list of all tasks */ static struct list_item sched_list; +int arch_cpu_get_id(void) +{ + return host_current_core_id; +} + +void host_cpu_set_id(int id) +{ + host_current_core_id = id; +} + void schedule_ll_run_tasks(void) { struct list_item *tlist, *tlist_; struct task *task; enum task_state state; + uint64_t core_cycles[CONFIG_CORE_COUNT] = { 0 }; + uint32_t core_tasks[CONFIG_CORE_COUNT] = { 0 }; + int c; /* list empty then return */ if (list_is_empty(&sched_list)) fprintf(stdout, "LL scheduler thread exit - list empty\n"); + else + host_telem_ll_tick_begin(); /* iterate through the task list */ list_for_item_safe(tlist, tlist_, &sched_list) { @@ -43,9 +59,18 @@ void schedule_ll_run_tasks(void) /* only run queued tasks */ if (task->state == SOF_TASK_STATE_QUEUED) { + uint64_t t0, t1; + int core = (task->core < CONFIG_CORE_COUNT) ? task->core : 0; + task->state = SOF_TASK_STATE_RUNNING; + host_cpu_set_id(task->core); + t0 = host_telem_timestamp_cycles(); state = task_run(task); + t1 = host_telem_timestamp_cycles(); + + core_tasks[core]++; + core_cycles[core] += (t1 > t0) ? (t1 - t0) : 1; if (state == SOF_TASK_STATE_COMPLETED) { task->state = SOF_TASK_STATE_COMPLETED; @@ -55,6 +80,17 @@ void schedule_ll_run_tasks(void) } } } + + for (c = 0; c < CONFIG_CORE_COUNT; c++) { + if (core_tasks[c] > 0) + host_telem_core_ll_tick(c, core_tasks[c], core_cycles[c]); + } + + host_cpu_set_id(0); + +#if CONFIG_ZEPHYR_DP_SCHEDULER + scheduler_dp_ll_tick(); +#endif } /* schedule new LL task */ @@ -121,7 +157,7 @@ int schedule_task_init_ll(struct task *task, int16_t priority, enum task_state (*run)(void *data), void *data, uint16_t core, uint32_t flags) { - return schedule_task_init(task, uid, SOF_SCHEDULE_LL_TIMER, 0, run, + return schedule_task_init(task, uid, SOF_SCHEDULE_LL_TIMER, priority, run, data, core, flags); } diff --git a/src/platform/library/schedule/ll_schedule_pthread.c b/src/platform/library/schedule/ll_schedule_pthread.c new file mode 100644 index 000000000000..411fc0b39967 --- /dev/null +++ b/src/platform/library/schedule/ll_schedule_pthread.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2019-2026 Intel Corporation. All rights reserved. +// +// Author: Tomasz Lauda + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "host_schedule.h" + +/* Host multi-threaded LL scheduler */ + +SOF_DEFINE_REG_UUID(ll_sched_lib); + +DECLARE_TR_CTX(ll_tr, SOF_UUID(ll_sched_lib_uuid), LOG_LEVEL_INFO); + +struct ll_core_worker { + int core_id; + int host_cpu; + struct list_item sched_list; + pthread_t thread_id; + bool thread_created; + pthread_cond_t wake_cond; + bool run_tick; + bool stop; +}; + +static __thread int host_current_core_id; +static cpu_set_t host_allowed_cpuset; +static bool host_cpuset_initialized; + +static struct ll_core_worker ll_workers[CONFIG_CORE_COUNT]; +static pthread_mutex_t ll_sched_lock; +static pthread_cond_t ll_tick_done_cond; +static uint32_t ll_running_cores; + +int arch_cpu_get_id(void) +{ + return host_current_core_id; +} + +void host_cpu_set_id(int id) +{ + host_current_core_id = id; +} + +static void init_host_cpuset(void) +{ + if (host_cpuset_initialized) + return; + CPU_ZERO(&host_allowed_cpuset); + if (sched_getaffinity(0, sizeof(host_allowed_cpuset), &host_allowed_cpuset) == 0 && + CPU_COUNT(&host_allowed_cpuset) > 0) + host_cpuset_initialized = true; +} + +int host_pin_thread_to_dsp_core(pthread_t thread, int dsp_core) +{ + cpu_set_t cpuset; + int num_cpus, target_idx, count = 0; + int host_cpu = -1; + int i; + + init_host_cpuset(); + if (!host_cpuset_initialized) + return -EINVAL; + + num_cpus = CPU_COUNT(&host_allowed_cpuset); + if (num_cpus <= 0) + return -EINVAL; + + target_idx = ((dsp_core >= 0) ? dsp_core : 0) % num_cpus; + for (i = 0; i < CPU_SETSIZE; i++) { + if (CPU_ISSET(i, &host_allowed_cpuset)) { + if (count == target_idx) { + host_cpu = i; + break; + } + count++; + } + } + + if (host_cpu < 0) + return -EINVAL; + + CPU_ZERO(&cpuset); + CPU_SET(host_cpu, &cpuset); + if (pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset) != 0) + return -errno; + + return host_cpu; +} + +static void *ll_core_thread_fn(void *arg) +{ + struct ll_core_worker *worker = arg; + struct list_item *tlist, *tlist_; + struct task *task; + enum task_state state; + + host_cpu_set_id(worker->core_id); + + pthread_mutex_lock(&ll_sched_lock); + + while (!worker->stop) { + while (!worker->run_tick && !worker->stop) + pthread_cond_wait(&worker->wake_cond, &ll_sched_lock); + + if (worker->stop) + break; + + worker->run_tick = false; + pthread_mutex_unlock(&ll_sched_lock); + + uint64_t tick_start = host_telem_timestamp_cycles(); + uint32_t tasks_run = 0; + + /* iterate through this core's LL task list */ + list_for_item_safe(tlist, tlist_, &worker->sched_list) { + task = container_of(tlist, struct task, list); + + /* only run queued tasks */ + if (task->state == SOF_TASK_STATE_QUEUED) { + task->state = SOF_TASK_STATE_RUNNING; + + state = task_run(task); + tasks_run++; + + if (state == SOF_TASK_STATE_COMPLETED) { + task->state = SOF_TASK_STATE_COMPLETED; + list_item_del(&task->list); + } else if (task->state == SOF_TASK_STATE_RUNNING) { + task->state = SOF_TASK_STATE_QUEUED; + } + } + } + + if (tasks_run > 0) + host_telem_core_ll_tick(worker->core_id, tasks_run, + host_telem_timestamp_cycles() - tick_start); + + pthread_mutex_lock(&ll_sched_lock); + if (ll_running_cores > 0) { + ll_running_cores--; + if (ll_running_cores == 0) + pthread_cond_broadcast(&ll_tick_done_cond); + } + } + + pthread_mutex_unlock(&ll_sched_lock); + return NULL; +} + +void schedule_ll_run_tasks(void) +{ + bool any_active = false; + int c; + + pthread_mutex_lock(&ll_sched_lock); + + for (c = 0; c < CONFIG_CORE_COUNT; c++) { + if (!list_is_empty(&ll_workers[c].sched_list)) { + if (!any_active) { + host_telem_ll_tick_begin(); + any_active = true; + } + ll_workers[c].run_tick = true; + ll_running_cores++; + pthread_cond_signal(&ll_workers[c].wake_cond); + } + } + + if (!any_active) + fprintf(stdout, "LL scheduler thread exit - list empty\n"); + + while (ll_running_cores > 0) + pthread_cond_wait(&ll_tick_done_cond, &ll_sched_lock); + + pthread_mutex_unlock(&ll_sched_lock); + +#if CONFIG_ZEPHYR_DP_SCHEDULER + scheduler_dp_ll_tick(); +#endif +} + +/* schedule new LL task */ +static int schedule_ll_task(void *data, struct task *task, uint64_t start, + uint64_t period) +{ + int core = (task->core < CONFIG_CORE_COUNT) ? task->core : 0; + struct ll_core_worker *worker = &ll_workers[core]; + int ret; + + pthread_mutex_lock(&ll_sched_lock); + + if (task->state == SOF_TASK_STATE_QUEUED || + task->state == SOF_TASK_STATE_RUNNING) { + pthread_mutex_unlock(&ll_sched_lock); + return 0; + } + + if (!worker->thread_created) { + worker->stop = false; + worker->run_tick = false; + ret = pthread_create(&worker->thread_id, NULL, ll_core_thread_fn, worker); + if (ret != 0) { + pthread_mutex_unlock(&ll_sched_lock); + return -ret; + } + worker->host_cpu = host_pin_thread_to_dsp_core(worker->thread_id, core); + worker->thread_created = true; + tr_info(&ll_tr, "LL worker thread created for DSP core %d (host CPU %d)", + core, worker->host_cpu); + } else if (list_is_empty(&worker->sched_list)) { + tr_info(&ll_tr, "LL worker thread created for DSP core %d (host CPU %d)", + core, worker->host_cpu); + } + + /* add task to core's list */ + list_item_prepend(&task->list, &worker->sched_list); + task->state = SOF_TASK_STATE_QUEUED; + task->start = 0; + + pthread_mutex_unlock(&ll_sched_lock); + + return 0; +} + +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY +static void ll_scheduler_free(void *data, uint32_t flags) +{ + int c; + + for (c = 0; c < CONFIG_CORE_COUNT; c++) { + pthread_t tid = 0; + bool join = false; + + pthread_mutex_lock(&ll_sched_lock); + if (ll_workers[c].thread_created) { + ll_workers[c].stop = true; + pthread_cond_signal(&ll_workers[c].wake_cond); + tid = ll_workers[c].thread_id; + ll_workers[c].thread_created = false; + join = true; + } + pthread_mutex_unlock(&ll_sched_lock); + + if (join) + pthread_join(tid, NULL); + + pthread_cond_destroy(&ll_workers[c].wake_cond); + } + + pthread_mutex_destroy(&ll_sched_lock); + pthread_cond_destroy(&ll_tick_done_cond); + free(data); +} +#endif + +/* TODO: scheduler free and cancel APIs can merge as part of Zephyr */ +static int schedule_ll_task_cancel(void *data, struct task *task) +{ + pthread_mutex_lock(&ll_sched_lock); + + if (task->state == SOF_TASK_STATE_QUEUED || + task->state == SOF_TASK_STATE_RUNNING) + list_item_del(&task->list); + + /* delete task */ + task->state = SOF_TASK_STATE_CANCEL; + + pthread_mutex_unlock(&ll_sched_lock); + + return 0; +} + +/* TODO: scheduler free and cancel APIs can merge as part of Zephyr */ +static int schedule_ll_task_free(void *data, struct task *task) +{ + pthread_mutex_lock(&ll_sched_lock); + + if (task->state == SOF_TASK_STATE_QUEUED || + task->state == SOF_TASK_STATE_RUNNING) + list_item_del(&task->list); + + task->state = SOF_TASK_STATE_FREE; + + pthread_mutex_unlock(&ll_sched_lock); + + return 0; +} + +static struct scheduler_ops schedule_ll_ops = { + .schedule_task = schedule_ll_task, + .schedule_task_running = NULL, + .reschedule_task = NULL, + .schedule_task_cancel = schedule_ll_task_cancel, + .schedule_task_free = schedule_ll_task_free, +#if CONFIG_SOF_BOOT_TEST_STANDALONE || CONFIG_LIBRARY + .scheduler_free = ll_scheduler_free, +#endif +}; + +int schedule_task_init_ll(struct task *task, + const struct sof_uuid_entry *uid, uint16_t type, + int16_t priority, enum task_state (*run)(void *data), + void *data, uint16_t core, uint32_t flags) +{ + return schedule_task_init(task, uid, SOF_SCHEDULE_LL_TIMER, priority, run, + data, core, flags); +} + +/* initialize scheduler */ +int scheduler_init_ll(struct ll_schedule_domain *domain) +{ + int c; + + tr_info(&ll_tr, "ll_scheduler_init()"); + + pthread_mutex_init(&ll_sched_lock, NULL); + pthread_cond_init(&ll_tick_done_cond, NULL); + + for (c = 0; c < CONFIG_CORE_COUNT; c++) { + ll_workers[c].core_id = c; + ll_workers[c].host_cpu = -1; + ll_workers[c].thread_created = false; + ll_workers[c].run_tick = false; + ll_workers[c].stop = false; + list_init(&ll_workers[c].sched_list); + pthread_cond_init(&ll_workers[c].wake_cond, NULL); + } + + scheduler_init(SOF_SCHEDULE_LL_TIMER, &schedule_ll_ops, NULL); + + return 0; +} diff --git a/src/schedule/Kconfig b/src/schedule/Kconfig index f1383e70103f..a61f697d29a3 100644 --- a/src/schedule/Kconfig +++ b/src/schedule/Kconfig @@ -71,3 +71,44 @@ config TWB_IPC_TASK depends on ZEPHYR_TWB_SCHEDULER help Switch IPC task to TWB scheduler. + +config PIPELINE_2_0 + bool "Enable pipeline 2.0 changes" + depends on IPC_MAJOR_4 + default y if ACE || LIBRARY + help + This flag enables changes to new pipeline structure, known as pipeline2_0 + It is required for certain new features, like DP_SCHEDULER. + +config ZEPHYR_DP_SCHEDULER + bool "use thread based DP scheduler" + default y if ACE || LIBRARY + depends on IPC_MAJOR_4 + depends on (ZEPHYR_SOF_MODULE && ACE) || LIBRARY + depends on PIPELINE_2_0 + imply XTENSA_HIFI_SHARING if XTENSA_HIFI + help + Enable Data Processing preemptive scheduler based on + preemptive threads (Zephyr threads on firmware targets, pthreads on host). + DP modules can be located in different cores than LL pipeline modules, may have + different tick (i.e. 300ms for speech recognition, etc.) + +config ZEPHYR_DP_SCHEDULER_MIN_STACK_SIZE + int "Minimum stack size for DP processing thread" + default 512 + help + Defines the minimum stack size allowed for DP processing + threads despite what is requested in the module init IPC + ext_init payload. If the stack size requested in the IPC is + smaller than this, then the value defined here takes over. + +config CROSS_CORE_STREAM + bool "Enable cross-core connected pipelines" + default y if IPC_MAJOR_4 + help + Enables support for pipelines from different cores to be + connected together cross-core. So stream can travel from one + core to another. Note, this is different from "multicore" + support. In SOF "multicore" support means different streams + can be processed on different cores, however, each stream + is processed entirely on single core. diff --git a/tools/testbench/CMakeLists.txt b/tools/testbench/CMakeLists.txt index d73d20ed2c5b..47d62d365bf3 100644 --- a/tools/testbench/CMakeLists.txt +++ b/tools/testbench/CMakeLists.txt @@ -4,10 +4,19 @@ cmake_minimum_required(VERSION 3.13) project(SOF_TESTBENCH C) +option(TESTBENCH_XTENSA_SIM "Build single-threaded testbench for Xtensa simulator" OFF) +if(CMAKE_C_COMPILER MATCHES "xt-|xtensa-" OR DEFINED ENV{XTENSA_CORE}) + set(TESTBENCH_XTENSA_SIM ON CACHE BOOL "Build single-threaded testbench for Xtensa simulator" FORCE) +endif() + include(../../scripts/cmake/misc.cmake) include(CheckCCompilerFlag) -set(default_asoc_h "/usr/include/alsa/sound/uapi/asoc.h") +if(EXISTS "/usr/include/alsa/sound/uapi/asoc.h") + set(default_asoc_h "/usr/include/alsa/sound/uapi/asoc.h") +else() + set(default_asoc_h "/usr/include/sound/asoc.h") +endif() file(READ "../../src/arch/host/configs/library_defconfig" library_defconfig) @@ -28,6 +37,9 @@ add_executable(${testbench} topology_ipc3.c topology_ipc4.c ) +if(NOT TESTBENCH_XTENSA_SIM) + target_sources(${testbench} PRIVATE remote_server.c) +endif() sof_append_relative_path_definitions(${testbench}) @@ -56,11 +68,28 @@ endif() target_compile_options(${testbench} PRIVATE -g -O3 -Wall -Werror -Wmissing-prototypes ${implicit_fallthrough} -DCONFIG_LIBRARY -imacros${config_h}) +if(TESTBENCH_XTENSA_SIM) + target_compile_definitions(${testbench} PRIVATE CONFIG_XT_SIMULATOR=1) +endif() +if(CMAKE_C_COMPILER MATCHES "xt-|xtensa-") + target_compile_options(${testbench} PRIVATE + -U__INT32_TYPE__ -D__INT32_TYPE__=int + -U__UINT32_TYPE__ "-D__UINT32_TYPE__=unsigned int" + -Wno-format) +endif() target_link_libraries(${testbench} PRIVATE -lm) install(TARGETS ${testbench} DESTINATION bin) +if(NOT TESTBENCH_XTENSA_SIM) + add_executable(sof-tb-ctl tb_ctl.c) + target_include_directories(sof-tb-ctl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) + target_compile_options(sof-tb-ctl PRIVATE -g -O3 -Wall -Werror -Wmissing-prototypes + ${implicit_fallthrough}) + install(TARGETS sof-tb-ctl DESTINATION bin) +endif() + include(ExternalProject) # Should we do this? Only for sof_ep, not for parser_ep? @@ -75,6 +104,7 @@ ExternalProject_Add(sof_ep -DCMAKE_VERBOSE_MAKEFILE=${CMAKE_VERBOSE_MAKEFILE} -DINIT_CONFIG=library_defconfig -DCONFIG_H_PATH=${config_h} + -DTESTBENCH_XTENSA_SIM=${TESTBENCH_XTENSA_SIM} BUILD_ALWAYS 1 BUILD_BYPRODUCTS "${sof_install_directory}/lib/libsof.a" ) @@ -106,6 +136,9 @@ add_dependencies(${testbench} sof_parser_lib) target_link_libraries(${testbench} PRIVATE sof_library) target_link_libraries(${testbench} PRIVATE sof_parser_lib) target_link_libraries(${testbench} PRIVATE m) +if(NOT TESTBENCH_XTENSA_SIM) + target_link_libraries(${testbench} PRIVATE pthread) +endif() target_include_directories(${testbench} PRIVATE ${sof_install_directory}/include) target_include_directories(${testbench} PRIVATE ${parser_install_dir}/include) @@ -114,3 +147,32 @@ set_target_properties(${testbench} INSTALL_RPATH "${sof_install_directory}/lib" INSTALL_RPATH_USE_LINK_PATH TRUE ) + +if(NOT TESTBENCH_XTENSA_SIM) + enable_testing() + add_test(NAME testbench_tier1_modules + COMMAND ${CMAKE_COMMAND} -E env + TB_BIN=$ + TB_CTL_BIN=$ + ${CMAKE_CURRENT_SOURCE_DIR}/run_test_suite.sh --tier 1) + add_test(NAME testbench_tier2_complex_dags + COMMAND ${CMAKE_COMMAND} -E env + TB_BIN=$ + TB_CTL_BIN=$ + ${CMAKE_CURRENT_SOURCE_DIR}/run_test_suite.sh --tier 2) + add_test(NAME testbench_tier3_ll_dp + COMMAND ${CMAKE_COMMAND} -E env + TB_BIN=$ + TB_CTL_BIN=$ + ${CMAKE_CURRENT_SOURCE_DIR}/run_test_suite.sh --tier 3) + add_test(NAME testbench_tier4_multicore + COMMAND ${CMAKE_COMMAND} -E env + TB_BIN=$ + TB_CTL_BIN=$ + ${CMAKE_CURRENT_SOURCE_DIR}/run_test_suite.sh --tier 4) + add_test(NAME testbench_tier5_controls_remote + COMMAND ${CMAKE_COMMAND} -E env + TB_BIN=$ + TB_CTL_BIN=$ + ${CMAKE_CURRENT_SOURCE_DIR}/run_test_suite.sh --tier 5) +endif() diff --git a/tools/testbench/README.md b/tools/testbench/README.md index 82e1630dc771..a0d55162f574 100644 --- a/tools/testbench/README.md +++ b/tools/testbench/README.md @@ -4,6 +4,13 @@ * Simulate IPC3 and IPC4 SOF versions with run of set of processing components based on desired topology. + * Supports arbitrary multi-pipeline DAG topologies, including linear chains, + 1-to-N fan-out (e.g., crossover), N-to-1 fan-in (e.g., AEC reference + mic, + deepbuffer + main playback mixing), diamond graphs, `module-copier` + (`SND_SOC_TPLG_DAPM_BUFFER`), and concurrent multi-PCM playback and capture. + * Multi-core and multi-threaded execution on host CPUs (`pthread` + + `pthread_setaffinity_np`) honoring topology `core` / `core_id` assignments + for both Low Latency (LL) pipelines and Data Processing (DP) modules. * Replaces host and dai components with file I/O with raw binary S16_LE/S24_LE/S32_LE or text format files for audio waveforms. * Much faster than real-time execution in native build, e.g. x86 on @@ -14,6 +21,105 @@ * Allows easy use of conventional debugger, profiler, leak and memory check tools usage for DSP firmware code. +### Architecture + +#### Multi-Pipeline Graphs & Multi-Pin Binding + +`sof-testbench4` parses the full topology widget, route, and PCM lists and constructs the active subgraph selected via `-p `: + + * **Multi-Source / Multi-Sink Discovery**: Automatically discovers all active `AIF_IN` (playback host), `AIF_OUT` (capture host), `DAI_IN` (playback DAI), and `DAI_OUT` (capture DAI) endpoints across enabled pipelines and binds them in order to the comma-separated `-i` input files and `-o` output files. + * **Pin Binding & Sink Formats**: Parses `SOF_TKN_COMP_INPUT_PIN_BINDING_WNAME` and `SOF_TKN_COMP_OUTPUT_PIN_BINDING_WNAME` tuples to assign exact source and destination queue IDs (`src_queue_id`, `dst_queue_id`) for multi-pin modules (such as `crossover`, `mixin`/`mixout`, `google-rtc-audio-processing`, and multi-sink `copier`s), and sends `SOF_IPC4_COPIER_MODULE_CFG_PARAM_SET_SINK_FORMAT` IPCs for non-zero output pins. + * **DAG Traversal**: Prepares, sets up, binds, triggers, and tears down branching and merging pipeline graphs without duplicate widget initialization or double unbinding. + +#### Multi-Core LL & DP Thread Scheduling + +`sof-testbench4` supports up to `CONFIG_CORE_COUNT=8` virtual DSP cores (`CONFIG_CROSS_CORE_STREAM=y`) mapped onto host CPU cores via `sched_getaffinity` and `pthread_setaffinity_np`: + + * **Topology Core Assignment**: + * Pipelines (`SND_SOC_TPLG_DAPM_SCHEDULER`) use `SOF_TKN_SCHED_CORE` (`core`) and `SOF_TKN_SCHED_PRIORITY` (`priority`), propagated via `SOF_IPC4_GLB_CREATE_PIPELINE`. + * Widgets use `SOF_TKN_COMP_CORE_ID` (`core_id`) and `SOF_TKN_COMP_SCHED_DOMAIN` (`LL` vs. `DP`), propagated via `SOF_IPC4_MOD_INIT_INSTANCE`. LL widgets inherit their parent pipeline's `core_id`, while DP widgets can reside on any DSP core independent of their parent LL pipeline. + * **Per-Core LL Worker Threads (`ll_schedule_pthread.c`)**: + * Each active DSP core `c` (`0 .. CONFIG_CORE_COUNT - 1`) spawns a dedicated persistent `pthread` worker pinned to host CPU `c % num_allowed_host_cpus` with thread-local `cpu_get_id() == c`. + * On each 1 ms tick (`schedule_ll_run_tasks()`), all active LL core worker threads run their core's queued LL pipeline tasks concurrently in parallel across host CPU cores and synchronize at the end of the LL cycle before triggering the DP scheduler. + * **Per-Module DP Worker Threads (`dp_schedule_pthread.c`)**: + * Each Data Processing (DP) module (`COMP_PROCESSING_DOMAIN_DP`) runs in its own dedicated `pthread` pinned to the host CPU core corresponding to the module's topology `core_id`. + * Cross-core and same-core LL-to-DP and DP-to-LL connections communicate through lock-free single-producer single-consumer (SPSC) shared `ring_buffer` instances. + * **Xtensa Simulator & Single-Threaded C Library Partitioning (`ll_schedule.c` / `dp_schedule.c`)**: + * When built for the Xtensa simulator (`xt-run` via `scripts/rebuild-testbench.sh -p `, or manually with `-DTESTBENCH_XTENSA_SIM=ON`), CMake automatically selects the pure standard-C cooperative schedulers (`ll_schedule.c` and `dp_schedule.c`) instead of `ll_schedule_pthread.c` and `dp_schedule_pthread.c`. + * This eliminates dependencies on ``, `-lpthread`, ``, and `clock_gettime` that are unavailable in the Xtensa newlib (`-mlsp=sim`) runtime, while executing DP modules synchronously inside `scheduler_dp_ll_tick()` so their instruction cycles are included in `XT_RSR_CCOUNT()` profiling. + +#### Per-Core, Per-Pipeline, and Per-Module Scheduling & Buffer Entry/Exit Telemetry (`telemetry_sched.c`) + +`sof-testbench4` instruments every DSP core's LL and DP schedulers, every pipeline walk (`pipeline_copy()`), and every LL (`comp_copy()`) and DP (`dp_task_run()`) module execution: + + * **Per-Core LL & DP Scheduler Telemetry**: Tracks LL tick counts, active LL tasks, LL average/peak/total DSP cycles and KCPS, DP thread run counts, active DP threads, DP average/peak/total DSP cycles and KCPS, and combined core utilization (`total_util_x100`) on every DSP core (`0 .. CONFIG_CORE_COUNT - 1`). + * **Per-Pipeline Telemetry**: Tracks copy iterations, priority, period, core assignment, LL and DP module counts, average/peak/total cycles, KCPS, and XRUN counts for each active pipeline. + * **Per-Module Scheduling & Buffer Entry/Exit Telemetry**: + * Tracks `iterations`, `last/avg/peak/total_cycles`, `avg/peak_kcps`, `ibs`, `obs`, `cpc` budget and `cpc_violations`, plus LL ring-buffer sync iterations and cycles for DP modules. + * On **entry** (before processing) and **exit** (after processing) of every module's `copy()` / `dp_task_run()` call, captures for each input (producer) and output (consumer) pin: + * Primary LL `comp_buffer` fill levels (`entry_avail_bytes`, `entry_free_bytes`, `exit_avail_bytes`, `exit_free_bytes`) and secondary DP `ring_buffer` fill levels (`dp_ring_size`, `dp_ring_entry_avail`, `dp_ring_exit_avail`). + * Exact bytes consumed/produced (`last_bytes_transferred`, `total_bytes_transferred`). + * Signal levels in `0.01 dBFS` (`entry_rms_dbfs_x100`, `entry_peak_dbfs_x100`, `exit_rms_dbfs_x100`, `exit_peak_dbfs_x100`) and rolling S16-normalized sample taps (`entry_samples[64]`, `exit_samples[64]`). + * **CLI & Remote Exposure**: Printed automatically at the end of every testbench run, exposed in SRAM Windows 2 & 3 and `SOF_NET_OP_TELEM_SNAPSHOT`, and queryable live via `sof-remote shell "sched-telem"` and `sof-remote shell "mod-buffers [comp_id|name]"`. + * **Accurate CPC, Core Frequency & SRAM/DRAM Workload Execution Time Simulation**: + * Enabled via `-w <0|1|2|off|virtual|realtime>`, `-F `, and `-M ` or dynamically over TCP via `SOF_NET_OP_WORKLOAD_SIM_CFG` (`0x0029`) and `sof-remote shell "workload-sim ..."`. + * Computes module cycle consumption from each module's topology `SOF_TKN_COMP_CPC` (`base_cfg.cpc`, per-module override, or calibrated module-type CPC), scales cycles by memory residency (`SRAM` 1.00x vs `DRAM` cache/bus stall multiplier + per-word transfer penalty), and computes execution time ($T_{\text{exec}} = \text{cycles} / f_{\text{core}}$), core utilization %, and 1 ms LL tick deadline overruns against each core's target clock frequency. + * Supports both fast virtual cycle/time accounting (`-w 1` / `virtual`) and real-time wall-clock pacing (`-w 2` / `realtime`). + +#### Network Remote Control & Telemetry Interface (`remote_server.c`) + +`sof-testbench4` exposes the `sof-remote` TCP binary framing protocol (`SOF_NET_MAGIC = 0x534F4652` `'SOFR'`, `SOF_NET_VERSION = 1`, default TCP port `9998`) via `-S ` and `-W`: + + * **Supported Opcodes**: + * `SOF_NET_OP_PING`, `SOF_NET_OP_GET_INFO`, `SOF_NET_OP_PM_CTL` (`query`, `release`/D3 pause, `wake`/D0 resume) + * `SOF_NET_OP_IPC_TX` (direct IPC4 message dispatch) and `SOF_NET_OP_IPC_LARGE_CFG` (BaseFW params 9, 12, 14, 15, 17, 18, 30 and live module `LARGE_CONFIG_GET`/`SET`) + * `SOF_NET_OP_WIN_INFO`, `SOF_NET_OP_WIN_READ`, `SOF_NET_OP_WIN_WRITE`, `SOF_NET_OP_WINSTREAM_IO` (emulated SRAM Windows 0–3 and debug slots) + * `SOF_NET_OP_TELEM_SNAPSHOT` (single-RPC composite telemetry snapshot consumed by `tools/softop/softop.py` and `sof-remote telem`) + * `SOF_NET_OP_WORKLOAD_SIM_CFG` (`0x0029`, query/configure CPC workload execution simulation mode, SRAM/DRAM residency, DRAM stall multiplier, per-core target clock frequencies, and per-module CPC/memory overrides via `sof-remote workload-sim ...`) + * `SOF_NET_OP_SHELL_EXEC` (`info`, `pipelines`, `modules`, `tplg-load [p1,p2,...]`, `ppl-create`, `ppl-state`, `ppl-delete`, `mod-bind`, `mod-unbind`, `mod-delete`, `controls`, live `amixer cset ...`, live `sof-ctl ...`, `stats`, `heap`, `sched-telem`, `sched-telem-reset`, `workload-sim [status|enable|disable|freq|mem|mod|reset]`, `mod-buffers [id|name]`, `kernel cycles`, `kernel threads`, `pause`, `resume`, `step [N]`, `loglevel <0..4>`, `quit`) + * `SOF_NET_OP_LLEXT_LOAD` (upload and load dynamic `.so` / `.llext` module images via `dlopen` / LLEXT table registration) + * `SOF_NET_OP_PROBE_CTL` (enumerate and read inter-component `struct comp_buffer` audio streams as probe points) + * `SOF_NET_OP_PCM_CTL` (`start-tone`, `stop-tone`, `status`, and `capture` from output streams) + * **Tick-Boundary Synchronization**: All network RPCs that inspect or mutate IPC/module/topology state acquire `g_tb_lock` between 1 ms LL ticks (`tb_remote_tick_begin()` / `tb_remote_tick_end()`), guaranteeing thread safety across multi-core LL and DP worker threads. + +### Command-Line Options + +``` +Usage: sof-testbench4 -i -o + +Options for processing: + -t + -i (up to 16 comma-separated input files) + -o (up to 16 comma-separated output files) + +Options to control test: + -d Sets the traces print level: + 0 all traces are suppressed + 1 shows error traces + 2 shows warning traces and previous + 3 shows info traces and previous + 4 shows debug traces and previous, plus other testbench debug messages + -p (up to 16 comma-separated pipeline IDs, default: 1) + -C + -P + -s