Skip to content

Commit 40f76f0

Browse files
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 <knut.eldhuset@nordicsemi.no> Signed-off-by: Piotr Pryga <piotr.pryga@nordicsemi.no> Co-authored-by: Piotr Pryga <piotr.pryga@nordicsemi.no>
1 parent 34740f1 commit 40f76f0

File tree

8 files changed

+423
-86
lines changed

8 files changed

+423
-86
lines changed

include/mpsl/mpsl_work.h

+42-20
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
/**
88
* @file mpsl_work.h
99
*
10-
* @defgroup mpsl_work Multiprotocol Service Layer workqueue.
10+
* @defgroup mpsl_work Multiprotocol Service Layer work queue.
1111
*
12-
* @brief Internal MPSL workqueue.
12+
* @brief Internal MPSL work queue.
1313
* @{
1414
*/
1515

@@ -24,23 +24,22 @@ extern "C" {
2424

2525
extern struct k_work_q mpsl_work_q;
2626

27-
/** @brief Submit a work item to the MPSL workqueue.
27+
/** @brief Submit a work item to the MPSL work queue.
2828
*
2929
* This routine submits work item @a work to be processed by the MPSL
30-
* workqueue. If the work item is already pending in the MPSL workqueue or
31-
* any other workqueue as a result of an earlier submission, this routine
30+
* work queue. If the work item is already pending in the MPSL work queue or
31+
* any other work queue as a result of an earlier submission, this routine
3232
* has no effect on the work item. If the work item has already been
3333
* processed, or is currently being processed, its work is considered
3434
* complete and the work item can be resubmitted.
3535
*
36-
* @note
37-
* Work items submitted to the MPSL workqueue should avoid using handlers
38-
* that block or yield since this may prevent the MPSL workqueue from
39-
* processing other work items in a timely manner.
36+
* @note Work items submitted to the MPSL work queue should avoid using handlers
37+
* that block or yield since this may prevent the MPSL work queue from
38+
* processing other work items in a timely manner.
4039
*
4140
* @note Can be called by ISRs.
4241
*
43-
* @param work Address of work item.
42+
* @param work address of work item.
4443
*/
4544
static inline void mpsl_work_submit(struct k_work *work)
4645
{
@@ -49,23 +48,22 @@ static inline void mpsl_work_submit(struct k_work *work)
4948
}
5049
}
5150

52-
/** @brief Submit an idle work item to the MPSL workqueue after a delay.
51+
/** @brief Submit an idle work item to the MPSL work queue after a delay.
5352
*
5453
* @note Can be called by ISRs.
5554
*
56-
* @note
57-
* Work items submitted to the MPSL workqueue should avoid using handlers
58-
* that block or yield since this may prevent the MPSL workqueue from
59-
* processing other work items in a timely manner.
55+
* @note Work items submitted to the MPSL work queue should avoid using handlers
56+
* that block or yield since this may prevent the MPSL work queue from
57+
* processing other work items in a timely manner.
6058
*
6159
* @note This is a no-op if the work item is already scheduled or submitted,
62-
* even if @p delay is @c K_NO_WAIT.
60+
* even if @p delay is @c K_NO_WAIT. See @ref mpsl_work_reschedule().
6361
*
64-
* @param dwork Address of delayable work item.
62+
* @param dwork address of delayable work item.
6563
*
66-
* @param delay the time to wait before submitting the work item. If @c
67-
* K_NO_WAIT and the work is not pending this is equivalent to
68-
* mpsl_work_submit().
64+
* @param delay the time to wait before submitting the work item.
65+
* If @c K_NO_WAIT and the work is not pending this is equivalent to
66+
* mpsl_work_submit().
6967
*/
7068
static inline void mpsl_work_schedule(struct k_work_delayable *dwork, k_timeout_t delay)
7169
{
@@ -74,6 +72,30 @@ static inline void mpsl_work_schedule(struct k_work_delayable *dwork, k_timeout_
7472
}
7573
}
7674

75+
/** @brief Reschedule a work item to the MPSL work queue after a delay.
76+
*
77+
* @note Can be called by ISRs.
78+
*
79+
* @note Work items submitted to the MPSL work queue should avoid using handlers
80+
* that block or yield since this may prevent the MPSL work queue from
81+
* processing other work items in a timely manner.
82+
*
83+
* @note If the work item has not been scheduled before, this bahavior is
84+
* the same as @ref mpsl_work_schedule().
85+
*
86+
* @param dwork address of delayable work item.
87+
*
88+
* @param delay the time to wait before submitting the work item.
89+
* If @c K_NO_WAIT and the work is not pending this is equivalent to
90+
* mpsl_work_submit().
91+
*/
92+
static inline void mpsl_work_reschedule(struct k_work_delayable *dwork, k_timeout_t delay)
93+
{
94+
if (k_work_reschedule_for_queue(&mpsl_work_q, dwork, delay) < 0) {
95+
__ASSERT(false, "k_work_reschedule_for_queue() failed.");
96+
}
97+
}
98+
7799
#ifdef __cplusplus
78100
}
79101
#endif

subsys/mpsl/init/mpsl_init.c

+19-7
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
#endif
2727

2828
#if IS_ENABLED(CONFIG_MPSL_USE_ZEPHYR_PM)
29-
#include <mpsl/mpsl_pm_utils.h>
29+
#include "../pm/mpsl_pm_utils.h"
3030
#endif
3131

3232
#if IS_ENABLED(CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL)
@@ -398,6 +398,13 @@ static int32_t mpsl_lib_init_internal(void)
398398
}
399399
#endif /* CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL */
400400

401+
#if defined(CONFIG_MPSL_USE_ZEPHYR_PM)
402+
err = mpsl_pm_utils_init();
403+
if (err) {
404+
return err;
405+
}
406+
#endif /* CONFIG_MPSL_USE_ZEPHYR_PM */
407+
401408
#if defined(CONFIG_SOC_SERIES_NRF54HX)
402409
/* Secure domain no longer enables DPPI channels for local domains,
403410
* MPSL now has to enable the ones it uses.
@@ -442,10 +449,6 @@ static int mpsl_lib_init_sys(void)
442449
return err;
443450
}
444451

445-
#if IS_ENABLED(CONFIG_MPSL_USE_ZEPHYR_PM)
446-
mpsl_pm_utils_init();
447-
#endif
448-
449452
#if IS_ENABLED(CONFIG_MPSL_DYNAMIC_INTERRUPTS)
450453
/* Ensure IRQs are disabled before attaching. */
451454
mpsl_lib_irq_disable();
@@ -518,6 +521,10 @@ int32_t mpsl_lib_init(void)
518521
int32_t mpsl_lib_uninit(void)
519522
{
520523
#if IS_ENABLED(CONFIG_MPSL_DYNAMIC_INTERRUPTS)
524+
#if defined(CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL) || defined(CONFIG_MPSL_USE_ZEPHYR_PM)
525+
int err;
526+
#endif /* CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL || CONFIG_MPSL_USE_ZEPHYR_PM */
527+
521528
#if defined(CONFIG_MPSL_CALIBRATION_PERIOD)
522529
atomic_set(&do_calibration, 0);
523530
#endif /* CONFIG_MPSL_CALIBRATION_PERIOD */
@@ -527,14 +534,19 @@ int32_t mpsl_lib_uninit(void)
527534
mpsl_uninit();
528535

529536
#if defined(CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL)
530-
int err;
531-
532537
err = mpsl_clock_ctrl_uninit();
533538
if (err) {
534539
return err;
535540
}
536541
#endif /* CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL */
537542

543+
#if defined(CONFIG_MPSL_USE_ZEPHYR_PM)
544+
err = mpsl_pm_utils_uninit();
545+
if (err) {
546+
return err;
547+
}
548+
#endif /* CONFIG_MPSL_USE_ZEPHYR_PM */
549+
538550
return 0;
539551
#else /* !IS_ENABLED(CONFIG_MPSL_DYNAMIC_INTERRUPTS) */
540552
return -NRF_EPERM;

subsys/mpsl/pm/Kconfig

+14
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ config MPSL_USE_ZEPHYR_PM
1515

1616
if MPSL_USE_ZEPHYR_PM
1717

18+
config MPSL_PM_UNINIT_WORK_WAIT_TIME_MS
19+
int "Timeout value the MPSL PM uninit procedure will wait for work queue to complete, in milliseconds"
20+
default 100
21+
help
22+
The option specifies a timeout value in milliseconds, the MPLS PM uninit procedure
23+
will wait for MPSL work queue to complete handling of scheduled uninit work item.
24+
25+
config MPSL_PM_NO_RADIO_EVENT_PERIOD_LATENCY_US
26+
int "Latency value the MPSL PM allows in periods outside of radio events, in microseconds"
27+
default 499999
28+
help
29+
The option specifies the latency in microseconds that is allowed by MPSL PM
30+
outside of radio events.
31+
1832
config MPSL_PM_USE_MRAM_LATENCY_SERVICE
1933
bool "Use Zephyr's MRAM latency service"
2034
select MRAM_LATENCY

subsys/mpsl/pm/mpsl_pm_utils.c

+103-23
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,42 @@
1414
#include <mram_latency.h>
1515
#endif /* CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE */
1616

17-
#include <mpsl/mpsl_pm_utils.h>
17+
#include <mpsl/mpsl_work.h>
18+
#include "mpsl_pm_utils.h"
1819

1920
LOG_MODULE_REGISTER(mpsl_pm_utils, CONFIG_MPSL_LOG_LEVEL);
2021

21-
/* These constants must be updated once the Zephyr PM Policy API is updated
22-
* to handle low latency events. Ideally, the Policy API should be changed to use
23-
* absolute time instead of relative time. This would remove the need for safety
24-
* margins and allow optimal power savings.
25-
*/
26-
#define TIME_TO_REGISTER_EVENT_IN_ZEPHYR_US 1000
27-
#define PM_MAX_LATENCY_HCI_COMMANDS_US 499999
22+
#define NO_RADIO_EVENT_PERIOD_LATENCY_US CONFIG_MPSL_PM_NO_RADIO_EVENT_PERIOD_LATENCY_US
23+
#define UNINIT_WORK_WAIT_TIMEOUT_MS K_MSEC(CONFIG_MPSL_PM_UNINIT_WORK_WAIT_TIME_MS)
2824

29-
static uint8_t m_pm_prev_flag_value;
30-
static bool m_pm_event_is_registered;
31-
static uint32_t m_prev_lat_value_us;
25+
/* All MPSL PM event and latency actions are triggered inside the library.
26+
* The uninitialization may be started outside of the library thgough public API.
27+
* To avoid interference with other MPSL work items we need dedicated work
28+
* item for uninitialization purpose.
29+
*/
30+
static void m_pm_uninit_work_handler(struct k_work *work);
31+
static K_WORK_DELAYABLE_DEFINE(m_pm_uninit_work, m_pm_uninit_work_handler);
32+
33+
enum MPLS_PM_STATE {
34+
MPSL_PM_UNINITIALIZED,
35+
MPSL_PM_UNINITIALIZING,
36+
MPSL_PM_INITIALIZED
37+
};
38+
39+
static uint8_t m_pm_prev_flag_value;
40+
static bool m_pm_event_is_registered;
41+
static uint32_t m_prev_lat_value_us;
3242
static struct pm_policy_latency_request m_latency_req;
33-
static struct pm_policy_event m_evt;
43+
static struct pm_policy_event m_evt;
44+
45+
static atomic_t m_pm_state = (atomic_val_t)MPSL_PM_UNINITIALIZED;
46+
struct k_sem m_uninit_wait_sem;
3447

3548
#if defined(CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE)
3649
#define LOW_LATENCY_ATOMIC_BITS_NUM 2
37-
#define LOW_LATENCY_PM_BIT 0
38-
#define LOW_LATENCY_MRAM_BIT 0
39-
#define LOW_LATENCY_BITS_MASK 0x3
50+
#define LOW_LATENCY_PM_BIT 0
51+
#define LOW_LATENCY_MRAM_BIT 0
52+
#define LOW_LATENCY_BITS_MASK 0x3
4053

4154
static ATOMIC_DEFINE(m_low_latency_req_state, LOW_LATENCY_ATOMIC_BITS_NUM);
4255
/* 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)
5669

5770
static void m_register_event(void)
5871
{
59-
mpsl_pm_params_t params = {0};
72+
mpsl_pm_params_t params = {0};
6073
bool pm_param_valid = mpsl_pm_params_get(&params);
6174

6275
if (m_pm_prev_flag_value == params.cnt_flag) {
@@ -130,7 +143,7 @@ static void m_register_latency(void)
130143
#if defined(CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE)
131144
m_mram_low_latency_release();
132145
#endif /* CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE */
133-
m_update_latency_request(PM_MAX_LATENCY_HCI_COMMANDS_US);
146+
m_update_latency_request(NO_RADIO_EVENT_PERIOD_LATENCY_US);
134147
#if defined(CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE)
135148
atomic_clear_bit(m_low_latency_req_state, LOW_LATENCY_PM_BIT);
136149
#endif /* CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE*/
@@ -201,21 +214,88 @@ static void m_mram_low_latency_release(void)
201214
}
202215
#endif /* CONFIG_MPSL_PM_USE_MRAM_LATENCY_SERVICE */
203216

217+
static void m_pm_uninit_work_handler(struct k_work *work)
218+
{
219+
ARG_UNUSED(work);
220+
221+
mpsl_pm_utils_work_handler();
222+
}
223+
204224
void mpsl_pm_utils_work_handler(void)
205225
{
206-
m_register_event();
207-
m_register_latency();
226+
enum MPLS_PM_STATE pm_state = (enum MPLS_PM_STATE)atomic_get(&m_pm_state);
227+
228+
if (pm_state == MPSL_PM_INITIALIZED) {
229+
m_register_event();
230+
m_register_latency();
231+
} else if (pm_state == MPSL_PM_UNINITIALIZING) {
232+
233+
/* The uninitialization is handled by all MPSL work items as well as by dedicated
234+
* uninit work item. In case regular MPSL work item cleans MPSL PM the uninit
235+
* work queue will not do anything.
236+
*/
237+
pm_policy_latency_request_remove(&m_latency_req);
238+
pm_policy_event_unregister(&m_evt);
239+
240+
/* The MPSL PM status is updated here to make the code unit testable.
241+
* There is no work queue when running UTs, so the mpsl_pm_utils_work_handler()
242+
* is manualy executed after mpsl_pm_utils_uninit() returns.
243+
*/
244+
atomic_set(&m_pm_state, (atomic_val_t)MPSL_PM_UNINITIALIZED);
245+
246+
k_sem_give(&m_uninit_wait_sem);
247+
}
208248
}
209249

210-
void mpsl_pm_utils_init(void)
250+
int32_t mpsl_pm_utils_init(void)
211251
{
212252
mpsl_pm_params_t params = {0};
213253

214-
pm_policy_latency_request_add(&m_latency_req, PM_MAX_LATENCY_HCI_COMMANDS_US);
215-
m_prev_lat_value_us = PM_MAX_LATENCY_HCI_COMMANDS_US;
254+
if (atomic_get(&m_pm_state) != (atomic_val_t)MPSL_PM_UNINITIALIZED) {
255+
return -NRF_EPERM;
256+
}
257+
258+
pm_policy_latency_request_add(&m_latency_req, NO_RADIO_EVENT_PERIOD_LATENCY_US);
259+
m_prev_lat_value_us = NO_RADIO_EVENT_PERIOD_LATENCY_US;
216260

217261
mpsl_pm_init();
218-
mpsl_pm_params_get(&params);
262+
/* On init there should be no update from high-prio, returned value can be ignored */
263+
(void)mpsl_pm_params_get(&params);
219264
m_pm_prev_flag_value = params.cnt_flag;
220265
m_pm_event_is_registered = false;
266+
267+
atomic_set(&m_pm_state, (atomic_val_t)MPSL_PM_INITIALIZED);
268+
269+
return 0;
270+
}
271+
272+
int32_t mpsl_pm_utils_uninit(void)
273+
{
274+
int err;
275+
276+
if (atomic_get(&m_pm_state) != (atomic_val_t)MPSL_PM_INITIALIZED) {
277+
return -NRF_EPERM;
278+
}
279+
280+
mpsl_pm_uninit();
281+
atomic_set(&m_pm_state, (atomic_val_t)MPSL_PM_UNINITIALIZING);
282+
/* In case there is any pended MPSL work item that was not handled, a dedicated
283+
* work item is used to remove PM policy event and unregister latency request.
284+
*/
285+
(void)k_sem_init(&m_uninit_wait_sem, 0, 1);
286+
287+
mpsl_work_reschedule(&m_pm_uninit_work, K_NO_WAIT);
288+
289+
/* Wait for completion of the uninit work item to make sure user can re-initialize
290+
* MPSL PM after return.
291+
*/
292+
err = k_sem_take(&m_uninit_wait_sem, UNINIT_WORK_WAIT_TIMEOUT_MS);
293+
if (err == -EAGAIN) {
294+
return -NRF_ETIMEDOUT;
295+
} else if (err != 0) {
296+
__ASSERT(false, "MPSL PM uninit failed to complete: %d", err);
297+
return -NRF_EFAULT;
298+
}
299+
300+
return 0;
221301
}

include/mpsl/mpsl_pm_utils.h subsys/mpsl/pm/mpsl_pm_utils.h

+15-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,22 @@ extern "C" {
1414
/** @brief Initialize MPSL Power Management
1515
*
1616
* This routine initializes MPSL PM (via `mpsl_pm_init`).
17+
*
18+
* @retval 0 MPSL PM was initialized successfully.
19+
* @retval -NRF_EPERM MPSL PM is already initialized.
20+
*/
21+
int32_t mpsl_pm_utils_init(void);
22+
23+
/** @brief Unitialize MPSL Power Management
24+
*
25+
* This routine uninitializes MPSL PM (via `mpsl_pm_uninit`).
26+
*
27+
* @retval 0 MPSL PM was uninitialized successfully.
28+
* @retval -NRF_EPERM MPSL was not initialized before the call.
29+
* @retval -NRF_ETIMEDOUT MPSL PM uninitialization timed out while waiting for completion.
30+
* @retval -NRF_EFAULT MPSL PM uninitialization failed due to unknown reason.
1731
*/
18-
void mpsl_pm_utils_init(void);
32+
int32_t mpsl_pm_utils_uninit(void);
1933

2034
/** @brief Handles MPSL Power Management work
2135
*

0 commit comments

Comments
 (0)