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)