diff --git a/include/mpsl/mpsl_work.h b/include/mpsl/mpsl_work.h index bfa52de9caca..8043efdb9882 100644 --- a/include/mpsl/mpsl_work.h +++ b/include/mpsl/mpsl_work.h @@ -59,7 +59,7 @@ static inline void mpsl_work_submit(struct k_work *work) * processing other work items in a timely manner. * * @note This is a no-op if the work item is already scheduled or submitted, - * even if @p delay is @c K_NO_WAIT. + * even if @p delay is @c K_NO_WAIT. See @ref mpsl_work_reschedule(). * * @param dwork Address of delayable work item. * @@ -74,6 +74,31 @@ static inline void mpsl_work_schedule(struct k_work_delayable *dwork, k_timeout_ } } +/** @brief Reschedule a work item to the MPSL workqueue after a delay. + * + * @note Can be called by ISRs. + * + * @note + * Work items submitted to the MPSL workqueue should avoid using handlers + * that block or yield since this may prevent the MPSL workqueue from + * processing other work items in a timely manner. + * + * @note If the work item has not been scheduled before, this behaves the same + * as @ref mpsl_work_schedule(). + * + * @param dwork Address of delayable work item. + * + * @param delay the time to wait before submitting the work item. If @c + * K_NO_WAIT and the work is not pending this is equivalent to + * mpsl_work_submit(). + */ +static inline void mpsl_work_reschedule(struct k_work_delayable *dwork, k_timeout_t delay) +{ + if (k_work_reschedule_for_queue(&mpsl_work_q, dwork, delay) < 0) { + __ASSERT(false, "k_work_reschedule_for_queue() failed."); + } +} + #ifdef __cplusplus } #endif diff --git a/subsys/mpsl/init/mpsl_init.c b/subsys/mpsl/init/mpsl_init.c index af1131ad601b..1f6e6e9d00f8 100644 --- a/subsys/mpsl/init/mpsl_init.c +++ b/subsys/mpsl/init/mpsl_init.c @@ -26,7 +26,7 @@ #endif #if IS_ENABLED(CONFIG_MPSL_USE_ZEPHYR_PM) -#include +#include "../pm/mpsl_pm_utils.h> #endif #if IS_ENABLED(CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL) @@ -524,6 +524,10 @@ int32_t mpsl_lib_uninit(void) mpsl_lib_irq_disable(); +#if IS_ENABLED(CONFIG_MPSL_USE_ZEPHYR_PM) + mpsl_pm_utils_uninit(); +#endif + mpsl_uninit(); #if defined(CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL) diff --git a/subsys/mpsl/pm/mpsl_pm_utils.c b/subsys/mpsl/pm/mpsl_pm_utils.c index b6f39ec8d165..2e46aca00d87 100644 --- a/subsys/mpsl/pm/mpsl_pm_utils.c +++ b/subsys/mpsl/pm/mpsl_pm_utils.c @@ -10,7 +10,8 @@ #include #include -#include +#include +#include "mpsl_pm_utils.h" LOG_MODULE_REGISTER(mpsl_pm_utils, CONFIG_MPSL_LOG_LEVEL); @@ -22,6 +23,10 @@ LOG_MODULE_REGISTER(mpsl_pm_utils, CONFIG_MPSL_LOG_LEVEL); #define TIME_TO_REGISTER_EVENT_IN_ZEPHYR_US 1000 #define PM_MAX_LATENCY_HCI_COMMANDS_US 499999 +static void m_work_handler(struct k_work *work); +static K_WORK_DELAYABLE_DEFINE(m_pm_uninit_work, m_work_handler); +static atomic_t m_pm_enabled; + static uint8_t m_pm_prev_flag_value; static bool m_pm_event_is_registered; static uint32_t m_prev_lat_value_us; @@ -101,8 +106,19 @@ static void m_register_latency(void) } } +static void m_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + mpsl_pm_utils_work_handler(); +} + void mpsl_pm_utils_work_handler(void) { + if (!atomic_get(&m_pm_enabled)) { + pm_policy_latency_request_remove(&m_latency_req); + pm_policy_event_unregister(&m_evt); + return; + } m_register_event(); m_register_latency(); } @@ -111,6 +127,8 @@ void mpsl_pm_utils_init(void) { mpsl_pm_params_t params = {0}; + atomic_set(&m_pm_enabled, 1); + pm_policy_latency_request_add(&m_latency_req, PM_MAX_LATENCY_HCI_COMMANDS_US); m_prev_lat_value_us = PM_MAX_LATENCY_HCI_COMMANDS_US; @@ -119,3 +137,10 @@ void mpsl_pm_utils_init(void) m_pm_prev_flag_value = params.cnt_flag; m_pm_event_is_registered = false; } + +void mpsl_pm_utils_uninit(void) +{ + mpsl_pm_uninit(); + atomic_set(&m_pm_enabled, 0); + mpsl_work_reschedule(&m_pm_uninit_work, K_NO_WAIT); +} diff --git a/include/mpsl/mpsl_pm_utils.h b/subsys/mpsl/pm/mpsl_pm_utils.h similarity index 81% rename from include/mpsl/mpsl_pm_utils.h rename to subsys/mpsl/pm/mpsl_pm_utils.h index bfcea5d99459..060e8552b1ff 100644 --- a/include/mpsl/mpsl_pm_utils.h +++ b/subsys/mpsl/pm/mpsl_pm_utils.h @@ -17,6 +17,12 @@ extern "C" { */ void mpsl_pm_utils_init(void); +/** @brief Unitialize MPSL Power Management + * + * This routine uninitializes MPSL PM (via `mpsl_pm_uninit`). + */ +void mpsl_pm_utils_uninit(void); + /** @brief Handles MPSL Power Management work * * This calls Zephyr Power Management policy functions according diff --git a/tests/subsys/mpsl/pm/CMakeLists.txt b/tests/subsys/mpsl/pm/CMakeLists.txt index 1903f9f8c715..ead80ae195dc 100644 --- a/tests/subsys/mpsl/pm/CMakeLists.txt +++ b/tests/subsys/mpsl/pm/CMakeLists.txt @@ -13,9 +13,11 @@ project(pm_test) test_runner_generate(pm_test.c) # Create mocks for pm module. +cmock_handle(${CMAKE_CURRENT_SOURCE_DIR}/kernel_minimal_mock.h) cmock_handle(${ZEPHYR_BASE}/include/zephyr/pm/policy.h) cmock_handle(${ZEPHYR_NRFXLIB_MODULE_DIR}/mpsl/include/mpsl_pm.h) cmock_handle(${ZEPHYR_NRFXLIB_MODULE_DIR}/mpsl/include/mpsl_pm_config.h) +cmock_handle(${ZEPHYR_NRF_MODULE_DIR}/include/mpsl/mpsl_work.h) # Add Unit Under Test source files target_sources(app PRIVATE ${ZEPHYR_NRF_MODULE_DIR}/subsys/mpsl/pm/mpsl_pm_utils.c) @@ -24,7 +26,13 @@ target_sources(app PRIVATE ${ZEPHYR_NRF_MODULE_DIR}/subsys/mpsl/pm/mpsl_pm_utils target_sources(app PRIVATE pm_test.c) # Include paths -target_include_directories(app PRIVATE src) +target_include_directories(app PRIVATE + src + ${ZEPHYR_NRF_MODULE_DIR}/subsys/mpsl/pm) + +# Preinclude file to the UUT to redefine kernel and mpsl_work functions. +set_property(SOURCE ${ZEPHYR_NRF_MODULE_DIR}/subsys/mpsl/pm/mpsl_pm_utils.c + PROPERTY COMPILE_FLAGS "-include mocks/kernel_minimal_mock.h -include mocks/mpsl_work.h") # Options that cannot be passed through Kconfig fragments. target_compile_options(app PRIVATE diff --git a/tests/subsys/mpsl/pm/kernel_minimal_mock.h b/tests/subsys/mpsl/pm/kernel_minimal_mock.h new file mode 100644 index 000000000000..2fd1fbaa6562 --- /dev/null +++ b/tests/subsys/mpsl/pm/kernel_minimal_mock.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZEPHYR_INCLUDE_KERNEL_H_ +#define ZEPHYR_INCLUDE_KERNEL_H_ + +/* CMock chokes on zephyr/kernel.h, so we define the minimum required to build + * mpsl_pm and tests, then let CMock generate mocks from this file instead. + */ + +#include + +struct k_work { + void *handler; +}; + +typedef struct k_timeout { + uint64_t value; +} k_timeout_t; + +struct k_work_delayable { + void *handler; +}; + +#define Z_WORK_DELAYABLE_INITIALIZER(work_handler) {work_handler} +#define K_WORK_DELAYABLE_DEFINE(work, work_handler) \ + struct k_work_delayable work = Z_WORK_DELAYABLE_INITIALIZER(work_handler) + +#define Z_WORK_INITIALIZER(work_handler) {work_handler} +#define K_WORK_DEFINE(work, work_handler) \ + struct k_work_delayable work = Z_WORK_INITIALIZER(work_handler) + +k_timeout_t K_USEC(uint64_t d); + +#define K_NO_WAIT (k_timeout_t) {0} + +int64_t k_uptime_get(void); + +#endif /* ZEPHYR_INCLUDE_KERNEL_H_ */ diff --git a/tests/subsys/mpsl/pm/pm_test.c b/tests/subsys/mpsl/pm/pm_test.c index fda2c50846c5..53d94480e40a 100644 --- a/tests/subsys/mpsl/pm/pm_test.c +++ b/tests/subsys/mpsl/pm/pm_test.c @@ -13,8 +13,10 @@ #include "cmock_policy.h" #include "cmock_mpsl_pm.h" #include "cmock_mpsl_pm_config.h" +#include "cmock_mpsl_work.h" +#include "cmock_kernel_minimal_mock.h" -#include +#include "mpsl_pm_utils.h" #define PM_MAX_LATENCY_HCI_COMMANDS_US 499999 @@ -208,6 +210,35 @@ void test_register_update_and_deregister_event(void) run_test(&test_vectors[0], ARRAY_SIZE(test_vectors)); } +void test_uninit(void) +{ + mpsl_pm_params_t pm_params_initial = {0}; + + __cmock_pm_policy_latency_request_add_Expect(0, PM_MAX_LATENCY_HCI_COMMANDS_US); + __cmock_pm_policy_latency_request_add_IgnoreArg_req(); + + __cmock_mpsl_pm_init_Expect(); + + __cmock_mpsl_pm_params_get_ExpectAnyArgsAndReturn(true); + __cmock_mpsl_pm_params_get_ReturnThruPtr_p_params(&pm_params_initial); + + mpsl_pm_utils_init(); + + __cmock_mpsl_work_reschedule_Expect( + 0, (k_timeout_t) { 0 }); + __cmock_mpsl_work_reschedule_IgnoreArg_dwork(); + __cmock_mpsl_pm_uninit_Expect(); + + mpsl_pm_utils_uninit(); + + __cmock_pm_policy_latency_request_remove_Expect(0); + __cmock_pm_policy_latency_request_remove_IgnoreArg_req(); + __cmock_pm_policy_event_unregister_Expect(0); + __cmock_pm_policy_event_unregister_IgnoreArg_evt(); + + mpsl_pm_utils_work_handler(); +} + int main(void) { (void)unity_main();