From 208474b9481c485c1c9d423e35a483345d66ed8d Mon Sep 17 00:00:00 2001 From: Knut Eldhuset Date: Wed, 22 Jan 2025 15:16:31 +0100 Subject: [PATCH] mpsl: pm: Change init API and adds uninit API MPSL PM Refactor initialization API and add uninitialization. Change the initialization API to do not allow call it when it is already initialized or if uninitialization is started but not completed. The uninitialization API will wait for scheduled work queue uninit item to complete. If that is late the API will timeout. Uninitialize API will fail when the MPSL PM is not in initialized state. Unit tests are changed to cover changes in the API. Signed-off-by: Knut Eldhuset Signed-off-by: Piotr Pryga Co-authored-by: Piotr Pryga --- include/mpsl/mpsl_work.h | 62 ++++-- subsys/mpsl/init/mpsl_init.c | 26 ++- subsys/mpsl/pm/Kconfig | 14 ++ subsys/mpsl/pm/mpsl_pm_utils.c | 126 +++++++++-- .../mpsl => subsys/mpsl/pm}/mpsl_pm_utils.h | 16 +- tests/subsys/mpsl/pm/CMakeLists.txt | 17 +- tests/subsys/mpsl/pm/kernel_minimal_mock.h | 38 ++++ tests/subsys/mpsl/pm/pm_test.c | 210 +++++++++++++++--- 8 files changed, 423 insertions(+), 86 deletions(-) rename {include/mpsl => subsys/mpsl/pm}/mpsl_pm_utils.h (51%) create mode 100644 tests/subsys/mpsl/pm/kernel_minimal_mock.h diff --git a/include/mpsl/mpsl_work.h b/include/mpsl/mpsl_work.h index bfa52de9caca..e1d59705dd26 100644 --- a/include/mpsl/mpsl_work.h +++ b/include/mpsl/mpsl_work.h @@ -7,9 +7,9 @@ /** * @file mpsl_work.h * - * @defgroup mpsl_work Multiprotocol Service Layer workqueue. + * @defgroup mpsl_work Multiprotocol Service Layer work queue. * - * @brief Internal MPSL workqueue. + * @brief Internal MPSL work queue. * @{ */ @@ -24,23 +24,22 @@ extern "C" { extern struct k_work_q mpsl_work_q; -/** @brief Submit a work item to the MPSL workqueue. +/** @brief Submit a work item to the MPSL work queue. * * This routine submits work item @a work to be processed by the MPSL - * workqueue. If the work item is already pending in the MPSL workqueue or - * any other workqueue as a result of an earlier submission, this routine + * work queue. If the work item is already pending in the MPSL work queue or + * any other work queue as a result of an earlier submission, this routine * has no effect on the work item. If the work item has already been * processed, or is currently being processed, its work is considered * complete and the work item can be resubmitted. * - * @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 Work items submitted to the MPSL work queue should avoid using handlers + * that block or yield since this may prevent the MPSL work queue from + * processing other work items in a timely manner. * * @note Can be called by ISRs. * - * @param work Address of work item. + * @param work address of work item. */ static inline void mpsl_work_submit(struct k_work *work) { @@ -49,23 +48,22 @@ static inline void mpsl_work_submit(struct k_work *work) } } -/** @brief Submit an idle work item to the MPSL workqueue after a delay. +/** @brief Submit an idle work item to the MPSL work queue 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 Work items submitted to the MPSL work queue should avoid using handlers + * that block or yield since this may prevent the MPSL work queue from + * 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. + * @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(). + * @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_schedule(struct k_work_delayable *dwork, k_timeout_t delay) { @@ -74,6 +72,30 @@ static inline void mpsl_work_schedule(struct k_work_delayable *dwork, k_timeout_ } } +/** @brief Reschedule a work item to the MPSL work queue after a delay. + * + * @note Can be called by ISRs. + * + * @note Work items submitted to the MPSL work queue should avoid using handlers + * that block or yield since this may prevent the MPSL work queue from + * processing other work items in a timely manner. + * + * @note If the work item has not been scheduled before, this behavior is + * 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..4fd96e36047a 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) @@ -398,6 +398,13 @@ static int32_t mpsl_lib_init_internal(void) } #endif /* CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL */ +#if defined(CONFIG_MPSL_USE_ZEPHYR_PM) + err = mpsl_pm_utils_init(); + if (err) { + return err; + } +#endif /* CONFIG_MPSL_USE_ZEPHYR_PM */ + #if defined(CONFIG_SOC_SERIES_NRF54HX) /* Secure domain no longer enables DPPI channels for local domains, * MPSL now has to enable the ones it uses. @@ -442,10 +449,6 @@ static int mpsl_lib_init_sys(void) return err; } -#if IS_ENABLED(CONFIG_MPSL_USE_ZEPHYR_PM) - mpsl_pm_utils_init(); -#endif - #if IS_ENABLED(CONFIG_MPSL_DYNAMIC_INTERRUPTS) /* Ensure IRQs are disabled before attaching. */ mpsl_lib_irq_disable(); @@ -518,6 +521,10 @@ int32_t mpsl_lib_init(void) int32_t mpsl_lib_uninit(void) { #if IS_ENABLED(CONFIG_MPSL_DYNAMIC_INTERRUPTS) +#if defined(CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL) || defined(CONFIG_MPSL_USE_ZEPHYR_PM) + int err; +#endif /* CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL || CONFIG_MPSL_USE_ZEPHYR_PM */ + #if defined(CONFIG_MPSL_CALIBRATION_PERIOD) atomic_set(&do_calibration, 0); #endif /* CONFIG_MPSL_CALIBRATION_PERIOD */ @@ -527,14 +534,19 @@ int32_t mpsl_lib_uninit(void) mpsl_uninit(); #if defined(CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL) - int err; - err = mpsl_clock_ctrl_uninit(); if (err) { return err; } #endif /* CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL */ +#if defined(CONFIG_MPSL_USE_ZEPHYR_PM) + err = mpsl_pm_utils_uninit(); + if (err) { + return err; + } +#endif /* CONFIG_MPSL_USE_ZEPHYR_PM */ + return 0; #else /* !IS_ENABLED(CONFIG_MPSL_DYNAMIC_INTERRUPTS) */ return -NRF_EPERM; diff --git a/subsys/mpsl/pm/Kconfig b/subsys/mpsl/pm/Kconfig index ba716f5d582e..32af2186671e 100644 --- a/subsys/mpsl/pm/Kconfig +++ b/subsys/mpsl/pm/Kconfig @@ -15,6 +15,20 @@ config MPSL_USE_ZEPHYR_PM if MPSL_USE_ZEPHYR_PM +config MPSL_PM_UNINIT_WORK_WAIT_TIME_MS + int "Timeout value the MPSL PM uninit procedure will wait for work queue to complete, in milliseconds" + default 100 + help + The option specifies a timeout value in milliseconds, the MPLS PM uninit procedure + will wait for MPSL work queue to complete handling of scheduled uninit work item. + +config MPSL_PM_NO_RADIO_EVENT_PERIOD_LATENCY_US + int "Latency value the MPSL PM allows in periods outside of radio events, in microseconds" + default 499999 + help + The option specifies the latency in microseconds that is allowed by MPSL PM + outside of radio events. + config MPSL_PM_USE_MRAM_LATENCY_SERVICE bool "Use Zephyr's MRAM latency service" select MRAM_LATENCY diff --git a/subsys/mpsl/pm/mpsl_pm_utils.c b/subsys/mpsl/pm/mpsl_pm_utils.c index 25dff9ccd21a..9d264fab3cd4 100644 --- a/subsys/mpsl/pm/mpsl_pm_utils.c +++ b/subsys/mpsl/pm/mpsl_pm_utils.c @@ -14,29 +14,42 @@ #include #endif /* CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE */ -#include +#include +#include "mpsl_pm_utils.h" LOG_MODULE_REGISTER(mpsl_pm_utils, CONFIG_MPSL_LOG_LEVEL); -/* These constants must be updated once the Zephyr PM Policy API is updated - * to handle low latency events. Ideally, the Policy API should be changed to use - * absolute time instead of relative time. This would remove the need for safety - * margins and allow optimal power savings. - */ -#define TIME_TO_REGISTER_EVENT_IN_ZEPHYR_US 1000 -#define PM_MAX_LATENCY_HCI_COMMANDS_US 499999 +#define NO_RADIO_EVENT_PERIOD_LATENCY_US CONFIG_MPSL_PM_NO_RADIO_EVENT_PERIOD_LATENCY_US +#define UNINIT_WORK_WAIT_TIMEOUT_MS K_MSEC(CONFIG_MPSL_PM_UNINIT_WORK_WAIT_TIME_MS) -static uint8_t m_pm_prev_flag_value; -static bool m_pm_event_is_registered; -static uint32_t m_prev_lat_value_us; +/* All MPSL PM event and latency actions are triggered inside the library. + * The uninitialization may be started outside of the library thgough public API. + * To avoid interference with other MPSL work items we need dedicated work + * item for uninitialization purpose. + */ +static void m_pm_uninit_work_handler(struct k_work *work); +static K_WORK_DELAYABLE_DEFINE(m_pm_uninit_work, m_pm_uninit_work_handler); + +enum MPLS_PM_STATE { + MPSL_PM_UNINITIALIZED, + MPSL_PM_UNINITIALIZING, + MPSL_PM_INITIALIZED +}; + +static uint8_t m_pm_prev_flag_value; +static bool m_pm_event_is_registered; +static uint32_t m_prev_lat_value_us; static struct pm_policy_latency_request m_latency_req; -static struct pm_policy_event m_evt; +static struct pm_policy_event m_evt; + +static atomic_t m_pm_state = (atomic_val_t)MPSL_PM_UNINITIALIZED; +struct k_sem m_uninit_wait_sem; #if defined(CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE) #define LOW_LATENCY_ATOMIC_BITS_NUM 2 -#define LOW_LATENCY_PM_BIT 0 -#define LOW_LATENCY_MRAM_BIT 0 -#define LOW_LATENCY_BITS_MASK 0x3 +#define LOW_LATENCY_PM_BIT 0 +#define LOW_LATENCY_MRAM_BIT 0 +#define LOW_LATENCY_BITS_MASK 0x3 static ATOMIC_DEFINE(m_low_latency_req_state, LOW_LATENCY_ATOMIC_BITS_NUM); /* Variable must be global to use it in on-off service cancel or release API */ @@ -56,7 +69,7 @@ static void m_update_latency_request(uint32_t lat_value_us) static void m_register_event(void) { - mpsl_pm_params_t params = {0}; + mpsl_pm_params_t params = {0}; bool pm_param_valid = mpsl_pm_params_get(¶ms); if (m_pm_prev_flag_value == params.cnt_flag) { @@ -130,7 +143,7 @@ static void m_register_latency(void) #if defined(CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE) m_mram_low_latency_release(); #endif /* CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE */ - m_update_latency_request(PM_MAX_LATENCY_HCI_COMMANDS_US); + m_update_latency_request(NO_RADIO_EVENT_PERIOD_LATENCY_US); #if defined(CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE) atomic_clear_bit(m_low_latency_req_state, LOW_LATENCY_PM_BIT); #endif /* CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE*/ @@ -201,21 +214,88 @@ static void m_mram_low_latency_release(void) } #endif /* CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE */ +static void m_pm_uninit_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + mpsl_pm_utils_work_handler(); +} + void mpsl_pm_utils_work_handler(void) { - m_register_event(); - m_register_latency(); + enum MPLS_PM_STATE pm_state = (enum MPLS_PM_STATE)atomic_get(&m_pm_state); + + if (pm_state == MPSL_PM_INITIALIZED) { + m_register_event(); + m_register_latency(); + } else if (pm_state == MPSL_PM_UNINITIALIZING) { + + /* The uninitialization is handled by all MPSL work items as well as by dedicated + * uninit work item. In case regular MPSL work item cleans MPSL PM the uninit + * work queue will not do anything. + */ + pm_policy_latency_request_remove(&m_latency_req); + pm_policy_event_unregister(&m_evt); + + /* The MPSL PM status is updated here to make the code unit testable. + * There is no work queue when running UTs, so the mpsl_pm_utils_work_handler() + * is manualy executed after mpsl_pm_utils_uninit() returns. + */ + atomic_set(&m_pm_state, (atomic_val_t)MPSL_PM_UNINITIALIZED); + + k_sem_give(&m_uninit_wait_sem); + } } -void mpsl_pm_utils_init(void) +int32_t mpsl_pm_utils_init(void) { mpsl_pm_params_t params = {0}; - 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; + if (atomic_get(&m_pm_state) != (atomic_val_t)MPSL_PM_UNINITIALIZED) { + return -NRF_EPERM; + } + + pm_policy_latency_request_add(&m_latency_req, NO_RADIO_EVENT_PERIOD_LATENCY_US); + m_prev_lat_value_us = NO_RADIO_EVENT_PERIOD_LATENCY_US; mpsl_pm_init(); - mpsl_pm_params_get(¶ms); + /* On init there should be no update from high-prio, returned value can be ignored */ + (void)mpsl_pm_params_get(¶ms); m_pm_prev_flag_value = params.cnt_flag; m_pm_event_is_registered = false; + + atomic_set(&m_pm_state, (atomic_val_t)MPSL_PM_INITIALIZED); + + return 0; +} + +int32_t mpsl_pm_utils_uninit(void) +{ + int err; + + if (atomic_get(&m_pm_state) != (atomic_val_t)MPSL_PM_INITIALIZED) { + return -NRF_EPERM; + } + + mpsl_pm_uninit(); + atomic_set(&m_pm_state, (atomic_val_t)MPSL_PM_UNINITIALIZING); + /* In case there is any pended MPSL work item that was not handled, a dedicated + * work item is used to remove PM policy event and unregister latency request. + */ + (void)k_sem_init(&m_uninit_wait_sem, 0, 1); + + mpsl_work_reschedule(&m_pm_uninit_work, K_NO_WAIT); + + /* Wait for completion of the uninit work item to make sure user can re-initialize + * MPSL PM after return. + */ + err = k_sem_take(&m_uninit_wait_sem, UNINIT_WORK_WAIT_TIMEOUT_MS); + if (err == -EAGAIN) { + return -NRF_ETIMEDOUT; + } else if (err != 0) { + __ASSERT(false, "MPSL PM uninit failed to complete: %d", err); + return -NRF_EFAULT; + } + + return 0; } diff --git a/include/mpsl/mpsl_pm_utils.h b/subsys/mpsl/pm/mpsl_pm_utils.h similarity index 51% rename from include/mpsl/mpsl_pm_utils.h rename to subsys/mpsl/pm/mpsl_pm_utils.h index bfcea5d99459..55a04c34755c 100644 --- a/include/mpsl/mpsl_pm_utils.h +++ b/subsys/mpsl/pm/mpsl_pm_utils.h @@ -14,8 +14,22 @@ extern "C" { /** @brief Initialize MPSL Power Management * * This routine initializes MPSL PM (via `mpsl_pm_init`). + * + * @retval 0 MPSL PM was initialized successfully. + * @retval -NRF_EPERM MPSL PM is already initialized. + */ +int32_t mpsl_pm_utils_init(void); + +/** @brief Unitialize MPSL Power Management + * + * This routine uninitializes MPSL PM (via `mpsl_pm_uninit`). + * + * @retval 0 MPSL PM was uninitialized successfully. + * @retval -NRF_EPERM MPSL was not initialized before the call. + * @retval -NRF_ETIMEDOUT MPSL PM uninitialization timed out while waiting for completion. + * @retval -NRF_EFAULT MPSL PM uninitialization failed due to unknown reason. */ -void mpsl_pm_utils_init(void); +int32_t mpsl_pm_utils_uninit(void); /** @brief Handles MPSL Power Management work * diff --git a/tests/subsys/mpsl/pm/CMakeLists.txt b/tests/subsys/mpsl/pm/CMakeLists.txt index 1903f9f8c715..1446fb4b11ae 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,10 +26,19 @@ 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 + ${ZEPHYR_NRFXLIB_MODULE_DIR}/mpsl/include) + +# 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 nrf_errno.h -include mocks/mpsl_work.h") # Options that cannot be passed through Kconfig fragments. target_compile_options(app PRIVATE - -DCONFIG_PM=y - -DCONFIG_MPSL_USE_ZEPHYR_PM=y + -DCONFIG_PM=y + -DCONFIG_MPSL_USE_ZEPHYR_PM=y + -DCONFIG_MPSL_PM_UNINIT_WORK_WAIT_TIME_MS=100 + -DCONFIG_MPSL_PM_NO_RADIO_EVENT_PERIOD_LATENCY_US=499999 ) 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..d64ef9430937 --- /dev/null +++ b/tests/subsys/mpsl/pm/kernel_minimal_mock.h @@ -0,0 +1,38 @@ +/* + * 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 generate mocks + * from this file instead. + * + * NOTE: This file is not included in the test source code, it is used only for mock generation. + */ + +#include + +typedef struct k_timeout { + uint64_t value; +} k_timeout_t; + +typedef struct { + /* We don't need full type definition for mockups */ +} _wait_q_t; + +struct k_sem { + _wait_q_t wait_q; + unsigned int count; + unsigned int limit; +}; + +int z_impl_k_sem_init(struct k_sem *sem, unsigned int initial_count, unsigned int limit); + +int z_impl_k_sem_take(struct k_sem *sem, k_timeout_t timeout); + +void z_impl_k_sem_give(struct k_sem *sem); + +#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..f8d212425274 100644 --- a/tests/subsys/mpsl/pm/pm_test.c +++ b/tests/subsys/mpsl/pm/pm_test.c @@ -7,17 +7,20 @@ #include #include - #include +#include #include "cmock_policy.h" +#include "cmock_kernel_minimal_mock.h" #include "cmock_mpsl_pm.h" #include "cmock_mpsl_pm_config.h" +#include "cmock_mpsl_work.h" -#include - -#define PM_MAX_LATENCY_HCI_COMMANDS_US 499999 +#include "mpsl_pm_utils.h" +#include "nrf_errno.h" +#define NO_RADIO_EVENT_PERIOD_LATENCY_US CONFIG_MPSL_PM_NO_RADIO_EVENT_PERIOD_LATENCY_US +#define UNINIT_WORK_WAIT_TIMEOUT_MS K_MSEC(CONFIG_MPSL_PM_UNINIT_WORK_WAIT_TIME_MS) /* The unity_main is not declared in any header file. It is only defined in the generated test * runner because of ncs' unity configuration. It is therefore declared here to avoid a compiler * warning. @@ -52,12 +55,157 @@ typedef struct { mpsl_pm_low_latency_state_t low_latency_state_next; } test_vector_latency_t; +/* Init and uninit tests */ +void start_uninit_module(void) +{ + __cmock_mpsl_work_reschedule_Expect(0, (k_timeout_t){0}); + __cmock_mpsl_work_reschedule_IgnoreArg_dwork(); + __cmock_mpsl_pm_uninit_Expect(); + + __cmock_z_impl_k_sem_init_ExpectAndReturn(NULL, 0, 1, 0); + __cmock_z_impl_k_sem_init_IgnoreArg_sem(); + + /* The k_sem_take will return immediately because there is no real work queue in the test. + * To make the proper uninitialization we must call mpsl_pm_utils_work_handler() after that. + * + * NOTE: There is an assumption the remaining part of unitialization is done by + * mpsl_pm_utils_work_handler(). The mpsl_pm_utils_uninit() just waits for it to end. + */ + __cmock_z_impl_k_sem_take_ExpectAndReturn(NULL, UNINIT_WORK_WAIT_TIMEOUT_MS, 0); + __cmock_z_impl_k_sem_take_IgnoreArg_sem(); + + TEST_ASSERT_EQUAL(0, mpsl_pm_utils_uninit()); +} + +void commit_uninit_module(void) +{ + __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(); + + /* There is no real work queue so the mpsl_pm_utils_uninit() is not waiting for this + * semaphore to be signaled. We must call this function to make unintialization to complete. + * + * NOTE: There is an assumption the remaining part of unitialization is done by + * mpsl_pm_utils_work_handler(). The mpsl_pm_utils_uninit() just waits for it to end. + */ + __cmock_z_impl_k_sem_give_Expect(NULL); + __cmock_z_impl_k_sem_give_IgnoreArg_sem(); + + mpsl_pm_utils_work_handler(); +} + +/* Manual teardown tested module, there is no way to overload it for given test case. */ +void uninit_tested_module(void) +{ + start_uninit_module(); + commit_uninit_module(); +} + +void init_expect_prepare(void) +{ + mpsl_pm_params_t pm_params_initial = {0}; + + __cmock_pm_policy_latency_request_add_Expect(0, NO_RADIO_EVENT_PERIOD_LATENCY_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); +} + +void test_init_uninit(void) +{ + init_expect_prepare(); + + TEST_ASSERT_EQUAL(0, mpsl_pm_utils_init()); + + uninit_tested_module(); +} + +void test_init_when_already_initialized(void) +{ + init_expect_prepare(); + + TEST_ASSERT_EQUAL(0, mpsl_pm_utils_init()); + + TEST_ASSERT_EQUAL(-NRF_EPERM, mpsl_pm_utils_init()); + + uninit_tested_module(); +} + +void test_init_when_uninit_started(void) +{ + init_expect_prepare(); + + TEST_ASSERT_EQUAL(0, mpsl_pm_utils_init()); + + start_uninit_module(); + + TEST_ASSERT_EQUAL(-NRF_EPERM, mpsl_pm_utils_init()); + + commit_uninit_module(); +} + +void test_uninit_when_not_initialized(void) +{ + TEST_ASSERT_EQUAL(-NRF_EPERM, mpsl_pm_utils_uninit()); +} + +void test_uninit_wait_timeout(void) +{ + init_expect_prepare(); + + TEST_ASSERT_EQUAL(0, 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(); + + __cmock_z_impl_k_sem_init_ExpectAndReturn(NULL, 0, 1, 0); + __cmock_z_impl_k_sem_init_IgnoreArg_sem(); + + __cmock_z_impl_k_sem_take_ExpectAndReturn(NULL, UNINIT_WORK_WAIT_TIMEOUT_MS, -EAGAIN); + __cmock_z_impl_k_sem_take_IgnoreArg_sem(); + + TEST_ASSERT_EQUAL(-NRF_ETIMEDOUT, mpsl_pm_utils_uninit()); + + /* Just to cleanup after the test case */ + commit_uninit_module(); +} + +void test_uninit_wait_fatal_err(void) +{ + init_expect_prepare(); + + TEST_ASSERT_EQUAL(0, 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(); + + __cmock_z_impl_k_sem_init_ExpectAndReturn(NULL, 0, 1, 0); + __cmock_z_impl_k_sem_init_IgnoreArg_sem(); + + __cmock_z_impl_k_sem_take_ExpectAndReturn(NULL, UNINIT_WORK_WAIT_TIMEOUT_MS, -EBUSY); + __cmock_z_impl_k_sem_take_IgnoreArg_sem(); + + TEST_ASSERT_EQUAL(-NRF_EFAULT, mpsl_pm_utils_uninit()); + + /* Just to cleanup after the test case */ + commit_uninit_module(); +} + +/* Latency and event handling tests */ + void run_test(test_vector_event_t *p_test_vectors, int num_test_vctr) { mpsl_pm_params_t pm_params_initial = {0}; - resetTest(); /* Verify expectations until now. */ - __cmock_pm_policy_latency_request_add_Expect(0, PM_MAX_LATENCY_HCI_COMMANDS_US); + verifyTest(); /* Verify expectations until now. */ + __cmock_pm_policy_latency_request_add_Expect(0, NO_RADIO_EVENT_PERIOD_LATENCY_US); __cmock_pm_policy_latency_request_add_IgnoreArg_req(); __cmock_mpsl_pm_init_Expect(); @@ -65,7 +213,7 @@ void run_test(test_vector_event_t *p_test_vectors, int num_test_vctr) __cmock_mpsl_pm_params_get_ExpectAnyArgsAndReturn(true); __cmock_mpsl_pm_params_get_ReturnThruPtr_p_params(&pm_params_initial); - mpsl_pm_utils_init(); + TEST_ASSERT_EQUAL(0, mpsl_pm_utils_init()); for (int i = 0; i < num_test_vctr; i++) { test_vector_event_t v = p_test_vectors[i]; @@ -100,8 +248,8 @@ void run_test_latency(test_vector_latency_t *p_test_vectors, int num_test_vctr) { mpsl_pm_params_t pm_params = {0}; - resetTest(); /* Verify expectations until now. */ - __cmock_pm_policy_latency_request_add_Expect(0, PM_MAX_LATENCY_HCI_COMMANDS_US); + verifyTest(); /* Verify expectations until now. */ + __cmock_pm_policy_latency_request_add_Expect(0, NO_RADIO_EVENT_PERIOD_LATENCY_US); __cmock_pm_policy_latency_request_add_IgnoreArg_req(); __cmock_mpsl_pm_init_Expect(); @@ -124,7 +272,7 @@ void run_test_latency(test_vector_latency_t *p_test_vectors, int num_test_vctr) __cmock_mpsl_pm_low_latency_requested_ExpectAndReturn( v.low_latency_requested); __cmock_pm_policy_latency_request_update_Expect( - 0, v.low_latency_requested ? 0 : PM_MAX_LATENCY_HCI_COMMANDS_US); + 0, v.low_latency_requested ? 0 : NO_RADIO_EVENT_PERIOD_LATENCY_US); __cmock_pm_policy_latency_request_update_IgnoreArg_req(); __cmock_mpsl_pm_low_latency_state_set_Expect( v.low_latency_state_transition); @@ -139,73 +287,71 @@ void run_test_latency(test_vector_latency_t *p_test_vectors, int num_test_vctr) } } -void test_init_only(void) -{ - run_test(NULL, 0); -} - void test_no_events(void) { test_vector_event_t test_vectors[] = { /* Init then no events*/ - {false, {0, MPSL_PM_EVENT_STATE_NO_EVENTS_LEFT, 0}, - EVENT_FUNC_NONE, 0, 0}, + {false, {0, MPSL_PM_EVENT_STATE_NO_EVENTS_LEFT, 0}, EVENT_FUNC_NONE, 0, 0}, }; run_test(&test_vectors[0], ARRAY_SIZE(test_vectors)); + + uninit_tested_module(); } void test_high_prio_changed_params(void) { test_vector_event_t test_vectors[] = { /* Pretend high prio changed parameters while we read them. */ - {false, {10000, MPSL_PM_EVENT_STATE_BEFORE_EVENT, 1}, - EVENT_FUNC_NONE, 0, 0}, + {false, {10000, MPSL_PM_EVENT_STATE_BEFORE_EVENT, 1}, EVENT_FUNC_NONE, 0, 0}, }; run_test(&test_vectors[0], ARRAY_SIZE(test_vectors)); + + uninit_tested_module(); } void test_latency_request(void) { test_vector_latency_t test_vectors[] = { - {LATENCY_FUNC_NONE, MPSL_PM_LOW_LATENCY_STATE_OFF, false, + {LATENCY_FUNC_NONE, MPSL_PM_LOW_LATENCY_STATE_OFF, false, MPSL_PM_LOW_LATENCY_STATE_OFF, MPSL_PM_LOW_LATENCY_STATE_OFF}, {LATENCY_FUNC_UPDATE, MPSL_PM_LOW_LATENCY_STATE_OFF, true, MPSL_PM_LOW_LATENCY_STATE_REQUESTING, MPSL_PM_LOW_LATENCY_STATE_ON}, - {LATENCY_FUNC_NONE, MPSL_PM_LOW_LATENCY_STATE_ON, true, + {LATENCY_FUNC_NONE, MPSL_PM_LOW_LATENCY_STATE_ON, true, MPSL_PM_LOW_LATENCY_STATE_ON, MPSL_PM_LOW_LATENCY_STATE_ON}, - {LATENCY_FUNC_UPDATE, MPSL_PM_LOW_LATENCY_STATE_ON, false, + {LATENCY_FUNC_UPDATE, MPSL_PM_LOW_LATENCY_STATE_ON, false, MPSL_PM_LOW_LATENCY_STATE_RELEASING, MPSL_PM_LOW_LATENCY_STATE_OFF}, }; run_test_latency(&test_vectors[0], ARRAY_SIZE(test_vectors)); + + uninit_tested_module(); } void test_register_and_deregister_event(void) { test_vector_event_t test_vectors[] = { /* Register event. */ - {true, {10000, MPSL_PM_EVENT_STATE_BEFORE_EVENT, 1}, - EVENT_FUNC_REGISTER, 10000, 0}, + {true, {10000, MPSL_PM_EVENT_STATE_BEFORE_EVENT, 1}, EVENT_FUNC_REGISTER, 10000, 0}, /* Deregister event. */ - {true, {0, MPSL_PM_EVENT_STATE_NO_EVENTS_LEFT, 2}, - EVENT_FUNC_UNREGISTER, 0, 0}, + {true, {0, MPSL_PM_EVENT_STATE_NO_EVENTS_LEFT, 2}, EVENT_FUNC_UNREGISTER, 0, 0}, }; run_test(&test_vectors[0], ARRAY_SIZE(test_vectors)); + + uninit_tested_module(); } void test_register_update_and_deregister_event(void) { test_vector_event_t test_vectors[] = { /* Register event. */ - {true, {10000, MPSL_PM_EVENT_STATE_BEFORE_EVENT, 1}, - EVENT_FUNC_REGISTER, 10000, 0}, + {true, {10000, MPSL_PM_EVENT_STATE_BEFORE_EVENT, 1}, EVENT_FUNC_REGISTER, 10000, 0}, /* Update event. */ - {true, {15000, MPSL_PM_EVENT_STATE_BEFORE_EVENT, 2}, - EVENT_FUNC_UPDATE, 15000, 0}, + {true, {15000, MPSL_PM_EVENT_STATE_BEFORE_EVENT, 2}, EVENT_FUNC_UPDATE, 15000, 0}, /* Deregister event. */ - {true, {0, MPSL_PM_EVENT_STATE_NO_EVENTS_LEFT, 3}, - EVENT_FUNC_UNREGISTER, 0, 0}, + {true, {0, MPSL_PM_EVENT_STATE_NO_EVENTS_LEFT, 3}, EVENT_FUNC_UNREGISTER, 0, 0}, }; run_test(&test_vectors[0], ARRAY_SIZE(test_vectors)); + + uninit_tested_module(); } int main(void)